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