content: /*
   * Copyright (C) 2008 The Android Open Source Project
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  package com.lb.launcher;
  
  import android.animation.Animator;
  import android.animation.AnimatorListenerAdapter;
  import android.animation.AnimatorSet;
  import android.animation.ObjectAnimator;
  import android.animation.PropertyValuesHolder;
  import android.content.Context;
  import android.content.res.Resources;
  import android.graphics.PointF;
  import android.graphics.Rect;
  import android.os.SystemClock;
  import android.support.v4.widget.AutoScrollHelper;
  import android.text.InputType;
  import android.text.Selection;
  import android.text.Spannable;
  import android.util.AttributeSet;
  import android.util.Log;
  import android.view.ActionMode;
  import android.view.KeyEvent;
  import android.view.LayoutInflater;
  import android.view.Menu;
  import android.view.MenuItem;
  import android.view.MotionEvent;
  import android.view.View;
  import android.view.accessibility.AccessibilityEvent;
  import android.view.accessibility.AccessibilityManager;
  import android.view.animation.AccelerateInterpolator;
  import android.view.inputmethod.EditorInfo;
  import android.view.inputmethod.InputMethodManager;
  import android.widget.LinearLayout;
  import android.widget.ScrollView;
  import android.widget.TextView;
  import com.lb.launcher.R;
  import com.lb.launcher.FolderInfo.FolderListener;
  import java.util.ArrayList;
  import java.util.Collections;
  import java.util.Comparator;
  
  /**
   * Represents a set of icons chosen by the user or generated by the system.
   */
  public class Folder extends LinearLayout implements DragSource, View.OnClickListener,
          View.OnLongClickListener, DropTarget, FolderListener, TextView.OnEditorActionListener,
          View.OnFocusChangeListener {
      private static final String TAG = "Launcher.Folder";
  
      protected DragController mDragController;
      protected Launcher mLauncher;
      protected FolderInfo mInfo;
  
      static final int STATE_NONE = -1;
      static final int STATE_SMALL = 0;
      static final int STATE_ANIMATING = 1;
      static final int STATE_OPEN = 2;
  
      private static final int CLOSE_FOLDER_DELAY_MS = 150;
  
      private int mExpandDuration;
      private int mMaterialExpandDuration;
      private int mMaterialExpandStagger;
      protected CellLayout mContent;
      private ScrollView mScrollView;
      private final LayoutInflater mInflater;
      private final IconCache mIconCache;
      private int mState = STATE_NONE;
      private static final int REORDER_ANIMATION_DURATION = 230;
      private static final int REORDER_DELAY = 250;
      private static final int ON_EXIT_CLOSE_DELAY = 400;
      private boolean mRearrangeOnClose = false;
      private FolderIcon mFolderIcon;
      private int mMaxCountX;
      private int mMaxCountY;
      private int mMaxNumItems;
      private ArrayList<View> mItemsInReadingOrder = new ArrayList<View>();
      boolean mItemsInvalidated = false;
      private ShortcutInfo mCurrentDragInfo;
      private View mCurrentDragView;
      private boolean mIsExternalDrag;
      boolean mSuppressOnAdd = false;
      private int[] mTargetCell = new int[2];
      private int[] mPreviousTargetCell = new int[2];
      private int[] mEmptyCell = new int[2];
      private Alarm mReorderAlarm = new Alarm();
      private Alarm mOnExitAlarm = new Alarm();
      private int mFolderNameHeight;
      private Rect mTempRect = new Rect();
      private boolean mDragInProgress = false;
      private boolean mDeleteFolderOnDropCompleted = false;
      private boolean mSuppressFolderDeletion = false;
      private boolean mItemAddedBackToSelfViaIcon = false;
      FolderEditText mFolderName;
      private float mFolderIconPivotX;
      private float mFolderIconPivotY;
  
      private boolean mIsEditingName = false;
      private InputMethodManager mInputMethodManager;
  
      private static String sDefaultFolderName;
      private static String sHintText;
  
      private FocusIndicatorView mFocusIndicatorHandler;
  
      // We avoid measuring the scroll view with a 0 width or height, as this
      // results in CellLayout being measured as UNSPECIFIED, which it does
      // not support.
      private static final int MIN_CONTENT_DIMEN = 5;
  
      private boolean mDestroyed;
  
      private AutoScrollHelper mAutoScrollHelper;
  
      private Runnable mDeferredAction;
      private boolean mDeferDropAfterUninstall;
      private boolean mUninstallSuccessful;
  
      /**
       * Used to inflate the Workspace from XML.
       *
       * @param context The application's context.
       * @param attrs The attribtues set containing the Workspace's customization values.
       */
      public Folder(Context context, AttributeSet attrs) {
          super(context, attrs);
  
          LauncherAppState app = LauncherAppState.getInstance();
          DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
          setAlwaysDrawnWithCacheEnabled(false);
          mInflater = LayoutInflater.from(context);
          mIconCache = app.getIconCache();
  
          Resources res = getResources();
          mMaxCountX = (int) grid.numColumns;
          // Allow scrolling folders when DISABLE_ALL_APPS is true.
          if (LauncherAppState.isDisableAllApps()) {
              mMaxCountY = mMaxNumItems = Integer.MAX_VALUE;
          } else {
              mMaxCountY = (int) grid.numRows;
              mMaxNumItems = mMaxCountX * mMaxCountY;
          }
  
          mInputMethodManager = (InputMethodManager)
                  getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
  
          mExpandDuration = res.getInteger(R.integer.config_folderExpandDuration);
          mMaterialExpandDuration = res.getInteger(R.integer.config_materialFolderExpandDuration);
          mMaterialExpandStagger = res.getInteger(R.integer.config_materialFolderExpandStagger);
  
          if (sDefaultFolderName == null) {
              sDefaultFolderName = res.getString(R.string.folder_name);
          }
          if (sHintText == null) {
              sHintText = res.getString(R.string.folder_hint_text);
          }
          mLauncher = (Launcher) context;
          // We need this view to be focusable in touch mode so that when text editing of the folder
          // name is complete, we have something to focus on, thus hiding the cursor and giving
          // reliable behvior when clicking the text field (since it will always gain focus on click).
          setFocusableInTouchMode(true);
      }
  
      @Override
      protected void onFinishInflate() {
          super.onFinishInflate();
          mScrollView = (ScrollView) findViewById(R.id.scroll_view);
          mContent = (CellLayout) findViewById(R.id.folder_content);
  
          mFocusIndicatorHandler = new FocusIndicatorView(getContext());
          mContent.addView(mFocusIndicatorHandler, 0);
          mFocusIndicatorHandler.getLayoutParams().height = FocusIndicatorView.DEFAULT_LAYOUT_SIZE;
          mFocusIndicatorHandler.getLayoutParams().width = FocusIndicatorView.DEFAULT_LAYOUT_SIZE;
  
          LauncherAppState app = LauncherAppState.getInstance();
          DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
  
          mContent.setCellDimensions(grid.folderCellWidthPx, grid.folderCellHeightPx);
          mContent.setGridSize(0, 0);
          mContent.getShortcutsAndWidgets().setMotionEventSplittingEnabled(false);
          mContent.setInvertIfRtl(true);
          mFolderName = (FolderEditText) findViewById(R.id.folder_name);
          mFolderName.setFolder(this);
          mFolderName.setOnFocusChangeListener(this);
  
          // We find out how tall the text view wants to be (it is set to wrap_content), so that
          // we can allocate the appropriate amount of space for it.
          int measureSpec = MeasureSpec.UNSPECIFIED;
          mFolderName.measure(measureSpec, measureSpec);
          mFolderNameHeight = mFolderName.getMeasuredHeight();
  
          // We disable action mode for now since it messes up the view on phones
          mFolderName.setCustomSelectionActionModeCallback(mActionModeCallback);
          mFolderName.setOnEditorActionListener(this);
          mFolderName.setSelectAllOnFocus(true);
          mFolderName.setInputType(mFolderName.getInputType() |
                  InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS | InputType.TYPE_TEXT_FLAG_CAP_WORDS);
          mAutoScrollHelper = new FolderAutoScrollHelper(mScrollView);
      }
  
      private ActionMode.Callback mActionModeCallback = new ActionMode.Callback() {
          public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
              return false;
          }
  
          public boolean onCreateActionMode(ActionMode mode, Menu menu) {
              return false;
          }
  
          public void onDestroyActionMode(ActionMode mode) {
          }
  
          public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
              return false;
          }
      };
  
      public void onClick(View v) {
          Object tag = v.getTag();
          if (tag instanceof ShortcutInfo) {
              mLauncher.onClick(v);
          }
      }
  
      public boolean onLongClick(View v) {
          // Return if global dragging is not enabled
          if (!mLauncher.isDraggingEnabled()) return true;
  
          Object tag = v.getTag();
          if (tag instanceof ShortcutInfo) {
              ShortcutInfo item = (ShortcutInfo) tag;
              if (!v.isInTouchMode()) {
                  return false;
              }
  
              mLauncher.getWorkspace().beginDragShared(v, this);
  
              mCurrentDragInfo = item;
              mEmptyCell[0] = item.cellX;
              mEmptyCell[1] = item.cellY;
              mCurrentDragView = v;
  
              mContent.removeView(mCurrentDragView);
              mInfo.remove(mCurrentDragInfo);
              mDragInProgress = true;
              mItemAddedBackToSelfViaIcon = false;
          }
          return true;
      }
  
      public boolean isEditingName() {
          return mIsEditingName;
      }
  
      public void startEditingFolderName() {
          mFolderName.setHint("");
          mIsEditingName = true;
      }
  
      public void dismissEditingName() {
          mInputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0);
          doneEditingFolderName(true);
      }
  
      public void doneEditingFolderName(boolean commit) {
          mFolderName.setHint(sHintText);
          // Convert to a string here to ensure that no other state associated with the text field
          // gets saved.
          String newTitle = mFolderName.getText().toString();
          mInfo.setTitle(newTitle);
          LauncherModel.updateItemInDatabase(mLauncher, mInfo);
  
          if (commit) {
              sendCustomAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED,
                      String.format(getContext().getString(R.string.folder_renamed), newTitle));
          }
          // In order to clear the focus from the text field, we set the focus on ourself. This
          // ensures that every time the field is clicked, focus is gained, giving reliable behavior.
          requestFocus();
  
          Selection.setSelection((Spannable) mFolderName.getText(), 0, 0);
          mIsEditingName = false;
      }
  
      public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
          if (actionId == EditorInfo.IME_ACTION_DONE) {
              dismissEditingName();
              return true;
          }
          return false;
      }
  
      public View getEditTextRegion() {
          return mFolderName;
      }
  
      public CellLayout getContent() {
          return mContent;
      }
  
      /**
       * We need to handle touch events to prevent them from falling through to the workspace below.
       */
      @Override
      public boolean onTouchEvent(MotionEvent ev) {
          return true;
      }
  
      public void setDragController(DragController dragController) {
          mDragController = dragController;
      }
  
      void setFolderIcon(FolderIcon icon) {
          mFolderIcon = icon;
      }
  
      @Override
      public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
          // When the folder gets focus, we don't want to announce the list of items.
          return true;
      }
  
      /**
       * @return the FolderInfo object associated with this folder
       */
      FolderInfo getInfo() {
          return mInfo;
      }
  
      private class GridComparator implements Comparator<ShortcutInfo> {
          int mNumCols;
          public GridComparator(int numCols) {
              mNumCols = numCols;
          }
  
          @Override
          public int compare(ShortcutInfo lhs, ShortcutInfo rhs) {
              int lhIndex = lhs.cellY * mNumCols + lhs.cellX;
              int rhIndex = rhs.cellY * mNumCols + rhs.cellX;
              return (lhIndex - rhIndex);
          }
      }
  
      private void placeInReadingOrder(ArrayList<ShortcutInfo> items) {
          int maxX = 0;
          int count = items.size();
          for (int i = 0; i < count; i++) {
              ShortcutInfo item = items.get(i);
              if (item.cellX > maxX) {
                  maxX = item.cellX;
              }
          }
  
          GridComparator gridComparator = new GridComparator(maxX + 1);
          Collections.sort(items, gridComparator);
          final int countX = mContent.getCountX();
          for (int i = 0; i < count; i++) {
              int x = i % countX;
              int y = i / countX;
              ShortcutInfo item = items.get(i);
              item.cellX = x;
              item.cellY = y;
          }
      }
  
      void bind(FolderInfo info) {
          mInfo = info;
          ArrayList<ShortcutInfo> children = info.contents;
          ArrayList<ShortcutInfo> overflow = new ArrayList<ShortcutInfo>();
          setupContentForNumItems(children.size());
          placeInReadingOrder(children);
          int count = 0;
          for (int i = 0; i < children.size(); i++) {
              ShortcutInfo child = (ShortcutInfo) children.get(i);
              if (createAndAddShortcut(child) == null) {
                  overflow.add(child);
              } else {
                  count++;
              }
          }
  
          // We rearrange the items in case there are any empty gaps
          setupContentForNumItems(count);
  
          // If our folder has too many items we prune them from the list. This is an issue
          // when upgrading from the old Folders implementation which could contain an unlimited
          // number of items.
          for (ShortcutInfo item: overflow) {
              mInfo.remove(item);
              LauncherModel.deleteItemFromDatabase(mLauncher, item);
          }
  
          mItemsInvalidated = true;
          updateTextViewFocus();
          mInfo.addListener(this);
  
          if (!sDefaultFolderName.contentEquals(mInfo.title)) {
              mFolderName.setText(mInfo.title);
          } else {
              mFolderName.setText("");
          }
          updateItemLocationsInDatabase();
  
          // In case any children didn't come across during loading, clean up the folder accordingly
          mFolderIcon.post(new Runnable() {
              public void run() {
                  if (getItemCount() <= 1) {
                      replaceFolderWithFinalItem();
                  }
              }
          });
      }
  
      /**
       * Creates a new UserFolder, inflated from R.layout.user_folder.
       *
       * @param context The application's context.
       *
       * @return A new UserFolder.
       */
      static Folder fromXml(Context context) {
          return (Folder) LayoutInflater.from(context).inflate(R.layout.user_folder, null);
      }
  
      /**
       * This method is intended to make the UserFolder to be visually identical in size and position
       * to its associated FolderIcon. This allows for a seamless transition into the expanded state.
       */
      private void positionAndSizeAsIcon() {
          if (!(getParent() instanceof DragLayer)) return;
          setScaleX(0.8f);
          setScaleY(0.8f);
          setAlpha(0f);
          mState = STATE_SMALL;
      }
  
      private void prepareReveal() {
          setScaleX(1f);
          setScaleY(1f);
          setAlpha(1f);
          mState = STATE_SMALL;
      }
  
      public void animateOpen() {
          if (!(getParent() instanceof DragLayer)) return;
  
          Animator openFolderAnim = null;
          final Runnable onCompleteRunnable;
          if (!Utilities.isLmpOrAbove()) {
              positionAndSizeAsIcon();
              centerAboutIcon();
  
              PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 1);
              PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 1.0f);
              PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", 1.0f);
              final ObjectAnimator oa =
                  LauncherAnimUtils.ofPropertyValuesHolder(this, alpha, scaleX, scaleY);
              oa.setDuration(mExpandDuration);
              openFolderAnim = oa;
  
              setLayerType(LAYER_TYPE_HARDWARE, null);
              onCompleteRunnable = new Runnable() {
                  @Override
                  public void run() {
                      setLayerType(LAYER_TYPE_NONE, null);
                  }
              };
          } else {
              prepareReveal();
              centerAboutIcon();
  
              int width = getPaddingLeft() + getPaddingRight() + mContent.getDesiredWidth();
              int height = getFolderHeight();
  
              float transX = - 0.075f * (width / 2 - getPivotX());
              float transY = - 0.075f * (height / 2 - getPivotY());
              setTranslationX(transX);
              setTranslationY(transY);
              PropertyValuesHolder tx = PropertyValuesHolder.ofFloat("translationX", transX, 0);
              PropertyValuesHolder ty = PropertyValuesHolder.ofFloat("translationY", transY, 0);
  
              int rx = (int) Math.max(Math.max(width - getPivotX(), 0), getPivotX());
              int ry = (int) Math.max(Math.max(height - getPivotY(), 0), getPivotY());
              float radius = (float) Math.sqrt(rx * rx + ry * ry);
              AnimatorSet anim = LauncherAnimUtils.createAnimatorSet();
              Animator reveal = LauncherAnimUtils.createCircularReveal(this, (int) getPivotX(),
                      (int) getPivotY(), 0, radius);
              reveal.setDuration(mMaterialExpandDuration);
              reveal.setInterpolator(new LogDecelerateInterpolator(100, 0));
  
              mContent.setAlpha(0f);
              Animator iconsAlpha = LauncherAnimUtils.ofFloat(mContent, "alpha", 0f, 1f);
              iconsAlpha.setDuration(mMaterialExpandDuration);
              iconsAlpha.setStartDelay(mMaterialExpandStagger);
              iconsAlpha.setInterpolator(new AccelerateInterpolator(1.5f));
  
              mFolderName.setAlpha(0f);
              Animator textAlpha = LauncherAnimUtils.ofFloat(mFolderName, "alpha", 0f, 1f);
              textAlpha.setDuration(mMaterialExpandDuration);
              textAlpha.setStartDelay(mMaterialExpandStagger);
              textAlpha.setInterpolator(new AccelerateInterpolator(1.5f));
  
              Animator drift = LauncherAnimUtils.ofPropertyValuesHolder(this, tx, ty);
              drift.setDuration(mMaterialExpandDuration);
              drift.setStartDelay(mMaterialExpandStagger);
              drift.setInterpolator(new LogDecelerateInterpolator(60, 0));
  
              anim.play(drift);
              anim.play(iconsAlpha);
              anim.play(textAlpha);
              anim.play(reveal);
  
              openFolderAnim = anim;
  
              mContent.setLayerType(LAYER_TYPE_HARDWARE, null);
              onCompleteRunnable = new Runnable() {
                  @Override
                  public void run() {
                      mContent.setLayerType(LAYER_TYPE_NONE, null);
                  }
              };
          }
          openFolderAnim.addListener(new AnimatorListenerAdapter() {
              @Override
              public void onAnimationStart(Animator animation) {
                  sendCustomAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED,
                          String.format(getContext().getString(R.string.folder_opened),
                          mContent.getCountX(), mContent.getCountY()));
                  mState = STATE_ANIMATING;
              }
              @Override
              public void onAnimationEnd(Animator animation) {
                  mState = STATE_OPEN;
  
                  if (onCompleteRunnable != null) {
                      onCompleteRunnable.run();
                  }
  
                  setFocusOnFirstChild();
              }
          });
          openFolderAnim.start();
  
          // Make sure the folder picks up the last drag move even if the finger doesn't move.
          if (mDragController.isDragging()) {
              mDragController.forceTouchMove();
          }
      }
  
      public void beginExternalDrag(ShortcutInfo item) {
          setupContentForNumItems(getItemCount() + 1);
          findAndSetEmptyCells(item);
  
          mCurrentDragInfo = item;
          mEmptyCell[0] = item.cellX;
          mEmptyCell[1] = item.cellY;
          mIsExternalDrag = true;
  
          mDragInProgress = true;
      }
  
      private void sendCustomAccessibilityEvent(int type, String text) {
          AccessibilityManager accessibilityManager = (AccessibilityManager)
                  getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
          if (accessibilityManager.isEnabled()) {
              AccessibilityEvent event = AccessibilityEvent.obtain(type);
              onInitializeAccessibilityEvent(event);
              event.getText().add(text);
              accessibilityManager.sendAccessibilityEvent(event);
          }
      }
  
      private void setFocusOnFirstChild() {
          View firstChild = mContent.getChildAt(0, 0);
          if (firstChild != null) {
              firstChild.requestFocus();
          }
      }
  
      public void animateClosed() {
          if (!(getParent() instanceof DragLayer)) return;
          PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 0);
          PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 0.9f);
          PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", 0.9f);
          final ObjectAnimator oa =
                  LauncherAnimUtils.ofPropertyValuesHolder(this, alpha, scaleX, scaleY);
  
          oa.addListener(new AnimatorListenerAdapter() {
              @Override
              public void onAnimationEnd(Animator animation) {
                  onCloseComplete();
                  setLayerType(LAYER_TYPE_NONE, null);
                  mState = STATE_SMALL;
              }
              @Override
              public void onAnimationStart(Animator animation) {
                  sendCustomAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED,
                          getContext().getString(R.string.folder_closed));
                  mState = STATE_ANIMATING;
              }
          });
          oa.setDuration(mExpandDuration);
          setLayerType(LAYER_TYPE_HARDWARE, null);
          oa.start();
      }
  
      public boolean acceptDrop(DragObject d) {
          final ItemInfo item = (ItemInfo) d.dragInfo;
          final int itemType = item.itemType;
          return ((itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION ||
                      itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) &&
                      !isFull());
      }
  
      protected boolean findAndSetEmptyCells(ShortcutInfo item) {
          int[] emptyCell = new int[2];
          if (mContent.findCellForSpan(emptyCell, item.spanX, item.spanY)) {
              item.cellX = emptyCell[0];
              item.cellY = emptyCell[1];
              return true;
          } else {
              return false;
          }
      }
  
      protected View createAndAddShortcut(ShortcutInfo item) {
          final BubbleTextView textView =
              (BubbleTextView) mInflater.inflate(R.layout.folder_application, this, false);
          textView.applyFromShortcutInfo(item, mIconCache, false);
  
          textView.setOnClickListener(this);
          textView.setOnLongClickListener(this);
          textView.setOnFocusChangeListener(mFocusIndicatorHandler);
  
          // We need to check here to verify that the given item's location isn't already occupied
          // by another item.
          if (mContent.getChildAt(item.cellX, item.cellY) != null || item.cellX < 0 || item.cellY < 0
                  || item.cellX >= mContent.getCountX() || item.cellY >= mContent.getCountY()) {
              // This shouldn't happen, log it.
              Log.e(TAG, "Folder order not properly persisted during bind");
              if (!findAndSetEmptyCells(item)) {
                  return null;
              }
          }
  
          CellLayout.LayoutParams lp =
              new CellLayout.LayoutParams(item.cellX, item.cellY, item.spanX, item.spanY);
          boolean insert = false;
          textView.setOnKeyListener(new FolderKeyEventListener());
          mContent.addViewToCellLayout(textView, insert ? 0 : -1, (int)item.id, lp, true);
          return textView;
      }
  
      public void onDragEnter(DragObject d) {
          mPreviousTargetCell[0] = -1;
          mPreviousTargetCell[1] = -1;
          mOnExitAlarm.cancelAlarm();
      }
  
      OnAlarmListener mReorderAlarmListener = new OnAlarmListener() {
          public void onAlarm(Alarm alarm) {
              realTimeReorder(mEmptyCell, mTargetCell);
          }
      };
  
      boolean readingOrderGreaterThan(int[] v1, int[] v2) {
          if (v1[1] > v2[1] || (v1[1] == v2[1] && v1[0] > v2[0])) {
              return true;
          } else {
              return false;
          }
      }
  
      private void realTimeReorder(int[] empty, int[] target) {
          boolean wrap;
          int startX;
          int endX;
          int startY;
          int delay = 0;
          float delayAmount = 30;
          if (readingOrderGreaterThan(target, empty)) {
              wrap = empty[0] >= mContent.getCountX() - 1;
              startY = wrap ? empty[1] + 1 : empty[1];
              for (int y = startY; y <= target[1]; y++) {
                  startX = y == empty[1] ? empty[0] + 1 : 0;
                  endX = y < target[1] ? mContent.getCountX() - 1 : target[0];
                  for (int x = startX; x <= endX; x++) {
                      View v = mContent.getChildAt(x,y);
                      if (mContent.animateChildToPosition(v, empty[0], empty[1],
                              REORDER_ANIMATION_DURATION, delay, true, true)) {
                          empty[0] = x;
                          empty[1] = y;
                          delay += delayAmount;
                          delayAmount *= 0.9;
                      }
                  }
              }
          } else {
              wrap = empty[0] == 0;
              startY = wrap ? empty[1] - 1 : empty[1];
              for (int y = startY; y >= target[1]; y--) {
                  startX = y == empty[1] ? empty[0] - 1 : mContent.getCountX() - 1;
                  endX = y > target[1] ? 0 : target[0];
                  for (int x = startX; x >= endX; x--) {
                      View v = mContent.getChildAt(x,y);
                      if (mContent.animateChildToPosition(v, empty[0], empty[1],
                              REORDER_ANIMATION_DURATION, delay, true, true)) {
                          empty[0] = x;
                          empty[1] = y;
                          delay += delayAmount;
                          delayAmount *= 0.9;
                      }
                  }
              }
          }
      }
  
      public boolean isLayoutRtl() {
          return (getLayoutDirection() == LAYOUT_DIRECTION_RTL);
      }
  
      public void onDragOver(DragObject d) {
          final DragView dragView = d.dragView;
          final int scrollOffset = mScrollView.getScrollY();
          final float[] r = getDragViewVisualCenter(d.x, d.y, d.xOffset, d.yOffset, dragView, null);
          r[0] -= getPaddingLeft();
          r[1] -= getPaddingTop();
  
          final long downTime = SystemClock.uptimeMillis();
          final MotionEvent translatedEv = MotionEvent.obtain(
                  downTime, downTime, MotionEvent.ACTION_MOVE, d.x, d.y, 0);
  
          if (!mAutoScrollHelper.isEnabled()) {
              mAutoScrollHelper.setEnabled(true);
          }
  
          final boolean handled = mAutoScrollHelper.onTouch(this, translatedEv);
          translatedEv.recycle();
  
          if (handled) {
              mReorderAlarm.cancelAlarm();
          } else {
              mTargetCell = mContent.findNearestArea(
                      (int) r[0], (int) r[1] + scrollOffset, 1, 1, mTargetCell);
              if (isLayoutRtl()) {
                  mTargetCell[0] = mContent.getCountX() - mTargetCell[0] - 1;
              }
              if (mTargetCell[0] != mPreviousTargetCell[0]
                      || mTargetCell[1] != mPreviousTargetCell[1]) {
                  mReorderAlarm.cancelAlarm();
                  mReorderAlarm.setOnAlarmListener(mReorderAlarmListener);
                  mReorderAlarm.setAlarm(REORDER_DELAY);
                  mPreviousTargetCell[0] = mTargetCell[0];
                  mPreviousTargetCell[1] = mTargetCell[1];
              }
          }
      }
  
      // This is used to compute the visual center of the dragView. The idea is that
      // the visual center represents the user's interpretation of where the item is, and hence
      // is the appropriate point to use when determining drop location.
      private float[] getDragViewVisualCenter(int x, int y, int xOffset, int yOffset,
              DragView dragView, float[] recycle) {
          float res[];
          if (recycle == null) {
              res = new float[2];
          } else {
              res = recycle;
          }
  
          // These represent the visual top and left of drag view if a dragRect was provided.
          // If a dragRect was not provided, then they correspond to the actual view left and
          // top, as the dragRect is in that case taken to be the entire dragView.
          // R.dimen.dragViewOffsetY.
          int left = x - xOffset;
          int top = y - yOffset;
  
          // In order to find the visual center, we shift by half the dragRect
          res[0] = left + dragView.getDragRegion().width() / 2;
          res[1] = top + dragView.getDragRegion().height() / 2;
  
          return res;
      }
  
      OnAlarmListener mOnExitAlarmListener = new OnAlarmListener() {
          public void onAlarm(Alarm alarm) {
              completeDragExit();
          }
      };
  
      public void completeDragExit() {
          mLauncher.closeFolder();
          mCurrentDragInfo = null;
          mCurrentDragView = null;
          mSuppressOnAdd = false;
          mRearrangeOnClose = true;
          mIsExternalDrag = false;
      }
  
      public void onDragExit(DragObject d) {
          // Exiting folder; stop the auto scroller.
          mAutoScrollHelper.setEnabled(false);
          // We only close the folder if this is a true drag exit, ie. not because
          // a drop has occurred above the folder.
          if (!d.dragComplete) {
              mOnExitAlarm.setOnAlarmListener(mOnExitAlarmListener);
              mOnExitAlarm.setAlarm(ON_EXIT_CLOSE_DELAY);
          }
          mReorderAlarm.cancelAlarm();
      }
  
      public void onDropCompleted(final View target, final DragObject d,
              final boolean isFlingToDelete, final boolean success) {
          if (mDeferDropAfterUninstall) {
              Log.d(TAG, "Deferred handling drop because waiting for uninstall.");
              mDeferredAction = new Runnable() {
                      public void run() {
                          onDropCompleted(target, d, isFlingToDelete, success);
                          mDeferredAction = null;
                      }
                  };
              return;
          }
  
          boolean beingCalledAfterUninstall = mDeferredAction != null;
          boolean successfulDrop =
                  success && (!beingCalledAfterUninstall || mUninstallSuccessful);
  
          if (successfulDrop) {
              if (mDeleteFolderOnDropCompleted && !mItemAddedBackToSelfViaIcon && target != this) {
                  replaceFolderWithFinalItem();
              }
          } else {
              setupContentForNumItems(getItemCount());
              // The drag failed, we need to return the item to the folder
              mFolderIcon.onDrop(d);
          }
  
          if (target != this) {
              if (mOnExitAlarm.alarmPending()) {
                  mOnExitAlarm.cancelAlarm();
                  if (!successfulDrop) {
                      mSuppressFolderDeletion = true;
                  }
                  completeDragExit();
              }
          }
  
          mDeleteFolderOnDropCompleted = false;
          mDragInProgress = false;
          mItemAddedBackToSelfViaIcon = false;
          mCurrentDragInfo = null;
          mCurrentDragView = null;
          mSuppressOnAdd = false;
  
          // Reordering may have occured, and we need to save the new item locations. We do this once
          // at the end to prevent unnecessary database operations.
          updateItemLocationsInDatabaseBatch();
      }
  
      public void deferCompleteDropAfterUninstallActivity() {
          mDeferDropAfterUninstall = true;
      }
  
      public void onUninstallActivityReturned(boolean success) {
          mDeferDropAfterUninstall = false;
          mUninstallSuccessful = success;
          if (mDeferredAction != null) {
              mDeferredAction.run();
          }
      }
  
      @Override
      public float getIntrinsicIconScaleFactor() {
          return 1f;
      }
  
      @Override
      public boolean supportsFlingToDelete() {
          return true;
      }
  
      @Override
      public boolean supportsAppInfoDropTarget() {
          return false;
      }
  
      @Override
      public boolean supportsDeleteDropTarget() {
          return true;
      }
  
      public void onFlingToDelete(DragObject d, int x, int y, PointF vec) {
          // Do nothing
      }
  
      @Override
      public void onFlingToDeleteCompleted() {
          // Do nothing
      }
  
      private void updateItemLocationsInDatabase() {
          ArrayList<View> list = getItemsInReadingOrder();
          for (int i = 0; i < list.size(); i++) {
              View v = list.get(i);
              ItemInfo info = (ItemInfo) v.getTag();
              LauncherModel.moveItemInDatabase(mLauncher, info, mInfo.id, 0,
                      info.cellX, info.cellY);
          }
      }
  
      private void updateItemLocationsInDatabaseBatch() {
          ArrayList<View> list = getItemsInReadingOrder();
          ArrayList<ItemInfo> items = new ArrayList<ItemInfo>();
          for (int i = 0; i < list.size(); i++) {
              View v = list.get(i);
              ItemInfo info = (ItemInfo) v.getTag();
              items.add(info);
          }
  
          LauncherModel.moveItemsInDatabase(mLauncher, items, mInfo.id, 0);
      }
  
      public void addItemLocationsInDatabase() {
          ArrayList<View> list = getItemsInReadingOrder();
          for (int i = 0; i < list.size(); i++) {
              View v = list.get(i);
              ItemInfo info = (ItemInfo) v.getTag();
              LauncherModel.addItemToDatabase(mLauncher, info, mInfo.id, 0,
                          info.cellX, info.cellY, false);
          }
      }
  
      public void notifyDrop() {
          if (mDragInProgress) {
              mItemAddedBackToSelfViaIcon = true;
          }
      }
  
      public boolean isDropEnabled() {
          return true;
      }
  
      private void setupContentDimensions(int count) {
          ArrayList<View> list = getItemsInReadingOrder();
  
          int countX = mContent.getCountX();
          int countY = mContent.getCountY();
          boolean done = false;
  
          while (!done) {
              int oldCountX = countX;
              int oldCountY = countY;
              if (countX * countY < count) {
                  // Current grid is too small, expand it
                  if ((countX <= countY || countY == mMaxCountY) && countX < mMaxCountX) {
                      countX++;
                  } else if (countY < mMaxCountY) {
                      countY++;
                  }
                  if (countY == 0) countY++;
              } else if ((countY - 1) * countX >= count && countY >= countX) {
                  countY = Math.max(0, countY - 1);
              } else if ((countX - 1) * countY >= count) {
                  countX = Math.max(0, countX - 1);
              }
              done = countX == oldCountX && countY == oldCountY;
          }
          mContent.setGridSize(countX, countY);
          arrangeChildren(list);
      }
  
      public boolean isFull() {
          return getItemCount() >= mMaxNumItems;
      }
  
      private void centerAboutIcon() {
          DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams();
  
          DragLayer parent = (DragLayer) mLauncher.findViewById(R.id.drag_layer);
          int width = getPaddingLeft() + getPaddingRight() + mContent.getDesiredWidth();
          int height = getFolderHeight();
  
          float scale = parent.getDescendantRectRelativeToSelf(mFolderIcon, mTempRect);
  
          LauncherAppState app = LauncherAppState.getInstance();
          DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
  
          int centerX = (int) (mTempRect.left + mTempRect.width() * scale / 2);
          int centerY = (int) (mTempRect.top + mTempRect.height() * scale / 2);
          int centeredLeft = centerX - width / 2;
          int centeredTop = centerY - height / 2;
          int currentPage = mLauncher.getWorkspace().getNextPage();
          // In case the workspace is scrolling, we need to use the final scroll to compute
          // the folders bounds.
          mLauncher.getWorkspace().setFinalScrollForPageChange(currentPage);
          // We first fetch the currently visible CellLayoutChildren
          CellLayout currentLayout = (CellLayout) mLauncher.getWorkspace().getChildAt(currentPage);
          ShortcutAndWidgetContainer boundingLayout = currentLayout.getShortcutsAndWidgets();
          Rect bounds = new Rect();
          parent.getDescendantRectRelativeToSelf(boundingLayout, bounds);
          // We reset the workspaces scroll
          mLauncher.getWorkspace().resetFinalScrollForPageChange(currentPage);
  
          // We need to bound the folder to the currently visible CellLayoutChildren
          int left = Math.min(Math.max(bounds.left, centeredLeft),
                  bounds.left + bounds.width() - width);
          int top = Math.min(Math.max(bounds.top, centeredTop),
                  bounds.top + bounds.height() - height);
          if (grid.isPhone() && (grid.availableWidthPx - width) < grid.iconSizePx) {
              // Center the folder if it is full (on phones only)
              left = (grid.availableWidthPx - width) / 2;
          } else if (width >= bounds.width()) {
              // If the folder doesn't fit within the bounds, center it about the desired bounds
              left = bounds.left + (bounds.width() - width) / 2;
          }
          if (height >= bounds.height()) {
              top = bounds.top + (bounds.height() - height) / 2;
          }
  
          int folderPivotX = width / 2 + (centeredLeft - left);
          int folderPivotY = height / 2 + (centeredTop - top);
          setPivotX(folderPivotX);
          setPivotY(folderPivotY);
          mFolderIconPivotX = (int) (mFolderIcon.getMeasuredWidth() *
                  (1.0f * folderPivotX / width));
          mFolderIconPivotY = (int) (mFolderIcon.getMeasuredHeight() *
                  (1.0f * folderPivotY / height));
  
          lp.width = width;
          lp.height = height;
          lp.x = left;
          lp.y = top;
      }
  
      float getPivotXForIconAnimation() {
          return mFolderIconPivotX;
      }
      float getPivotYForIconAnimation() {
          return mFolderIconPivotY;
      }
  
      private void setupContentForNumItems(int count) {
          setupContentDimensions(count);
  
          DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams();
          if (lp == null) {
              lp = new DragLayer.LayoutParams(0, 0);
              lp.customPosition = true;
              setLayoutParams(lp);
          }
          centerAboutIcon();
      }
  
      private int getContentAreaHeight() {
          LauncherAppState app = LauncherAppState.getInstance();
          DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
          Rect workspacePadding = grid.getWorkspacePadding(grid.isLandscape ?
                  CellLayout.LANDSCAPE : CellLayout.PORTRAIT);
          int maxContentAreaHeight = grid.availableHeightPx -
                  workspacePadding.top - workspacePadding.bottom -
                  mFolderNameHeight;
          int height = Math.min(maxContentAreaHeight,
                  mContent.getDesiredHeight());
          return Math.max(height, MIN_CONTENT_DIMEN);
      }
  
      private int getContentAreaWidth() {
          return Math.max(mContent.getDesiredWidth(), MIN_CONTENT_DIMEN);
      }
  
      private int getFolderHeight() {
          int height = getPaddingTop() + getPaddingBottom()
                  + getContentAreaHeight() + mFolderNameHeight;
          return height;
      }
  
      protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
          int width = getPaddingLeft() + getPaddingRight() + mContent.getDesiredWidth();
          int height = getFolderHeight();
          int contentAreaWidthSpec = MeasureSpec.makeMeasureSpec(getContentAreaWidth(),
                  MeasureSpec.EXACTLY);
          int contentAreaHeightSpec = MeasureSpec.makeMeasureSpec(getContentAreaHeight(),
                  MeasureSpec.EXACTLY);
  
          if (LauncherAppState.isDisableAllApps()) {
              // Don't cap the height of the content to allow scrolling.
              mContent.setFixedSize(getContentAreaWidth(), mContent.getDesiredHeight());
          } else {
              mContent.setFixedSize(getContentAreaWidth(), getContentAreaHeight());
          }
  
          mScrollView.measure(contentAreaWidthSpec, contentAreaHeightSpec);
          mFolderName.measure(contentAreaWidthSpec,
                  MeasureSpec.makeMeasureSpec(mFolderNameHeight, MeasureSpec.EXACTLY));
          setMeasuredDimension(width, height);
      }
  
      private void arrangeChildren(ArrayList<View> list) {
          int[] vacant = new int[2];
          if (list == null) {
              list = getItemsInReadingOrder();
          }
          mContent.removeAllViews();
  
          for (int i = 0; i < list.size(); i++) {
              View v = list.get(i);
              mContent.getVacantCell(vacant, 1, 1);
              CellLayout.LayoutParams lp = (CellLayout.LayoutParams) v.getLayoutParams();
              lp.cellX = vacant[0];
              lp.cellY = vacant[1];
              ItemInfo info = (ItemInfo) v.getTag();
              if (info.cellX != vacant[0] || info.cellY != vacant[1]) {
                  info.cellX = vacant[0];
                  info.cellY = vacant[1];
                  LauncherModel.addOrMoveItemInDatabase(mLauncher, info, mInfo.id, 0,
                          info.cellX, info.cellY);
              }
              boolean insert = false;
              mContent.addViewToCellLayout(v, insert ? 0 : -1, (int)info.id, lp, true);
          }
          mItemsInvalidated = true;
      }
  
      public int getItemCount() {
          return mContent.getShortcutsAndWidgets().getChildCount();
      }
  
      public View getItemAt(int index) {
          return mContent.getShortcutsAndWidgets().getChildAt(index);
      }
  
      private void onCloseComplete() {
          DragLayer parent = (DragLayer) getParent();
          if (parent != null) {
              parent.removeView(this);
          }
          mDragController.removeDropTarget((DropTarget) this);
          clearFocus();
          mFolderIcon.requestFocus();
  
          if (mRearrangeOnClose) {
              setupContentForNumItems(getItemCount());
              mRearrangeOnClose = false;
          }
          if (getItemCount() <= 1) {
              if (!mDragInProgress && !mSuppressFolderDeletion) {
                  replaceFolderWithFinalItem();
              } else if (mDragInProgress) {
                  mDeleteFolderOnDropCompleted = true;
              }
          }
          mSuppressFolderDeletion = false;
      }
  
      private void replaceFolderWithFinalItem() {
          // Add the last remaining child to the workspace in place of the folder
          Runnable onCompleteRunnable = new Runnable() {
              @Override
              public void run() {
                  CellLayout cellLayout = mLauncher.getCellLayout(mInfo.container, mInfo.screenId);
  
                  View child = null;
                  // Move the item from the folder to the workspace, in the position of the folder
                  if (getItemCount() == 1) {
                      ShortcutInfo finalItem = mInfo.contents.get(0);
                      child = mLauncher.createShortcut(R.layout.application, cellLayout,
                              finalItem);
                      LauncherModel.addOrMoveItemInDatabase(mLauncher, finalItem, mInfo.container,
                              mInfo.screenId, mInfo.cellX, mInfo.cellY);
                  }
                  if (getItemCount() <= 1) {
                      // Remove the folder
                      LauncherModel.deleteItemFromDatabase(mLauncher, mInfo);
                      if (cellLayout != null) {
                          // b/12446428 -- sometimes the cell layout has already gone away?
                          cellLayout.removeView(mFolderIcon);
                      }
                      if (mFolderIcon instanceof DropTarget) {
                          mDragController.removeDropTarget((DropTarget) mFolderIcon);
                      }
                      mLauncher.removeFolder(mInfo);
                  }
                  // We add the child after removing the folder to prevent both from existing at
                  // the same time in the CellLayout.  We need to add the new item with addInScreenFromBind()
                  // to ensure that hotseat items are placed correctly.
                  if (child != null) {
                      mLauncher.getWorkspace().addInScreenFromBind(child, mInfo.container, mInfo.screenId,
                              mInfo.cellX, mInfo.cellY, mInfo.spanX, mInfo.spanY);
                  }
              }
          };
          View finalChild = getItemAt(0);
          if (finalChild != null) {
              mFolderIcon.performDestroyAnimation(finalChild, onCompleteRunnable);
          } else {
              onCompleteRunnable.run();
          }
          mDestroyed = true;
      }
  
      boolean isDestroyed() {
          return mDestroyed;
      }
  
      // This method keeps track of the last item in the folder for the purposes
      // of keyboard focus
      private void updateTextViewFocus() {
          View lastChild = getItemAt(getItemCount() - 1);
          getItemAt(getItemCount() - 1);
          if (lastChild != null) {
              mFolderName.setNextFocusDownId(lastChild.getId());
              mFolderName.setNextFocusRightId(lastChild.getId());
              mFolderName.setNextFocusLeftId(lastChild.getId());
              mFolderName.setNextFocusUpId(lastChild.getId());
          }
      }
  
      public void onDrop(DragObject d) {
          Runnable cleanUpRunnable = null;
  
          // If we are coming from All Apps space, we defer removing the extra empty screen
          // until the folder closes
          if (d.dragSource != mLauncher.getWorkspace() && !(d.dragSource instanceof Folder)) {
              cleanUpRunnable = new Runnable() {
                  @Override
                  public void run() {
                      mLauncher.exitSpringLoadedDragModeDelayed(true,
                              Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT,
                              null);
                  }
              };
          }
  
          View currentDragView;
          ShortcutInfo si = mCurrentDragInfo;
          if (mIsExternalDrag) {
              si.cellX = mEmptyCell[0];
              si.cellY = mEmptyCell[1];
  
              // Actually move the item in the database if it was an external drag. Call this
              // before creating the view, so that ShortcutInfo is updated appropriately.
              LauncherModel.addOrMoveItemInDatabase(
                      mLauncher, si, mInfo.id, 0, si.cellX, si.cellY);
  
              // We only need to update the locations if it doesn't get handled in #onDropCompleted.
              if (d.dragSource != this) {
                  updateItemLocationsInDatabaseBatch();
              }
              mIsExternalDrag = false;
  
              currentDragView = createAndAddShortcut(si);
          } else {
              currentDragView = mCurrentDragView;
              CellLayout.LayoutParams lp = (CellLayout.LayoutParams) currentDragView.getLayoutParams();
              si.cellX = lp.cellX = mEmptyCell[0];
              si.cellX = lp.cellY = mEmptyCell[1];
              mContent.addViewToCellLayout(currentDragView, -1, (int) si.id, lp, true);
          }
  
          if (d.dragView.hasDrawn()) {
  
              // Temporarily reset the scale such that the animation target gets calculated correctly.
              float scaleX = getScaleX();
              float scaleY = getScaleY();
              setScaleX(1.0f);
              setScaleY(1.0f);
              mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, currentDragView,
                      cleanUpRunnable, null);
              setScaleX(scaleX);
              setScaleY(scaleY);
          } else {
              d.deferDragViewCleanupPostAnimation = false;
              currentDragView.setVisibility(VISIBLE);
          }
          mItemsInvalidated = true;
          setupContentDimensions(getItemCount());
  
          // Temporarily suppress the listener, as we did all the work already here.
          mSuppressOnAdd = true;
          mInfo.add(si);
          mSuppressOnAdd = false;
          // Clear the drag info, as it is no longer being dragged.
          mCurrentDragInfo = null;
      }
  
      // This is used so the item doesn't immediately appear in the folder when added. In one case
      // we need to create the illusion that the item isn't added back to the folder yet, to
      // to correspond to the animation of the icon back into the folder. This is
      public void hideItem(ShortcutInfo info) {
          View v = getViewForInfo(info);
          v.setVisibility(INVISIBLE);
      }
      public void showItem(ShortcutInfo info) {
          View v = getViewForInfo(info);
          v.setVisibility(VISIBLE);
      }
  
      public void onAdd(ShortcutInfo item) {
          mItemsInvalidated = true;
          // If the item was dropped onto this open folder, we have done the work associated
          // with adding the item to the folder, as indicated by mSuppressOnAdd being set
          if (mSuppressOnAdd) return;
          if (!findAndSetEmptyCells(item)) {
              // The current layout is full, can we expand it?
              setupContentForNumItems(getItemCount() + 1);
              findAndSetEmptyCells(item);
          }
          createAndAddShortcut(item);
          LauncherModel.addOrMoveItemInDatabase(
                  mLauncher, item, mInfo.id, 0, item.cellX, item.cellY);
      }
  
      public void onRemove(ShortcutInfo item) {
          mItemsInvalidated = true;
          // If this item is being dragged from this open folder, we have already handled
          // the work associated with removing the item, so we don't have to do anything here.
          if (item == mCurrentDragInfo) return;
          View v = getViewForInfo(item);
          mContent.removeView(v);
          if (mState == STATE_ANIMATING) {
              mRearrangeOnClose = true;
          } else {
              setupContentForNumItems(getItemCount());
          }
          if (getItemCount() <= 1) {
              replaceFolderWithFinalItem();
          }
      }
  
      private View getViewForInfo(ShortcutInfo item) {
          for (int j = 0; j < mContent.getCountY(); j++) {
              for (int i = 0; i < mContent.getCountX(); i++) {
                  View v = mContent.getChildAt(i, j);
                  if (v.getTag() == item) {
                      return v;
                  }
              }
          }
          return null;
      }
  
      public void onItemsChanged() {
          updateTextViewFocus();
      }
  
      public void onTitleChanged(CharSequence title) {
      }
  
      public ArrayList<View> getItemsInReadingOrder() {
          if (mItemsInvalidated) {
              mItemsInReadingOrder.clear();
              for (int j = 0; j < mContent.getCountY(); j++) {
                  for (int i = 0; i < mContent.getCountX(); i++) {
                      View v = mContent.getChildAt(i, j);
                      if (v != null) {
                          mItemsInReadingOrder.add(v);
                      }
                  }
              }
              mItemsInvalidated = false;
          }
          return mItemsInReadingOrder;
      }
  
      public void getLocationInDragLayer(int[] loc) {
          mLauncher.getDragLayer().getLocationInDragLayer(this, loc);
      }
  
      public void onFocusChange(View v, boolean hasFocus) {
          if (v == mFolderName && hasFocus) {
              startEditingFolderName();
          }
      }
  
      @Override
      public void getHitRectRelativeToDragLayer(Rect outRect) {
          getHitRect(outRect);
      }
  }
  