Home | History | Annotate | Download | only in launcher2
      1 /*
      2  * Copyright (C) 2008 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.launcher2;
     18 
     19 import android.animation.Animator;
     20 import android.animation.AnimatorListenerAdapter;
     21 import android.animation.ObjectAnimator;
     22 import android.animation.PropertyValuesHolder;
     23 import android.animation.ValueAnimator;
     24 import android.animation.ValueAnimator.AnimatorUpdateListener;
     25 import android.content.Context;
     26 import android.content.res.Resources;
     27 import android.graphics.Rect;
     28 import android.graphics.drawable.Drawable;
     29 import android.text.InputType;
     30 import android.text.Selection;
     31 import android.text.Spannable;
     32 import android.util.AttributeSet;
     33 import android.view.ActionMode;
     34 import android.view.KeyEvent;
     35 import android.view.LayoutInflater;
     36 import android.view.Menu;
     37 import android.view.MenuItem;
     38 import android.view.MotionEvent;
     39 import android.view.View;
     40 import android.view.animation.AccelerateInterpolator;
     41 import android.view.animation.DecelerateInterpolator;
     42 import android.view.inputmethod.EditorInfo;
     43 import android.view.inputmethod.InputMethodManager;
     44 import android.widget.LinearLayout;
     45 import android.widget.TextView;
     46 
     47 import com.android.launcher.R;
     48 import com.android.launcher2.FolderInfo.FolderListener;
     49 
     50 import java.util.ArrayList;
     51 
     52 /**
     53  * Represents a set of icons chosen by the user or generated by the system.
     54  */
     55 public class Folder extends LinearLayout implements DragSource, View.OnClickListener,
     56         View.OnLongClickListener, DropTarget, FolderListener, TextView.OnEditorActionListener,
     57         View.OnFocusChangeListener {
     58 
     59     private static final String TAG = "Launcher.Folder";
     60 
     61     protected DragController mDragController;
     62     protected Launcher mLauncher;
     63     protected FolderInfo mInfo;
     64 
     65     static final int STATE_NONE = -1;
     66     static final int STATE_SMALL = 0;
     67     static final int STATE_ANIMATING = 1;
     68     static final int STATE_OPEN = 2;
     69 
     70     private int mExpandDuration;
     71     protected CellLayout mContent;
     72     private final LayoutInflater mInflater;
     73     private final IconCache mIconCache;
     74     private int mState = STATE_NONE;
     75     private static final int FULL_GROW = 0;
     76     private static final int PARTIAL_GROW = 1;
     77     private static final int REORDER_ANIMATION_DURATION = 230;
     78     private static final int ON_EXIT_CLOSE_DELAY = 800;
     79     private int mMode = PARTIAL_GROW;
     80     private boolean mRearrangeOnClose = false;
     81     private FolderIcon mFolderIcon;
     82     private int mMaxCountX;
     83     private int mMaxCountY;
     84     private Rect mNewSize = new Rect();
     85     private Rect mIconRect = new Rect();
     86     private ArrayList<View> mItemsInReadingOrder = new ArrayList<View>();
     87     private Drawable mIconDrawable;
     88     boolean mItemsInvalidated = false;
     89     private ShortcutInfo mCurrentDragInfo;
     90     private View mCurrentDragView;
     91     boolean mSuppressOnAdd = false;
     92     private int[] mTargetCell = new int[2];
     93     private int[] mPreviousTargetCell = new int[2];
     94     private int[] mEmptyCell = new int[2];
     95     private Alarm mReorderAlarm = new Alarm();
     96     private Alarm mOnExitAlarm = new Alarm();
     97     private int mFolderNameHeight;
     98     private Rect mHitRect = new Rect();
     99     private Rect mTempRect = new Rect();
    100     private boolean mDragInProgress = false;
    101     private boolean mDeleteFolderOnDropCompleted = false;
    102     private boolean mSuppressFolderDeletion = false;
    103     private boolean mItemAddedBackToSelfViaIcon = false;
    104     FolderEditText mFolderName;
    105 
    106     private boolean mIsEditingName = false;
    107     private InputMethodManager mInputMethodManager;
    108 
    109     private static String sDefaultFolderName;
    110     private static String sHintText;
    111 
    112     /**
    113      * Used to inflate the Workspace from XML.
    114      *
    115      * @param context The application's context.
    116      * @param attrs The attribtues set containing the Workspace's customization values.
    117      */
    118     public Folder(Context context, AttributeSet attrs) {
    119         super(context, attrs);
    120         setAlwaysDrawnWithCacheEnabled(false);
    121         mInflater = LayoutInflater.from(context);
    122         mIconCache = ((LauncherApplication)context.getApplicationContext()).getIconCache();
    123         mMaxCountX = LauncherModel.getCellCountX();
    124         mMaxCountY = LauncherModel.getCellCountY();
    125 
    126         mInputMethodManager = (InputMethodManager)
    127                 mContext.getSystemService(Context.INPUT_METHOD_SERVICE);
    128 
    129         Resources res = getResources();
    130         mExpandDuration = res.getInteger(R.integer.config_folderAnimDuration);
    131 
    132         if (sDefaultFolderName == null) {
    133             sDefaultFolderName = res.getString(R.string.folder_name);
    134         }
    135         if (sHintText == null) {
    136             sHintText = res.getString(R.string.folder_hint_text);
    137         }
    138         mLauncher = (Launcher) context;
    139         // We need this view to be focusable in touch mode so that when text editing of the folder
    140         // name is complete, we have something to focus on, thus hiding the cursor and giving
    141         // reliable behvior when clicking the text field (since it will always gain focus on click).
    142         setFocusableInTouchMode(true);
    143     }
    144 
    145     @Override
    146     protected void onFinishInflate() {
    147         super.onFinishInflate();
    148         mContent = (CellLayout) findViewById(R.id.folder_content);
    149         mContent.setGridSize(0, 0);
    150         mFolderName = (FolderEditText) findViewById(R.id.folder_name);
    151         mFolderName.setFolder(this);
    152         mFolderName.setOnFocusChangeListener(this);
    153 
    154         // We find out how tall the text view wants to be (it is set to wrap_content), so that
    155         // we can allocate the appropriate amount of space for it.
    156         int measureSpec = MeasureSpec.UNSPECIFIED;
    157         mFolderName.measure(measureSpec, measureSpec);
    158         mFolderNameHeight = mFolderName.getMeasuredHeight();
    159 
    160         // We disable action mode for now since it messes up the view on phones
    161         mFolderName.setCustomSelectionActionModeCallback(mActionModeCallback);
    162         mFolderName.setOnEditorActionListener(this);
    163         mFolderName.setSelectAllOnFocus(true);
    164         mFolderName.setInputType(mFolderName.getInputType() |
    165                 InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS | InputType.TYPE_TEXT_FLAG_CAP_WORDS);
    166     }
    167 
    168     private ActionMode.Callback mActionModeCallback = new ActionMode.Callback() {
    169         public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
    170             return false;
    171         }
    172 
    173         public boolean onCreateActionMode(ActionMode mode, Menu menu) {
    174             return false;
    175         }
    176 
    177         public void onDestroyActionMode(ActionMode mode) {
    178         }
    179 
    180         public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
    181             return false;
    182         }
    183     };
    184 
    185     public void onClick(View v) {
    186         Object tag = v.getTag();
    187         if (tag instanceof ShortcutInfo) {
    188             // refactor this code from Folder
    189             ShortcutInfo item = (ShortcutInfo) tag;
    190             int[] pos = new int[2];
    191             v.getLocationOnScreen(pos);
    192             item.intent.setSourceBounds(new Rect(pos[0], pos[1],
    193                     pos[0] + v.getWidth(), pos[1] + v.getHeight()));
    194             mLauncher.startActivitySafely(item.intent, item);
    195         }
    196     }
    197 
    198     public boolean onLongClick(View v) {
    199         Object tag = v.getTag();
    200         if (tag instanceof ShortcutInfo) {
    201             ShortcutInfo item = (ShortcutInfo) tag;
    202             if (!v.isInTouchMode()) {
    203                 return false;
    204             }
    205 
    206             mLauncher.dismissFolderCling(null);
    207 
    208             mLauncher.getWorkspace().onDragStartedWithItem(v);
    209             mLauncher.getWorkspace().beginDragShared(v, this);
    210             mIconDrawable = ((TextView) v).getCompoundDrawables()[1];
    211 
    212             mCurrentDragInfo = item;
    213             mEmptyCell[0] = item.cellX;
    214             mEmptyCell[1] = item.cellY;
    215             mCurrentDragView = v;
    216 
    217             mContent.removeView(mCurrentDragView);
    218             mInfo.remove(mCurrentDragInfo);
    219             mDragInProgress = true;
    220             mItemAddedBackToSelfViaIcon = false;
    221         }
    222         return true;
    223     }
    224 
    225     public boolean isEditingName() {
    226         return mIsEditingName;
    227     }
    228 
    229     public void startEditingFolderName() {
    230         mFolderName.setHint("");
    231         mIsEditingName = true;
    232     }
    233 
    234     public void dismissEditingName() {
    235         mInputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0);
    236         doneEditingFolderName(true);
    237     }
    238 
    239     public void doneEditingFolderName(boolean commit) {
    240         mFolderName.setHint(sHintText);
    241         // Convert to a string here to ensure that no other state associated with the text field
    242         // gets saved.
    243         mInfo.setTitle(mFolderName.getText().toString());
    244         LauncherModel.updateItemInDatabase(mLauncher, mInfo);
    245 
    246         // In order to clear the focus from the text field, we set the focus on ourself. This
    247         // ensures that every time the field is clicked, focus is gained, giving reliable behavior.
    248         requestFocus();
    249 
    250         Selection.setSelection((Spannable) mFolderName.getText(), 0, 0);
    251         mIsEditingName = false;
    252     }
    253 
    254     public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
    255         if (actionId == EditorInfo.IME_ACTION_DONE) {
    256             dismissEditingName();
    257             return true;
    258         }
    259         return false;
    260     }
    261 
    262     public View getEditTextRegion() {
    263         return mFolderName;
    264     }
    265 
    266     public Drawable getDragDrawable() {
    267         return mIconDrawable;
    268     }
    269 
    270     /**
    271      * We need to handle touch events to prevent them from falling through to the workspace below.
    272      */
    273     @Override
    274     public boolean onTouchEvent(MotionEvent ev) {
    275         return true;
    276     }
    277 
    278     public void setDragController(DragController dragController) {
    279         mDragController = dragController;
    280     }
    281 
    282     void setFolderIcon(FolderIcon icon) {
    283         mFolderIcon = icon;
    284     }
    285 
    286     /**
    287      * @return the FolderInfo object associated with this folder
    288      */
    289     FolderInfo getInfo() {
    290         return mInfo;
    291     }
    292 
    293     void bind(FolderInfo info) {
    294         mInfo = info;
    295         ArrayList<ShortcutInfo> children = info.contents;
    296         ArrayList<ShortcutInfo> overflow = new ArrayList<ShortcutInfo>();
    297         setupContentForNumItems(children.size());
    298         int count = 0;
    299         for (int i = 0; i < children.size(); i++) {
    300             ShortcutInfo child = (ShortcutInfo) children.get(i);
    301             if (!createAndAddShortcut(child)) {
    302                 overflow.add(child);
    303             } else {
    304                 count++;
    305             }
    306         }
    307 
    308         // We rearrange the items in case there are any empty gaps
    309         setupContentForNumItems(count);
    310 
    311         // If our folder has too many items we prune them from the list. This is an issue
    312         // when upgrading from the old Folders implementation which could contain an unlimited
    313         // number of items.
    314         for (ShortcutInfo item: overflow) {
    315             mInfo.remove(item);
    316             LauncherModel.deleteItemFromDatabase(mLauncher, item);
    317         }
    318 
    319         mItemsInvalidated = true;
    320         updateTextViewFocus();
    321         mInfo.addListener(this);
    322 
    323         if (!sDefaultFolderName.contentEquals(mInfo.title)) {
    324             mFolderName.setText(mInfo.title);
    325         } else {
    326             mFolderName.setText("");
    327         }
    328     }
    329 
    330     /**
    331      * Creates a new UserFolder, inflated from R.layout.user_folder.
    332      *
    333      * @param context The application's context.
    334      *
    335      * @return A new UserFolder.
    336      */
    337     static Folder fromXml(Context context) {
    338         return (Folder) LayoutInflater.from(context).inflate(R.layout.user_folder, null);
    339     }
    340 
    341     /**
    342      * This method is intended to make the UserFolder to be visually identical in size and position
    343      * to its associated FolderIcon. This allows for a seamless transition into the expanded state.
    344      */
    345     private void positionAndSizeAsIcon() {
    346         if (!(getParent() instanceof DragLayer)) return;
    347 
    348         DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams();
    349 
    350         if (mMode == PARTIAL_GROW) {
    351             setScaleX(0.8f);
    352             setScaleY(0.8f);
    353             setAlpha(0f);
    354         } else {
    355             mLauncher.getDragLayer().getDescendantRectRelativeToSelf(mFolderIcon, mIconRect);
    356             lp.width = mIconRect.width();
    357             lp.height = mIconRect.height();
    358             lp.x = mIconRect.left;
    359             lp.y = mIconRect.top;
    360             mContent.setAlpha(0);
    361         }
    362         mState = STATE_SMALL;
    363     }
    364 
    365     public void animateOpen() {
    366         positionAndSizeAsIcon();
    367 
    368         if (!(getParent() instanceof DragLayer)) return;
    369 
    370         ObjectAnimator oa;
    371         DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams();
    372 
    373         centerAboutIcon();
    374         if (mMode == PARTIAL_GROW) {
    375             PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 1);
    376             PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 1.0f);
    377             PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", 1.0f);
    378             oa = ObjectAnimator.ofPropertyValuesHolder(this, alpha, scaleX, scaleY);
    379         } else {
    380             PropertyValuesHolder width = PropertyValuesHolder.ofInt("width", mNewSize.width());
    381             PropertyValuesHolder height = PropertyValuesHolder.ofInt("height", mNewSize.height());
    382             PropertyValuesHolder x = PropertyValuesHolder.ofInt("x", mNewSize.left);
    383             PropertyValuesHolder y = PropertyValuesHolder.ofInt("y", mNewSize.top);
    384             oa = ObjectAnimator.ofPropertyValuesHolder(lp, width, height, x, y);
    385             oa.addUpdateListener(new AnimatorUpdateListener() {
    386                 public void onAnimationUpdate(ValueAnimator animation) {
    387                     requestLayout();
    388                 }
    389             });
    390 
    391             PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 1.0f);
    392             ObjectAnimator alphaOa = ObjectAnimator.ofPropertyValuesHolder(mContent, alpha);
    393             alphaOa.setDuration(mExpandDuration);
    394             alphaOa.setInterpolator(new AccelerateInterpolator(2.0f));
    395             alphaOa.start();
    396         }
    397 
    398         oa.addListener(new AnimatorListenerAdapter() {
    399             @Override
    400             public void onAnimationStart(Animator animation) {
    401                 mState = STATE_ANIMATING;
    402             }
    403             @Override
    404             public void onAnimationEnd(Animator animation) {
    405                 mState = STATE_OPEN;
    406 
    407                 Cling cling = mLauncher.showFirstRunFoldersCling();
    408                 if (cling != null) {
    409                     cling.bringToFront();
    410                 }
    411                 setFocusOnFirstChild();
    412             }
    413         });
    414         oa.setDuration(mExpandDuration);
    415         oa.start();
    416     }
    417 
    418     private void setFocusOnFirstChild() {
    419         View firstChild = mContent.getChildAt(0, 0);
    420         if (firstChild != null) {
    421             firstChild.requestFocus();
    422         }
    423     }
    424 
    425     public void animateClosed() {
    426         if (!(getParent() instanceof DragLayer)) return;
    427 
    428         ObjectAnimator oa;
    429         if (mMode == PARTIAL_GROW) {
    430             PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 0);
    431             PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 0.9f);
    432             PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", 0.9f);
    433             oa = ObjectAnimator.ofPropertyValuesHolder(this, alpha, scaleX, scaleY);
    434         } else {
    435             DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams();
    436 
    437             PropertyValuesHolder width = PropertyValuesHolder.ofInt("width", mIconRect.width());
    438             PropertyValuesHolder height = PropertyValuesHolder.ofInt("height", mIconRect.height());
    439             PropertyValuesHolder x = PropertyValuesHolder.ofInt("x", mIconRect.left);
    440             PropertyValuesHolder y = PropertyValuesHolder.ofInt("y", mIconRect.top);
    441             oa = ObjectAnimator.ofPropertyValuesHolder(lp, width, height, x, y);
    442             oa.addUpdateListener(new AnimatorUpdateListener() {
    443                 public void onAnimationUpdate(ValueAnimator animation) {
    444                     requestLayout();
    445                 }
    446             });
    447 
    448             PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 0f);
    449             ObjectAnimator alphaOa = ObjectAnimator.ofPropertyValuesHolder(mContent, alpha);
    450             alphaOa.setDuration(mExpandDuration);
    451             alphaOa.setInterpolator(new DecelerateInterpolator(2.0f));
    452             alphaOa.start();
    453         }
    454 
    455         oa.addListener(new AnimatorListenerAdapter() {
    456             @Override
    457             public void onAnimationEnd(Animator animation) {
    458                 onCloseComplete();
    459                 mState = STATE_SMALL;
    460             }
    461             @Override
    462             public void onAnimationStart(Animator animation) {
    463                 mState = STATE_ANIMATING;
    464             }
    465         });
    466         oa.setDuration(mExpandDuration);
    467         oa.start();
    468     }
    469 
    470     void notifyDataSetChanged() {
    471         // recreate all the children if the data set changes under us. We may want to do this more
    472         // intelligently (ie just removing the views that should no longer exist)
    473         mContent.removeAllViewsInLayout();
    474         bind(mInfo);
    475     }
    476 
    477     public boolean acceptDrop(DragObject d) {
    478         final ItemInfo item = (ItemInfo) d.dragInfo;
    479         final int itemType = item.itemType;
    480         return ((itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION ||
    481                     itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) &&
    482                     !isFull());
    483     }
    484 
    485     protected boolean findAndSetEmptyCells(ShortcutInfo item) {
    486         int[] emptyCell = new int[2];
    487         if (mContent.findCellForSpan(emptyCell, item.spanX, item.spanY)) {
    488             item.cellX = emptyCell[0];
    489             item.cellY = emptyCell[1];
    490             return true;
    491         } else {
    492             return false;
    493         }
    494     }
    495 
    496     protected boolean createAndAddShortcut(ShortcutInfo item) {
    497         final TextView textView =
    498             (TextView) mInflater.inflate(R.layout.application, this, false);
    499         textView.setCompoundDrawablesWithIntrinsicBounds(null,
    500                 new FastBitmapDrawable(item.getIcon(mIconCache)), null, null);
    501         textView.setText(item.title);
    502         textView.setTag(item);
    503 
    504         textView.setOnClickListener(this);
    505         textView.setOnLongClickListener(this);
    506 
    507         // We need to check here to verify that the given item's location isn't already occupied
    508         // by another item. If it is, we need to find the next available slot and assign
    509         // it that position. This is an issue when upgrading from the old Folders implementation
    510         // which could contain an unlimited number of items.
    511         if (mContent.getChildAt(item.cellX, item.cellY) != null || item.cellX < 0 || item.cellY < 0
    512                 || item.cellX >= mContent.getCountX() || item.cellY >= mContent.getCountY()) {
    513             if (!findAndSetEmptyCells(item)) {
    514                 return false;
    515             }
    516         }
    517 
    518         CellLayout.LayoutParams lp =
    519             new CellLayout.LayoutParams(item.cellX, item.cellY, item.spanX, item.spanY);
    520         boolean insert = false;
    521         textView.setOnKeyListener(new FolderKeyEventListener());
    522         mContent.addViewToCellLayout(textView, insert ? 0 : -1, (int)item.id, lp, true);
    523         return true;
    524     }
    525 
    526     public void onDragEnter(DragObject d) {
    527         mPreviousTargetCell[0] = -1;
    528         mPreviousTargetCell[1] = -1;
    529         mOnExitAlarm.cancelAlarm();
    530     }
    531 
    532     OnAlarmListener mReorderAlarmListener = new OnAlarmListener() {
    533         public void onAlarm(Alarm alarm) {
    534             realTimeReorder(mEmptyCell, mTargetCell);
    535         }
    536     };
    537 
    538     boolean readingOrderGreaterThan(int[] v1, int[] v2) {
    539         if (v1[1] > v2[1] || (v1[1] == v2[1] && v1[0] > v2[0])) {
    540             return true;
    541         } else {
    542             return false;
    543         }
    544     }
    545 
    546     private void realTimeReorder(int[] empty, int[] target) {
    547         boolean wrap;
    548         int startX;
    549         int endX;
    550         int startY;
    551         int delay = 0;
    552         float delayAmount = 30;
    553         if (readingOrderGreaterThan(target, empty)) {
    554             wrap = empty[0] >= mContent.getCountX() - 1;
    555             startY = wrap ? empty[1] + 1 : empty[1];
    556             for (int y = startY; y <= target[1]; y++) {
    557                 startX = y == empty[1] ? empty[0] + 1 : 0;
    558                 endX = y < target[1] ? mContent.getCountX() - 1 : target[0];
    559                 for (int x = startX; x <= endX; x++) {
    560                     View v = mContent.getChildAt(x,y);
    561                     if (mContent.animateChildToPosition(v, empty[0], empty[1],
    562                             REORDER_ANIMATION_DURATION, delay)) {
    563                         empty[0] = x;
    564                         empty[1] = y;
    565                         delay += delayAmount;
    566                         delayAmount *= 0.9;
    567                     }
    568                 }
    569             }
    570         } else {
    571             wrap = empty[0] == 0;
    572             startY = wrap ? empty[1] - 1 : empty[1];
    573             for (int y = startY; y >= target[1]; y--) {
    574                 startX = y == empty[1] ? empty[0] - 1 : mContent.getCountX() - 1;
    575                 endX = y > target[1] ? 0 : target[0];
    576                 for (int x = startX; x >= endX; x--) {
    577                     View v = mContent.getChildAt(x,y);
    578                     if (mContent.animateChildToPosition(v, empty[0], empty[1],
    579                             REORDER_ANIMATION_DURATION, delay)) {
    580                         empty[0] = x;
    581                         empty[1] = y;
    582                         delay += delayAmount;
    583                         delayAmount *= 0.9;
    584                     }
    585                 }
    586             }
    587         }
    588     }
    589 
    590     public void onDragOver(DragObject d) {
    591         float[] r = getDragViewVisualCenter(d.x, d.y, d.xOffset, d.yOffset, d.dragView, null);
    592         mTargetCell = mContent.findNearestArea((int) r[0], (int) r[1], 1, 1, mTargetCell);
    593 
    594         if (mTargetCell[0] != mPreviousTargetCell[0] || mTargetCell[1] != mPreviousTargetCell[1]) {
    595             mReorderAlarm.cancelAlarm();
    596             mReorderAlarm.setOnAlarmListener(mReorderAlarmListener);
    597             mReorderAlarm.setAlarm(150);
    598             mPreviousTargetCell[0] = mTargetCell[0];
    599             mPreviousTargetCell[1] = mTargetCell[1];
    600         }
    601     }
    602 
    603     // This is used to compute the visual center of the dragView. The idea is that
    604     // the visual center represents the user's interpretation of where the item is, and hence
    605     // is the appropriate point to use when determining drop location.
    606     private float[] getDragViewVisualCenter(int x, int y, int xOffset, int yOffset,
    607             DragView dragView, float[] recycle) {
    608         float res[];
    609         if (recycle == null) {
    610             res = new float[2];
    611         } else {
    612             res = recycle;
    613         }
    614 
    615         // These represent the visual top and left of drag view if a dragRect was provided.
    616         // If a dragRect was not provided, then they correspond to the actual view left and
    617         // top, as the dragRect is in that case taken to be the entire dragView.
    618         // R.dimen.dragViewOffsetY.
    619         int left = x - xOffset;
    620         int top = y - yOffset;
    621 
    622         // In order to find the visual center, we shift by half the dragRect
    623         res[0] = left + dragView.getDragRegion().width() / 2;
    624         res[1] = top + dragView.getDragRegion().height() / 2;
    625 
    626         return res;
    627     }
    628 
    629     OnAlarmListener mOnExitAlarmListener = new OnAlarmListener() {
    630         public void onAlarm(Alarm alarm) {
    631             completeDragExit();
    632         }
    633     };
    634 
    635     public void completeDragExit() {
    636         mLauncher.closeFolder();
    637         mCurrentDragInfo = null;
    638         mCurrentDragView = null;
    639         mSuppressOnAdd = false;
    640         mRearrangeOnClose = true;
    641     }
    642 
    643     public void onDragExit(DragObject d) {
    644         // We only close the folder if this is a true drag exit, ie. not because a drop
    645         // has occurred above the folder.
    646         if (!d.dragComplete) {
    647             mOnExitAlarm.setOnAlarmListener(mOnExitAlarmListener);
    648             mOnExitAlarm.setAlarm(ON_EXIT_CLOSE_DELAY);
    649         }
    650         mReorderAlarm.cancelAlarm();
    651     }
    652 
    653     public void onDropCompleted(View target, DragObject d, boolean success) {
    654         if (success) {
    655             if (mDeleteFolderOnDropCompleted && !mItemAddedBackToSelfViaIcon) {
    656                 replaceFolderWithFinalItem();
    657             }
    658         } else {
    659             // The drag failed, we need to return the item to the folder
    660             mFolderIcon.onDrop(d);
    661 
    662             // We're going to trigger a "closeFolder" which may occur before this item has
    663             // been added back to the folder -- this could cause the folder to be deleted
    664             if (mOnExitAlarm.alarmPending()) {
    665                 mSuppressFolderDeletion = true;
    666             }
    667         }
    668 
    669         if (target != this) {
    670             if (mOnExitAlarm.alarmPending()) {
    671                 mOnExitAlarm.cancelAlarm();
    672                 completeDragExit();
    673             }
    674         }
    675         mDeleteFolderOnDropCompleted = false;
    676         mDragInProgress = false;
    677         mItemAddedBackToSelfViaIcon = false;
    678         mCurrentDragInfo = null;
    679         mCurrentDragView = null;
    680         mSuppressOnAdd = false;
    681 
    682         // Reordering may have occured, and we need to save the new item locations. We do this once
    683         // at the end to prevent unnecessary database operations.
    684         updateItemLocationsInDatabase();
    685     }
    686 
    687     private void updateItemLocationsInDatabase() {
    688         ArrayList<View> list = getItemsInReadingOrder();
    689         for (int i = 0; i < list.size(); i++) {
    690             View v = list.get(i);
    691             ItemInfo info = (ItemInfo) v.getTag();
    692             LauncherModel.moveItemInDatabase(mLauncher, info, mInfo.id, 0,
    693                         info.cellX, info.cellY);
    694         }
    695     }
    696 
    697     public void notifyDrop() {
    698         if (mDragInProgress) {
    699             mItemAddedBackToSelfViaIcon = true;
    700         }
    701     }
    702 
    703     public boolean isDropEnabled() {
    704         return true;
    705     }
    706 
    707     public DropTarget getDropTargetDelegate(DragObject d) {
    708         return null;
    709     }
    710 
    711     private void setupContentDimensions(int count) {
    712         ArrayList<View> list = getItemsInReadingOrder();
    713 
    714         int countX = mContent.getCountX();
    715         int countY = mContent.getCountY();
    716         boolean done = false;
    717 
    718         while (!done) {
    719             int oldCountX = countX;
    720             int oldCountY = countY;
    721             if (countX * countY < count) {
    722                 // Current grid is too small, expand it
    723                 if (countX <= countY && countX < mMaxCountX) {
    724                     countX++;
    725                 } else if (countY < mMaxCountY) {
    726                     countY++;
    727                 }
    728                 if (countY == 0) countY++;
    729             } else if ((countY - 1) * countX >= count && countY >= countX) {
    730                 countY = Math.max(0, countY - 1);
    731             } else if ((countX - 1) * countY >= count) {
    732                 countX = Math.max(0, countX - 1);
    733             }
    734             done = countX == oldCountX && countY == oldCountY;
    735         }
    736         mContent.setGridSize(countX, countY);
    737         arrangeChildren(list);
    738     }
    739 
    740     public boolean isFull() {
    741         return getItemCount() >= mMaxCountX * mMaxCountY;
    742     }
    743 
    744     private void centerAboutIcon() {
    745         DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams();
    746 
    747         int width = getPaddingLeft() + getPaddingRight() + mContent.getDesiredWidth();
    748         int height = getPaddingTop() + getPaddingBottom() + mContent.getDesiredHeight()
    749                 + mFolderNameHeight;
    750         DragLayer parent = (DragLayer) mLauncher.findViewById(R.id.drag_layer);
    751 
    752         parent.getDescendantRectRelativeToSelf(mFolderIcon, mTempRect);
    753 
    754         int centerX = mTempRect.centerX();
    755         int centerY = mTempRect.centerY();
    756         int centeredLeft = centerX - width / 2;
    757         int centeredTop = centerY - height / 2;
    758 
    759         // We first fetch the currently visible CellLayoutChildren
    760         CellLayout currentPage = mLauncher.getWorkspace().getCurrentDropLayout();
    761         CellLayoutChildren boundingLayout = currentPage.getChildrenLayout();
    762         Rect bounds = new Rect();
    763         parent.getDescendantRectRelativeToSelf(boundingLayout, bounds);
    764 
    765         // We need to bound the folder to the currently visible CellLayoutChildren
    766         int left = Math.min(Math.max(bounds.left, centeredLeft),
    767                 bounds.left + bounds.width() - width);
    768         int top = Math.min(Math.max(bounds.top, centeredTop),
    769                 bounds.top + bounds.height() - height);
    770         // If the folder doesn't fit within the bounds, center it about the desired bounds
    771         if (width >= bounds.width()) {
    772             left = bounds.left + (bounds.width() - width) / 2;
    773         }
    774         if (height >= bounds.height()) {
    775             top = bounds.top + (bounds.height() - height) / 2;
    776         }
    777 
    778         int folderPivotX = width / 2 + (centeredLeft - left);
    779         int folderPivotY = height / 2 + (centeredTop - top);
    780         setPivotX(folderPivotX);
    781         setPivotY(folderPivotY);
    782         int folderIconPivotX = (int) (mFolderIcon.getMeasuredWidth() *
    783                 (1.0f * folderPivotX / width));
    784         int folderIconPivotY = (int) (mFolderIcon.getMeasuredHeight() *
    785                 (1.0f * folderPivotY / height));
    786         mFolderIcon.setPivotX(folderIconPivotX);
    787         mFolderIcon.setPivotY(folderIconPivotY);
    788 
    789         if (mMode == PARTIAL_GROW) {
    790             lp.width = width;
    791             lp.height = height;
    792             lp.x = left;
    793             lp.y = top;
    794         } else {
    795             mNewSize.set(left, top, left + width, top + height);
    796         }
    797     }
    798 
    799     private void setupContentForNumItems(int count) {
    800         setupContentDimensions(count);
    801 
    802         DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams();
    803         if (lp == null) {
    804             lp = new DragLayer.LayoutParams(0, 0);
    805             lp.customPosition = true;
    806             setLayoutParams(lp);
    807         }
    808         centerAboutIcon();
    809     }
    810 
    811     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    812         int width = getPaddingLeft() + getPaddingRight() + mContent.getDesiredWidth();
    813         int height = getPaddingTop() + getPaddingBottom() + mContent.getDesiredHeight()
    814                 + mFolderNameHeight;
    815 
    816         int contentWidthSpec = MeasureSpec.makeMeasureSpec(mContent.getDesiredWidth(),
    817                 MeasureSpec.EXACTLY);
    818         int contentHeightSpec = MeasureSpec.makeMeasureSpec(mContent.getDesiredHeight(),
    819                 MeasureSpec.EXACTLY);
    820         mContent.measure(contentWidthSpec, contentHeightSpec);
    821 
    822         mFolderName.measure(contentWidthSpec,
    823                 MeasureSpec.makeMeasureSpec(mFolderNameHeight, MeasureSpec.EXACTLY));
    824         setMeasuredDimension(width, height);
    825     }
    826 
    827     private void arrangeChildren(ArrayList<View> list) {
    828         int[] vacant = new int[2];
    829         if (list == null) {
    830             list = getItemsInReadingOrder();
    831         }
    832         mContent.removeAllViews();
    833 
    834         for (int i = 0; i < list.size(); i++) {
    835             View v = list.get(i);
    836             mContent.getVacantCell(vacant, 1, 1);
    837             CellLayout.LayoutParams lp = (CellLayout.LayoutParams) v.getLayoutParams();
    838             lp.cellX = vacant[0];
    839             lp.cellY = vacant[1];
    840             ItemInfo info = (ItemInfo) v.getTag();
    841             if (info.cellX != vacant[0] || info.cellY != vacant[1]) {
    842                 info.cellX = vacant[0];
    843                 info.cellY = vacant[1];
    844                 LauncherModel.addOrMoveItemInDatabase(mLauncher, info, mInfo.id, 0,
    845                         info.cellX, info.cellY);
    846             }
    847             boolean insert = false;
    848             mContent.addViewToCellLayout(v, insert ? 0 : -1, (int)info.id, lp, true);
    849         }
    850         mItemsInvalidated = true;
    851     }
    852 
    853     public int getItemCount() {
    854         return mContent.getChildrenLayout().getChildCount();
    855     }
    856 
    857     public View getItemAt(int index) {
    858         return mContent.getChildrenLayout().getChildAt(index);
    859     }
    860 
    861     private void onCloseComplete() {
    862         DragLayer parent = (DragLayer) getParent();
    863         parent.removeView(this);
    864         mDragController.removeDropTarget((DropTarget) this);
    865         clearFocus();
    866         mFolderIcon.requestFocus();
    867 
    868         if (mRearrangeOnClose) {
    869             setupContentForNumItems(getItemCount());
    870             mRearrangeOnClose = false;
    871         }
    872         if (getItemCount() <= 1) {
    873             if (!mDragInProgress && !mSuppressFolderDeletion) {
    874                 replaceFolderWithFinalItem();
    875             } else if (mDragInProgress) {
    876                 mDeleteFolderOnDropCompleted = true;
    877             }
    878         }
    879         mSuppressFolderDeletion = false;
    880     }
    881 
    882     private void replaceFolderWithFinalItem() {
    883         ItemInfo finalItem = null;
    884 
    885         if (getItemCount() == 1) {
    886             finalItem = mInfo.contents.get(0);
    887         }
    888 
    889         // Remove the folder completely
    890         CellLayout cellLayout = mLauncher.getCellLayout(mInfo.container, mInfo.screen);
    891         cellLayout.removeView(mFolderIcon);
    892         if (mFolderIcon instanceof DropTarget) {
    893             mDragController.removeDropTarget((DropTarget) mFolderIcon);
    894         }
    895         mLauncher.removeFolder(mInfo);
    896 
    897         if (finalItem != null) {
    898             LauncherModel.addOrMoveItemInDatabase(mLauncher, finalItem, mInfo.container,
    899                     mInfo.screen, mInfo.cellX, mInfo.cellY);
    900         }
    901         LauncherModel.deleteItemFromDatabase(mLauncher, mInfo);
    902 
    903         // Add the last remaining child to the workspace in place of the folder
    904         if (finalItem != null) {
    905             View child = mLauncher.createShortcut(R.layout.application, cellLayout,
    906                     (ShortcutInfo) finalItem);
    907 
    908             mLauncher.getWorkspace().addInScreen(child, mInfo.container, mInfo.screen, mInfo.cellX,
    909                     mInfo.cellY, mInfo.spanX, mInfo.spanY);
    910         }
    911     }
    912 
    913     // This method keeps track of the last item in the folder for the purposes
    914     // of keyboard focus
    915     private void updateTextViewFocus() {
    916         View lastChild = getItemAt(getItemCount() - 1);
    917         getItemAt(getItemCount() - 1);
    918         if (lastChild != null) {
    919             mFolderName.setNextFocusDownId(lastChild.getId());
    920             mFolderName.setNextFocusRightId(lastChild.getId());
    921             mFolderName.setNextFocusLeftId(lastChild.getId());
    922             mFolderName.setNextFocusUpId(lastChild.getId());
    923         }
    924     }
    925 
    926     public void onDrop(DragObject d) {
    927         ShortcutInfo item;
    928         if (d.dragInfo instanceof ApplicationInfo) {
    929             // Came from all apps -- make a copy
    930             item = ((ApplicationInfo) d.dragInfo).makeShortcut();
    931             item.spanX = 1;
    932             item.spanY = 1;
    933         } else {
    934             item = (ShortcutInfo) d.dragInfo;
    935         }
    936         // Dragged from self onto self, currently this is the only path possible, however
    937         // we keep this as a distinct code path.
    938         if (item == mCurrentDragInfo) {
    939             ShortcutInfo si = (ShortcutInfo) mCurrentDragView.getTag();
    940             CellLayout.LayoutParams lp = (CellLayout.LayoutParams) mCurrentDragView.getLayoutParams();
    941             si.cellX = lp.cellX = mEmptyCell[0];
    942             si.cellX = lp.cellY = mEmptyCell[1];
    943             mContent.addViewToCellLayout(mCurrentDragView, -1, (int)item.id, lp, true);
    944             if (d.dragView.hasDrawn()) {
    945                 mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, mCurrentDragView);
    946             } else {
    947                 mCurrentDragView.setVisibility(VISIBLE);
    948             }
    949             mItemsInvalidated = true;
    950             setupContentDimensions(getItemCount());
    951             mSuppressOnAdd = true;
    952         }
    953         mInfo.add(item);
    954     }
    955 
    956     public void onAdd(ShortcutInfo item) {
    957         mItemsInvalidated = true;
    958         // If the item was dropped onto this open folder, we have done the work associated
    959         // with adding the item to the folder, as indicated by mSuppressOnAdd being set
    960         if (mSuppressOnAdd) return;
    961         if (!findAndSetEmptyCells(item)) {
    962             // The current layout is full, can we expand it?
    963             setupContentForNumItems(getItemCount() + 1);
    964             findAndSetEmptyCells(item);
    965         }
    966         createAndAddShortcut(item);
    967         LauncherModel.addOrMoveItemInDatabase(
    968                 mLauncher, item, mInfo.id, 0, item.cellX, item.cellY);
    969     }
    970 
    971     public void onRemove(ShortcutInfo item) {
    972         mItemsInvalidated = true;
    973         // If this item is being dragged from this open folder, we have already handled
    974         // the work associated with removing the item, so we don't have to do anything here.
    975         if (item == mCurrentDragInfo) return;
    976         View v = getViewForInfo(item);
    977         mContent.removeView(v);
    978         if (mState == STATE_ANIMATING) {
    979             mRearrangeOnClose = true;
    980         } else {
    981             setupContentForNumItems(getItemCount());
    982         }
    983         if (getItemCount() <= 1) {
    984             replaceFolderWithFinalItem();
    985         }
    986     }
    987 
    988     private View getViewForInfo(ShortcutInfo item) {
    989         for (int j = 0; j < mContent.getCountY(); j++) {
    990             for (int i = 0; i < mContent.getCountX(); i++) {
    991                 View v = mContent.getChildAt(i, j);
    992                 if (v.getTag() == item) {
    993                     return v;
    994                 }
    995             }
    996         }
    997         return null;
    998     }
    999 
   1000     public void onItemsChanged() {
   1001         updateTextViewFocus();
   1002     }
   1003 
   1004     public void onTitleChanged(CharSequence title) {
   1005     }
   1006 
   1007     public ArrayList<View> getItemsInReadingOrder() {
   1008         return getItemsInReadingOrder(true);
   1009     }
   1010 
   1011     public ArrayList<View> getItemsInReadingOrder(boolean includeCurrentDragItem) {
   1012         if (mItemsInvalidated) {
   1013             mItemsInReadingOrder.clear();
   1014             for (int j = 0; j < mContent.getCountY(); j++) {
   1015                 for (int i = 0; i < mContent.getCountX(); i++) {
   1016                     View v = mContent.getChildAt(i, j);
   1017                     if (v != null) {
   1018                         ShortcutInfo info = (ShortcutInfo) v.getTag();
   1019                         if (info != mCurrentDragInfo || includeCurrentDragItem) {
   1020                             mItemsInReadingOrder.add(v);
   1021                         }
   1022                     }
   1023                 }
   1024             }
   1025             mItemsInvalidated = false;
   1026         }
   1027         return mItemsInReadingOrder;
   1028     }
   1029 
   1030     public void getLocationInDragLayer(int[] loc) {
   1031         mLauncher.getDragLayer().getLocationInDragLayer(this, loc);
   1032     }
   1033 
   1034     public void onFocusChange(View v, boolean hasFocus) {
   1035         if (v == mFolderName && hasFocus) {
   1036             startEditingFolderName();
   1037         }
   1038     }
   1039 }
   1040