Folder.java revision afb01ee74243cede19088e694ca82cea5983c603
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
17package com.android.launcher2;
18
19import android.animation.Animator;
20import android.animation.AnimatorListenerAdapter;
21import android.animation.ObjectAnimator;
22import android.animation.PropertyValuesHolder;
23import android.animation.ValueAnimator;
24import android.animation.ValueAnimator.AnimatorUpdateListener;
25import android.content.Context;
26import android.content.res.Resources;
27import android.graphics.Rect;
28import android.graphics.drawable.Drawable;
29import android.util.AttributeSet;
30import android.view.ActionMode;
31import android.view.KeyEvent;
32import android.view.LayoutInflater;
33import android.view.Menu;
34import android.view.MenuItem;
35import android.view.MotionEvent;
36import android.view.View;
37import android.view.View.OnClickListener;
38import android.view.animation.AccelerateInterpolator;
39import android.view.animation.DecelerateInterpolator;
40import android.view.inputmethod.EditorInfo;
41import android.view.inputmethod.InputMethodManager;
42import android.widget.AdapterView;
43import android.widget.LinearLayout;
44import android.widget.TextView;
45import android.widget.AdapterView.OnItemClickListener;
46import android.widget.AdapterView.OnItemLongClickListener;
47
48import com.android.launcher.R;
49import com.android.launcher2.FolderInfo.FolderListener;
50
51import java.util.ArrayList;
52
53/**
54 * Represents a set of icons chosen by the user or generated by the system.
55 */
56public class Folder extends LinearLayout implements DragSource, OnItemLongClickListener,
57        OnItemClickListener, OnClickListener, View.OnLongClickListener, DropTarget, FolderListener,
58        TextView.OnEditorActionListener {
59
60    protected DragController mDragController;
61
62    protected Launcher mLauncher;
63
64    protected FolderInfo mInfo;
65
66    private static final String TAG = "Launcher.Folder";
67
68    static final int STATE_NONE = -1;
69    static final int STATE_SMALL = 0;
70    static final int STATE_ANIMATING = 1;
71    static final int STATE_OPEN = 2;
72
73    private int mExpandDuration;
74    protected CellLayout mContent;
75    private final LayoutInflater mInflater;
76    private final IconCache mIconCache;
77    private int mState = STATE_NONE;
78    private int[] mDragItemPosition = new int[2];
79    private static final int FULL_GROW = 0;
80    private static final int PARTIAL_GROW = 1;
81    private static final int REORDER_ANIMATION_DURATION = 230;
82    private static final int ON_EXIT_CLOSE_DELAY = 800;
83    private int mMode = PARTIAL_GROW;
84    private boolean mRearrangeOnClose = false;
85    private FolderIcon mFolderIcon;
86    private int mMaxCountX;
87    private int mMaxCountY;
88    private Rect mNewSize = new Rect();
89    private ArrayList<View> mItemsInReadingOrder = new ArrayList<View>();
90    private Drawable mIconDrawable;
91    boolean mItemsInvalidated = false;
92    private ShortcutInfo mCurrentDragInfo;
93    private View mCurrentDragView;
94    boolean mSuppressOnAdd = false;
95    private int[] mTargetCell = new int[2];
96    private int[] mPreviousTargetCell = new int[2];
97    private int[] mEmptyCell = new int[2];
98    private Alarm mReorderAlarm = new Alarm();
99    private Alarm mOnExitAlarm = new Alarm();
100    private TextView mFolderName;
101    private int mFolderNameHeight;
102    private static String sDefaultFolderName;
103    private Rect mHitRect = new Rect();
104
105    private boolean mIsEditingName = false;
106    private InputMethodManager mInputMethodManager;
107
108    /**
109     * Used to inflate the Workspace from XML.
110     *
111     * @param context The application's context.
112     * @param attrs The attribtues set containing the Workspace's customization values.
113     */
114    public Folder(Context context, AttributeSet attrs) {
115        super(context, attrs);
116        setAlwaysDrawnWithCacheEnabled(false);
117        mInflater = LayoutInflater.from(context);
118        mIconCache = ((LauncherApplication)context.getApplicationContext()).getIconCache();
119        mMaxCountX = LauncherModel.getCellCountX() - 1;
120        mMaxCountY = LauncherModel.getCellCountY() - 1;
121
122        mInputMethodManager = (InputMethodManager)
123                mContext.getSystemService(Context.INPUT_METHOD_SERVICE);
124
125        Resources res = getResources();
126        mExpandDuration = res.getInteger(R.integer.config_folderAnimDuration);
127
128        if (sDefaultFolderName == null) {
129            sDefaultFolderName = res.getString(R.string.folder_name);
130        }
131    }
132
133    @Override
134    protected void onFinishInflate() {
135        super.onFinishInflate();
136        mContent = (CellLayout) findViewById(R.id.folder_content);
137        mContent.setGridSize(0, 0);
138        mContent.enableHardwareLayers();
139        mFolderName = (TextView) findViewById(R.id.folder_name);
140
141        // We find out how tall the text view wants to be (it is set to wrap_content), so that
142        // we can allocate the appropriate amount of space for it.
143        int measureSpec = MeasureSpec.UNSPECIFIED;
144        mFolderName.measure(measureSpec, measureSpec);
145        mFolderNameHeight = mFolderName.getMeasuredHeight();
146
147        // We disable action mode for now since it messes up the view on phones
148        mFolderName.setCustomSelectionActionModeCallback(mActionModeCallback);
149        mFolderName.setCursorVisible(false);
150        mFolderName.setOnEditorActionListener(this);
151        mFolderName.setSelectAllOnFocus(true);
152    }
153
154    private ActionMode.Callback mActionModeCallback = new ActionMode.Callback() {
155        public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
156            return false;
157        }
158
159        public boolean onCreateActionMode(ActionMode mode, Menu menu) {
160            return false;
161        }
162
163        public void onDestroyActionMode(ActionMode mode) {
164        }
165
166        public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
167            return false;
168        }
169    };
170
171    public void onItemClick(AdapterView parent, View v, int position, long id) {
172        ShortcutInfo app = (ShortcutInfo) parent.getItemAtPosition(position);
173        int[] pos = new int[2];
174        v.getLocationOnScreen(pos);
175        app.intent.setSourceBounds(new Rect(pos[0], pos[1],
176                pos[0] + v.getWidth(), pos[1] + v.getHeight()));
177        mLauncher.startActivitySafely(app.intent, app);
178    }
179
180    public void onClick(View v) {
181        Object tag = v.getTag();
182        if (tag instanceof ShortcutInfo) {
183            // refactor this code from Folder
184            ShortcutInfo item = (ShortcutInfo) tag;
185            int[] pos = new int[2];
186            v.getLocationOnScreen(pos);
187            item.intent.setSourceBounds(new Rect(pos[0], pos[1],
188                    pos[0] + v.getWidth(), pos[1] + v.getHeight()));
189            mLauncher.startActivitySafely(item.intent, item);
190        }
191    }
192
193    public boolean onInterceptTouchEvent(MotionEvent ev) {
194        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
195            mFolderName.getHitRect(mHitRect);
196            if (mHitRect.contains((int) ev.getX(), (int) ev.getY()) && !mIsEditingName) {
197                startEditingFolderName();
198            }
199        }
200        return false;
201    }
202
203    public boolean onLongClick(View v) {
204        Object tag = v.getTag();
205        if (tag instanceof ShortcutInfo) {
206            ShortcutInfo item = (ShortcutInfo) tag;
207            if (!v.isInTouchMode()) {
208                return false;
209            }
210
211            mLauncher.getWorkspace().onDragStartedWithItem(v);
212            mDragController.startDrag(v, this, item, DragController.DRAG_ACTION_COPY);
213            mDragItemPosition[0] = item.cellX;
214            mDragItemPosition[1] = item.cellY;
215            mIconDrawable = ((TextView) v).getCompoundDrawables()[1];
216
217            mCurrentDragInfo = item;
218            mEmptyCell[0] = item.cellX;
219            mEmptyCell[1] = item.cellY;
220            mCurrentDragView = v;
221            mContent.removeView(mCurrentDragView);
222            mInfo.remove(item);
223        }
224        return true;
225    }
226
227    public boolean isEditingName() {
228        return mIsEditingName;
229    }
230
231    public void startEditingFolderName() {
232        mFolderName.setCursorVisible(true);
233        mIsEditingName = true;
234    }
235
236    public void dismissEditingName() {
237        mInputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0);
238        doneEditingFolderName(true);
239    }
240
241    public void doneEditingFolderName(boolean commit) {
242        mInfo.setTitle(mFolderName.getText());
243        LauncherModel.updateItemInDatabase(mLauncher, mInfo);
244        mFolderName.setCursorVisible(false);
245        mFolderName.clearFocus();
246        mIsEditingName = false;
247    }
248
249    public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
250        if (actionId == EditorInfo.IME_ACTION_DONE) {
251            dismissEditingName();
252            return true;
253        }
254        return false;
255    }
256
257    public View getEditTextRegion() {
258        return mFolderName;
259    }
260
261    public Drawable getDragDrawable() {
262        return mIconDrawable;
263    }
264
265    /**
266     * We need to handle touch events to prevent them from falling through to the workspace below.
267     */
268    @Override
269    public boolean onTouchEvent(MotionEvent ev) {
270        return true;
271    }
272
273    public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
274        if (!view.isInTouchMode()) {
275            return false;
276        }
277
278        ShortcutInfo app = (ShortcutInfo) parent.getItemAtPosition(position);
279
280        mDragController.startDrag(view, this, app, DragController.DRAG_ACTION_COPY);
281        mLauncher.closeFolder(this);
282        return true;
283    }
284
285    public void setDragController(DragController dragController) {
286        mDragController = dragController;
287    }
288
289    public void onDragViewVisible() {
290    }
291
292    void setLauncher(Launcher launcher) {
293        mLauncher = launcher;
294    }
295
296    void setFolderIcon(FolderIcon icon) {
297        mFolderIcon = icon;
298    }
299
300    /**
301     * @return the FolderInfo object associated with this folder
302     */
303    FolderInfo getInfo() {
304        return mInfo;
305    }
306
307    void onOpen() {
308        // When the folder opens, we need to refresh the GridView's selection by
309        // forcing a layout
310        // TODO: find out if this is still necessary
311        mContent.requestLayout();
312    }
313
314    void onClose() {
315        CellLayoutChildren clc = (CellLayoutChildren) getParent();
316        final CellLayout cellLayout = (CellLayout) clc.getParent();
317        cellLayout.removeViewWithoutMarkingCells(Folder.this);
318        clearFocus();
319    }
320
321    void bind(FolderInfo info) {
322        mInfo = info;
323        ArrayList<ShortcutInfo> children = info.contents;
324        setupContentForNumItems(children.size());
325        for (int i = 0; i < children.size(); i++) {
326            ShortcutInfo child = (ShortcutInfo) children.get(i);
327            createAndAddShortcut(child);
328        }
329        mItemsInvalidated = true;
330        mInfo.addListener(this);
331
332        if (!sDefaultFolderName.contentEquals(mInfo.title)) {
333            mFolderName.setText(mInfo.title);
334        } else {
335            mFolderName.setText("");
336        }
337    }
338
339    /**
340     * Creates a new UserFolder, inflated from R.layout.user_folder.
341     *
342     * @param context The application's context.
343     *
344     * @return A new UserFolder.
345     */
346    static Folder fromXml(Context context) {
347        return (Folder) LayoutInflater.from(context).inflate(R.layout.user_folder, null);
348    }
349
350    /**
351     * This method is intended to make the UserFolder to be visually identical in size and position
352     * to its associated FolderIcon. This allows for a seamless transition into the expanded state.
353     */
354    private void positionAndSizeAsIcon() {
355        if (!(getParent() instanceof CellLayoutChildren)) return;
356
357        CellLayout.LayoutParams iconLp = (CellLayout.LayoutParams) mFolderIcon.getLayoutParams();
358        CellLayout.LayoutParams lp = (CellLayout.LayoutParams) getLayoutParams();
359
360        if (mMode == PARTIAL_GROW) {
361            setScaleX(0.8f);
362            setScaleY(0.8f);
363            setAlpha(0f);
364        } else {
365            lp.width = iconLp.width;
366            lp.height = iconLp.height;
367            lp.x = iconLp.x;
368            lp.y = iconLp.y;
369            mContent.setAlpha(0);
370        }
371        mState = STATE_SMALL;
372    }
373
374    public void animateOpen() {
375        if (mState != STATE_SMALL) {
376            positionAndSizeAsIcon();
377        }
378        if (!(getParent() instanceof CellLayoutChildren)) return;
379
380        ObjectAnimator oa;
381        CellLayout.LayoutParams lp = (CellLayout.LayoutParams) getLayoutParams();
382
383        centerAboutIcon();
384        if (mMode == PARTIAL_GROW) {
385            PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 1);
386            PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 1.0f);
387            PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", 1.0f);
388            oa = ObjectAnimator.ofPropertyValuesHolder(this, alpha, scaleX, scaleY);
389        } else {
390            PropertyValuesHolder width = PropertyValuesHolder.ofInt("width", mNewSize.width());
391            PropertyValuesHolder height = PropertyValuesHolder.ofInt("height", mNewSize.height());
392            PropertyValuesHolder x = PropertyValuesHolder.ofInt("x", mNewSize.left);
393            PropertyValuesHolder y = PropertyValuesHolder.ofInt("y", mNewSize.top);
394            oa = ObjectAnimator.ofPropertyValuesHolder(lp, width, height, x, y);
395            oa.addUpdateListener(new AnimatorUpdateListener() {
396                public void onAnimationUpdate(ValueAnimator animation) {
397                    requestLayout();
398                }
399            });
400
401            PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 1.0f);
402            ObjectAnimator alphaOa = ObjectAnimator.ofPropertyValuesHolder(mContent, alpha);
403            alphaOa.setDuration(mExpandDuration);
404            alphaOa.setInterpolator(new AccelerateInterpolator(2.0f));
405            alphaOa.start();
406        }
407
408        oa.addListener(new AnimatorListenerAdapter() {
409            @Override
410            public void onAnimationStart(Animator animation) {
411                mState = STATE_ANIMATING;
412            }
413            @Override
414            public void onAnimationEnd(Animator animation) {
415                mState = STATE_OPEN;
416            }
417        });
418        oa.setDuration(mExpandDuration);
419        oa.start();
420    }
421
422    public void animateClosed() {
423        if (!(getParent() instanceof CellLayoutChildren)) return;
424
425        ObjectAnimator oa;
426        if (mMode == PARTIAL_GROW) {
427            PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 0);
428            PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 0.9f);
429            PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", 0.9f);
430            oa = ObjectAnimator.ofPropertyValuesHolder(this, alpha, scaleX, scaleY);
431        } else {
432            CellLayout.LayoutParams iconLp = (CellLayout.LayoutParams) mFolderIcon.getLayoutParams();
433            CellLayout.LayoutParams lp = (CellLayout.LayoutParams) getLayoutParams();
434
435            PropertyValuesHolder width = PropertyValuesHolder.ofInt("width", iconLp.width);
436            PropertyValuesHolder height = PropertyValuesHolder.ofInt("height", iconLp.height);
437            PropertyValuesHolder x = PropertyValuesHolder.ofInt("x",iconLp.x);
438            PropertyValuesHolder y = PropertyValuesHolder.ofInt("y", iconLp.y);
439            oa = ObjectAnimator.ofPropertyValuesHolder(lp, width, height, x, y);
440            oa.addUpdateListener(new AnimatorUpdateListener() {
441                public void onAnimationUpdate(ValueAnimator animation) {
442                    requestLayout();
443                }
444            });
445
446            PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 0f);
447            ObjectAnimator alphaOa = ObjectAnimator.ofPropertyValuesHolder(mContent, alpha);
448            alphaOa.setDuration(mExpandDuration);
449            alphaOa.setInterpolator(new DecelerateInterpolator(2.0f));
450            alphaOa.start();
451        }
452
453        oa.addListener(new AnimatorListenerAdapter() {
454            @Override
455            public void onAnimationEnd(Animator animation) {
456                onClose();
457                onCloseComplete();
458                mState = STATE_SMALL;
459            }
460            @Override
461            public void onAnimationStart(Animator animation) {
462                mState = STATE_ANIMATING;
463            }
464        });
465        oa.setDuration(mExpandDuration);
466        oa.start();
467    }
468
469    void notifyDataSetChanged() {
470        // recreate all the children if the data set changes under us. We may want to do this more
471        // intelligently (ie just removing the views that should no longer exist)
472        mContent.removeAllViewsInLayout();
473        bind(mInfo);
474    }
475
476    public boolean acceptDrop(DragObject d) {
477        final ItemInfo item = (ItemInfo) d.dragInfo;
478        final int itemType = item.itemType;
479        return ((itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION ||
480                    itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) &&
481                    !isFull());
482    }
483
484    protected boolean findAndSetEmptyCells(ShortcutInfo item) {
485        int[] emptyCell = new int[2];
486        if (mContent.findCellForSpan(emptyCell, item.spanX, item.spanY)) {
487            item.cellX = emptyCell[0];
488            item.cellY = emptyCell[1];
489            return true;
490        } else {
491            return false;
492        }
493    }
494
495    protected void createAndAddShortcut(ShortcutInfo item) {
496        final TextView textView =
497            (TextView) mInflater.inflate(R.layout.application_boxed, this, false);
498        textView.setCompoundDrawablesWithIntrinsicBounds(null,
499                new FastBitmapDrawable(item.getIcon(mIconCache)), null, null);
500        textView.setText(item.title);
501        textView.setTag(item);
502
503        textView.setOnClickListener(this);
504        textView.setOnLongClickListener(this);
505
506        CellLayout.LayoutParams lp =
507            new CellLayout.LayoutParams(item.cellX, item.cellY, item.spanX, item.spanY);
508        boolean insert = false;
509        mContent.addViewToCellLayout(textView, insert ? 0 : -1, (int)item.id, lp, true);
510    }
511
512    public void onDragEnter(DragObject d) {
513        mPreviousTargetCell[0] = -1;
514        mPreviousTargetCell[1] = -1;
515        mContent.onDragEnter();
516        mOnExitAlarm.cancelAlarm();
517    }
518
519    OnAlarmListener mReorderAlarmListener = new OnAlarmListener() {
520        public void onAlarm(Alarm alarm) {
521            realTimeReorder(mEmptyCell, mTargetCell);
522        }
523    };
524
525    boolean readingOrderGreaterThan(int[] v1, int[] v2) {
526        if (v1[1] > v2[1] || (v1[1] == v2[1] && v1[0] > v2[0])) {
527            return true;
528        } else {
529            return false;
530        }
531    }
532
533    private void realTimeReorder(int[] empty, int[] target) {
534        boolean wrap;
535        int startX;
536        int endX;
537        int startY;
538        int delay = 0;
539        float delayAmount = 30;
540        if (readingOrderGreaterThan(target, empty)) {
541            wrap = empty[0] >= mContent.getCountX() - 1;
542            startY = wrap ? empty[1] + 1 : empty[1];
543            for (int y = startY; y <= target[1]; y++) {
544                startX = y == empty[1] ? empty[0] + 1 : 0;
545                endX = y < target[1] ? mContent.getCountX() - 1 : target[0];
546                for (int x = startX; x <= endX; x++) {
547                    View v = mContent.getChildAt(x,y);
548                    if (mContent.animateChildToPosition(v, empty[0], empty[1],
549                            REORDER_ANIMATION_DURATION, delay)) {
550                        empty[0] = x;
551                        empty[1] = y;
552                        delay += delayAmount;
553                        delayAmount *= 0.9;
554                    }
555                }
556            }
557        } else {
558            wrap = empty[0] == 0;
559            startY = wrap ? empty[1] - 1 : empty[1];
560            for (int y = startY; y >= target[1]; y--) {
561                startX = y == empty[1] ? empty[0] - 1 : mContent.getCountX() - 1;
562                endX = y > target[1] ? 0 : target[0];
563                for (int x = startX; x >= endX; x--) {
564                    View v = mContent.getChildAt(x,y);
565                    if (mContent.animateChildToPosition(v, empty[0], empty[1],
566                            REORDER_ANIMATION_DURATION, delay)) {
567                        empty[0] = x;
568                        empty[1] = y;
569                        delay += delayAmount;
570                        delayAmount *= 0.9;
571                    }
572                }
573            }
574        }
575    }
576
577    public void onDragOver(DragObject d) {
578        float[] r = getDragViewVisualCenter(d.x, d.y, d.xOffset, d.yOffset, d.dragView, null);
579        mTargetCell = mContent.findNearestArea((int) r[0], (int) r[1], 1, 1, mTargetCell);
580
581        if (mTargetCell[0] != mPreviousTargetCell[0] || mTargetCell[1] != mPreviousTargetCell[1]) {
582            mReorderAlarm.cancelAlarm();
583            mReorderAlarm.setOnAlarmListener(mReorderAlarmListener);
584            mReorderAlarm.setAlarm(150);
585            mPreviousTargetCell[0] = mTargetCell[0];
586            mPreviousTargetCell[1] = mTargetCell[1];
587        }
588    }
589
590    // This is used to compute the visual center of the dragView. The idea is that
591    // the visual center represents the user's interpretation of where the item is, and hence
592    // is the appropriate point to use when determining drop location.
593    private float[] getDragViewVisualCenter(int x, int y, int xOffset, int yOffset,
594            DragView dragView, float[] recycle) {
595        float res[];
596        if (recycle == null) {
597            res = new float[2];
598        } else {
599            res = recycle;
600        }
601
602        // These represent the visual top and left of drag view if a dragRect was provided.
603        // If a dragRect was not provided, then they correspond to the actual view left and
604        // top, as the dragRect is in that case taken to be the entire dragView.
605        // R.dimen.dragViewOffsetY.
606        int left = x - xOffset;
607        int top = y - yOffset;
608
609        // In order to find the visual center, we shift by half the dragRect
610        res[0] = left + dragView.getDragRegion().width() / 2;
611        res[1] = top + dragView.getDragRegion().height() / 2;
612
613        return res;
614    }
615
616    OnAlarmListener mOnExitAlarmListener = new OnAlarmListener() {
617        public void onAlarm(Alarm alarm) {
618            mLauncher.closeFolder();
619            mCurrentDragInfo = null;
620            mCurrentDragView = null;
621            mSuppressOnAdd = false;
622            mRearrangeOnClose = true;
623        }
624    };
625
626    public void onDragExit(DragObject d) {
627        // We only close the folder if this is a true drag exit, ie. not because a drop
628        // has occurred above the folder.
629        if (!d.dragComplete) {
630            mOnExitAlarm.setOnAlarmListener(mOnExitAlarmListener);
631            mOnExitAlarm.setAlarm(ON_EXIT_CLOSE_DELAY);
632        }
633        mReorderAlarm.cancelAlarm();
634        mContent.onDragExit();
635    }
636
637    public void onDropCompleted(View target, DragObject d, boolean success) {
638        mCurrentDragInfo = null;
639        mCurrentDragView = null;
640        mSuppressOnAdd = false;
641        if (!success) {
642            if (d.dragView != null) {
643                if (target instanceof CellLayout) {
644                    // TODO: we should animate the item back to the folder in this case
645                }
646            }
647            // TODO: if the drag fails, we need to re-add the item
648        }
649    }
650
651    public boolean isDropEnabled() {
652        return true;
653    }
654
655    public DropTarget getDropTargetDelegate(DragObject d) {
656        return null;
657    }
658
659    private void setupContentDimension(int count) {
660        ArrayList<View> list = getItemsInReadingOrder();
661
662        int countX = mContent.getCountX();
663        int countY = mContent.getCountY();
664        boolean done = false;
665
666        while (!done) {
667            int oldCountX = countX;
668            int oldCountY = countY;
669            if (countX * countY < count) {
670                // Current grid is too small, expand it
671                if (countX <= countY && countX < mMaxCountX) {
672                    countX++;
673                } else if (countY < mMaxCountY) {
674                    countY++;
675                }
676                if (countY == 0) countY++;
677            } else if ((countY - 1) * countX >= count && countY >= countX) {
678                countY = Math.max(0, countY - 1);
679            } else if ((countX - 1) * countY >= count) {
680                countX = Math.max(0, countX - 1);
681            }
682            done = countX == oldCountX && countY == oldCountY;
683        }
684        mContent.setGridSize(countX, countY);
685        arrangeChildren(list);
686    }
687
688    public boolean isFull() {
689        return getItemCount() >= mMaxCountX * mMaxCountY;
690    }
691
692    private void centerAboutIcon() {
693        CellLayout.LayoutParams iconLp = (CellLayout.LayoutParams) mFolderIcon.getLayoutParams();
694        CellLayout.LayoutParams lp = (CellLayout.LayoutParams) getLayoutParams();
695
696        int width = getPaddingLeft() + getPaddingRight() + mContent.getDesiredWidth();
697        // Technically there is no padding at the bottom, but we add space equal to the padding
698        // and have to account for that here.
699        int height = getPaddingTop() + mContent.getDesiredHeight() + mFolderNameHeight;
700
701        int centerX = iconLp.x + iconLp.width / 2;
702        int centerY = iconLp.y + iconLp.height / 2;
703        int centeredLeft = centerX - width / 2;
704        int centeredTop = centerY - height / 2;
705
706        CellLayoutChildren clc = (CellLayoutChildren) getParent();
707        int parentWidth = 0;
708        int parentHeight = 0;
709        if (clc != null) {
710            parentWidth = clc.getMeasuredWidth();
711            parentHeight = clc.getMeasuredHeight();
712        }
713
714        int left = Math.min(Math.max(0, centeredLeft), parentWidth - width);
715        int top = Math.min(Math.max(0, centeredTop), parentHeight - height);
716
717        int folderPivotX = width / 2 + (centeredLeft - left);
718        int folderPivotY = height / 2 + (centeredTop - top);
719        setPivotX(folderPivotX);
720        setPivotY(folderPivotY);
721        int folderIconPivotX = (int) (mFolderIcon.getMeasuredWidth() *
722                (1.0f * folderPivotX / width));
723        int folderIconPivotY = (int) (mFolderIcon.getMeasuredHeight() *
724                (1.0f * folderPivotY / height));
725        mFolderIcon.setPivotX(folderIconPivotX);
726        mFolderIcon.setPivotY(folderIconPivotY);
727
728        if (mMode == PARTIAL_GROW) {
729            lp.width = width;
730            lp.height = height;
731            lp.x = left;
732            lp.y = top;
733        } else {
734            mNewSize.set(left, top, left + width, top + height);
735        }
736    }
737
738    private void setupContentForNumItems(int count) {
739        setupContentDimension(count);
740
741        CellLayout.LayoutParams lp = (CellLayout.LayoutParams) getLayoutParams();
742        if (lp == null) {
743            lp = new CellLayout.LayoutParams(0, 0, -1, -1);
744            lp.isLockedToGrid = false;
745            setLayoutParams(lp);
746        }
747        centerAboutIcon();
748    }
749
750    private void arrangeChildren(ArrayList<View> list) {
751        int[] vacant = new int[2];
752        if (list == null) {
753            list = getItemsInReadingOrder();
754        }
755        mContent.removeAllViews();
756
757        for (int i = 0; i < list.size(); i++) {
758            View v = list.get(i);
759            mContent.getVacantCell(vacant, 1, 1);
760            CellLayout.LayoutParams lp = (CellLayout.LayoutParams) v.getLayoutParams();
761            lp.cellX = vacant[0];
762            lp.cellY = vacant[1];
763            ItemInfo info = (ItemInfo) v.getTag();
764            info.cellX = vacant[0];
765            info.cellY = vacant[1];
766            boolean insert = false;
767            mContent.addViewToCellLayout(v, insert ? 0 : -1, (int)info.id, lp, true);
768            LauncherModel.addOrMoveItemInDatabase(mLauncher, info, mInfo.id, 0,
769                    info.cellX, info.cellY);
770        }
771        mItemsInvalidated = true;
772    }
773
774    public int getItemCount() {
775        return mContent.getChildrenLayout().getChildCount();
776    }
777
778    public View getItemAt(int index) {
779        return mContent.getChildrenLayout().getChildAt(index);
780    }
781
782    private void onCloseComplete() {
783        if (mRearrangeOnClose) {
784            setupContentForNumItems(getItemCount());
785            mRearrangeOnClose = false;
786        }
787        if (getItemCount() <= 1) {
788            replaceFolderWithFinalItem();
789        }
790    }
791
792    private void replaceFolderWithFinalItem() {
793        ItemInfo finalItem = null;
794
795        if (getItemCount() == 1) {
796            finalItem = mInfo.contents.get(0);
797        }
798
799        // Remove the folder completely
800        final CellLayout cellLayout = (CellLayout)
801                mLauncher.getWorkspace().getChildAt(mInfo.screen);
802        cellLayout.removeView(mFolderIcon);
803        if (mFolderIcon instanceof DropTarget) {
804            mDragController.removeDropTarget((DropTarget) mFolderIcon);
805        }
806        mLauncher.removeFolder(mInfo);
807
808        if (finalItem != null) {
809            LauncherModel.addOrMoveItemInDatabase(mLauncher, finalItem,
810                    LauncherSettings.Favorites.CONTAINER_DESKTOP, mInfo.screen,
811                    mInfo.cellX, mInfo.cellY);
812        }
813        LauncherModel.deleteFolderContentsFromDatabase(mLauncher, mInfo, true);
814
815        // Add the last remaining child to the workspace in place of the folder
816        if (finalItem != null) {
817            View child = mLauncher.createShortcut(R.layout.application, cellLayout,
818                    (ShortcutInfo) finalItem);
819
820            mLauncher.getWorkspace().addInScreen(child, mInfo.screen, mInfo.cellX, mInfo.cellY,
821                    mInfo.spanX, mInfo.spanY);
822        }
823    }
824
825    public void onDrop(DragObject d) {
826        ShortcutInfo item;
827        if (d.dragInfo instanceof ApplicationInfo) {
828            // Came from all apps -- make a copy
829            item = ((ApplicationInfo) d.dragInfo).makeShortcut();
830            item.spanX = 1;
831            item.spanY = 1;
832        } else {
833            item = (ShortcutInfo) d.dragInfo;
834        }
835        // Dragged from self onto self
836        if (item == mCurrentDragInfo) {
837            ShortcutInfo si = (ShortcutInfo) mCurrentDragView.getTag();
838            CellLayout.LayoutParams lp = (CellLayout.LayoutParams) mCurrentDragView.getLayoutParams();
839            si.cellX = lp.cellX = mEmptyCell[0];
840            si.cellX = lp.cellY = mEmptyCell[1];
841            mContent.addViewToCellLayout(mCurrentDragView, -1, (int)item.id, lp, true);
842            mSuppressOnAdd = true;
843            mItemsInvalidated = true;
844            setupContentDimension(getItemCount());
845        }
846        mInfo.add(item);
847    }
848
849    public void onAdd(ShortcutInfo item) {
850        mItemsInvalidated = true;
851        if (mSuppressOnAdd) return;
852        if (!findAndSetEmptyCells(item)) {
853            // The current layout is full, can we expand it?
854            setupContentForNumItems(getItemCount() + 1);
855            findAndSetEmptyCells(item);
856        }
857        createAndAddShortcut(item);
858        LauncherModel.addOrMoveItemInDatabase(
859                mLauncher, item, mInfo.id, 0, item.cellX, item.cellY);
860    }
861
862    public void onRemove(ShortcutInfo item) {
863        mItemsInvalidated = true;
864        if (item == mCurrentDragInfo) return;
865        View v = mContent.getChildAt(mDragItemPosition[0], mDragItemPosition[1]);
866        mContent.removeView(v);
867        if (mState == STATE_ANIMATING) {
868            mRearrangeOnClose = true;
869        } else {
870            setupContentForNumItems(getItemCount());
871        }
872        if (getItemCount() <= 1) {
873            replaceFolderWithFinalItem();
874        }
875    }
876
877    public void onItemsChanged() {
878    }
879    public void onTitleChanged(CharSequence title) {
880    }
881
882    public ArrayList<View> getItemsInReadingOrder() {
883        return getItemsInReadingOrder(true);
884    }
885
886    public ArrayList<View> getItemsInReadingOrder(boolean includeCurrentDragItem) {
887        if (mItemsInvalidated) {
888            mItemsInReadingOrder.clear();
889            for (int j = 0; j < mContent.getCountY(); j++) {
890                for (int i = 0; i < mContent.getCountX(); i++) {
891                    View v = mContent.getChildAt(i, j);
892                    if (v != null) {
893                        ShortcutInfo info = (ShortcutInfo) v.getTag();
894                        if (info != mCurrentDragInfo || includeCurrentDragItem) {
895                            mItemsInReadingOrder.add(v);
896                        }
897                    }
898                }
899            }
900            mItemsInvalidated = false;
901        }
902        return mItemsInReadingOrder;
903    }
904}
905