Workspace.java revision e77c08d15f23c403293dbb40c6a36967de822c89
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.content.Context;
20import android.content.Intent;
21import android.content.ComponentName;
22import android.content.res.TypedArray;
23import android.graphics.Bitmap;
24import android.graphics.Canvas;
25import android.graphics.Paint;
26import android.graphics.RectF;
27import android.graphics.Rect;
28import android.graphics.Region;
29import android.graphics.drawable.Drawable;
30import android.util.AttributeSet;
31import android.util.Log;
32import android.view.MotionEvent;
33import android.view.VelocityTracker;
34import android.view.View;
35import android.view.ViewConfiguration;
36import android.view.ViewGroup;
37import android.view.ViewParent;
38import android.widget.Scroller;
39import android.widget.TextView;
40import android.os.Parcelable;
41import android.os.Parcel;
42
43import java.util.ArrayList;
44
45/**
46 * The workspace is a wide area with a wallpaper and a finite number of screens. Each
47 * screen contains a number of icons, folders or widgets the user can interact with.
48 * A workspace is meant to be used with a fixed width only.
49 */
50public class Workspace extends ViewGroup implements DropTarget, DragSource, DragScroller,
51        TweenCallback {
52    private static final int INVALID_SCREEN = -1;
53
54    /**
55     * The velocity at which a fling gesture will cause us to snap to the next screen
56     */
57    private static final int SNAP_VELOCITY = 1000;
58
59    private int mDefaultScreen;
60    private View mWallpaper;
61
62    private boolean mFirstLayout = true;
63
64    private int mCurrentScreen;
65    private int mNextScreen = INVALID_SCREEN;
66    private Scroller mScroller;
67    private VelocityTracker mVelocityTracker;
68
69    private SymmetricalLinearTween mTween;
70    private int mAlpha = 255;
71
72    /**
73     * CellInfo for the cell that is currently being dragged
74     */
75    private CellLayout.CellInfo mDragInfo;
76
77    /**
78     * Target drop area calculated during last acceptDrop call.
79     */
80    private int[] mTargetCell = null;
81
82    private float mLastMotionX;
83    private float mLastMotionY;
84
85    private final static int TOUCH_STATE_REST = 0;
86    private final static int TOUCH_STATE_SCROLLING = 1;
87
88    private int mTouchState = TOUCH_STATE_REST;
89
90    private OnLongClickListener mLongClickListener;
91
92    private Launcher mLauncher;
93    private DragController mDragger;
94
95    /**
96     * Cache of vacant cells, used during drag events and invalidated as needed.
97     */
98    private CellLayout.CellInfo mVacantCache = null;
99
100    private int[] mTempCell = new int[2];
101    private int[] mTempEstimate = new int[2];
102
103    private boolean mAllowLongPress;
104    private boolean mLocked;
105
106    private int mTouchSlop;
107    private int mMaximumVelocity;
108
109    final Rect mDrawerBounds = new Rect();
110    final Rect mClipBounds = new Rect();
111    int mDrawerContentHeight;
112    int mDrawerContentWidth;
113
114    /**
115     * Used to inflate the Workspace from XML.
116     *
117     * @param context The application's context.
118     * @param attrs The attribtues set containing the Workspace's customization values.
119     */
120    public Workspace(Context context, AttributeSet attrs) {
121        this(context, attrs, 0);
122    }
123
124    /**
125     * Used to inflate the Workspace from XML.
126     *
127     * @param context The application's context.
128     * @param attrs The attribtues set containing the Workspace's customization values.
129     * @param defStyle Unused.
130     */
131    public Workspace(Context context, AttributeSet attrs, int defStyle) {
132        super(context, attrs, defStyle);
133
134        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Workspace, defStyle, 0);
135        mDefaultScreen = a.getInt(R.styleable.Workspace_defaultScreen, 1);
136        a.recycle();
137
138        initWorkspace();
139    }
140
141    /**
142     * Initializes various states for this workspace.
143     */
144    private void initWorkspace() {
145        mScroller = new Scroller(getContext());
146        mCurrentScreen = mDefaultScreen;
147        Launcher.setScreen(mCurrentScreen);
148
149        final ViewConfiguration configuration = ViewConfiguration.get(getContext());
150        mTouchSlop = configuration.getScaledTouchSlop();
151        mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
152
153        mTween = new SymmetricalLinearTween(true, 250/*ms*/, this);
154    }
155
156    void setWallpaper(View wallpaper) {
157        mWallpaper = wallpaper;
158    }
159
160    @Override
161    public void addView(View child, int index, LayoutParams params) {
162        if (!(child instanceof CellLayout)) {
163            throw new IllegalArgumentException("A Workspace can only have CellLayout children.");
164        }
165        super.addView(child, index, params);
166    }
167
168    @Override
169    public void addView(View child) {
170        if (!(child instanceof CellLayout)) {
171            throw new IllegalArgumentException("A Workspace can only have CellLayout children.");
172        }
173        super.addView(child);
174    }
175
176    @Override
177    public void addView(View child, int index) {
178        if (!(child instanceof CellLayout)) {
179            throw new IllegalArgumentException("A Workspace can only have CellLayout children.");
180        }
181        super.addView(child, index);
182    }
183
184    @Override
185    public void addView(View child, int width, int height) {
186        if (!(child instanceof CellLayout)) {
187            throw new IllegalArgumentException("A Workspace can only have CellLayout children.");
188        }
189        super.addView(child, width, height);
190    }
191
192    @Override
193    public void addView(View child, LayoutParams params) {
194        if (!(child instanceof CellLayout)) {
195            throw new IllegalArgumentException("A Workspace can only have CellLayout children.");
196        }
197        super.addView(child, params);
198    }
199
200    /**
201     * @return The open folder on the current screen, or null if there is none
202     */
203    Folder getOpenFolder() {
204        CellLayout currentScreen = (CellLayout) getChildAt(mCurrentScreen);
205        int count = currentScreen.getChildCount();
206        for (int i = 0; i < count; i++) {
207            View child = currentScreen.getChildAt(i);
208            CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
209            if (lp.cellHSpan == 4 && lp.cellVSpan == 4 && child instanceof Folder) {
210                return (Folder) child;
211            }
212        }
213        return null;
214    }
215
216    ArrayList<Folder> getOpenFolders() {
217        final int screens = getChildCount();
218        ArrayList<Folder> folders = new ArrayList<Folder>(screens);
219
220        for (int screen = 0; screen < screens; screen++) {
221            CellLayout currentScreen = (CellLayout) getChildAt(screen);
222            int count = currentScreen.getChildCount();
223            for (int i = 0; i < count; i++) {
224                View child = currentScreen.getChildAt(i);
225                CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
226                if (lp.cellHSpan == 4 && lp.cellVSpan == 4 && child instanceof Folder) {
227                    folders.add((Folder) child);
228                    break;
229                }
230            }
231        }
232
233        return folders;
234    }
235
236    boolean isDefaultScreenShowing() {
237        return mCurrentScreen == mDefaultScreen;
238    }
239
240    /**
241     * Returns the index of the currently displayed screen.
242     *
243     * @return The index of the currently displayed screen.
244     */
245    int getCurrentScreen() {
246        return mCurrentScreen;
247    }
248
249    /**
250     * Returns how many screens there are.
251     */
252    int getScreenCount() {
253        return getChildCount();
254    }
255
256    /**
257     * Computes a bounding rectangle for a range of cells
258     *
259     * @param cellX X coordinate of upper left corner expressed as a cell position
260     * @param cellY Y coordinate of upper left corner expressed as a cell position
261     * @param cellHSpan Width in cells
262     * @param cellVSpan Height in cells
263     * @param rect Rectnagle into which to put the results
264     */
265    public void cellToRect(int cellX, int cellY, int cellHSpan, int cellVSpan, RectF rect) {
266        ((CellLayout)getChildAt(mCurrentScreen)).cellToRect(cellX, cellY,
267                cellHSpan, cellVSpan, rect);
268    }
269
270    /**
271     * Sets the current screen.
272     *
273     * @param currentScreen
274     */
275    void setCurrentScreen(int currentScreen) {
276        clearVacantCache();
277        mCurrentScreen = Math.max(0, Math.min(currentScreen, getChildCount() - 1));
278        scrollTo(mCurrentScreen * getWidth(), 0);
279        invalidate();
280    }
281
282    /**
283     * Shows the default screen (defined by the firstScreen attribute in XML.)
284     */
285    void showDefaultScreen() {
286        setCurrentScreen(mDefaultScreen);
287    }
288
289    /**
290     * Adds the specified child in the current screen. The position and dimension of
291     * the child are defined by x, y, spanX and spanY.
292     *
293     * @param child The child to add in one of the workspace's screens.
294     * @param x The X position of the child in the screen's grid.
295     * @param y The Y position of the child in the screen's grid.
296     * @param spanX The number of cells spanned horizontally by the child.
297     * @param spanY The number of cells spanned vertically by the child.
298     */
299    void addInCurrentScreen(View child, int x, int y, int spanX, int spanY) {
300        addInScreen(child, mCurrentScreen, x, y, spanX, spanY, false);
301    }
302
303    /**
304     * Adds the specified child in the current screen. The position and dimension of
305     * the child are defined by x, y, spanX and spanY.
306     *
307     * @param child The child to add in one of the workspace's screens.
308     * @param x The X position of the child in the screen's grid.
309     * @param y The Y position of the child in the screen's grid.
310     * @param spanX The number of cells spanned horizontally by the child.
311     * @param spanY The number of cells spanned vertically by the child.
312     * @param insert When true, the child is inserted at the beginning of the children list.
313     */
314    void addInCurrentScreen(View child, int x, int y, int spanX, int spanY, boolean insert) {
315        addInScreen(child, mCurrentScreen, x, y, spanX, spanY, insert);
316    }
317
318    /**
319     * Adds the specified child in the specified screen. The position and dimension of
320     * the child are defined by x, y, spanX and spanY.
321     *
322     * @param child The child to add in one of the workspace's screens.
323     * @param screen The screen in which to add the child.
324     * @param x The X position of the child in the screen's grid.
325     * @param y The Y position of the child in the screen's grid.
326     * @param spanX The number of cells spanned horizontally by the child.
327     * @param spanY The number of cells spanned vertically by the child.
328     */
329    void addInScreen(View child, int screen, int x, int y, int spanX, int spanY) {
330        addInScreen(child, screen, x, y, spanX, spanY, false);
331    }
332
333    /**
334     * Adds the specified child in the specified screen. The position and dimension of
335     * the child are defined by x, y, spanX and spanY.
336     *
337     * @param child The child to add in one of the workspace's screens.
338     * @param screen The screen in which to add the child.
339     * @param x The X position of the child in the screen's grid.
340     * @param y The Y position of the child in the screen's grid.
341     * @param spanX The number of cells spanned horizontally by the child.
342     * @param spanY The number of cells spanned vertically by the child.
343     * @param insert When true, the child is inserted at the beginning of the children list.
344     */
345    void addInScreen(View child, int screen, int x, int y, int spanX, int spanY, boolean insert) {
346        if (screen < 0 || screen >= getChildCount()) {
347            throw new IllegalStateException("The screen must be >= 0 and < " + getChildCount());
348        }
349
350        clearVacantCache();
351
352        final CellLayout group = (CellLayout) getChildAt(screen);
353        CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
354        if (lp == null) {
355            lp = new CellLayout.LayoutParams(x, y, spanX, spanY);
356        } else {
357            lp.cellX = x;
358            lp.cellY = y;
359            lp.cellHSpan = spanX;
360            lp.cellVSpan = spanY;
361        }
362        group.addView(child, insert ? 0 : -1, lp);
363        if (!(child instanceof Folder)) {
364            child.setOnLongClickListener(mLongClickListener);
365        }
366    }
367
368    void addWidget(View view, Widget widget) {
369        addInScreen(view, widget.screen, widget.cellX, widget.cellY, widget.spanX,
370                widget.spanY, false);
371    }
372
373    void addWidget(View view, Widget widget, boolean insert) {
374        addInScreen(view, widget.screen, widget.cellX, widget.cellY, widget.spanX,
375                widget.spanY, insert);
376    }
377
378    CellLayout.CellInfo findAllVacantCells(boolean[] occupied) {
379        CellLayout group = (CellLayout) getChildAt(mCurrentScreen);
380        if (group != null) {
381            return group.findAllVacantCells(occupied, null);
382        }
383        return null;
384    }
385
386    private void clearVacantCache() {
387        if (mVacantCache != null) {
388            mVacantCache.clearVacantCells();
389            mVacantCache = null;
390        }
391    }
392
393    /**
394     * Returns the coordinate of a vacant cell for the current screen.
395     */
396    boolean getVacantCell(int[] vacant, int spanX, int spanY) {
397        CellLayout group = (CellLayout) getChildAt(mCurrentScreen);
398        if (group != null) {
399            return group.getVacantCell(vacant, spanX, spanY);
400        }
401        return false;
402    }
403
404    /**
405     * Adds the specified child in the current screen. The position and dimension of
406     * the child are defined by x, y, spanX and spanY.
407     *
408     * @param child The child to add in one of the workspace's screens.
409     * @param spanX The number of cells spanned horizontally by the child.
410     * @param spanY The number of cells spanned vertically by the child.
411     */
412    void fitInCurrentScreen(View child, int spanX, int spanY) {
413        fitInScreen(child, mCurrentScreen, spanX, spanY);
414    }
415
416    /**
417     * Adds the specified child in the specified screen. The position and dimension of
418     * the child are defined by x, y, spanX and spanY.
419     *
420     * @param child The child to add in one of the workspace's screens.
421     * @param screen The screen in which to add the child.
422     * @param spanX The number of cells spanned horizontally by the child.
423     * @param spanY The number of cells spanned vertically by the child.
424     */
425    void fitInScreen(View child, int screen, int spanX, int spanY) {
426        if (screen < 0 || screen >= getChildCount()) {
427            throw new IllegalStateException("The screen must be >= 0 and < " + getChildCount());
428        }
429
430        final CellLayout group = (CellLayout) getChildAt(screen);
431        boolean vacant = group.getVacantCell(mTempCell, spanX, spanY);
432        if (vacant) {
433            group.addView(child,
434                    new CellLayout.LayoutParams(mTempCell[0], mTempCell[1], spanX, spanY));
435            child.setOnLongClickListener(mLongClickListener);
436            if (!(child instanceof Folder)) {
437                child.setOnLongClickListener(mLongClickListener);
438            }
439        }
440    }
441
442    /**
443     * Registers the specified listener on each screen contained in this workspace.
444     *
445     * @param l The listener used to respond to long clicks.
446     */
447    @Override
448    public void setOnLongClickListener(OnLongClickListener l) {
449        mLongClickListener = l;
450        final int count = getChildCount();
451        for (int i = 0; i < count; i++) {
452            getChildAt(i).setOnLongClickListener(l);
453        }
454    }
455
456    @Override
457    public void computeScroll() {
458        if (mScroller.computeScrollOffset()) {
459            mScrollX = mScroller.getCurrX();
460            mScrollY = mScroller.getCurrY();
461            mWallpaper.scrollTo(mScrollX, mScrollY);
462            postInvalidate();
463        } else if (mNextScreen != INVALID_SCREEN) {
464            mCurrentScreen = Math.max(0, Math.min(mNextScreen, getChildCount() - 1));
465            Launcher.setScreen(mCurrentScreen);
466            mNextScreen = INVALID_SCREEN;
467            clearChildrenCache();
468        }
469    }
470
471    @Override
472    protected void dispatchDraw(Canvas canvas) {
473        boolean restore = false;
474        int restoreCount = 0;
475
476        // For the fade.  If view gets setAlpha(), use that instead.
477        int alpha = mAlpha;
478        if (alpha < 255) {
479            int sx = mScrollX;
480            restoreCount = canvas.saveLayerAlpha(sx, 0, sx+getWidth(), getHeight(), alpha,
481                    Canvas.HAS_ALPHA_LAYER_SAVE_FLAG | Canvas.CLIP_TO_LAYER_SAVE_FLAG);
482            restore = true;
483        }
484
485        // ViewGroup.dispatchDraw() supports many features we don't need:
486        // clip to padding, layout animation, animation listener, disappearing
487        // children, etc. The following implementation attempts to fast-track
488        // the drawing dispatch by drawing only what we know needs to be drawn.
489
490        boolean fastDraw = mTouchState != TOUCH_STATE_SCROLLING && mNextScreen == INVALID_SCREEN;
491        // If we are not scrolling or flinging, draw only the current screen
492        if (fastDraw) {
493            drawChild(canvas, getChildAt(mCurrentScreen), getDrawingTime());
494        } else {
495            final long drawingTime = getDrawingTime();
496            // If we are flinging, draw only the current screen and the target screen
497            if (mNextScreen >= 0 && mNextScreen < getChildCount() &&
498                    Math.abs(mCurrentScreen - mNextScreen) == 1) {
499                drawChild(canvas, getChildAt(mCurrentScreen), drawingTime);
500                drawChild(canvas, getChildAt(mNextScreen), drawingTime);
501            } else {
502                // If we are scrolling, draw all of our children
503                final int count = getChildCount();
504                for (int i = 0; i < count; i++) {
505                    drawChild(canvas, getChildAt(i), drawingTime);
506                }
507            }
508        }
509
510        if (restore) {
511            canvas.restoreToCount(restoreCount);
512        }
513    }
514
515    @Override
516    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
517        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
518
519        final int width = MeasureSpec.getSize(widthMeasureSpec);
520        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
521        if (widthMode != MeasureSpec.EXACTLY) {
522            throw new IllegalStateException("Workspace can only be used in EXACTLY mode.");
523        }
524
525        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
526        if (heightMode != MeasureSpec.EXACTLY) {
527            throw new IllegalStateException("Workspace can only be used in EXACTLY mode.");
528        }
529
530        // The children are given the same width and height as the workspace
531        final int count = getChildCount();
532        for (int i = 0; i < count; i++) {
533            getChildAt(i).measure(widthMeasureSpec, heightMeasureSpec);
534        }
535
536        if (mFirstLayout) {
537            scrollTo(mCurrentScreen * width, 0);
538            mFirstLayout = false;
539        }
540    }
541
542    @Override
543    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
544        int childLeft = 0;
545
546        final int count = getChildCount();
547        for (int i = 0; i < count; i++) {
548            final View child = getChildAt(i);
549            if (child.getVisibility() != View.GONE) {
550                final int childWidth = child.getMeasuredWidth();
551                child.layout(childLeft, 0, childLeft + childWidth, child.getMeasuredHeight());
552                childLeft += childWidth;
553            }
554        }
555    }
556
557    @Override
558    public boolean requestChildRectangleOnScreen(View child, Rect rectangle, boolean immediate) {
559        int screen = indexOfChild(child);
560        if (screen != mCurrentScreen || !mScroller.isFinished()) {
561            if (!mLauncher.isWorkspaceLocked()) {
562                snapToScreen(screen);
563            }
564            return true;
565        }
566        return false;
567    }
568
569    @Override
570    protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
571        if (mLauncher.isDrawerDown()) {
572            final Folder openFolder = getOpenFolder();
573            if (openFolder != null) {
574                return openFolder.requestFocus(direction, previouslyFocusedRect);
575            } else {
576                int focusableScreen;
577                if (mNextScreen != INVALID_SCREEN) {
578                    focusableScreen = mNextScreen;
579                } else {
580                    focusableScreen = mCurrentScreen;
581                }
582                getChildAt(focusableScreen).requestFocus(direction, previouslyFocusedRect);
583            }
584        }
585        return false;
586    }
587
588    @Override
589    public boolean dispatchUnhandledMove(View focused, int direction) {
590        if (direction == View.FOCUS_LEFT) {
591            if (getCurrentScreen() > 0) {
592                snapToScreen(getCurrentScreen() - 1);
593                return true;
594            }
595        } else if (direction == View.FOCUS_RIGHT) {
596            if (getCurrentScreen() < getChildCount() - 1) {
597                snapToScreen(getCurrentScreen() + 1);
598                return true;
599            }
600        }
601        return super.dispatchUnhandledMove(focused, direction);
602    }
603
604    @Override
605    public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
606        if (mLauncher.isDrawerDown()) {
607            final Folder openFolder = getOpenFolder();
608            if (openFolder == null) {
609                getChildAt(mCurrentScreen).addFocusables(views, direction);
610                if (direction == View.FOCUS_LEFT) {
611                    if (mCurrentScreen > 0) {
612                        getChildAt(mCurrentScreen - 1).addFocusables(views, direction);
613                    }
614                } else if (direction == View.FOCUS_RIGHT){
615                    if (mCurrentScreen < getChildCount() - 1) {
616                        getChildAt(mCurrentScreen + 1).addFocusables(views, direction);
617                    }
618                }
619            } else {
620                openFolder.addFocusables(views, direction);
621            }
622        }
623    }
624
625    @Override
626    public boolean onInterceptTouchEvent(MotionEvent ev) {
627        if (mLocked || !mLauncher.isDrawerDown()) {
628            return true;
629        }
630
631        /*
632         * This method JUST determines whether we want to intercept the motion.
633         * If we return true, onTouchEvent will be called and we do the actual
634         * scrolling there.
635         */
636
637        /*
638         * Shortcut the most recurring case: the user is in the dragging
639         * state and he is moving his finger.  We want to intercept this
640         * motion.
641         */
642        final int action = ev.getAction();
643        if ((action == MotionEvent.ACTION_MOVE) && (mTouchState != TOUCH_STATE_REST)) {
644            return true;
645        }
646
647        final float x = ev.getX();
648        final float y = ev.getY();
649
650        switch (action) {
651            case MotionEvent.ACTION_MOVE:
652                /*
653                 * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
654                 * whether the user has moved far enough from his original down touch.
655                 */
656
657                /*
658                 * Locally do absolute value. mLastMotionX is set to the y value
659                 * of the down event.
660                 */
661                final int xDiff = (int) Math.abs(x - mLastMotionX);
662                final int yDiff = (int) Math.abs(y - mLastMotionY);
663
664                final int touchSlop = mTouchSlop;
665                boolean xMoved = xDiff > touchSlop;
666                boolean yMoved = yDiff > touchSlop;
667
668                if (xMoved || yMoved) {
669
670                    if (xMoved) {
671                        // Scroll if the user moved far enough along the X axis
672                        mTouchState = TOUCH_STATE_SCROLLING;
673                        enableChildrenCache();
674                    }
675                    // Either way, cancel any pending longpress
676                    if (mAllowLongPress) {
677                        mAllowLongPress = false;
678                        // Try canceling the long press. It could also have been scheduled
679                        // by a distant descendant, so use the mAllowLongPress flag to block
680                        // everything
681                        final View currentScreen = getChildAt(mCurrentScreen);
682                        currentScreen.cancelLongPress();
683                    }
684                }
685                break;
686
687            case MotionEvent.ACTION_DOWN:
688                // Remember location of down touch
689                mLastMotionX = x;
690                mLastMotionY = y;
691                mAllowLongPress = true;
692
693                /*
694                 * If being flinged and user touches the screen, initiate drag;
695                 * otherwise don't.  mScroller.isFinished should be false when
696                 * being flinged.
697                 */
698                mTouchState = mScroller.isFinished() ? TOUCH_STATE_REST : TOUCH_STATE_SCROLLING;
699                break;
700
701            case MotionEvent.ACTION_CANCEL:
702            case MotionEvent.ACTION_UP:
703                // Release the drag
704                clearChildrenCache();
705                mTouchState = TOUCH_STATE_REST;
706                mAllowLongPress = false;
707                break;
708        }
709
710        /*
711         * The only time we want to intercept motion events is if we are in the
712         * drag mode.
713         */
714        return mTouchState != TOUCH_STATE_REST;
715    }
716
717    void enableChildrenCache() {
718        final int count = getChildCount();
719        for (int i = 0; i < count; i++) {
720            final CellLayout layout = (CellLayout) getChildAt(i);
721            layout.setChildrenDrawnWithCacheEnabled(true);
722            layout.setChildrenDrawingCacheEnabled(true);
723        }
724    }
725
726    void clearChildrenCache() {
727        final int count = getChildCount();
728        for (int i = 0; i < count; i++) {
729            final CellLayout layout = (CellLayout) getChildAt(i);
730            layout.setChildrenDrawnWithCacheEnabled(false);
731        }
732    }
733
734    @Override
735    public boolean onTouchEvent(MotionEvent ev) {
736
737        if (mLocked || !mLauncher.isDrawerDown()) {
738            return true;
739        }
740
741        if (mVelocityTracker == null) {
742            mVelocityTracker = VelocityTracker.obtain();
743        }
744        mVelocityTracker.addMovement(ev);
745
746        final int action = ev.getAction();
747        final float x = ev.getX();
748
749        switch (action) {
750        case MotionEvent.ACTION_DOWN:
751            /*
752             * If being flinged and user touches, stop the fling. isFinished
753             * will be false if being flinged.
754             */
755            if (!mScroller.isFinished()) {
756                mScroller.abortAnimation();
757            }
758
759            // Remember where the motion event started
760            mLastMotionX = x;
761            break;
762        case MotionEvent.ACTION_MOVE:
763            if (mTouchState == TOUCH_STATE_SCROLLING) {
764                // Scroll to follow the motion event
765                final int deltaX = (int) (mLastMotionX - x);
766                mLastMotionX = x;
767
768                if (deltaX < 0) {
769                    if (mScrollX > 0) {
770                        scrollBy(Math.max(-mScrollX, deltaX), 0);
771                        mWallpaper.scrollTo(mScrollX, mScrollY);
772                    }
773                } else if (deltaX > 0) {
774                    final int availableToScroll = getChildAt(getChildCount() - 1).getRight() -
775                            mScrollX - getWidth();
776                    if (availableToScroll > 0) {
777                        scrollBy(Math.min(availableToScroll, deltaX), 0);
778                        mWallpaper.scrollTo(mScrollX, mScrollY);
779                    }
780                }
781            }
782            break;
783        case MotionEvent.ACTION_UP:
784            if (mTouchState == TOUCH_STATE_SCROLLING) {
785                final VelocityTracker velocityTracker = mVelocityTracker;
786                velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
787                int velocityX = (int) velocityTracker.getXVelocity();
788
789                if (velocityX > SNAP_VELOCITY && mCurrentScreen > 0) {
790                    // Fling hard enough to move left
791                    snapToScreen(mCurrentScreen - 1);
792                } else if (velocityX < -SNAP_VELOCITY && mCurrentScreen < getChildCount() - 1) {
793                    // Fling hard enough to move right
794                    snapToScreen(mCurrentScreen + 1);
795                } else {
796                    snapToDestination();
797                }
798
799                if (mVelocityTracker != null) {
800                    mVelocityTracker.recycle();
801                    mVelocityTracker = null;
802                }
803            }
804            mTouchState = TOUCH_STATE_REST;
805            break;
806        case MotionEvent.ACTION_CANCEL:
807            mTouchState = TOUCH_STATE_REST;
808        }
809
810        return true;
811    }
812
813    private void snapToDestination() {
814        final int screenWidth = getWidth();
815        final int whichScreen = (mScrollX + (screenWidth / 2)) / screenWidth;
816
817        snapToScreen(whichScreen);
818    }
819
820    void snapToScreen(int whichScreen) {
821        if (!mScroller.isFinished()) return;
822
823        clearVacantCache();
824        enableChildrenCache();
825
826        whichScreen = Math.max(0, Math.min(whichScreen, getChildCount() - 1));
827        boolean changingScreens = whichScreen != mCurrentScreen;
828
829        mNextScreen = whichScreen;
830
831        View focusedChild = getFocusedChild();
832        if (focusedChild != null && changingScreens && focusedChild == getChildAt(mCurrentScreen)) {
833            focusedChild.clearFocus();
834        }
835
836        final int newX = whichScreen * getWidth();
837        final int delta = newX - mScrollX;
838        mScroller.startScroll(mScrollX, 0, delta, 0, Math.abs(delta) * 2);
839        invalidate();
840    }
841
842    void startDrag(CellLayout.CellInfo cellInfo) {
843        View child = cellInfo.cell;
844
845        // Make sure the drag was started by a long press as opposed to a long click.
846        // Note that Search takes focus when clicked rather than entering touch mode
847        if (!child.isInTouchMode() && !(child instanceof Search)) {
848            return;
849        }
850
851        mDragInfo = cellInfo;
852        mDragInfo.screen = mCurrentScreen;
853
854        CellLayout current = ((CellLayout) getChildAt(mCurrentScreen));
855
856        current.onDragChild(child);
857        mDragger.startDrag(child, this, child.getTag(), DragController.DRAG_ACTION_MOVE);
858        invalidate();
859    }
860
861    @Override
862    protected Parcelable onSaveInstanceState() {
863        final SavedState state = new SavedState(super.onSaveInstanceState());
864        state.currentScreen = mCurrentScreen;
865        return state;
866    }
867
868    @Override
869    protected void onRestoreInstanceState(Parcelable state) {
870        SavedState savedState = (SavedState) state;
871        super.onRestoreInstanceState(savedState.getSuperState());
872        if (savedState.currentScreen != -1) {
873            mCurrentScreen = savedState.currentScreen;
874            Launcher.setScreen(mCurrentScreen);
875        }
876    }
877
878    void addApplicationShortcut(ApplicationInfo info, CellLayout.CellInfo cellInfo) {
879        addApplicationShortcut(info, cellInfo, false);
880    }
881
882    void addApplicationShortcut(ApplicationInfo info, CellLayout.CellInfo cellInfo,
883            boolean insertAtFirst) {
884        final CellLayout layout = (CellLayout) getChildAt(cellInfo.screen);
885        final int[] result = new int[2];
886
887        layout.cellToPoint(cellInfo.cellX, cellInfo.cellY, result);
888        onDropExternal(result[0], result[1], info, layout, insertAtFirst);
889    }
890
891    public void onDrop(DragSource source, int x, int y, int xOffset, int yOffset, Object dragInfo) {
892        final CellLayout cellLayout = getCurrentDropLayout();
893        if (source != this) {
894            onDropExternal(x - xOffset, y - yOffset, dragInfo, cellLayout);
895        } else {
896            // Move internally
897            if (mDragInfo != null) {
898                final View cell = mDragInfo.cell;
899                if (mCurrentScreen != mDragInfo.screen) {
900                    final CellLayout originalCellLayout = (CellLayout) getChildAt(mDragInfo.screen);
901                    originalCellLayout.removeView(cell);
902                    cellLayout.addView(cell);
903                }
904                mTargetCell = estimateDropCell(x - xOffset, y - yOffset,
905                        mDragInfo.spanX, mDragInfo.spanY, cell, cellLayout, mTargetCell);
906                cellLayout.onDropChild(cell, mTargetCell);
907
908                final ItemInfo info = (ItemInfo)cell.getTag();
909                CellLayout.LayoutParams lp = (CellLayout.LayoutParams) cell.getLayoutParams();
910                LauncherModel.moveItemInDatabase(mLauncher, info,
911                        LauncherSettings.Favorites.CONTAINER_DESKTOP, mCurrentScreen, lp.cellX, lp.cellY);
912            }
913        }
914    }
915
916    public void onDragEnter(DragSource source, int x, int y, int xOffset, int yOffset,
917            Object dragInfo) {
918        clearVacantCache();
919    }
920
921    public void onDragOver(DragSource source, int x, int y, int xOffset, int yOffset,
922            Object dragInfo) {
923    }
924
925    public void onDragExit(DragSource source, int x, int y, int xOffset, int yOffset,
926            Object dragInfo) {
927        clearVacantCache();
928    }
929
930    private void onDropExternal(int x, int y, Object dragInfo, CellLayout cellLayout) {
931        onDropExternal(x, y, dragInfo, cellLayout, false);
932    }
933
934    private void onDropExternal(int x, int y, Object dragInfo, CellLayout cellLayout,
935            boolean insertAtFirst) {
936        // Drag from somewhere else
937        ItemInfo info = (ItemInfo) dragInfo;
938
939        View view;
940
941        switch (info.itemType) {
942        case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
943        case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
944            if (info.container == NO_ID) {
945                // Came from all apps -- make a copy
946                info = new ApplicationInfo((ApplicationInfo) info);
947            }
948            view = mLauncher.createShortcut(R.layout.application, cellLayout,
949                    (ApplicationInfo) info);
950            break;
951        case LauncherSettings.Favorites.ITEM_TYPE_USER_FOLDER:
952            view = FolderIcon.fromXml(R.layout.folder_icon, mLauncher,
953                    (ViewGroup) getChildAt(mCurrentScreen), ((UserFolderInfo) info));
954            break;
955        default:
956            throw new IllegalStateException("Unknown item type: " + info.itemType);
957        }
958
959        cellLayout.addView(view, insertAtFirst ? 0 : -1);
960        view.setOnLongClickListener(mLongClickListener);
961        mTargetCell = estimateDropCell(x, y, 1, 1, view, cellLayout, mTargetCell);
962        cellLayout.onDropChild(view, mTargetCell);
963        CellLayout.LayoutParams lp = (CellLayout.LayoutParams) view.getLayoutParams();
964
965        final LauncherModel model = Launcher.getModel();
966        model.addDesktopItem(info);
967        LauncherModel.addOrMoveItemInDatabase(mLauncher, info,
968                LauncherSettings.Favorites.CONTAINER_DESKTOP, mCurrentScreen, lp.cellX, lp.cellY);
969    }
970
971    /**
972     * Return the current {@link CellLayout}, correctly picking the destination
973     * screen while a scroll is in progress.
974     */
975    private CellLayout getCurrentDropLayout() {
976        int index = mScroller.isFinished() ? mCurrentScreen : mNextScreen;
977        return (CellLayout) getChildAt(index);
978    }
979
980    /**
981     * {@inheritDoc}
982     */
983    public boolean acceptDrop(DragSource source, int x, int y,
984            int xOffset, int yOffset, Object dragInfo) {
985        final CellLayout layout = getCurrentDropLayout();
986        final CellLayout.CellInfo cellInfo = mDragInfo;
987        final int spanX = cellInfo == null ? 1 : cellInfo.spanX;
988        final int spanY = cellInfo == null ? 1 : cellInfo.spanY;
989
990        if (mVacantCache == null) {
991            final View ignoreView = cellInfo == null ? null : cellInfo.cell;
992            mVacantCache = layout.findAllVacantCells(null, ignoreView);
993        }
994
995        return mVacantCache.findCellForSpan(mTempEstimate, spanX, spanY, false);
996    }
997
998    /**
999     * {@inheritDoc}
1000     */
1001    public Rect estimateDropLocation(DragSource source, int x, int y,
1002            int xOffset, int yOffset, Object dragInfo, Rect recycle) {
1003        final CellLayout layout = getCurrentDropLayout();
1004
1005        final CellLayout.CellInfo cellInfo = mDragInfo;
1006        final int spanX = cellInfo == null ? 1 : cellInfo.spanX;
1007        final int spanY = cellInfo == null ? 1 : cellInfo.spanY;
1008        final View ignoreView = cellInfo == null ? null : cellInfo.cell;
1009
1010        final Rect location = recycle != null ? recycle : new Rect();
1011
1012        // Find drop cell and convert into rectangle
1013        int[] dropCell = estimateDropCell(x - xOffset, y - yOffset,
1014                spanX, spanY, ignoreView, layout, mTempCell);
1015
1016        if (dropCell == null) {
1017            return null;
1018        }
1019
1020        layout.cellToPoint(dropCell[0], dropCell[1], mTempEstimate);
1021        location.left = mTempEstimate[0];
1022        location.top = mTempEstimate[1];
1023
1024        layout.cellToPoint(dropCell[0] + spanX, dropCell[1] + spanY, mTempEstimate);
1025        location.right = mTempEstimate[0];
1026        location.bottom = mTempEstimate[1];
1027
1028        return location;
1029    }
1030
1031    /**
1032     * Calculate the nearest cell where the given object would be dropped.
1033     */
1034    private int[] estimateDropCell(int pixelX, int pixelY,
1035            int spanX, int spanY, View ignoreView, CellLayout layout, int[] recycle) {
1036        // Create vacant cell cache if none exists
1037        if (mVacantCache == null) {
1038            mVacantCache = layout.findAllVacantCells(null, ignoreView);
1039        }
1040
1041        // Find the best target drop location
1042        return layout.findNearestVacantArea(pixelX, pixelY,
1043                spanX, spanY, mVacantCache, recycle);
1044    }
1045
1046    void setLauncher(Launcher launcher) {
1047        mLauncher = launcher;
1048    }
1049
1050    public void setDragger(DragController dragger) {
1051        mDragger = dragger;
1052    }
1053
1054    public void onDropCompleted(View target, boolean success) {
1055        if (success){
1056            if (target != this && mDragInfo != null) {
1057                final CellLayout cellLayout = (CellLayout) getChildAt(mDragInfo.screen);
1058                cellLayout.removeView(mDragInfo.cell);
1059                final Object tag = mDragInfo.cell.getTag();
1060                Launcher.getModel().removeDesktopItem((ItemInfo) tag);
1061            }
1062        } else {
1063            if (mDragInfo != null) {
1064                final CellLayout cellLayout = (CellLayout) getChildAt(mDragInfo.screen);
1065                cellLayout.onDropAborted(mDragInfo.cell);
1066            }
1067        }
1068
1069        mDragInfo = null;
1070    }
1071
1072    public void scrollLeft() {
1073        clearVacantCache();
1074        if (mNextScreen == INVALID_SCREEN && mCurrentScreen > 0 && mScroller.isFinished()) {
1075            snapToScreen(mCurrentScreen - 1);
1076        }
1077    }
1078
1079    public void scrollRight() {
1080        clearVacantCache();
1081        if (mNextScreen == INVALID_SCREEN && mCurrentScreen < getChildCount() -1 &&
1082                mScroller.isFinished()) {
1083            snapToScreen(mCurrentScreen + 1);
1084        }
1085    }
1086
1087    public int getScreenForView(View v) {
1088        int result = -1;
1089        if (v != null) {
1090            ViewParent vp = v.getParent();
1091            int count = getChildCount();
1092            for (int i = 0; i < count; i++) {
1093                if (vp == getChildAt(i)) {
1094                    return i;
1095                }
1096            }
1097        }
1098        return result;
1099    }
1100
1101    /**
1102     * Find a search widget on the given screen
1103     */
1104    private Search findSearchWidget(CellLayout screen) {
1105        final int count = screen.getChildCount();
1106        for (int i = 0; i < count; i++) {
1107            View v = screen.getChildAt(i);
1108            if (v instanceof Search) {
1109                return (Search) v;
1110            }
1111        }
1112        return null;
1113    }
1114
1115    /**
1116     * Gets the first search widget on the current screen, if there is one.
1117     * Returns <code>null</code> otherwise.
1118     */
1119    public Search findSearchWidgetOnCurrentScreen() {
1120        CellLayout currentScreen = (CellLayout)getChildAt(mCurrentScreen);
1121        return findSearchWidget(currentScreen);
1122    }
1123
1124    public Folder getFolderForTag(Object tag) {
1125        int screenCount = getChildCount();
1126        for (int screen = 0; screen < screenCount; screen++) {
1127            CellLayout currentScreen = ((CellLayout) getChildAt(screen));
1128            int count = currentScreen.getChildCount();
1129            for (int i = 0; i < count; i++) {
1130                View child = currentScreen.getChildAt(i);
1131                CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
1132                if (lp.cellHSpan == 4 && lp.cellVSpan == 4 && child instanceof Folder) {
1133                    Folder f = (Folder) child;
1134                    if (f.getInfo() == tag) {
1135                        return f;
1136                    }
1137                }
1138            }
1139        }
1140        return null;
1141    }
1142
1143    public View getViewForTag(Object tag) {
1144        int screenCount = getChildCount();
1145        for (int screen = 0; screen < screenCount; screen++) {
1146            CellLayout currentScreen = ((CellLayout) getChildAt(screen));
1147            int count = currentScreen.getChildCount();
1148            for (int i = 0; i < count; i++) {
1149                View child = currentScreen.getChildAt(i);
1150                if (child.getTag() == tag) {
1151                    return child;
1152                }
1153            }
1154        }
1155        return null;
1156    }
1157
1158    /**
1159     * Unlocks the SlidingDrawer so that touch events are processed.
1160     *
1161     * @see #lock()
1162     */
1163    public void unlock() {
1164        mLocked = false;
1165    }
1166
1167    /**
1168     * Locks the SlidingDrawer so that touch events are ignores.
1169     *
1170     * @see #unlock()
1171     */
1172    public void lock() {
1173        mLocked = true;
1174    }
1175
1176    /**
1177     * @return True is long presses are still allowed for the current touch
1178     */
1179    public boolean allowLongPress() {
1180        return mAllowLongPress;
1181    }
1182
1183    /**
1184     * Set true to allow long-press events to be triggered, usually checked by
1185     * {@link Launcher} to accept or block dpad-initiated long-presses.
1186     */
1187    public void setAllowLongPress(boolean allowLongPress) {
1188        mAllowLongPress = allowLongPress;
1189    }
1190
1191    void removeShortcutsForPackage(String packageName) {
1192        final ArrayList<View> childrenToRemove = new ArrayList<View>();
1193        final LauncherModel model = Launcher.getModel();
1194        final int count = getChildCount();
1195
1196        for (int i = 0; i < count; i++) {
1197            final CellLayout layout = (CellLayout) getChildAt(i);
1198            int childCount = layout.getChildCount();
1199
1200            childrenToRemove.clear();
1201
1202            for (int j = 0; j < childCount; j++) {
1203                final View view = layout.getChildAt(j);
1204                Object tag = view.getTag();
1205
1206                if (tag instanceof ApplicationInfo) {
1207                    final ApplicationInfo info = (ApplicationInfo) tag;
1208                    // We need to check for ACTION_MAIN otherwise getComponent() might
1209                    // return null for some shortcuts (for instance, for shortcuts to
1210                    // web pages.)
1211                    final Intent intent = info.intent;
1212                    final ComponentName name = intent.getComponent();
1213
1214                    if (Intent.ACTION_MAIN.equals(intent.getAction()) &&
1215                            name != null && packageName.equals(name.getPackageName())) {
1216                        model.removeDesktopItem(info);
1217                        LauncherModel.deleteItemFromDatabase(mLauncher, info);
1218                        childrenToRemove.add(view);
1219                    }
1220                } else if (tag instanceof UserFolderInfo) {
1221                    final UserFolderInfo info = (UserFolderInfo) tag;
1222                    final ArrayList<ApplicationInfo> contents = info.contents;
1223                    final ArrayList<ApplicationInfo> toRemove = new ArrayList<ApplicationInfo>(1);
1224                    final int contentsCount = contents.size();
1225                    boolean removedFromFolder = false;
1226
1227                    for (int k = 0; k < contentsCount; k++) {
1228                        final ApplicationInfo appInfo = contents.get(k);
1229                        final Intent intent = appInfo.intent;
1230                        final ComponentName name = intent.getComponent();
1231
1232                        if (Intent.ACTION_MAIN.equals(intent.getAction()) &&
1233                                name != null && packageName.equals(name.getPackageName())) {
1234                            toRemove.add(appInfo);
1235                            LauncherModel.deleteItemFromDatabase(mLauncher, appInfo);
1236                            removedFromFolder = true;
1237                        }
1238                    }
1239
1240                    contents.removeAll(toRemove);
1241                    if (removedFromFolder) {
1242                        final Folder folder = getOpenFolder();
1243                        if (folder != null) folder.notifyDataSetChanged();
1244                    }
1245                }
1246            }
1247
1248            childCount = childrenToRemove.size();
1249            for (int j = 0; j < childCount; j++) {
1250                layout.removeViewInLayout(childrenToRemove.get(j));
1251            }
1252
1253            if (childCount > 0) {
1254                layout.requestLayout();
1255                layout.invalidate();
1256            }
1257        }
1258    }
1259
1260    void updateShortcutsForPackage(String packageName) {
1261        final int count = getChildCount();
1262        for (int i = 0; i < count; i++) {
1263            final CellLayout layout = (CellLayout) getChildAt(i);
1264            int childCount = layout.getChildCount();
1265            for (int j = 0; j < childCount; j++) {
1266                final View view = layout.getChildAt(j);
1267                Object tag = view.getTag();
1268                if (tag instanceof ApplicationInfo) {
1269                    ApplicationInfo info = (ApplicationInfo) tag;
1270                    // We need to check for ACTION_MAIN otherwise getComponent() might
1271                    // return null for some shortcuts (for instance, for shortcuts to
1272                    // web pages.)
1273                    final Intent intent = info.intent;
1274                    final ComponentName name = intent.getComponent();
1275                    if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION &&
1276                            Intent.ACTION_MAIN.equals(intent.getAction()) && name != null &&
1277                            packageName.equals(name.getPackageName())) {
1278
1279                        final Drawable icon = Launcher.getModel().getApplicationInfoIcon(
1280                                mLauncher.getPackageManager(), info);
1281                        if (icon != null && icon != info.icon) {
1282                            info.icon.setCallback(null);
1283                            info.icon = Utilities.createIconThumbnail(icon, mContext);
1284                            info.filtered = true;
1285                            ((TextView) view).setCompoundDrawablesWithIntrinsicBounds(null,
1286                                    info.icon, null, null);
1287                        }
1288                    }
1289                }
1290            }
1291        }
1292    }
1293
1294    void moveToDefaultScreen() {
1295        snapToScreen(mDefaultScreen);
1296        getChildAt(mDefaultScreen).requestFocus();
1297    }
1298
1299    public static class SavedState extends BaseSavedState {
1300        int currentScreen = -1;
1301
1302        SavedState(Parcelable superState) {
1303            super(superState);
1304        }
1305
1306        private SavedState(Parcel in) {
1307            super(in);
1308            currentScreen = in.readInt();
1309        }
1310
1311        @Override
1312        public void writeToParcel(Parcel out, int flags) {
1313            super.writeToParcel(out, flags);
1314            out.writeInt(currentScreen);
1315        }
1316
1317        public static final Parcelable.Creator<SavedState> CREATOR =
1318                new Parcelable.Creator<SavedState>() {
1319            public SavedState createFromParcel(Parcel in) {
1320                return new SavedState(in);
1321            }
1322
1323            public SavedState[] newArray(int size) {
1324                return new SavedState[size];
1325            }
1326        };
1327    }
1328
1329    void show() {
1330        mTween.start(true);
1331    }
1332
1333    void hide() {
1334        mTween.start(false);
1335    }
1336
1337    public void onTweenValueChanged(float value, float oldValue) {
1338        mAlpha = (int)(255*value);
1339        invalidate();
1340    }
1341
1342    public void onTweenStarted() {
1343        // TODO: This conflicts with the cache for drawing.  Ref count instead?
1344        // TODO: Don't cache all three.
1345        enableChildrenCache();
1346    }
1347
1348    public void onTweenFinished() {
1349        // TODO: This conflicts with the cache for drawing.  Ref count instead?
1350        // TODO: Don't cache all three.
1351        clearChildrenCache();
1352    }
1353}
1354