• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.launcher3;
18 
19 import android.animation.Animator;
20 import android.animation.AnimatorListenerAdapter;
21 import android.animation.ObjectAnimator;
22 import android.animation.PropertyValuesHolder;
23 import android.content.Context;
24 import android.content.res.Resources;
25 import android.graphics.PointF;
26 import android.graphics.Rect;
27 import android.os.SystemClock;
28 import android.support.v4.widget.AutoScrollHelper;
29 import android.text.InputType;
30 import android.text.Selection;
31 import android.text.Spannable;
32 import android.util.AttributeSet;
33 import android.util.Log;
34 import android.view.ActionMode;
35 import android.view.KeyEvent;
36 import android.view.LayoutInflater;
37 import android.view.Menu;
38 import android.view.MenuItem;
39 import android.view.MotionEvent;
40 import android.view.View;
41 import android.view.accessibility.AccessibilityEvent;
42 import android.view.accessibility.AccessibilityManager;
43 import android.view.inputmethod.EditorInfo;
44 import android.view.inputmethod.InputMethodManager;
45 import android.widget.LinearLayout;
46 import android.widget.ScrollView;
47 import android.widget.TextView;
48 
49 import com.android.launcher3.FolderInfo.FolderListener;
50 
51 import java.util.ArrayList;
52 import java.util.Collections;
53 import java.util.Comparator;
54 
55 /**
56  * Represents a set of icons chosen by the user or generated by the system.
57  */
58 public class Folder extends LinearLayout implements DragSource, View.OnClickListener,
59         View.OnLongClickListener, DropTarget, FolderListener, TextView.OnEditorActionListener,
60         View.OnFocusChangeListener {
61     private static final String TAG = "Launcher.Folder";
62 
63     protected DragController mDragController;
64     protected Launcher mLauncher;
65     protected FolderInfo mInfo;
66 
67     static final int STATE_NONE = -1;
68     static final int STATE_SMALL = 0;
69     static final int STATE_ANIMATING = 1;
70     static final int STATE_OPEN = 2;
71 
72     private static final int CLOSE_FOLDER_DELAY_MS = 150;
73 
74     private int mExpandDuration;
75     protected CellLayout mContent;
76     private ScrollView mScrollView;
77     private final LayoutInflater mInflater;
78     private final IconCache mIconCache;
79     private int mState = STATE_NONE;
80     private static final int REORDER_ANIMATION_DURATION = 230;
81     private static final int REORDER_DELAY = 250;
82     private static final int ON_EXIT_CLOSE_DELAY = 400;
83     private boolean mRearrangeOnClose = false;
84     private FolderIcon mFolderIcon;
85     private int mMaxCountX;
86     private int mMaxCountY;
87     private int mMaxNumItems;
88     private ArrayList<View> mItemsInReadingOrder = new ArrayList<View>();
89     boolean mItemsInvalidated = false;
90     private ShortcutInfo mCurrentDragInfo;
91     private View mCurrentDragView;
92     private boolean mIsExternalDrag;
93     boolean mSuppressOnAdd = false;
94     private int[] mTargetCell = new int[2];
95     private int[] mPreviousTargetCell = new int[2];
96     private int[] mEmptyCell = new int[2];
97     private Alarm mReorderAlarm = new Alarm();
98     private Alarm mOnExitAlarm = new Alarm();
99     private int mFolderNameHeight;
100     private Rect mTempRect = new Rect();
101     private boolean mDragInProgress = false;
102     private boolean mDeleteFolderOnDropCompleted = false;
103     private boolean mSuppressFolderDeletion = false;
104     private boolean mItemAddedBackToSelfViaIcon = false;
105     FolderEditText mFolderName;
106     private float mFolderIconPivotX;
107     private float mFolderIconPivotY;
108 
109     private boolean mIsEditingName = false;
110     private InputMethodManager mInputMethodManager;
111 
112     private static String sDefaultFolderName;
113     private static String sHintText;
114 
115     private int DRAG_MODE_NONE = 0;
116     private int DRAG_MODE_REORDER = 1;
117     private int mDragMode = DRAG_MODE_NONE;
118 
119     // We avoid measuring the scroll view with a 0 width or height, as this
120     // results in CellLayout being measured as UNSPECIFIED, which it does
121     // not support.
122     private static final int MIN_CONTENT_DIMEN = 5;
123 
124     private boolean mDestroyed;
125 
126     private AutoScrollHelper mAutoScrollHelper;
127 
128     private Runnable mDeferredAction;
129     private boolean mDeferDropAfterUninstall;
130     private boolean mUninstallSuccessful;
131 
132     /**
133      * Used to inflate the Workspace from XML.
134      *
135      * @param context The application's context.
136      * @param attrs The attribtues set containing the Workspace's customization values.
137      */
Folder(Context context, AttributeSet attrs)138     public Folder(Context context, AttributeSet attrs) {
139         super(context, attrs);
140 
141         LauncherAppState app = LauncherAppState.getInstance();
142         DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
143         setAlwaysDrawnWithCacheEnabled(false);
144         mInflater = LayoutInflater.from(context);
145         mIconCache = app.getIconCache();
146 
147         Resources res = getResources();
148         mMaxCountX = (int) grid.numColumns;
149         // Allow scrolling folders when DISABLE_ALL_APPS is true.
150         if (LauncherAppState.isDisableAllApps()) {
151             mMaxCountY = mMaxNumItems = Integer.MAX_VALUE;
152         } else {
153             mMaxCountY = (int) grid.numRows;
154             mMaxNumItems = mMaxCountX * mMaxCountY;
155         }
156 
157         mInputMethodManager = (InputMethodManager)
158                 getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
159 
160         mExpandDuration = res.getInteger(R.integer.config_folderAnimDuration);
161 
162         if (sDefaultFolderName == null) {
163             sDefaultFolderName = res.getString(R.string.folder_name);
164         }
165         if (sHintText == null) {
166             sHintText = res.getString(R.string.folder_hint_text);
167         }
168         mLauncher = (Launcher) context;
169         // We need this view to be focusable in touch mode so that when text editing of the folder
170         // name is complete, we have something to focus on, thus hiding the cursor and giving
171         // reliable behvior when clicking the text field (since it will always gain focus on click).
172         setFocusableInTouchMode(true);
173     }
174 
175     @Override
onFinishInflate()176     protected void onFinishInflate() {
177         super.onFinishInflate();
178         mScrollView = (ScrollView) findViewById(R.id.scroll_view);
179         mContent = (CellLayout) findViewById(R.id.folder_content);
180 
181         LauncherAppState app = LauncherAppState.getInstance();
182         DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
183 
184         mContent.setCellDimensions(grid.folderCellWidthPx, grid.folderCellHeightPx);
185         mContent.setGridSize(0, 0);
186         mContent.getShortcutsAndWidgets().setMotionEventSplittingEnabled(false);
187         mContent.setInvertIfRtl(true);
188         mFolderName = (FolderEditText) findViewById(R.id.folder_name);
189         mFolderName.setFolder(this);
190         mFolderName.setOnFocusChangeListener(this);
191 
192         // We find out how tall the text view wants to be (it is set to wrap_content), so that
193         // we can allocate the appropriate amount of space for it.
194         int measureSpec = MeasureSpec.UNSPECIFIED;
195         mFolderName.measure(measureSpec, measureSpec);
196         mFolderNameHeight = mFolderName.getMeasuredHeight();
197 
198         // We disable action mode for now since it messes up the view on phones
199         mFolderName.setCustomSelectionActionModeCallback(mActionModeCallback);
200         mFolderName.setOnEditorActionListener(this);
201         mFolderName.setSelectAllOnFocus(true);
202         mFolderName.setInputType(mFolderName.getInputType() |
203                 InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS | InputType.TYPE_TEXT_FLAG_CAP_WORDS);
204         mAutoScrollHelper = new FolderAutoScrollHelper(mScrollView);
205     }
206 
207     private ActionMode.Callback mActionModeCallback = new ActionMode.Callback() {
208         public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
209             return false;
210         }
211 
212         public boolean onCreateActionMode(ActionMode mode, Menu menu) {
213             return false;
214         }
215 
216         public void onDestroyActionMode(ActionMode mode) {
217         }
218 
219         public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
220             return false;
221         }
222     };
223 
onClick(View v)224     public void onClick(View v) {
225         Object tag = v.getTag();
226         if (tag instanceof ShortcutInfo) {
227             mLauncher.onClick(v);
228         }
229     }
230 
onLongClick(View v)231     public boolean onLongClick(View v) {
232         // Return if global dragging is not enabled
233         if (!mLauncher.isDraggingEnabled()) return true;
234 
235         Object tag = v.getTag();
236         if (tag instanceof ShortcutInfo) {
237             ShortcutInfo item = (ShortcutInfo) tag;
238             if (!v.isInTouchMode()) {
239                 return false;
240             }
241 
242             mLauncher.getLauncherClings().dismissFolderCling(null);
243 
244             mLauncher.getWorkspace().onDragStartedWithItem(v);
245             mLauncher.getWorkspace().beginDragShared(v, this);
246 
247             mCurrentDragInfo = item;
248             mEmptyCell[0] = item.cellX;
249             mEmptyCell[1] = item.cellY;
250             mCurrentDragView = v;
251 
252             mContent.removeView(mCurrentDragView);
253             mInfo.remove(mCurrentDragInfo);
254             mDragInProgress = true;
255             mItemAddedBackToSelfViaIcon = false;
256         }
257         return true;
258     }
259 
isEditingName()260     public boolean isEditingName() {
261         return mIsEditingName;
262     }
263 
startEditingFolderName()264     public void startEditingFolderName() {
265         mFolderName.setHint("");
266         mIsEditingName = true;
267     }
268 
dismissEditingName()269     public void dismissEditingName() {
270         mInputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0);
271         doneEditingFolderName(true);
272     }
273 
doneEditingFolderName(boolean commit)274     public void doneEditingFolderName(boolean commit) {
275         mFolderName.setHint(sHintText);
276         // Convert to a string here to ensure that no other state associated with the text field
277         // gets saved.
278         String newTitle = mFolderName.getText().toString();
279         mInfo.setTitle(newTitle);
280         LauncherModel.updateItemInDatabase(mLauncher, mInfo);
281 
282         if (commit) {
283             sendCustomAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED,
284                     String.format(getContext().getString(R.string.folder_renamed), newTitle));
285         }
286         // In order to clear the focus from the text field, we set the focus on ourself. This
287         // ensures that every time the field is clicked, focus is gained, giving reliable behavior.
288         requestFocus();
289 
290         Selection.setSelection((Spannable) mFolderName.getText(), 0, 0);
291         mIsEditingName = false;
292     }
293 
onEditorAction(TextView v, int actionId, KeyEvent event)294     public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
295         if (actionId == EditorInfo.IME_ACTION_DONE) {
296             dismissEditingName();
297             return true;
298         }
299         return false;
300     }
301 
getEditTextRegion()302     public View getEditTextRegion() {
303         return mFolderName;
304     }
305 
306     /**
307      * We need to handle touch events to prevent them from falling through to the workspace below.
308      */
309     @Override
onTouchEvent(MotionEvent ev)310     public boolean onTouchEvent(MotionEvent ev) {
311         return true;
312     }
313 
setDragController(DragController dragController)314     public void setDragController(DragController dragController) {
315         mDragController = dragController;
316     }
317 
setFolderIcon(FolderIcon icon)318     void setFolderIcon(FolderIcon icon) {
319         mFolderIcon = icon;
320     }
321 
322     @Override
dispatchPopulateAccessibilityEvent(AccessibilityEvent event)323     public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
324         // When the folder gets focus, we don't want to announce the list of items.
325         return true;
326     }
327 
328     /**
329      * @return the FolderInfo object associated with this folder
330      */
getInfo()331     FolderInfo getInfo() {
332         return mInfo;
333     }
334 
335     private class GridComparator implements Comparator<ShortcutInfo> {
336         int mNumCols;
GridComparator(int numCols)337         public GridComparator(int numCols) {
338             mNumCols = numCols;
339         }
340 
341         @Override
compare(ShortcutInfo lhs, ShortcutInfo rhs)342         public int compare(ShortcutInfo lhs, ShortcutInfo rhs) {
343             int lhIndex = lhs.cellY * mNumCols + lhs.cellX;
344             int rhIndex = rhs.cellY * mNumCols + rhs.cellX;
345             return (lhIndex - rhIndex);
346         }
347     }
348 
placeInReadingOrder(ArrayList<ShortcutInfo> items)349     private void placeInReadingOrder(ArrayList<ShortcutInfo> items) {
350         int maxX = 0;
351         int count = items.size();
352         for (int i = 0; i < count; i++) {
353             ShortcutInfo item = items.get(i);
354             if (item.cellX > maxX) {
355                 maxX = item.cellX;
356             }
357         }
358 
359         GridComparator gridComparator = new GridComparator(maxX + 1);
360         Collections.sort(items, gridComparator);
361         final int countX = mContent.getCountX();
362         for (int i = 0; i < count; i++) {
363             int x = i % countX;
364             int y = i / countX;
365             ShortcutInfo item = items.get(i);
366             item.cellX = x;
367             item.cellY = y;
368         }
369     }
370 
bind(FolderInfo info)371     void bind(FolderInfo info) {
372         mInfo = info;
373         ArrayList<ShortcutInfo> children = info.contents;
374         ArrayList<ShortcutInfo> overflow = new ArrayList<ShortcutInfo>();
375         setupContentForNumItems(children.size());
376         placeInReadingOrder(children);
377         int count = 0;
378         for (int i = 0; i < children.size(); i++) {
379             ShortcutInfo child = (ShortcutInfo) children.get(i);
380             if (createAndAddShortcut(child) == null) {
381                 overflow.add(child);
382             } else {
383                 count++;
384             }
385         }
386 
387         // We rearrange the items in case there are any empty gaps
388         setupContentForNumItems(count);
389 
390         // If our folder has too many items we prune them from the list. This is an issue
391         // when upgrading from the old Folders implementation which could contain an unlimited
392         // number of items.
393         for (ShortcutInfo item: overflow) {
394             mInfo.remove(item);
395             LauncherModel.deleteItemFromDatabase(mLauncher, item);
396         }
397 
398         mItemsInvalidated = true;
399         updateTextViewFocus();
400         mInfo.addListener(this);
401 
402         if (!sDefaultFolderName.contentEquals(mInfo.title)) {
403             mFolderName.setText(mInfo.title);
404         } else {
405             mFolderName.setText("");
406         }
407         updateItemLocationsInDatabase();
408 
409         // In case any children didn't come across during loading, clean up the folder accordingly
410         mFolderIcon.post(new Runnable() {
411             public void run() {
412                 if (getItemCount() <= 1) {
413                     replaceFolderWithFinalItem();
414                 }
415             }
416         });
417     }
418 
419     /**
420      * Creates a new UserFolder, inflated from R.layout.user_folder.
421      *
422      * @param context The application's context.
423      *
424      * @return A new UserFolder.
425      */
fromXml(Context context)426     static Folder fromXml(Context context) {
427         return (Folder) LayoutInflater.from(context).inflate(R.layout.user_folder, null);
428     }
429 
430     /**
431      * This method is intended to make the UserFolder to be visually identical in size and position
432      * to its associated FolderIcon. This allows for a seamless transition into the expanded state.
433      */
positionAndSizeAsIcon()434     private void positionAndSizeAsIcon() {
435         if (!(getParent() instanceof DragLayer)) return;
436         setScaleX(0.8f);
437         setScaleY(0.8f);
438         setAlpha(0f);
439         mState = STATE_SMALL;
440     }
441 
animateOpen()442     public void animateOpen() {
443         positionAndSizeAsIcon();
444 
445         if (!(getParent() instanceof DragLayer)) return;
446         centerAboutIcon();
447         PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 1);
448         PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 1.0f);
449         PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", 1.0f);
450         final ObjectAnimator oa =
451             LauncherAnimUtils.ofPropertyValuesHolder(this, alpha, scaleX, scaleY);
452 
453         oa.addListener(new AnimatorListenerAdapter() {
454             @Override
455             public void onAnimationStart(Animator animation) {
456                 sendCustomAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED,
457                         String.format(getContext().getString(R.string.folder_opened),
458                         mContent.getCountX(), mContent.getCountY()));
459                 mState = STATE_ANIMATING;
460             }
461             @Override
462             public void onAnimationEnd(Animator animation) {
463                 mState = STATE_OPEN;
464                 setLayerType(LAYER_TYPE_NONE, null);
465 
466                 // Only show cling if we are not in the middle of a drag - this would be quite jarring.
467                 if (!mDragController.isDragging()) {
468                     Cling cling = mLauncher.getLauncherClings().showFoldersCling();
469                     if (cling != null) {
470                         cling.bringScrimToFront();
471                         bringToFront();
472                         cling.bringToFront();
473                     }
474                 }
475                 setFocusOnFirstChild();
476             }
477         });
478         oa.setDuration(mExpandDuration);
479         setLayerType(LAYER_TYPE_HARDWARE, null);
480         oa.start();
481 
482         // Make sure the folder picks up the last drag move even if the finger doesn't move.
483         if (mDragController.isDragging()) {
484             mDragController.forceTouchMove();
485         }
486     }
487 
beginExternalDrag(ShortcutInfo item)488     public void beginExternalDrag(ShortcutInfo item) {
489         setupContentForNumItems(getItemCount() + 1);
490         findAndSetEmptyCells(item);
491 
492         mCurrentDragInfo = item;
493         mEmptyCell[0] = item.cellX;
494         mEmptyCell[1] = item.cellY;
495         mIsExternalDrag = true;
496 
497         mDragInProgress = true;
498     }
499 
sendCustomAccessibilityEvent(int type, String text)500     private void sendCustomAccessibilityEvent(int type, String text) {
501         AccessibilityManager accessibilityManager = (AccessibilityManager)
502                 getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
503         if (accessibilityManager.isEnabled()) {
504             AccessibilityEvent event = AccessibilityEvent.obtain(type);
505             onInitializeAccessibilityEvent(event);
506             event.getText().add(text);
507             accessibilityManager.sendAccessibilityEvent(event);
508         }
509     }
510 
setFocusOnFirstChild()511     private void setFocusOnFirstChild() {
512         View firstChild = mContent.getChildAt(0, 0);
513         if (firstChild != null) {
514             firstChild.requestFocus();
515         }
516     }
517 
animateClosed()518     public void animateClosed() {
519         if (!(getParent() instanceof DragLayer)) return;
520         PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 0);
521         PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 0.9f);
522         PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", 0.9f);
523         final ObjectAnimator oa =
524                 LauncherAnimUtils.ofPropertyValuesHolder(this, alpha, scaleX, scaleY);
525 
526         oa.addListener(new AnimatorListenerAdapter() {
527             @Override
528             public void onAnimationEnd(Animator animation) {
529                 onCloseComplete();
530                 setLayerType(LAYER_TYPE_NONE, null);
531                 mState = STATE_SMALL;
532             }
533             @Override
534             public void onAnimationStart(Animator animation) {
535                 sendCustomAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED,
536                         getContext().getString(R.string.folder_closed));
537                 mState = STATE_ANIMATING;
538             }
539         });
540         oa.setDuration(mExpandDuration);
541         setLayerType(LAYER_TYPE_HARDWARE, null);
542         oa.start();
543     }
544 
acceptDrop(DragObject d)545     public boolean acceptDrop(DragObject d) {
546         final ItemInfo item = (ItemInfo) d.dragInfo;
547         final int itemType = item.itemType;
548         return ((itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION ||
549                     itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) &&
550                     !isFull());
551     }
552 
findAndSetEmptyCells(ShortcutInfo item)553     protected boolean findAndSetEmptyCells(ShortcutInfo item) {
554         int[] emptyCell = new int[2];
555         if (mContent.findCellForSpan(emptyCell, item.spanX, item.spanY)) {
556             item.cellX = emptyCell[0];
557             item.cellY = emptyCell[1];
558             return true;
559         } else {
560             return false;
561         }
562     }
563 
createAndAddShortcut(ShortcutInfo item)564     protected View createAndAddShortcut(ShortcutInfo item) {
565         final BubbleTextView textView =
566             (BubbleTextView) mInflater.inflate(R.layout.application, this, false);
567         textView.setCompoundDrawables(null,
568                 Utilities.createIconDrawable(item.getIcon(mIconCache)), null, null);
569         textView.setText(item.title);
570         textView.setTag(item);
571         textView.setTextColor(getResources().getColor(R.color.folder_items_text_color));
572         textView.setShadowsEnabled(false);
573         textView.setGlowColor(getResources().getColor(R.color.folder_items_glow_color));
574 
575         textView.setOnClickListener(this);
576         textView.setOnLongClickListener(this);
577 
578         // We need to check here to verify that the given item's location isn't already occupied
579         // by another item.
580         if (mContent.getChildAt(item.cellX, item.cellY) != null || item.cellX < 0 || item.cellY < 0
581                 || item.cellX >= mContent.getCountX() || item.cellY >= mContent.getCountY()) {
582             // This shouldn't happen, log it.
583             Log.e(TAG, "Folder order not properly persisted during bind");
584             if (!findAndSetEmptyCells(item)) {
585                 return null;
586             }
587         }
588 
589         CellLayout.LayoutParams lp =
590             new CellLayout.LayoutParams(item.cellX, item.cellY, item.spanX, item.spanY);
591         boolean insert = false;
592         textView.setOnKeyListener(new FolderKeyEventListener());
593         mContent.addViewToCellLayout(textView, insert ? 0 : -1, (int)item.id, lp, true);
594         return textView;
595     }
596 
onDragEnter(DragObject d)597     public void onDragEnter(DragObject d) {
598         mPreviousTargetCell[0] = -1;
599         mPreviousTargetCell[1] = -1;
600         mOnExitAlarm.cancelAlarm();
601     }
602 
603     OnAlarmListener mReorderAlarmListener = new OnAlarmListener() {
604         public void onAlarm(Alarm alarm) {
605             realTimeReorder(mEmptyCell, mTargetCell);
606         }
607     };
608 
readingOrderGreaterThan(int[] v1, int[] v2)609     boolean readingOrderGreaterThan(int[] v1, int[] v2) {
610         if (v1[1] > v2[1] || (v1[1] == v2[1] && v1[0] > v2[0])) {
611             return true;
612         } else {
613             return false;
614         }
615     }
616 
realTimeReorder(int[] empty, int[] target)617     private void realTimeReorder(int[] empty, int[] target) {
618         boolean wrap;
619         int startX;
620         int endX;
621         int startY;
622         int delay = 0;
623         float delayAmount = 30;
624         if (readingOrderGreaterThan(target, empty)) {
625             wrap = empty[0] >= mContent.getCountX() - 1;
626             startY = wrap ? empty[1] + 1 : empty[1];
627             for (int y = startY; y <= target[1]; y++) {
628                 startX = y == empty[1] ? empty[0] + 1 : 0;
629                 endX = y < target[1] ? mContent.getCountX() - 1 : target[0];
630                 for (int x = startX; x <= endX; x++) {
631                     View v = mContent.getChildAt(x,y);
632                     if (mContent.animateChildToPosition(v, empty[0], empty[1],
633                             REORDER_ANIMATION_DURATION, delay, true, true)) {
634                         empty[0] = x;
635                         empty[1] = y;
636                         delay += delayAmount;
637                         delayAmount *= 0.9;
638                     }
639                 }
640             }
641         } else {
642             wrap = empty[0] == 0;
643             startY = wrap ? empty[1] - 1 : empty[1];
644             for (int y = startY; y >= target[1]; y--) {
645                 startX = y == empty[1] ? empty[0] - 1 : mContent.getCountX() - 1;
646                 endX = y > target[1] ? 0 : target[0];
647                 for (int x = startX; x >= endX; x--) {
648                     View v = mContent.getChildAt(x,y);
649                     if (mContent.animateChildToPosition(v, empty[0], empty[1],
650                             REORDER_ANIMATION_DURATION, delay, true, true)) {
651                         empty[0] = x;
652                         empty[1] = y;
653                         delay += delayAmount;
654                         delayAmount *= 0.9;
655                     }
656                 }
657             }
658         }
659     }
660 
isLayoutRtl()661     public boolean isLayoutRtl() {
662         return (getLayoutDirection() == LAYOUT_DIRECTION_RTL);
663     }
664 
onDragOver(DragObject d)665     public void onDragOver(DragObject d) {
666         final DragView dragView = d.dragView;
667         final int scrollOffset = mScrollView.getScrollY();
668         final float[] r = getDragViewVisualCenter(d.x, d.y, d.xOffset, d.yOffset, dragView, null);
669         r[0] -= getPaddingLeft();
670         r[1] -= getPaddingTop();
671 
672         final long downTime = SystemClock.uptimeMillis();
673         final MotionEvent translatedEv = MotionEvent.obtain(
674                 downTime, downTime, MotionEvent.ACTION_MOVE, d.x, d.y, 0);
675 
676         if (!mAutoScrollHelper.isEnabled()) {
677             mAutoScrollHelper.setEnabled(true);
678         }
679 
680         final boolean handled = mAutoScrollHelper.onTouch(this, translatedEv);
681         translatedEv.recycle();
682 
683         if (handled) {
684             mReorderAlarm.cancelAlarm();
685         } else {
686             mTargetCell = mContent.findNearestArea(
687                     (int) r[0], (int) r[1] + scrollOffset, 1, 1, mTargetCell);
688             if (isLayoutRtl()) {
689                 mTargetCell[0] = mContent.getCountX() - mTargetCell[0] - 1;
690             }
691             if (mTargetCell[0] != mPreviousTargetCell[0]
692                     || mTargetCell[1] != mPreviousTargetCell[1]) {
693                 mReorderAlarm.cancelAlarm();
694                 mReorderAlarm.setOnAlarmListener(mReorderAlarmListener);
695                 mReorderAlarm.setAlarm(REORDER_DELAY);
696                 mPreviousTargetCell[0] = mTargetCell[0];
697                 mPreviousTargetCell[1] = mTargetCell[1];
698                 mDragMode = DRAG_MODE_REORDER;
699             } else {
700                 mDragMode = DRAG_MODE_NONE;
701             }
702         }
703     }
704 
705     // This is used to compute the visual center of the dragView. The idea is that
706     // the visual center represents the user's interpretation of where the item is, and hence
707     // is the appropriate point to use when determining drop location.
getDragViewVisualCenter(int x, int y, int xOffset, int yOffset, DragView dragView, float[] recycle)708     private float[] getDragViewVisualCenter(int x, int y, int xOffset, int yOffset,
709             DragView dragView, float[] recycle) {
710         float res[];
711         if (recycle == null) {
712             res = new float[2];
713         } else {
714             res = recycle;
715         }
716 
717         // These represent the visual top and left of drag view if a dragRect was provided.
718         // If a dragRect was not provided, then they correspond to the actual view left and
719         // top, as the dragRect is in that case taken to be the entire dragView.
720         // R.dimen.dragViewOffsetY.
721         int left = x - xOffset;
722         int top = y - yOffset;
723 
724         // In order to find the visual center, we shift by half the dragRect
725         res[0] = left + dragView.getDragRegion().width() / 2;
726         res[1] = top + dragView.getDragRegion().height() / 2;
727 
728         return res;
729     }
730 
731     OnAlarmListener mOnExitAlarmListener = new OnAlarmListener() {
732         public void onAlarm(Alarm alarm) {
733             completeDragExit();
734         }
735     };
736 
completeDragExit()737     public void completeDragExit() {
738         mLauncher.closeFolder();
739         mCurrentDragInfo = null;
740         mCurrentDragView = null;
741         mSuppressOnAdd = false;
742         mRearrangeOnClose = true;
743         mIsExternalDrag = false;
744     }
745 
onDragExit(DragObject d)746     public void onDragExit(DragObject d) {
747         // Exiting folder; stop the auto scroller.
748         mAutoScrollHelper.setEnabled(false);
749         // We only close the folder if this is a true drag exit, ie. not because
750         // a drop has occurred above the folder.
751         if (!d.dragComplete) {
752             mOnExitAlarm.setOnAlarmListener(mOnExitAlarmListener);
753             mOnExitAlarm.setAlarm(ON_EXIT_CLOSE_DELAY);
754         }
755         mReorderAlarm.cancelAlarm();
756         mDragMode = DRAG_MODE_NONE;
757     }
758 
onDropCompleted(final View target, final DragObject d, final boolean isFlingToDelete, final boolean success)759     public void onDropCompleted(final View target, final DragObject d,
760             final boolean isFlingToDelete, final boolean success) {
761         if (mDeferDropAfterUninstall) {
762             Log.d(TAG, "Deferred handling drop because waiting for uninstall.");
763             mDeferredAction = new Runnable() {
764                     public void run() {
765                         onDropCompleted(target, d, isFlingToDelete, success);
766                         mDeferredAction = null;
767                     }
768                 };
769             return;
770         }
771 
772         boolean beingCalledAfterUninstall = mDeferredAction != null;
773         boolean successfulDrop =
774                 success && (!beingCalledAfterUninstall || mUninstallSuccessful);
775 
776         if (successfulDrop) {
777             if (mDeleteFolderOnDropCompleted && !mItemAddedBackToSelfViaIcon && target != this) {
778                 replaceFolderWithFinalItem();
779             }
780         } else {
781             setupContentForNumItems(getItemCount());
782             // The drag failed, we need to return the item to the folder
783             mFolderIcon.onDrop(d);
784         }
785 
786         if (target != this) {
787             if (mOnExitAlarm.alarmPending()) {
788                 mOnExitAlarm.cancelAlarm();
789                 if (!successfulDrop) {
790                     mSuppressFolderDeletion = true;
791                 }
792                 completeDragExit();
793             }
794         }
795 
796         // This is kind of hacky, but in general, dropping on the workspace handles removing
797         // the extra screen, but dropping elsewhere (back to self, or onto delete) doesn't.
798         if (target != mLauncher.getWorkspace()) {
799             mLauncher.getWorkspace().removeExtraEmptyScreen(true, null);
800         }
801 
802         mDeleteFolderOnDropCompleted = false;
803         mDragInProgress = false;
804         mItemAddedBackToSelfViaIcon = false;
805         mCurrentDragInfo = null;
806         mCurrentDragView = null;
807         mSuppressOnAdd = false;
808 
809         // Reordering may have occured, and we need to save the new item locations. We do this once
810         // at the end to prevent unnecessary database operations.
811         updateItemLocationsInDatabaseBatch();
812     }
813 
deferCompleteDropAfterUninstallActivity()814     public void deferCompleteDropAfterUninstallActivity() {
815         mDeferDropAfterUninstall = true;
816     }
817 
onUninstallActivityReturned(boolean success)818     public void onUninstallActivityReturned(boolean success) {
819         mDeferDropAfterUninstall = false;
820         mUninstallSuccessful = success;
821         if (mDeferredAction != null) {
822             mDeferredAction.run();
823         }
824     }
825 
826     @Override
getIntrinsicIconScaleFactor()827     public float getIntrinsicIconScaleFactor() {
828         return 1f;
829     }
830 
831     @Override
supportsFlingToDelete()832     public boolean supportsFlingToDelete() {
833         return true;
834     }
835 
836     @Override
supportsAppInfoDropTarget()837     public boolean supportsAppInfoDropTarget() {
838         return false;
839     }
840 
841     @Override
supportsDeleteDropTarget()842     public boolean supportsDeleteDropTarget() {
843         return true;
844     }
845 
onFlingToDelete(DragObject d, int x, int y, PointF vec)846     public void onFlingToDelete(DragObject d, int x, int y, PointF vec) {
847         // Do nothing
848     }
849 
850     @Override
onFlingToDeleteCompleted()851     public void onFlingToDeleteCompleted() {
852         // Do nothing
853     }
854 
updateItemLocationsInDatabase()855     private void updateItemLocationsInDatabase() {
856         ArrayList<View> list = getItemsInReadingOrder();
857         for (int i = 0; i < list.size(); i++) {
858             View v = list.get(i);
859             ItemInfo info = (ItemInfo) v.getTag();
860             LauncherModel.moveItemInDatabase(mLauncher, info, mInfo.id, 0,
861                     info.cellX, info.cellY);
862         }
863     }
864 
updateItemLocationsInDatabaseBatch()865     private void updateItemLocationsInDatabaseBatch() {
866         ArrayList<View> list = getItemsInReadingOrder();
867         ArrayList<ItemInfo> items = new ArrayList<ItemInfo>();
868         for (int i = 0; i < list.size(); i++) {
869             View v = list.get(i);
870             ItemInfo info = (ItemInfo) v.getTag();
871             items.add(info);
872         }
873 
874         LauncherModel.moveItemsInDatabase(mLauncher, items, mInfo.id, 0);
875     }
876 
addItemLocationsInDatabase()877     public void addItemLocationsInDatabase() {
878         ArrayList<View> list = getItemsInReadingOrder();
879         for (int i = 0; i < list.size(); i++) {
880             View v = list.get(i);
881             ItemInfo info = (ItemInfo) v.getTag();
882             LauncherModel.addItemToDatabase(mLauncher, info, mInfo.id, 0,
883                         info.cellX, info.cellY, false);
884         }
885     }
886 
notifyDrop()887     public void notifyDrop() {
888         if (mDragInProgress) {
889             mItemAddedBackToSelfViaIcon = true;
890         }
891     }
892 
isDropEnabled()893     public boolean isDropEnabled() {
894         return true;
895     }
896 
setupContentDimensions(int count)897     private void setupContentDimensions(int count) {
898         ArrayList<View> list = getItemsInReadingOrder();
899 
900         int countX = mContent.getCountX();
901         int countY = mContent.getCountY();
902         boolean done = false;
903 
904         while (!done) {
905             int oldCountX = countX;
906             int oldCountY = countY;
907             if (countX * countY < count) {
908                 // Current grid is too small, expand it
909                 if ((countX <= countY || countY == mMaxCountY) && countX < mMaxCountX) {
910                     countX++;
911                 } else if (countY < mMaxCountY) {
912                     countY++;
913                 }
914                 if (countY == 0) countY++;
915             } else if ((countY - 1) * countX >= count && countY >= countX) {
916                 countY = Math.max(0, countY - 1);
917             } else if ((countX - 1) * countY >= count) {
918                 countX = Math.max(0, countX - 1);
919             }
920             done = countX == oldCountX && countY == oldCountY;
921         }
922         mContent.setGridSize(countX, countY);
923         arrangeChildren(list);
924     }
925 
isFull()926     public boolean isFull() {
927         return getItemCount() >= mMaxNumItems;
928     }
929 
centerAboutIcon()930     private void centerAboutIcon() {
931         DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams();
932 
933         DragLayer parent = (DragLayer) mLauncher.findViewById(R.id.drag_layer);
934         int width = getPaddingLeft() + getPaddingRight() + mContent.getDesiredWidth();
935         int height = getFolderHeight();
936 
937         float scale = parent.getDescendantRectRelativeToSelf(mFolderIcon, mTempRect);
938 
939         LauncherAppState app = LauncherAppState.getInstance();
940         DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
941 
942         int centerX = (int) (mTempRect.left + mTempRect.width() * scale / 2);
943         int centerY = (int) (mTempRect.top + mTempRect.height() * scale / 2);
944         int centeredLeft = centerX - width / 2;
945         int centeredTop = centerY - height / 2;
946         int currentPage = mLauncher.getWorkspace().getNextPage();
947         // In case the workspace is scrolling, we need to use the final scroll to compute
948         // the folders bounds.
949         mLauncher.getWorkspace().setFinalScrollForPageChange(currentPage);
950         // We first fetch the currently visible CellLayoutChildren
951         CellLayout currentLayout = (CellLayout) mLauncher.getWorkspace().getChildAt(currentPage);
952         ShortcutAndWidgetContainer boundingLayout = currentLayout.getShortcutsAndWidgets();
953         Rect bounds = new Rect();
954         parent.getDescendantRectRelativeToSelf(boundingLayout, bounds);
955         // We reset the workspaces scroll
956         mLauncher.getWorkspace().resetFinalScrollForPageChange(currentPage);
957 
958         // We need to bound the folder to the currently visible CellLayoutChildren
959         int left = Math.min(Math.max(bounds.left, centeredLeft),
960                 bounds.left + bounds.width() - width);
961         int top = Math.min(Math.max(bounds.top, centeredTop),
962                 bounds.top + bounds.height() - height);
963         if (grid.isPhone() && (grid.availableWidthPx - width) < grid.iconSizePx) {
964             // Center the folder if it is full (on phones only)
965             left = (grid.availableWidthPx - width) / 2;
966         } else if (width >= bounds.width()) {
967             // If the folder doesn't fit within the bounds, center it about the desired bounds
968             left = bounds.left + (bounds.width() - width) / 2;
969         }
970         if (height >= bounds.height()) {
971             top = bounds.top + (bounds.height() - height) / 2;
972         }
973 
974         int folderPivotX = width / 2 + (centeredLeft - left);
975         int folderPivotY = height / 2 + (centeredTop - top);
976         setPivotX(folderPivotX);
977         setPivotY(folderPivotY);
978         mFolderIconPivotX = (int) (mFolderIcon.getMeasuredWidth() *
979                 (1.0f * folderPivotX / width));
980         mFolderIconPivotY = (int) (mFolderIcon.getMeasuredHeight() *
981                 (1.0f * folderPivotY / height));
982 
983         lp.width = width;
984         lp.height = height;
985         lp.x = left;
986         lp.y = top;
987     }
988 
getPivotXForIconAnimation()989     float getPivotXForIconAnimation() {
990         return mFolderIconPivotX;
991     }
getPivotYForIconAnimation()992     float getPivotYForIconAnimation() {
993         return mFolderIconPivotY;
994     }
995 
setupContentForNumItems(int count)996     private void setupContentForNumItems(int count) {
997         setupContentDimensions(count);
998 
999         DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams();
1000         if (lp == null) {
1001             lp = new DragLayer.LayoutParams(0, 0);
1002             lp.customPosition = true;
1003             setLayoutParams(lp);
1004         }
1005         centerAboutIcon();
1006     }
1007 
getContentAreaHeight()1008     private int getContentAreaHeight() {
1009         LauncherAppState app = LauncherAppState.getInstance();
1010         DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
1011         Rect workspacePadding = grid.getWorkspacePadding(grid.isLandscape ?
1012                 CellLayout.LANDSCAPE : CellLayout.PORTRAIT);
1013         int maxContentAreaHeight = grid.availableHeightPx -
1014                 workspacePadding.top - workspacePadding.bottom -
1015                 mFolderNameHeight;
1016         int height = Math.min(maxContentAreaHeight,
1017                 mContent.getDesiredHeight());
1018         return Math.max(height, MIN_CONTENT_DIMEN);
1019     }
1020 
getContentAreaWidth()1021     private int getContentAreaWidth() {
1022         return Math.max(mContent.getDesiredWidth(), MIN_CONTENT_DIMEN);
1023     }
1024 
getFolderHeight()1025     private int getFolderHeight() {
1026         int height = getPaddingTop() + getPaddingBottom()
1027                 + getContentAreaHeight() + mFolderNameHeight;
1028         return height;
1029     }
1030 
onMeasure(int widthMeasureSpec, int heightMeasureSpec)1031     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
1032         int width = getPaddingLeft() + getPaddingRight() + mContent.getDesiredWidth();
1033         int height = getFolderHeight();
1034         int contentAreaWidthSpec = MeasureSpec.makeMeasureSpec(getContentAreaWidth(),
1035                 MeasureSpec.EXACTLY);
1036         int contentAreaHeightSpec = MeasureSpec.makeMeasureSpec(getContentAreaHeight(),
1037                 MeasureSpec.EXACTLY);
1038 
1039         if (LauncherAppState.isDisableAllApps()) {
1040             // Don't cap the height of the content to allow scrolling.
1041             mContent.setFixedSize(getContentAreaWidth(), mContent.getDesiredHeight());
1042         } else {
1043             mContent.setFixedSize(getContentAreaWidth(), getContentAreaHeight());
1044         }
1045 
1046         mScrollView.measure(contentAreaWidthSpec, contentAreaHeightSpec);
1047         mFolderName.measure(contentAreaWidthSpec,
1048                 MeasureSpec.makeMeasureSpec(mFolderNameHeight, MeasureSpec.EXACTLY));
1049         setMeasuredDimension(width, height);
1050     }
1051 
arrangeChildren(ArrayList<View> list)1052     private void arrangeChildren(ArrayList<View> list) {
1053         int[] vacant = new int[2];
1054         if (list == null) {
1055             list = getItemsInReadingOrder();
1056         }
1057         mContent.removeAllViews();
1058 
1059         for (int i = 0; i < list.size(); i++) {
1060             View v = list.get(i);
1061             mContent.getVacantCell(vacant, 1, 1);
1062             CellLayout.LayoutParams lp = (CellLayout.LayoutParams) v.getLayoutParams();
1063             lp.cellX = vacant[0];
1064             lp.cellY = vacant[1];
1065             ItemInfo info = (ItemInfo) v.getTag();
1066             if (info.cellX != vacant[0] || info.cellY != vacant[1]) {
1067                 info.cellX = vacant[0];
1068                 info.cellY = vacant[1];
1069                 LauncherModel.addOrMoveItemInDatabase(mLauncher, info, mInfo.id, 0,
1070                         info.cellX, info.cellY);
1071             }
1072             boolean insert = false;
1073             mContent.addViewToCellLayout(v, insert ? 0 : -1, (int)info.id, lp, true);
1074         }
1075         mItemsInvalidated = true;
1076     }
1077 
getItemCount()1078     public int getItemCount() {
1079         return mContent.getShortcutsAndWidgets().getChildCount();
1080     }
1081 
getItemAt(int index)1082     public View getItemAt(int index) {
1083         return mContent.getShortcutsAndWidgets().getChildAt(index);
1084     }
1085 
onCloseComplete()1086     private void onCloseComplete() {
1087         DragLayer parent = (DragLayer) getParent();
1088         if (parent != null) {
1089             parent.removeView(this);
1090         }
1091         mDragController.removeDropTarget((DropTarget) this);
1092         clearFocus();
1093         mFolderIcon.requestFocus();
1094 
1095         if (mRearrangeOnClose) {
1096             setupContentForNumItems(getItemCount());
1097             mRearrangeOnClose = false;
1098         }
1099         if (getItemCount() <= 1) {
1100             if (!mDragInProgress && !mSuppressFolderDeletion) {
1101                 replaceFolderWithFinalItem();
1102             } else if (mDragInProgress) {
1103                 mDeleteFolderOnDropCompleted = true;
1104             }
1105         }
1106         mSuppressFolderDeletion = false;
1107     }
1108 
replaceFolderWithFinalItem()1109     private void replaceFolderWithFinalItem() {
1110         // Add the last remaining child to the workspace in place of the folder
1111         Runnable onCompleteRunnable = new Runnable() {
1112             @Override
1113             public void run() {
1114                 CellLayout cellLayout = mLauncher.getCellLayout(mInfo.container, mInfo.screenId);
1115 
1116                 View child = null;
1117                 // Move the item from the folder to the workspace, in the position of the folder
1118                 if (getItemCount() == 1) {
1119                     ShortcutInfo finalItem = mInfo.contents.get(0);
1120                     child = mLauncher.createShortcut(R.layout.application, cellLayout,
1121                             finalItem);
1122                     LauncherModel.addOrMoveItemInDatabase(mLauncher, finalItem, mInfo.container,
1123                             mInfo.screenId, mInfo.cellX, mInfo.cellY);
1124                 }
1125                 if (getItemCount() <= 1) {
1126                     // Remove the folder
1127                     LauncherModel.deleteItemFromDatabase(mLauncher, mInfo);
1128                     if (cellLayout != null) {
1129                         // b/12446428 -- sometimes the cell layout has already gone away?
1130                         cellLayout.removeView(mFolderIcon);
1131                     }
1132                     if (mFolderIcon instanceof DropTarget) {
1133                         mDragController.removeDropTarget((DropTarget) mFolderIcon);
1134                     }
1135                     mLauncher.removeFolder(mInfo);
1136                 }
1137                 // We add the child after removing the folder to prevent both from existing at
1138                 // the same time in the CellLayout.  We need to add the new item with addInScreenFromBind()
1139                 // to ensure that hotseat items are placed correctly.
1140                 if (child != null) {
1141                     mLauncher.getWorkspace().addInScreenFromBind(child, mInfo.container, mInfo.screenId,
1142                             mInfo.cellX, mInfo.cellY, mInfo.spanX, mInfo.spanY);
1143                 }
1144             }
1145         };
1146         View finalChild = getItemAt(0);
1147         if (finalChild != null) {
1148             mFolderIcon.performDestroyAnimation(finalChild, onCompleteRunnable);
1149         } else {
1150             onCompleteRunnable.run();
1151         }
1152         mDestroyed = true;
1153     }
1154 
isDestroyed()1155     boolean isDestroyed() {
1156         return mDestroyed;
1157     }
1158 
1159     // This method keeps track of the last item in the folder for the purposes
1160     // of keyboard focus
updateTextViewFocus()1161     private void updateTextViewFocus() {
1162         View lastChild = getItemAt(getItemCount() - 1);
1163         getItemAt(getItemCount() - 1);
1164         if (lastChild != null) {
1165             mFolderName.setNextFocusDownId(lastChild.getId());
1166             mFolderName.setNextFocusRightId(lastChild.getId());
1167             mFolderName.setNextFocusLeftId(lastChild.getId());
1168             mFolderName.setNextFocusUpId(lastChild.getId());
1169         }
1170     }
1171 
onDrop(DragObject d)1172     public void onDrop(DragObject d) {
1173         Runnable cleanUpRunnable = null;
1174 
1175         // If we are coming from All Apps space, we need to remove the extra empty screen (which is
1176         // normally done in Workspace#onDropExternal, as well zoom back in and close the folder.
1177         if (d.dragSource != mLauncher.getWorkspace() && !(d.dragSource instanceof Folder)) {
1178             cleanUpRunnable = new Runnable() {
1179                 @Override
1180                 public void run() {
1181                     mLauncher.getWorkspace().removeExtraEmptyScreen(false, new Runnable() {
1182                         @Override
1183                         public void run() {
1184                             mLauncher.closeFolder();
1185                             mLauncher.exitSpringLoadedDragModeDelayed(true,
1186                                     Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT_FOLDER_CLOSE,
1187                                     null);
1188                         }
1189                     }, CLOSE_FOLDER_DELAY_MS, false);
1190                 }
1191             };
1192         }
1193 
1194         View currentDragView;
1195         ShortcutInfo si = mCurrentDragInfo;
1196         if (mIsExternalDrag) {
1197             si.cellX = mEmptyCell[0];
1198             si.cellY = mEmptyCell[1];
1199             currentDragView = createAndAddShortcut(si);
1200         } else {
1201             currentDragView = mCurrentDragView;
1202             CellLayout.LayoutParams lp = (CellLayout.LayoutParams) currentDragView.getLayoutParams();
1203             si.cellX = lp.cellX = mEmptyCell[0];
1204             si.cellX = lp.cellY = mEmptyCell[1];
1205             mContent.addViewToCellLayout(currentDragView, -1, (int) si.id, lp, true);
1206         }
1207 
1208         if (d.dragView.hasDrawn()) {
1209 
1210             // Temporarily reset the scale such that the animation target gets calculated correctly.
1211             float scaleX = getScaleX();
1212             float scaleY = getScaleY();
1213             setScaleX(1.0f);
1214             setScaleY(1.0f);
1215             mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, currentDragView,
1216                     cleanUpRunnable, null);
1217             setScaleX(scaleX);
1218             setScaleY(scaleY);
1219         } else {
1220             d.deferDragViewCleanupPostAnimation = false;
1221             currentDragView.setVisibility(VISIBLE);
1222         }
1223         mItemsInvalidated = true;
1224         setupContentDimensions(getItemCount());
1225 
1226         // Actually move the item in the database if it was an external drag.
1227         if (mIsExternalDrag) {
1228             LauncherModel.addOrMoveItemInDatabase(
1229                     mLauncher, si, mInfo.id, 0, si.cellX, si.cellY);
1230 
1231             // We only need to update the locations if it doesn't get handled in #onDropCompleted.
1232             if (d.dragSource != this) {
1233                 updateItemLocationsInDatabaseBatch();
1234             }
1235             mIsExternalDrag = false;
1236         }
1237 
1238         // Temporarily suppress the listener, as we did all the work already here.
1239         mSuppressOnAdd = true;
1240         mInfo.add(si);
1241         mSuppressOnAdd = false;
1242     }
1243 
1244     // This is used so the item doesn't immediately appear in the folder when added. In one case
1245     // we need to create the illusion that the item isn't added back to the folder yet, to
1246     // to correspond to the animation of the icon back into the folder. This is
hideItem(ShortcutInfo info)1247     public void hideItem(ShortcutInfo info) {
1248         View v = getViewForInfo(info);
1249         v.setVisibility(INVISIBLE);
1250     }
showItem(ShortcutInfo info)1251     public void showItem(ShortcutInfo info) {
1252         View v = getViewForInfo(info);
1253         v.setVisibility(VISIBLE);
1254     }
1255 
onAdd(ShortcutInfo item)1256     public void onAdd(ShortcutInfo item) {
1257         mItemsInvalidated = true;
1258         // If the item was dropped onto this open folder, we have done the work associated
1259         // with adding the item to the folder, as indicated by mSuppressOnAdd being set
1260         if (mSuppressOnAdd) return;
1261         if (!findAndSetEmptyCells(item)) {
1262             // The current layout is full, can we expand it?
1263             setupContentForNumItems(getItemCount() + 1);
1264             findAndSetEmptyCells(item);
1265         }
1266         createAndAddShortcut(item);
1267         LauncherModel.addOrMoveItemInDatabase(
1268                 mLauncher, item, mInfo.id, 0, item.cellX, item.cellY);
1269     }
1270 
onRemove(ShortcutInfo item)1271     public void onRemove(ShortcutInfo item) {
1272         mItemsInvalidated = true;
1273         // If this item is being dragged from this open folder, we have already handled
1274         // the work associated with removing the item, so we don't have to do anything here.
1275         if (item == mCurrentDragInfo) return;
1276         View v = getViewForInfo(item);
1277         mContent.removeView(v);
1278         if (mState == STATE_ANIMATING) {
1279             mRearrangeOnClose = true;
1280         } else {
1281             setupContentForNumItems(getItemCount());
1282         }
1283         if (getItemCount() <= 1) {
1284             replaceFolderWithFinalItem();
1285         }
1286     }
1287 
getViewForInfo(ShortcutInfo item)1288     private View getViewForInfo(ShortcutInfo item) {
1289         for (int j = 0; j < mContent.getCountY(); j++) {
1290             for (int i = 0; i < mContent.getCountX(); i++) {
1291                 View v = mContent.getChildAt(i, j);
1292                 if (v.getTag() == item) {
1293                     return v;
1294                 }
1295             }
1296         }
1297         return null;
1298     }
1299 
onItemsChanged()1300     public void onItemsChanged() {
1301         updateTextViewFocus();
1302     }
1303 
onTitleChanged(CharSequence title)1304     public void onTitleChanged(CharSequence title) {
1305     }
1306 
getItemsInReadingOrder()1307     public ArrayList<View> getItemsInReadingOrder() {
1308         if (mItemsInvalidated) {
1309             mItemsInReadingOrder.clear();
1310             for (int j = 0; j < mContent.getCountY(); j++) {
1311                 for (int i = 0; i < mContent.getCountX(); i++) {
1312                     View v = mContent.getChildAt(i, j);
1313                     if (v != null) {
1314                         mItemsInReadingOrder.add(v);
1315                     }
1316                 }
1317             }
1318             mItemsInvalidated = false;
1319         }
1320         return mItemsInReadingOrder;
1321     }
1322 
getLocationInDragLayer(int[] loc)1323     public void getLocationInDragLayer(int[] loc) {
1324         mLauncher.getDragLayer().getLocationInDragLayer(this, loc);
1325     }
1326 
onFocusChange(View v, boolean hasFocus)1327     public void onFocusChange(View v, boolean hasFocus) {
1328         if (v == mFolderName && hasFocus) {
1329             startEditingFolderName();
1330         }
1331     }
1332 
1333     @Override
getHitRectRelativeToDragLayer(Rect outRect)1334     public void getHitRectRelativeToDragLayer(Rect outRect) {
1335         getHitRect(outRect);
1336     }
1337 }
1338