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