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