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