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