Folder.java revision 0c872ba544ecfd9b106bb66137da8680927590de
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 java.util.ArrayList;
20
21import android.animation.Animator;
22import android.animation.AnimatorListenerAdapter;
23import android.animation.AnimatorSet;
24import android.animation.ObjectAnimator;
25import android.animation.PropertyValuesHolder;
26import android.animation.ValueAnimator;
27import android.animation.ValueAnimator.AnimatorUpdateListener;
28import android.content.Context;
29import android.graphics.Rect;
30import android.util.AttributeSet;
31import android.view.LayoutInflater;
32import android.view.MotionEvent;
33import android.view.View;
34import android.view.View.OnClickListener;
35import android.widget.AdapterView;
36import android.widget.Button;
37import android.widget.LinearLayout;
38import android.widget.TextView;
39import android.widget.AdapterView.OnItemClickListener;
40import android.widget.AdapterView.OnItemLongClickListener;
41
42import com.android.launcher.R;
43import com.android.launcher2.FolderInfo.FolderListener;
44import com.android.launcher2.Workspace.ShrinkState;
45
46/**
47 * Represents a set of icons chosen by the user or generated by the system.
48 */
49public class Folder extends LinearLayout implements DragSource, OnItemLongClickListener,
50        OnItemClickListener, OnClickListener, View.OnLongClickListener, DropTarget, FolderListener {
51
52    protected DragController mDragController;
53
54    protected Launcher mLauncher;
55
56    protected Button mCloseButton;
57
58    protected FolderInfo mInfo;
59
60    /**
61     * Which item is being dragged
62     */
63    protected ShortcutInfo mDragItem;
64
65    private static final String TAG = "Launcher.Folder";
66
67    static final int STATE_NONE = -1;
68    static final int STATE_SMALL = 0;
69    static final int STATE_ANIMATING = 1;
70    static final int STATE_OPEN = 2;
71
72    private int mExpandDuration;
73    protected CellLayout mContent;
74    private final LayoutInflater mInflater;
75    private final IconCache mIconCache;
76    private int mState = STATE_NONE;
77    private int[] mDragItemPosition = new int[2];
78
79    /**
80     * Used to inflate the Workspace from XML.
81     *
82     * @param context The application's context.
83     * @param attrs The attribtues set containing the Workspace's customization values.
84     */
85    public Folder(Context context, AttributeSet attrs) {
86        super(context, attrs);
87        setAlwaysDrawnWithCacheEnabled(false);
88        mInflater = LayoutInflater.from(context);
89        mIconCache = ((LauncherApplication)context.getApplicationContext()).getIconCache();
90        mExpandDuration = getResources().getInteger(R.integer.config_folderAnimDuration);
91    }
92
93    @Override
94    protected void onFinishInflate() {
95        super.onFinishInflate();
96
97        mCloseButton = (Button) findViewById(R.id.folder_close);
98        mCloseButton.setOnClickListener(this);
99        mCloseButton.setOnLongClickListener(this);
100        mContent = (CellLayout) findViewById(R.id.folder_content);
101    }
102
103    public void onItemClick(AdapterView parent, View v, int position, long id) {
104        ShortcutInfo app = (ShortcutInfo) parent.getItemAtPosition(position);
105        int[] pos = new int[2];
106        v.getLocationOnScreen(pos);
107        app.intent.setSourceBounds(new Rect(pos[0], pos[1],
108                pos[0] + v.getWidth(), pos[1] + v.getHeight()));
109        mLauncher.startActivitySafely(app.intent, app);
110    }
111
112    public void onClick(View v) {
113        Object tag = v.getTag();
114        if (tag instanceof ShortcutInfo) {
115            // refactor this code from Folder
116            ShortcutInfo item = (ShortcutInfo) tag;
117            int[] pos = new int[2];
118            v.getLocationOnScreen(pos);
119            item.intent.setSourceBounds(new Rect(pos[0], pos[1],
120                    pos[0] + v.getWidth(), pos[1] + v.getHeight()));
121            mLauncher.startActivitySafely(item.intent, item);
122        } else {
123            mLauncher.closeFolder(this);
124        }
125    }
126
127    public boolean onLongClick(View v) {
128        Object tag = v.getTag();
129        if (tag instanceof ShortcutInfo) {
130         // refactor this code from Folder
131            ShortcutInfo item = (ShortcutInfo) tag;
132            if (!v.isInTouchMode()) {
133                return false;
134            }
135
136            mLauncher.getWorkspace().onDragStartedWithItem(v);
137            mDragController.startDrag(v, this, item, DragController.DRAG_ACTION_COPY);
138            mDragItemPosition[0] = item.cellX;
139            mDragItemPosition[1] = item.cellY;
140            mLauncher.closeFolder(this);
141            mDragItem = item;
142        } else {
143            mLauncher.closeFolder(this);
144            mLauncher.showRenameDialog(mInfo);
145        }
146        return true;
147    }
148
149    /**
150     * We need to handle touch events to prevent them from falling through to the workspace below.
151     */
152    @Override
153    public boolean onTouchEvent(MotionEvent ev) {
154        return true;
155    }
156
157    public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
158        if (!view.isInTouchMode()) {
159            return false;
160        }
161
162        ShortcutInfo app = (ShortcutInfo) parent.getItemAtPosition(position);
163
164        mDragController.startDrag(view, this, app, DragController.DRAG_ACTION_COPY);
165        mLauncher.closeFolder(this);
166        mDragItem = app;
167
168        return true;
169    }
170
171    public void setDragController(DragController dragController) {
172        mDragController = dragController;
173    }
174
175    public void onDragViewVisible() {
176    }
177
178    void setLauncher(Launcher launcher) {
179        mLauncher = launcher;
180    }
181
182    /**
183     * @return the FolderInfo object associated with this folder
184     */
185    FolderInfo getInfo() {
186        return mInfo;
187    }
188
189    void onOpen() {
190        // When the folder opens, we need to refresh the GridView's selection by
191        // forcing a layout
192        // TODO: find out if this is still necessary
193        mContent.requestLayout();
194        requestFocus();
195    }
196
197    void onClose() {
198        final Workspace workspace = mLauncher.getWorkspace();
199        workspace.getChildAt(workspace.getCurrentPage()).requestFocus();
200    }
201
202    void bind(FolderInfo info) {
203        mInfo = info;
204        mCloseButton.setText(info.title);
205        ArrayList<ShortcutInfo> children = info.contents;
206        for (int i = 0; i < children.size(); i++) {
207            ShortcutInfo child = (ShortcutInfo) children.get(i);
208            if ((child.cellX == -1 && child.cellY == -1) ||
209                    mContent.isOccupied(child.cellX, child.cellY)) {
210                findAndSetEmptyCells(child);
211            }
212            createAndAddShortcut((ShortcutInfo) children.get(i));
213        }
214        mInfo.addListener(this);
215    }
216
217    /**
218     * Creates a new UserFolder, inflated from R.layout.user_folder.
219     *
220     * @param context The application's context.
221     *
222     * @return A new UserFolder.
223     */
224    static Folder fromXml(Context context) {
225        return (Folder) LayoutInflater.from(context).inflate(R.layout.user_folder, null);
226    }
227
228    /**
229     * This method is intended to make the UserFolder to be visually identical in size and position
230     * to its associated FolderIcon. This allows for a seamless transition into the expanded state.
231     */
232    private void positionAndSizeAsIcon() {
233        if (!(getParent() instanceof CellLayoutChildren)) return;
234
235        CellLayoutChildren clc = (CellLayoutChildren) getParent();
236        CellLayout cellLayout = (CellLayout) clc.getParent();
237
238        FolderIcon fi = (FolderIcon) cellLayout.getChildAt(mInfo.cellX, mInfo.cellY);
239        CellLayout.LayoutParams iconLp = (CellLayout.LayoutParams) fi.getLayoutParams();
240        CellLayout.LayoutParams lp = (CellLayout.LayoutParams) getLayoutParams();
241
242        lp.width = iconLp.width;
243        lp.height = iconLp.height;
244        lp.x = iconLp.x;
245        lp.y = iconLp.y;
246
247        mContent.setAlpha(0f);
248        mState = STATE_SMALL;
249    }
250
251    public void animateOpen() {
252        if (mState != STATE_SMALL) {
253            positionAndSizeAsIcon();
254        }
255        if (!(getParent() instanceof CellLayoutChildren)) return;
256
257        CellLayout.LayoutParams lp = (CellLayout.LayoutParams) getLayoutParams();
258
259        CellLayoutChildren clc = (CellLayoutChildren) getParent();
260        CellLayout cellLayout = (CellLayout) clc.getParent();
261        Rect r = cellLayout.getContentRect(null);
262
263        PropertyValuesHolder width = PropertyValuesHolder.ofInt("width", r.width());
264        PropertyValuesHolder height = PropertyValuesHolder.ofInt("height", r.height());
265        PropertyValuesHolder x = PropertyValuesHolder.ofInt("x", 0);
266        PropertyValuesHolder y = PropertyValuesHolder.ofInt("y", 0);
267
268        ObjectAnimator oa = ObjectAnimator.ofPropertyValuesHolder(lp, width, height, x, y);
269        oa.addUpdateListener(new AnimatorUpdateListener() {
270            public void onAnimationUpdate(ValueAnimator animation) {
271                requestLayout();
272            }
273        });
274
275        PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 1.0f);
276        ObjectAnimator oaContentAlpha = ObjectAnimator.ofPropertyValuesHolder(mContent, alpha);
277
278        AnimatorSet set = new AnimatorSet();
279        set.playTogether(oa, oaContentAlpha);
280        set.setDuration(mExpandDuration);
281        set.addListener(new AnimatorListenerAdapter() {
282            @Override
283            public void onAnimationStart(Animator animation) {
284                mState = STATE_ANIMATING;
285            }
286            @Override
287            public void onAnimationEnd(Animator animation) {
288                mState = STATE_SMALL;
289            }
290        });
291        set.start();
292    }
293
294    public void animateClosed() {
295        if (!(getParent() instanceof CellLayoutChildren)) return;
296
297        CellLayoutChildren clc = (CellLayoutChildren) getParent();
298        final CellLayout cellLayout = (CellLayout) clc.getParent();
299
300        FolderIcon fi = (FolderIcon) cellLayout.getChildAt(mInfo.cellX, mInfo.cellY);
301        CellLayout.LayoutParams iconLp = (CellLayout.LayoutParams) fi.getLayoutParams();
302        CellLayout.LayoutParams lp = (CellLayout.LayoutParams) getLayoutParams();
303
304        PropertyValuesHolder width = PropertyValuesHolder.ofInt("width", iconLp.width);
305        PropertyValuesHolder height = PropertyValuesHolder.ofInt("height", iconLp.height);
306        PropertyValuesHolder x = PropertyValuesHolder.ofInt("x",iconLp.x);
307        PropertyValuesHolder y = PropertyValuesHolder.ofInt("y", iconLp.y);
308
309        ObjectAnimator oa = ObjectAnimator.ofPropertyValuesHolder(lp, width, height, x, y);
310        oa.addUpdateListener(new AnimatorUpdateListener() {
311            public void onAnimationUpdate(ValueAnimator animation) {
312                requestLayout();
313            }
314        });
315
316        PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 0f);
317        ObjectAnimator oaContentAlpha = ObjectAnimator.ofPropertyValuesHolder(mContent, alpha);
318
319        AnimatorSet set = new AnimatorSet();
320        set.playTogether(oa, oaContentAlpha);
321        set.setDuration(mExpandDuration);
322
323        set.addListener(new AnimatorListenerAdapter() {
324            @Override
325            public void onAnimationEnd(Animator animation) {
326                cellLayout.removeViewWithoutMarkingCells(Folder.this);
327                mState = STATE_OPEN;
328            }
329            @Override
330            public void onAnimationStart(Animator animation) {
331                mState = STATE_ANIMATING;
332            }
333        });
334        set.start();
335    }
336
337    void notifyDataSetChanged() {
338        // recreate all the children if the data set changes under us. We may want to do this more
339        // intelligently (ie just removing the views that should no longer exist)
340        mContent.removeAllViewsInLayout();
341        bind(mInfo);
342    }
343
344    public boolean acceptDrop(DragSource source, int x, int y, int xOffset, int yOffset,
345            DragView dragView, Object dragInfo) {
346        final ItemInfo item = (ItemInfo) dragInfo;
347        final int itemType = item.itemType;
348        return (itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION ||
349                    itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT)
350                && item.container != mInfo.id;
351    }
352
353    public void onDrop(DragSource source, int x, int y, int xOffset, int yOffset,
354            DragView dragView, Object dragInfo) {
355        ShortcutInfo item;
356        if (dragInfo instanceof ApplicationInfo) {
357            // Came from all apps -- make a copy
358            item = ((ApplicationInfo)dragInfo).makeShortcut();
359            item.spanX = 1;
360            item.spanY = 1;
361        } else {
362            item = (ShortcutInfo)dragInfo;
363        }
364        findAndSetEmptyCells(item);
365        mInfo.add(item);
366        LauncherModel.addOrMoveItemInDatabase(mLauncher, item, mInfo.id, 0, item.cellX, item.cellY);
367    }
368
369    protected boolean findAndSetEmptyCells(ShortcutInfo item) {
370        int[] emptyCell = new int[2];
371        if (mContent.findCellForSpan(emptyCell, item.spanX, item.spanY)) {
372            item.cellX = emptyCell[0];
373            item.cellY = emptyCell[1];
374            LauncherModel.addOrMoveItemInDatabase(
375                    mLauncher, item, mInfo.id, 0, item.cellX, item.cellY);
376            return true;
377        } else {
378            return false;
379        }
380    }
381
382    protected void createAndAddShortcut(ShortcutInfo item) {
383        final TextView textView =
384            (TextView) mInflater.inflate(R.layout.application_boxed, this, false);
385        textView.setCompoundDrawablesWithIntrinsicBounds(null,
386                new FastBitmapDrawable(item.getIcon(mIconCache)), null, null);
387        textView.setText(item.title);
388        textView.setTag(item);
389
390        textView.setOnClickListener(this);
391        textView.setOnLongClickListener(this);
392
393        CellLayout.LayoutParams lp =
394            new CellLayout.LayoutParams(item.cellX, item.cellY, item.spanX, item.spanY);
395        boolean insert = false;
396        mContent.addViewToCellLayout(textView, insert ? 0 : -1, (int)item.id, lp, true);
397    }
398
399    public void onDragEnter(DragSource source, int x, int y, int xOffset, int yOffset,
400            DragView dragView, Object dragInfo) {
401    }
402
403    public void onDragOver(DragSource source, int x, int y, int xOffset, int yOffset,
404            DragView dragView, Object dragInfo) {
405    }
406
407    public void onDragExit(DragSource source, int x, int y, int xOffset, int yOffset,
408            DragView dragView, Object dragInfo) {
409    }
410
411    public void onDropCompleted(View target, Object dragInfo, boolean success) {
412        if (success) {
413            mInfo.remove(mDragItem);
414        }
415    }
416
417    public boolean isDropEnabled() {
418        return true;
419    }
420
421    public DropTarget getDropTargetDelegate(DragSource source, int x, int y, int xOffset, int yOffset,
422            DragView dragView, Object dragInfo) {
423        return null;
424    }
425
426    public void onAdd(ShortcutInfo item) {
427        if ((item.cellX == -1 && item.cellY == -1) ||
428                mContent.isOccupied(item.cellX, item.cellY)) {
429            findAndSetEmptyCells(item);
430        }
431        createAndAddShortcut(item);
432    }
433
434    public int getItemCount() {
435        return mContent.getChildrenLayout().getChildCount();
436    }
437
438    public View getItemAt(int index) {
439        return mContent.getChildrenLayout().getChildAt(index);
440    }
441
442    public void onRemove(ShortcutInfo item) {
443        View v = mContent.getChildAt(mDragItemPosition[0], mDragItemPosition[1]);
444        mContent.removeView(v);
445    }
446}
447