OSDN Git Service

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