Workspace.java revision fea5d0250767ab938f623a404e6292a32dd2fdf5
1/*
2 * Copyright (C) 2008 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.launcher2;
18
19import java.util.ArrayList;
20import java.util.HashSet;
21
22import android.app.WallpaperManager;
23import android.appwidget.AppWidgetManager;
24import android.appwidget.AppWidgetProviderInfo;
25import android.content.ComponentName;
26import android.content.Context;
27import android.content.Intent;
28import android.content.pm.PackageManager;
29import android.content.pm.ProviderInfo;
30import android.content.res.TypedArray;
31import android.graphics.Canvas;
32import android.graphics.Rect;
33import android.graphics.drawable.Drawable;
34import android.net.Uri;
35import android.os.IBinder;
36import android.os.Parcel;
37import android.os.Parcelable;
38import android.util.AttributeSet;
39import android.view.MotionEvent;
40import android.view.VelocityTracker;
41import android.view.View;
42import android.view.ViewConfiguration;
43import android.view.ViewGroup;
44import android.view.ViewParent;
45import android.view.animation.Interpolator;
46import android.widget.Scroller;
47import android.widget.TextView;
48
49import com.android.launcher.R;
50
51/**
52 * The workspace is a wide area with a wallpaper and a finite number of screens. Each
53 * screen contains a number of icons, folders or widgets the user can interact with.
54 * A workspace is meant to be used with a fixed width only.
55 */
56public class Workspace extends ViewGroup implements DropTarget, DragSource, DragScroller {
57    @SuppressWarnings({"UnusedDeclaration"})
58    private static final String TAG = "Launcher.Workspace";
59    private static final int INVALID_SCREEN = -1;
60
61    /**
62     * The velocity at which a fling gesture will cause us to snap to the next screen
63     */
64    private static final int SNAP_VELOCITY = 600;
65
66    private final WallpaperManager mWallpaperManager;
67
68    private int mDefaultScreen;
69
70    private boolean mFirstLayout = true;
71
72    private int mCurrentScreen;
73    private int mNextScreen = INVALID_SCREEN;
74    private Scroller mScroller;
75    private VelocityTracker mVelocityTracker;
76
77    /**
78     * CellInfo for the cell that is currently being dragged
79     */
80    private CellLayout.CellInfo mDragInfo;
81
82    /**
83     * Target drop area calculated during last acceptDrop call.
84     */
85    private int[] mTargetCell = null;
86
87    private float mLastMotionX;
88    private float mLastMotionY;
89
90    private final static int TOUCH_STATE_REST = 0;
91    private final static int TOUCH_STATE_SCROLLING = 1;
92
93    private int mTouchState = TOUCH_STATE_REST;
94
95    private OnLongClickListener mLongClickListener;
96
97    private Launcher mLauncher;
98    private IconCache mIconCache;
99    private DragController mDragController;
100
101    /**
102     * Cache of vacant cells, used during drag events and invalidated as needed.
103     */
104    private CellLayout.CellInfo mVacantCache = null;
105
106    private int[] mTempCell = new int[2];
107    private int[] mTempEstimate = new int[2];
108
109    private boolean mAllowLongPress = true;
110
111    private int mTouchSlop;
112    private int mPagingTouchSlop;
113    private int mMaximumVelocity;
114
115    private static final int INVALID_POINTER = -1;
116
117    private int mActivePointerId = INVALID_POINTER;
118
119    private Drawable mPreviousIndicator;
120    private Drawable mNextIndicator;
121
122    // Touch scrolling history for smoothing
123    private float[] mLastX = new float[6];
124    private int mLastCount = 0;
125
126    private WorkspaceOvershootInterpolator mScrollInterpolator;
127
128    private static final float BASELINE_FLING_VELOCITY = 2500.f;
129    private static final float FLING_VELOCITY_INFLUENCE = 0.4f;
130
131    private static class WorkspaceOvershootInterpolator implements Interpolator {
132        private static final float DEFAULT_TENSION = 1.3f;
133        private float mTension;
134
135        public WorkspaceOvershootInterpolator() {
136            mTension = DEFAULT_TENSION;
137        }
138
139        public void setDistance(int distance) {
140            mTension = distance > 0 ? DEFAULT_TENSION / distance : DEFAULT_TENSION;
141        }
142
143        public void disableSettle() {
144            mTension = 0.f;
145        }
146
147        public float getInterpolation(float t) {
148            // _o(t) = t * t * ((tension + 1) * t + tension)
149            // o(t) = _o(t - 1) + 1
150            t -= 1.0f;
151            return t * t * ((mTension + 1) * t + mTension) + 1.0f;
152        }
153    }
154
155    /**
156     * Used to inflate the Workspace from XML.
157     *
158     * @param context The application's context.
159     * @param attrs The attribtues set containing the Workspace's customization values.
160     */
161    public Workspace(Context context, AttributeSet attrs) {
162        this(context, attrs, 0);
163    }
164
165    /**
166     * Used to inflate the Workspace from XML.
167     *
168     * @param context The application's context.
169     * @param attrs The attribtues set containing the Workspace's customization values.
170     * @param defStyle Unused.
171     */
172    public Workspace(Context context, AttributeSet attrs, int defStyle) {
173        super(context, attrs, defStyle);
174
175        mWallpaperManager = WallpaperManager.getInstance(context);
176
177        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Workspace, defStyle, 0);
178        mDefaultScreen = a.getInt(R.styleable.Workspace_defaultScreen, 1);
179        a.recycle();
180
181        setHapticFeedbackEnabled(false);
182        initWorkspace();
183    }
184
185    /**
186     * Initializes various states for this workspace.
187     */
188    private void initWorkspace() {
189        Context context = getContext();
190        mScrollInterpolator = new WorkspaceOvershootInterpolator();
191        mScroller = new Scroller(context, mScrollInterpolator);
192        mCurrentScreen = mDefaultScreen;
193        Launcher.setScreen(mCurrentScreen);
194        LauncherApplication app = (LauncherApplication)context.getApplicationContext();
195        mIconCache = app.getIconCache();
196
197        final ViewConfiguration configuration = ViewConfiguration.get(getContext());
198        mTouchSlop = configuration.getScaledTouchSlop();
199        mPagingTouchSlop = configuration.getScaledPagingTouchSlop();
200        mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
201    }
202
203    @Override
204    public void addView(View child, int index, LayoutParams params) {
205        if (!(child instanceof CellLayout)) {
206            throw new IllegalArgumentException("A Workspace can only have CellLayout children.");
207        }
208        super.addView(child, index, params);
209    }
210
211    @Override
212    public void addView(View child) {
213        if (!(child instanceof CellLayout)) {
214            throw new IllegalArgumentException("A Workspace can only have CellLayout children.");
215        }
216        super.addView(child);
217    }
218
219    @Override
220    public void addView(View child, int index) {
221        if (!(child instanceof CellLayout)) {
222            throw new IllegalArgumentException("A Workspace can only have CellLayout children.");
223        }
224        super.addView(child, index);
225    }
226
227    @Override
228    public void addView(View child, int width, int height) {
229        if (!(child instanceof CellLayout)) {
230            throw new IllegalArgumentException("A Workspace can only have CellLayout children.");
231        }
232        super.addView(child, width, height);
233    }
234
235    @Override
236    public void addView(View child, LayoutParams params) {
237        if (!(child instanceof CellLayout)) {
238            throw new IllegalArgumentException("A Workspace can only have CellLayout children.");
239        }
240        super.addView(child, params);
241    }
242
243    /**
244     * @return The open folder on the current screen, or null if there is none
245     */
246    Folder getOpenFolder() {
247        CellLayout currentScreen = (CellLayout) getChildAt(mCurrentScreen);
248        int count = currentScreen.getChildCount();
249        for (int i = 0; i < count; i++) {
250            View child = currentScreen.getChildAt(i);
251            CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
252            if (lp.cellHSpan == 4 && lp.cellVSpan == 4 && child instanceof Folder) {
253                return (Folder) child;
254            }
255        }
256        return null;
257    }
258
259    ArrayList<Folder> getOpenFolders() {
260        final int screens = getChildCount();
261        ArrayList<Folder> folders = new ArrayList<Folder>(screens);
262
263        for (int screen = 0; screen < screens; screen++) {
264            CellLayout currentScreen = (CellLayout) getChildAt(screen);
265            int count = currentScreen.getChildCount();
266            for (int i = 0; i < count; i++) {
267                View child = currentScreen.getChildAt(i);
268                CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
269                if (lp.cellHSpan == 4 && lp.cellVSpan == 4 && child instanceof Folder) {
270                    folders.add((Folder) child);
271                    break;
272                }
273            }
274        }
275
276        return folders;
277    }
278
279    boolean isDefaultScreenShowing() {
280        return mCurrentScreen == mDefaultScreen;
281    }
282
283    /**
284     * Returns the index of the currently displayed screen.
285     *
286     * @return The index of the currently displayed screen.
287     */
288    int getCurrentScreen() {
289        return mCurrentScreen;
290    }
291
292    /**
293     * Sets the current screen.
294     *
295     * @param currentScreen
296     */
297    void setCurrentScreen(int currentScreen) {
298        if (!mScroller.isFinished()) mScroller.abortAnimation();
299        clearVacantCache();
300        mCurrentScreen = Math.max(0, Math.min(currentScreen, getChildCount() - 1));
301        mPreviousIndicator.setLevel(mCurrentScreen);
302        mNextIndicator.setLevel(mCurrentScreen);
303        scrollTo(mCurrentScreen * getWidth(), 0);
304        updateWallpaperOffset();
305        invalidate();
306    }
307
308    /**
309     * Adds the specified child in the current screen. The position and dimension of
310     * the child are defined by x, y, spanX and spanY.
311     *
312     * @param child The child to add in one of the workspace's screens.
313     * @param x The X position of the child in the screen's grid.
314     * @param y The Y position of the child in the screen's grid.
315     * @param spanX The number of cells spanned horizontally by the child.
316     * @param spanY The number of cells spanned vertically by the child.
317     */
318    void addInCurrentScreen(View child, int x, int y, int spanX, int spanY) {
319        addInScreen(child, mCurrentScreen, x, y, spanX, spanY, false);
320    }
321
322    /**
323     * Adds the specified child in the current screen. The position and dimension of
324     * the child are defined by x, y, spanX and spanY.
325     *
326     * @param child The child to add in one of the workspace's screens.
327     * @param x The X position of the child in the screen's grid.
328     * @param y The Y position of the child in the screen's grid.
329     * @param spanX The number of cells spanned horizontally by the child.
330     * @param spanY The number of cells spanned vertically by the child.
331     * @param insert When true, the child is inserted at the beginning of the children list.
332     */
333    void addInCurrentScreen(View child, int x, int y, int spanX, int spanY, boolean insert) {
334        addInScreen(child, mCurrentScreen, x, y, spanX, spanY, insert);
335    }
336
337    /**
338     * Adds the specified child in the specified screen. The position and dimension of
339     * the child are defined by x, y, spanX and spanY.
340     *
341     * @param child The child to add in one of the workspace's screens.
342     * @param screen The screen in which to add the child.
343     * @param x The X position of the child in the screen's grid.
344     * @param y The Y position of the child in the screen's grid.
345     * @param spanX The number of cells spanned horizontally by the child.
346     * @param spanY The number of cells spanned vertically by the child.
347     */
348    void addInScreen(View child, int screen, int x, int y, int spanX, int spanY) {
349        addInScreen(child, screen, x, y, spanX, spanY, false);
350    }
351
352    /**
353     * Adds the specified child in the specified screen. The position and dimension of
354     * the child are defined by x, y, spanX and spanY.
355     *
356     * @param child The child to add in one of the workspace's screens.
357     * @param screen The screen in which to add the child.
358     * @param x The X position of the child in the screen's grid.
359     * @param y The Y position of the child in the screen's grid.
360     * @param spanX The number of cells spanned horizontally by the child.
361     * @param spanY The number of cells spanned vertically by the child.
362     * @param insert When true, the child is inserted at the beginning of the children list.
363     */
364    void addInScreen(View child, int screen, int x, int y, int spanX, int spanY, boolean insert) {
365        if (screen < 0 || screen >= getChildCount()) {
366            throw new IllegalStateException("The screen must be >= 0 and < " + getChildCount());
367        }
368
369        clearVacantCache();
370
371        final CellLayout group = (CellLayout) getChildAt(screen);
372        CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
373        if (lp == null) {
374            lp = new CellLayout.LayoutParams(x, y, spanX, spanY);
375        } else {
376            lp.cellX = x;
377            lp.cellY = y;
378            lp.cellHSpan = spanX;
379            lp.cellVSpan = spanY;
380        }
381        group.addView(child, insert ? 0 : -1, lp);
382        if (!(child instanceof Folder)) {
383            child.setHapticFeedbackEnabled(false);
384            child.setOnLongClickListener(mLongClickListener);
385        }
386        if (child instanceof DropTarget) {
387            mDragController.addDropTarget((DropTarget)child);
388        }
389    }
390
391    CellLayout.CellInfo findAllVacantCells(boolean[] occupied) {
392        CellLayout group = (CellLayout) getChildAt(mCurrentScreen);
393        if (group != null) {
394            return group.findAllVacantCells(occupied, null);
395        }
396        return null;
397    }
398
399    private void clearVacantCache() {
400        if (mVacantCache != null) {
401            mVacantCache.clearVacantCells();
402            mVacantCache = null;
403        }
404    }
405
406    /**
407     * Registers the specified listener on each screen contained in this workspace.
408     *
409     * @param l The listener used to respond to long clicks.
410     */
411    @Override
412    public void setOnLongClickListener(OnLongClickListener l) {
413        mLongClickListener = l;
414        final int count = getChildCount();
415        for (int i = 0; i < count; i++) {
416            getChildAt(i).setOnLongClickListener(l);
417        }
418    }
419
420    private void updateWallpaperOffset() {
421        updateWallpaperOffset(getChildAt(getChildCount() - 1).getRight() - (mRight - mLeft));
422    }
423
424    private void updateWallpaperOffset(int scrollRange) {
425        IBinder token = getWindowToken();
426        if (token != null) {
427            mWallpaperManager.setWallpaperOffsetSteps(1.0f / (getChildCount() - 1), 0 );
428            mWallpaperManager.setWallpaperOffsets(getWindowToken(),
429                    Math.max(0.f, Math.min(mScrollX/(float)scrollRange, 1.f)), 0);
430        }
431    }
432
433    @Override
434    public void computeScroll() {
435        if (mScroller.computeScrollOffset()) {
436            mScrollX = mScroller.getCurrX();
437            mScrollY = mScroller.getCurrY();
438            updateWallpaperOffset();
439            postInvalidate();
440        } else if (mNextScreen != INVALID_SCREEN) {
441            mCurrentScreen = Math.max(0, Math.min(mNextScreen, getChildCount() - 1));
442            mPreviousIndicator.setLevel(mCurrentScreen);
443            mNextIndicator.setLevel(mCurrentScreen);
444            Launcher.setScreen(mCurrentScreen);
445            mNextScreen = INVALID_SCREEN;
446            clearChildrenCache();
447        }
448    }
449
450    @Override
451    protected void dispatchDraw(Canvas canvas) {
452        boolean restore = false;
453        int restoreCount = 0;
454
455        // ViewGroup.dispatchDraw() supports many features we don't need:
456        // clip to padding, layout animation, animation listener, disappearing
457        // children, etc. The following implementation attempts to fast-track
458        // the drawing dispatch by drawing only what we know needs to be drawn.
459
460        boolean fastDraw = mTouchState != TOUCH_STATE_SCROLLING && mNextScreen == INVALID_SCREEN;
461        // If we are not scrolling or flinging, draw only the current screen
462        if (fastDraw) {
463            drawChild(canvas, getChildAt(mCurrentScreen), getDrawingTime());
464        } else {
465            final long drawingTime = getDrawingTime();
466            final float scrollPos = (float) mScrollX / getWidth();
467            final int leftScreen = (int) scrollPos;
468            final int rightScreen = leftScreen + 1;
469            if (leftScreen >= 0) {
470                drawChild(canvas, getChildAt(leftScreen), drawingTime);
471            }
472            if (scrollPos != leftScreen && rightScreen < getChildCount()) {
473                drawChild(canvas, getChildAt(rightScreen), drawingTime);
474            }
475        }
476
477        if (restore) {
478            canvas.restoreToCount(restoreCount);
479        }
480    }
481
482    protected void onAttachedToWindow() {
483        super.onAttachedToWindow();
484        computeScroll();
485        mDragController.setWindowToken(getWindowToken());
486    }
487
488    @Override
489    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
490        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
491
492        final int width = MeasureSpec.getSize(widthMeasureSpec);
493        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
494        if (widthMode != MeasureSpec.EXACTLY) {
495            throw new IllegalStateException("Workspace can only be used in EXACTLY mode.");
496        }
497
498        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
499        if (heightMode != MeasureSpec.EXACTLY) {
500            throw new IllegalStateException("Workspace can only be used in EXACTLY mode.");
501        }
502
503        // The children are given the same width and height as the workspace
504        final int count = getChildCount();
505        for (int i = 0; i < count; i++) {
506            getChildAt(i).measure(widthMeasureSpec, heightMeasureSpec);
507        }
508
509
510        if (mFirstLayout) {
511            setHorizontalScrollBarEnabled(false);
512            scrollTo(mCurrentScreen * width, 0);
513            setHorizontalScrollBarEnabled(true);
514            updateWallpaperOffset(width * (getChildCount() - 1));
515            mFirstLayout = false;
516        }
517    }
518
519    @Override
520    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
521        int childLeft = 0;
522
523        final int count = getChildCount();
524        for (int i = 0; i < count; i++) {
525            final View child = getChildAt(i);
526            if (child.getVisibility() != View.GONE) {
527                final int childWidth = child.getMeasuredWidth();
528                child.layout(childLeft, 0, childLeft + childWidth, child.getMeasuredHeight());
529                childLeft += childWidth;
530            }
531        }
532    }
533
534    @Override
535    public boolean requestChildRectangleOnScreen(View child, Rect rectangle, boolean immediate) {
536        int screen = indexOfChild(child);
537        if (screen != mCurrentScreen || !mScroller.isFinished()) {
538            if (!mLauncher.isWorkspaceLocked()) {
539                snapToScreen(screen);
540            }
541            return true;
542        }
543        return false;
544    }
545
546    @Override
547    protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
548        if (!mLauncher.isAllAppsVisible()) {
549            final Folder openFolder = getOpenFolder();
550            if (openFolder != null) {
551                return openFolder.requestFocus(direction, previouslyFocusedRect);
552            } else {
553                int focusableScreen;
554                if (mNextScreen != INVALID_SCREEN) {
555                    focusableScreen = mNextScreen;
556                } else {
557                    focusableScreen = mCurrentScreen;
558                }
559                getChildAt(focusableScreen).requestFocus(direction, previouslyFocusedRect);
560            }
561        }
562        return false;
563    }
564
565    @Override
566    public boolean dispatchUnhandledMove(View focused, int direction) {
567        if (direction == View.FOCUS_LEFT) {
568            if (getCurrentScreen() > 0) {
569                snapToScreen(getCurrentScreen() - 1);
570                return true;
571            }
572        } else if (direction == View.FOCUS_RIGHT) {
573            if (getCurrentScreen() < getChildCount() - 1) {
574                snapToScreen(getCurrentScreen() + 1);
575                return true;
576            }
577        }
578        return super.dispatchUnhandledMove(focused, direction);
579    }
580
581    @Override
582    public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
583        if (!mLauncher.isAllAppsVisible()) {
584            final Folder openFolder = getOpenFolder();
585            if (openFolder == null) {
586                getChildAt(mCurrentScreen).addFocusables(views, direction);
587                if (direction == View.FOCUS_LEFT) {
588                    if (mCurrentScreen > 0) {
589                        getChildAt(mCurrentScreen - 1).addFocusables(views, direction);
590                    }
591                } else if (direction == View.FOCUS_RIGHT){
592                    if (mCurrentScreen < getChildCount() - 1) {
593                        getChildAt(mCurrentScreen + 1).addFocusables(views, direction);
594                    }
595                }
596            } else {
597                openFolder.addFocusables(views, direction);
598            }
599        }
600    }
601
602    @Override
603    public boolean dispatchTouchEvent(MotionEvent ev) {
604        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
605            if (mLauncher.isWorkspaceLocked() || mLauncher.isAllAppsVisible()) {
606                return false;
607            }
608        }
609        return super.dispatchTouchEvent(ev);
610    }
611
612    @Override
613    public boolean onInterceptTouchEvent(MotionEvent ev) {
614        final boolean workspaceLocked = mLauncher.isWorkspaceLocked();
615        final boolean allAppsVisible = mLauncher.isAllAppsVisible();
616        if (workspaceLocked || allAppsVisible) {
617            return false; // We don't want the events.  Let them fall through to the all apps view.
618        }
619
620        /*
621         * This method JUST determines whether we want to intercept the motion.
622         * If we return true, onTouchEvent will be called and we do the actual
623         * scrolling there.
624         */
625
626        /*
627         * Shortcut the most recurring case: the user is in the dragging
628         * state and he is moving his finger.  We want to intercept this
629         * motion.
630         */
631        final int action = ev.getAction();
632        if ((action == MotionEvent.ACTION_MOVE) && (mTouchState != TOUCH_STATE_REST)) {
633            return true;
634        }
635
636        if (mVelocityTracker == null) {
637            mVelocityTracker = VelocityTracker.obtain();
638        }
639        mVelocityTracker.addMovement(ev);
640
641        switch (action & MotionEvent.ACTION_MASK) {
642            case MotionEvent.ACTION_MOVE: {
643                /*
644                 * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
645                 * whether the user has moved far enough from his original down touch.
646                 */
647
648                /*
649                 * Locally do absolute value. mLastMotionX is set to the y value
650                 * of the down event.
651                 */
652                final int pointerIndex = ev.findPointerIndex(mActivePointerId);
653                final float x = ev.getX(pointerIndex);
654                final float y = ev.getY(pointerIndex);
655                final int xDiff = (int) Math.abs(x - mLastMotionX);
656                final int yDiff = (int) Math.abs(y - mLastMotionY);
657
658                final int touchSlop = mTouchSlop;
659                boolean xPaged = xDiff > mPagingTouchSlop;
660                boolean xMoved = xDiff > touchSlop;
661                boolean yMoved = yDiff > touchSlop;
662
663                if (xMoved || yMoved) {
664
665                    if (xPaged) {
666                        // Scroll if the user moved far enough along the X axis
667                        mTouchState = TOUCH_STATE_SCROLLING;
668                        mLastMotionX = x;
669                        enableChildrenCache(0, getChildCount());
670                    }
671                    // Either way, cancel any pending longpress
672                    if (mAllowLongPress) {
673                        mAllowLongPress = false;
674                        // Try canceling the long press. It could also have been scheduled
675                        // by a distant descendant, so use the mAllowLongPress flag to block
676                        // everything
677                        final View currentScreen = getChildAt(mCurrentScreen);
678                        currentScreen.cancelLongPress();
679                    }
680                }
681                break;
682            }
683
684            case MotionEvent.ACTION_DOWN: {
685                final float x = ev.getX();
686                final float y = ev.getY();
687                // Remember location of down touch
688                mLastMotionX = x;
689                mLastMotionY = y;
690                mActivePointerId = ev.getPointerId(0);
691                mAllowLongPress = true;
692
693                /*
694                 * If being flinged and user touches the screen, initiate drag;
695                 * otherwise don't.  mScroller.isFinished should be false when
696                 * being flinged.
697                 */
698                mTouchState = mScroller.isFinished() ? TOUCH_STATE_REST : TOUCH_STATE_SCROLLING;
699                break;
700            }
701
702            case MotionEvent.ACTION_CANCEL:
703            case MotionEvent.ACTION_UP:
704
705                if (mTouchState != TOUCH_STATE_SCROLLING) {
706                    final CellLayout currentScreen = (CellLayout)getChildAt(mCurrentScreen);
707                    if (!currentScreen.lastDownOnOccupiedCell()) {
708                        getLocationOnScreen(mTempCell);
709                        // Send a tap to the wallpaper if the last down was on empty space
710                        final int pointerIndex = ev.findPointerIndex(mActivePointerId);
711                        mWallpaperManager.sendWallpaperCommand(getWindowToken(),
712                                "android.wallpaper.tap",
713                                mTempCell[0] + (int) ev.getX(pointerIndex),
714                                mTempCell[1] + (int) ev.getY(pointerIndex), 0, null);
715                    }
716                }
717
718                // Release the drag
719                clearChildrenCache();
720                mTouchState = TOUCH_STATE_REST;
721                mActivePointerId = INVALID_POINTER;
722                mAllowLongPress = false;
723
724                if (mVelocityTracker != null) {
725                    mVelocityTracker.recycle();
726                    mVelocityTracker = null;
727                }
728
729                break;
730
731            case MotionEvent.ACTION_POINTER_UP:
732                onSecondaryPointerUp(ev);
733                break;
734        }
735
736        /*
737         * The only time we want to intercept motion events is if we are in the
738         * drag mode.
739         */
740        return mTouchState != TOUCH_STATE_REST;
741    }
742
743    private void onSecondaryPointerUp(MotionEvent ev) {
744        final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >>
745                MotionEvent.ACTION_POINTER_INDEX_SHIFT;
746        final int pointerId = ev.getPointerId(pointerIndex);
747        if (pointerId == mActivePointerId) {
748            // This was our active pointer going up. Choose a new
749            // active pointer and adjust accordingly.
750            // TODO: Make this decision more intelligent.
751            final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
752            mLastMotionX = ev.getX(newPointerIndex);
753            mLastMotionY = ev.getY(newPointerIndex);
754            mActivePointerId = ev.getPointerId(newPointerIndex);
755            if (mVelocityTracker != null) {
756                mVelocityTracker.clear();
757            }
758            resetFilter();
759        }
760    }
761
762    /**
763     * If one of our descendant views decides that it could be focused now, only
764     * pass that along if it's on the current screen.
765     *
766     * This happens when live folders requery, and if they're off screen, they
767     * end up calling requestFocus, which pulls it on screen.
768     */
769    @Override
770    public void focusableViewAvailable(View focused) {
771        View current = getChildAt(mCurrentScreen);
772        View v = focused;
773        while (true) {
774            if (v == current) {
775                super.focusableViewAvailable(focused);
776                return;
777            }
778            if (v == this) {
779                return;
780            }
781            ViewParent parent = v.getParent();
782            if (parent instanceof View) {
783                v = (View)v.getParent();
784            } else {
785                return;
786            }
787        }
788    }
789
790    void enableChildrenCache(int fromScreen, int toScreen) {
791        if (fromScreen > toScreen) {
792            final int temp = fromScreen;
793            fromScreen = toScreen;
794            toScreen = temp;
795        }
796
797        final int count = getChildCount();
798
799        fromScreen = Math.max(fromScreen, 0);
800        toScreen = Math.min(toScreen, count - 1);
801
802        for (int i = fromScreen; i <= toScreen; i++) {
803            final CellLayout layout = (CellLayout) getChildAt(i);
804            layout.setChildrenDrawnWithCacheEnabled(true);
805            layout.setChildrenDrawingCacheEnabled(true);
806        }
807    }
808
809    void clearChildrenCache() {
810        final int count = getChildCount();
811        for (int i = 0; i < count; i++) {
812            final CellLayout layout = (CellLayout) getChildAt(i);
813            layout.setChildrenDrawnWithCacheEnabled(false);
814        }
815    }
816
817    private float filterX(float x) {
818        final float[] lastX = mLastX;
819        final int len = lastX.length;
820        final int lastCount = Math.min(len, mLastCount + 1);
821
822        float sum = x;
823        for (int i = 1; i < lastCount; i++) {
824            sum += lastX[i] = lastX[i - 1];
825        }
826        lastX[0] = x;
827
828        mLastCount = lastCount;
829        return sum / lastCount;
830    }
831
832    private void resetFilter() {
833        mLastCount = 0;
834    }
835
836    @Override
837    public boolean onTouchEvent(MotionEvent ev) {
838
839        if (mLauncher.isWorkspaceLocked()) {
840            return false; // We don't want the events.  Let them fall through to the all apps view.
841        }
842        if (mLauncher.isAllAppsVisible()) {
843            // Cancel any scrolling that is in progress.
844            if (!mScroller.isFinished()) {
845                mScroller.abortAnimation();
846            }
847            snapToScreen(mCurrentScreen);
848            return false; // We don't want the events.  Let them fall through to the all apps view.
849        }
850
851        if (mVelocityTracker == null) {
852            mVelocityTracker = VelocityTracker.obtain();
853        }
854        mVelocityTracker.addMovement(ev);
855
856        final int action = ev.getAction();
857
858        switch (action & MotionEvent.ACTION_MASK) {
859        case MotionEvent.ACTION_DOWN:
860            /*
861             * If being flinged and user touches, stop the fling. isFinished
862             * will be false if being flinged.
863             */
864            if (!mScroller.isFinished()) {
865                mScroller.abortAnimation();
866            }
867
868            // Remember where the motion event started
869            mLastMotionX = ev.getX();
870            mActivePointerId = ev.getPointerId(0);
871            break;
872        case MotionEvent.ACTION_MOVE:
873            if (mTouchState == TOUCH_STATE_SCROLLING) {
874                // Scroll to follow the motion event
875                final int pointerIndex = ev.findPointerIndex(mActivePointerId);
876                final float x = filterX(ev.getX(pointerIndex));
877                final float deltaX = mLastMotionX - x;
878                mLastMotionX = x;
879
880                if (deltaX < 0) {
881                    if (mScrollX > 0) {
882                        scrollBy(Math.round(Math.max(-mScrollX, deltaX)), 0);
883                        updateWallpaperOffset();
884                    }
885                } else if (deltaX > 0) {
886                    final int availableToScroll = getChildAt(getChildCount() - 1).getRight() -
887                            mScrollX - getWidth();
888                    if (availableToScroll > 0) {
889                        scrollBy(Math.round(Math.min(availableToScroll, deltaX)), 0);
890                        updateWallpaperOffset();
891                    }
892                } else {
893                    awakenScrollBars();
894                }
895            }
896            break;
897        case MotionEvent.ACTION_UP:
898            if (mTouchState == TOUCH_STATE_SCROLLING) {
899                final VelocityTracker velocityTracker = mVelocityTracker;
900                velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
901                final int velocityX = (int) velocityTracker.getXVelocity(mActivePointerId);
902
903                final int screenWidth = getWidth();
904                final int whichScreen = (mScrollX + (screenWidth / 2)) / screenWidth;
905                final float scrolledPos = (float) mScrollX / screenWidth;
906
907                if (velocityX > SNAP_VELOCITY && mCurrentScreen > 0) {
908                    // Fling hard enough to move left.
909                    // Don't fling across more than one screen at a time.
910                    final int bound = scrolledPos < whichScreen ?
911                            mCurrentScreen - 1 : mCurrentScreen;
912                    snapToScreen(Math.min(whichScreen, bound), velocityX, true);
913                } else if (velocityX < -SNAP_VELOCITY && mCurrentScreen < getChildCount() - 1) {
914                    // Fling hard enough to move right
915                    // Don't fling across more than one screen at a time.
916                    final int bound = scrolledPos > whichScreen ?
917                            mCurrentScreen + 1 : mCurrentScreen;
918                    snapToScreen(Math.max(whichScreen, bound), velocityX, true);
919                } else {
920                    snapToScreen(whichScreen, 0, true);
921                }
922
923                if (mVelocityTracker != null) {
924                    mVelocityTracker.recycle();
925                    mVelocityTracker = null;
926                }
927                resetFilter();
928            }
929            mTouchState = TOUCH_STATE_REST;
930            mActivePointerId = INVALID_POINTER;
931            break;
932        case MotionEvent.ACTION_CANCEL:
933            mTouchState = TOUCH_STATE_REST;
934            mActivePointerId = INVALID_POINTER;
935            resetFilter();
936            break;
937        case MotionEvent.ACTION_POINTER_UP:
938            onSecondaryPointerUp(ev);
939            break;
940        }
941
942        return true;
943    }
944
945    void snapToScreen(int whichScreen) {
946        snapToScreen(whichScreen, 0, false);
947    }
948
949    private void snapToScreen(int whichScreen, int velocity, boolean settle) {
950        //if (!mScroller.isFinished()) return;
951
952        whichScreen = Math.max(0, Math.min(whichScreen, getChildCount() - 1));
953
954        clearVacantCache();
955        enableChildrenCache(mCurrentScreen, whichScreen);
956
957        mNextScreen = whichScreen;
958
959        mPreviousIndicator.setLevel(mNextScreen);
960        mNextIndicator.setLevel(mNextScreen);
961
962        View focusedChild = getFocusedChild();
963        if (focusedChild != null && whichScreen != mCurrentScreen &&
964                focusedChild == getChildAt(mCurrentScreen)) {
965            focusedChild.clearFocus();
966        }
967
968        final int screenDelta = Math.max(1, Math.abs(whichScreen - mCurrentScreen));
969        final int newX = whichScreen * getWidth();
970        final int delta = newX - mScrollX;
971        int duration = (screenDelta + 1) * 100;
972
973        if (!mScroller.isFinished()) {
974            mScroller.abortAnimation();
975        }
976
977        if (settle) {
978            mScrollInterpolator.setDistance(screenDelta);
979        } else {
980            mScrollInterpolator.disableSettle();
981        }
982
983        velocity = Math.abs(velocity);
984        if (velocity > 0) {
985            duration += (duration / (velocity / BASELINE_FLING_VELOCITY))
986                    * FLING_VELOCITY_INFLUENCE;
987        } else {
988            duration += 100;
989        }
990
991        awakenScrollBars(duration);
992        mScroller.startScroll(mScrollX, 0, delta, 0, duration);
993        invalidate();
994    }
995
996    void startDrag(CellLayout.CellInfo cellInfo) {
997        View child = cellInfo.cell;
998
999        // Make sure the drag was started by a long press as opposed to a long click.
1000        if (!child.isInTouchMode()) {
1001            return;
1002        }
1003
1004        mDragInfo = cellInfo;
1005        mDragInfo.screen = mCurrentScreen;
1006
1007        CellLayout current = ((CellLayout) getChildAt(mCurrentScreen));
1008
1009        current.onDragChild(child);
1010        mDragController.startDrag(child, this, child.getTag(), DragController.DRAG_ACTION_MOVE);
1011        invalidate();
1012    }
1013
1014    @Override
1015    protected Parcelable onSaveInstanceState() {
1016        final SavedState state = new SavedState(super.onSaveInstanceState());
1017        state.currentScreen = mCurrentScreen;
1018        return state;
1019    }
1020
1021    @Override
1022    protected void onRestoreInstanceState(Parcelable state) {
1023        SavedState savedState = (SavedState) state;
1024        super.onRestoreInstanceState(savedState.getSuperState());
1025        if (savedState.currentScreen != -1) {
1026            mCurrentScreen = savedState.currentScreen;
1027            Launcher.setScreen(mCurrentScreen);
1028        }
1029    }
1030
1031    void addApplicationShortcut(ShortcutInfo info, CellLayout.CellInfo cellInfo) {
1032        addApplicationShortcut(info, cellInfo, false);
1033    }
1034
1035    void addApplicationShortcut(ShortcutInfo info, CellLayout.CellInfo cellInfo,
1036            boolean insertAtFirst) {
1037        final CellLayout layout = (CellLayout) getChildAt(cellInfo.screen);
1038        final int[] result = new int[2];
1039
1040        layout.cellToPoint(cellInfo.cellX, cellInfo.cellY, result);
1041        onDropExternal(result[0], result[1], info, layout, insertAtFirst);
1042    }
1043
1044    public void onDrop(DragSource source, int x, int y, int xOffset, int yOffset,
1045            DragView dragView, Object dragInfo) {
1046        final CellLayout cellLayout = getCurrentDropLayout();
1047        if (source != this) {
1048            onDropExternal(x - xOffset, y - yOffset, dragInfo, cellLayout);
1049        } else {
1050            // Move internally
1051            if (mDragInfo != null) {
1052                final View cell = mDragInfo.cell;
1053                int index = mScroller.isFinished() ? mCurrentScreen : mNextScreen;
1054                if (index != mDragInfo.screen) {
1055                    final CellLayout originalCellLayout = (CellLayout) getChildAt(mDragInfo.screen);
1056                    originalCellLayout.removeView(cell);
1057                    cellLayout.addView(cell);
1058                }
1059                mTargetCell = estimateDropCell(x - xOffset, y - yOffset,
1060                        mDragInfo.spanX, mDragInfo.spanY, cell, cellLayout, mTargetCell);
1061                cellLayout.onDropChild(cell, mTargetCell);
1062
1063                final ItemInfo info = (ItemInfo) cell.getTag();
1064                CellLayout.LayoutParams lp = (CellLayout.LayoutParams) cell.getLayoutParams();
1065                LauncherModel.moveItemInDatabase(mLauncher, info,
1066                        LauncherSettings.Favorites.CONTAINER_DESKTOP, index, lp.cellX, lp.cellY);
1067            }
1068        }
1069    }
1070
1071    public void onDragEnter(DragSource source, int x, int y, int xOffset, int yOffset,
1072            DragView dragView, Object dragInfo) {
1073        clearVacantCache();
1074    }
1075
1076    public void onDragOver(DragSource source, int x, int y, int xOffset, int yOffset,
1077            DragView dragView, Object dragInfo) {
1078    }
1079
1080    public void onDragExit(DragSource source, int x, int y, int xOffset, int yOffset,
1081            DragView dragView, Object dragInfo) {
1082        clearVacantCache();
1083    }
1084
1085    private void onDropExternal(int x, int y, Object dragInfo, CellLayout cellLayout) {
1086        onDropExternal(x, y, dragInfo, cellLayout, false);
1087    }
1088
1089    private void onDropExternal(int x, int y, Object dragInfo, CellLayout cellLayout,
1090            boolean insertAtFirst) {
1091        // Drag from somewhere else
1092        ItemInfo info = (ItemInfo) dragInfo;
1093
1094        View view;
1095
1096        switch (info.itemType) {
1097        case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
1098        case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
1099            if (info.container == NO_ID && info instanceof ApplicationInfo) {
1100                // Came from all apps -- make a copy
1101                info = new ShortcutInfo((ApplicationInfo)info);
1102            }
1103            view = mLauncher.createShortcut(R.layout.application, cellLayout, (ShortcutInfo)info);
1104            break;
1105        case LauncherSettings.Favorites.ITEM_TYPE_USER_FOLDER:
1106            view = FolderIcon.fromXml(R.layout.folder_icon, mLauncher,
1107                    (ViewGroup) getChildAt(mCurrentScreen), ((UserFolderInfo) info));
1108            break;
1109        default:
1110            throw new IllegalStateException("Unknown item type: " + info.itemType);
1111        }
1112
1113        cellLayout.addView(view, insertAtFirst ? 0 : -1);
1114        view.setHapticFeedbackEnabled(false);
1115        view.setOnLongClickListener(mLongClickListener);
1116        if (view instanceof DropTarget) {
1117            mDragController.addDropTarget((DropTarget) view);
1118        }
1119
1120        mTargetCell = estimateDropCell(x, y, 1, 1, view, cellLayout, mTargetCell);
1121        cellLayout.onDropChild(view, mTargetCell);
1122        CellLayout.LayoutParams lp = (CellLayout.LayoutParams) view.getLayoutParams();
1123
1124        LauncherModel.addOrMoveItemInDatabase(mLauncher, info,
1125                LauncherSettings.Favorites.CONTAINER_DESKTOP, mCurrentScreen, lp.cellX, lp.cellY);
1126    }
1127
1128    /**
1129     * Return the current {@link CellLayout}, correctly picking the destination
1130     * screen while a scroll is in progress.
1131     */
1132    private CellLayout getCurrentDropLayout() {
1133        int index = mScroller.isFinished() ? mCurrentScreen : mNextScreen;
1134        return (CellLayout) getChildAt(index);
1135    }
1136
1137    /**
1138     * {@inheritDoc}
1139     */
1140    public boolean acceptDrop(DragSource source, int x, int y,
1141            int xOffset, int yOffset, DragView dragView, Object dragInfo) {
1142        final CellLayout layout = getCurrentDropLayout();
1143        final CellLayout.CellInfo cellInfo = mDragInfo;
1144        final int spanX = cellInfo == null ? 1 : cellInfo.spanX;
1145        final int spanY = cellInfo == null ? 1 : cellInfo.spanY;
1146
1147        if (mVacantCache == null) {
1148            final View ignoreView = cellInfo == null ? null : cellInfo.cell;
1149            mVacantCache = layout.findAllVacantCells(null, ignoreView);
1150        }
1151
1152        return mVacantCache.findCellForSpan(mTempEstimate, spanX, spanY, false);
1153    }
1154
1155    /**
1156     * {@inheritDoc}
1157     */
1158    public Rect estimateDropLocation(DragSource source, int x, int y,
1159            int xOffset, int yOffset, DragView dragView, Object dragInfo, Rect recycle) {
1160        final CellLayout layout = getCurrentDropLayout();
1161
1162        final CellLayout.CellInfo cellInfo = mDragInfo;
1163        final int spanX = cellInfo == null ? 1 : cellInfo.spanX;
1164        final int spanY = cellInfo == null ? 1 : cellInfo.spanY;
1165        final View ignoreView = cellInfo == null ? null : cellInfo.cell;
1166
1167        final Rect location = recycle != null ? recycle : new Rect();
1168
1169        // Find drop cell and convert into rectangle
1170        int[] dropCell = estimateDropCell(x - xOffset, y - yOffset,
1171                spanX, spanY, ignoreView, layout, mTempCell);
1172
1173        if (dropCell == null) {
1174            return null;
1175        }
1176
1177        layout.cellToPoint(dropCell[0], dropCell[1], mTempEstimate);
1178        location.left = mTempEstimate[0];
1179        location.top = mTempEstimate[1];
1180
1181        layout.cellToPoint(dropCell[0] + spanX, dropCell[1] + spanY, mTempEstimate);
1182        location.right = mTempEstimate[0];
1183        location.bottom = mTempEstimate[1];
1184
1185        return location;
1186    }
1187
1188    /**
1189     * Calculate the nearest cell where the given object would be dropped.
1190     */
1191    private int[] estimateDropCell(int pixelX, int pixelY,
1192            int spanX, int spanY, View ignoreView, CellLayout layout, int[] recycle) {
1193        // Create vacant cell cache if none exists
1194        if (mVacantCache == null) {
1195            mVacantCache = layout.findAllVacantCells(null, ignoreView);
1196        }
1197
1198        // Find the best target drop location
1199        return layout.findNearestVacantArea(pixelX, pixelY,
1200                spanX, spanY, mVacantCache, recycle);
1201    }
1202
1203    void setLauncher(Launcher launcher) {
1204        mLauncher = launcher;
1205    }
1206
1207    public void setDragController(DragController dragController) {
1208        mDragController = dragController;
1209    }
1210
1211    public void onDropCompleted(View target, boolean success) {
1212        clearVacantCache();
1213
1214        if (success){
1215            if (target != this && mDragInfo != null) {
1216                final CellLayout cellLayout = (CellLayout) getChildAt(mDragInfo.screen);
1217                cellLayout.removeView(mDragInfo.cell);
1218                if (mDragInfo.cell instanceof DropTarget) {
1219                    mDragController.removeDropTarget((DropTarget)mDragInfo.cell);
1220                }
1221                //final Object tag = mDragInfo.cell.getTag();
1222            }
1223        } else {
1224            if (mDragInfo != null) {
1225                final CellLayout cellLayout = (CellLayout) getChildAt(mDragInfo.screen);
1226                cellLayout.onDropAborted(mDragInfo.cell);
1227            }
1228        }
1229
1230        mDragInfo = null;
1231    }
1232
1233    public void scrollLeft() {
1234        clearVacantCache();
1235        if (mScroller.isFinished()) {
1236            if (mCurrentScreen > 0) snapToScreen(mCurrentScreen - 1);
1237        } else {
1238            if (mNextScreen > 0) snapToScreen(mNextScreen - 1);
1239        }
1240    }
1241
1242    public void scrollRight() {
1243        clearVacantCache();
1244        if (mScroller.isFinished()) {
1245            if (mCurrentScreen < getChildCount() -1) snapToScreen(mCurrentScreen + 1);
1246        } else {
1247            if (mNextScreen < getChildCount() -1) snapToScreen(mNextScreen + 1);
1248        }
1249    }
1250
1251    public int getScreenForView(View v) {
1252        int result = -1;
1253        if (v != null) {
1254            ViewParent vp = v.getParent();
1255            int count = getChildCount();
1256            for (int i = 0; i < count; i++) {
1257                if (vp == getChildAt(i)) {
1258                    return i;
1259                }
1260            }
1261        }
1262        return result;
1263    }
1264
1265    public Folder getFolderForTag(Object tag) {
1266        int screenCount = getChildCount();
1267        for (int screen = 0; screen < screenCount; screen++) {
1268            CellLayout currentScreen = ((CellLayout) getChildAt(screen));
1269            int count = currentScreen.getChildCount();
1270            for (int i = 0; i < count; i++) {
1271                View child = currentScreen.getChildAt(i);
1272                CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
1273                if (lp.cellHSpan == 4 && lp.cellVSpan == 4 && child instanceof Folder) {
1274                    Folder f = (Folder) child;
1275                    if (f.getInfo() == tag) {
1276                        return f;
1277                    }
1278                }
1279            }
1280        }
1281        return null;
1282    }
1283
1284    public View getViewForTag(Object tag) {
1285        int screenCount = getChildCount();
1286        for (int screen = 0; screen < screenCount; screen++) {
1287            CellLayout currentScreen = ((CellLayout) getChildAt(screen));
1288            int count = currentScreen.getChildCount();
1289            for (int i = 0; i < count; i++) {
1290                View child = currentScreen.getChildAt(i);
1291                if (child.getTag() == tag) {
1292                    return child;
1293                }
1294            }
1295        }
1296        return null;
1297    }
1298
1299    /**
1300     * @return True is long presses are still allowed for the current touch
1301     */
1302    public boolean allowLongPress() {
1303        return mAllowLongPress;
1304    }
1305
1306    /**
1307     * Set true to allow long-press events to be triggered, usually checked by
1308     * {@link Launcher} to accept or block dpad-initiated long-presses.
1309     */
1310    public void setAllowLongPress(boolean allowLongPress) {
1311        mAllowLongPress = allowLongPress;
1312    }
1313
1314    void removeItems(final ArrayList<ApplicationInfo> apps) {
1315        final int count = getChildCount();
1316        final PackageManager manager = getContext().getPackageManager();
1317        final AppWidgetManager widgets = AppWidgetManager.getInstance(getContext());
1318
1319        final HashSet<String> packageNames = new HashSet<String>();
1320        final int appCount = apps.size();
1321        for (int i = 0; i < appCount; i++) {
1322            packageNames.add(apps.get(i).componentName.getPackageName());
1323        }
1324
1325        for (int i = 0; i < count; i++) {
1326            final CellLayout layout = (CellLayout) getChildAt(i);
1327
1328            // Avoid ANRs by treating each screen separately
1329            post(new Runnable() {
1330                public void run() {
1331                    final ArrayList<View> childrenToRemove = new ArrayList<View>();
1332                    childrenToRemove.clear();
1333
1334                    int childCount = layout.getChildCount();
1335                    for (int j = 0; j < childCount; j++) {
1336                        final View view = layout.getChildAt(j);
1337                        Object tag = view.getTag();
1338
1339                        if (tag instanceof ShortcutInfo) {
1340                            final ShortcutInfo info = (ShortcutInfo) tag;
1341                            final Intent intent = info.intent;
1342                            final ComponentName name = intent.getComponent();
1343
1344                            if (Intent.ACTION_MAIN.equals(intent.getAction()) && name != null) {
1345                                for (String packageName: packageNames) {
1346                                    if (packageName.equals(name.getPackageName())) {
1347                                        // TODO: This should probably be done on a worker thread
1348                                        LauncherModel.deleteItemFromDatabase(mLauncher, info);
1349                                        childrenToRemove.add(view);
1350                                    }
1351                                }
1352                            }
1353                        } else if (tag instanceof UserFolderInfo) {
1354                            final UserFolderInfo info = (UserFolderInfo) tag;
1355                            final ArrayList<ShortcutInfo> contents = info.contents;
1356                            final ArrayList<ShortcutInfo> toRemove = new ArrayList<ShortcutInfo>(1);
1357                            final int contentsCount = contents.size();
1358                            boolean removedFromFolder = false;
1359
1360                            for (int k = 0; k < contentsCount; k++) {
1361                                final ShortcutInfo appInfo = contents.get(k);
1362                                final Intent intent = appInfo.intent;
1363                                final ComponentName name = intent.getComponent();
1364
1365                                if (Intent.ACTION_MAIN.equals(intent.getAction()) && name != null) {
1366                                    for (String packageName: packageNames) {
1367                                        if (packageName.equals(name.getPackageName())) {
1368                                            toRemove.add(appInfo);
1369                                            // TODO: This should probably be done on a worker thread
1370                                            LauncherModel.deleteItemFromDatabase(
1371                                                    mLauncher, appInfo);
1372                                            removedFromFolder = true;
1373                                        }
1374                                    }
1375                                }
1376                            }
1377
1378                            contents.removeAll(toRemove);
1379                            if (removedFromFolder) {
1380                                final Folder folder = getOpenFolder();
1381                                if (folder != null) folder.notifyDataSetChanged();
1382                            }
1383                        } else if (tag instanceof LiveFolderInfo) {
1384                            final LiveFolderInfo info = (LiveFolderInfo) tag;
1385                            final Uri uri = info.uri;
1386                            final ProviderInfo providerInfo = manager.resolveContentProvider(
1387                                    uri.getAuthority(), 0);
1388
1389                            if (providerInfo != null) {
1390                                for (String packageName: packageNames) {
1391                                    if (packageName.equals(providerInfo.packageName)) {
1392                                        // TODO: This should probably be done on a worker thread
1393                                        LauncherModel.deleteItemFromDatabase(mLauncher, info);
1394                                        childrenToRemove.add(view);
1395                                    }
1396                                }
1397                            }
1398                        } else if (tag instanceof LauncherAppWidgetInfo) {
1399                            final LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) tag;
1400                            final AppWidgetProviderInfo provider =
1401                                    widgets.getAppWidgetInfo(info.appWidgetId);
1402                            if (provider == null) {
1403                                for (String packageName: packageNames) {
1404                                    if (packageName.equals(provider.provider.getPackageName())) {
1405                                        // TODO: This should probably be done on a worker thread
1406                                        LauncherModel.deleteItemFromDatabase(mLauncher, info);
1407                                        childrenToRemove.add(view);
1408                                    }
1409                                }
1410                            }
1411                        }
1412                    }
1413
1414                    childCount = childrenToRemove.size();
1415                    for (int j = 0; j < childCount; j++) {
1416                        View child = childrenToRemove.get(j);
1417                        layout.removeViewInLayout(child);
1418                        if (child instanceof DropTarget) {
1419                            mDragController.removeDropTarget((DropTarget)child);
1420                        }
1421                    }
1422
1423                    if (childCount > 0) {
1424                        layout.requestLayout();
1425                        layout.invalidate();
1426                    }
1427                }
1428            });
1429        }
1430    }
1431
1432    void updateShortcuts(ArrayList<ApplicationInfo> apps) {
1433        final PackageManager pm = mLauncher.getPackageManager();
1434
1435        final int count = getChildCount();
1436        for (int i = 0; i < count; i++) {
1437            final CellLayout layout = (CellLayout) getChildAt(i);
1438            int childCount = layout.getChildCount();
1439            for (int j = 0; j < childCount; j++) {
1440                final View view = layout.getChildAt(j);
1441                Object tag = view.getTag();
1442                if (tag instanceof ShortcutInfo) {
1443                    ShortcutInfo info = (ShortcutInfo)tag;
1444                    // We need to check for ACTION_MAIN otherwise getComponent() might
1445                    // return null for some shortcuts (for instance, for shortcuts to
1446                    // web pages.)
1447                    final Intent intent = info.intent;
1448                    final ComponentName name = intent.getComponent();
1449                    if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION &&
1450                            Intent.ACTION_MAIN.equals(intent.getAction()) && name != null) {
1451                        final int appCount = apps.size();
1452                        for (int k=0; k<appCount; k++) {
1453                            ApplicationInfo app = apps.get(k);
1454                            if (app.componentName.equals(name)) {
1455                                info.setIcon(mIconCache.getIcon(info.intent));
1456                                ((TextView)view).setCompoundDrawablesWithIntrinsicBounds(null,
1457                                        new FastBitmapDrawable(info.getIcon(mIconCache)),
1458                                        null, null);
1459                                }
1460                        }
1461                    }
1462                }
1463            }
1464        }
1465    }
1466
1467    void moveToDefaultScreen(boolean animate) {
1468        if (animate) {
1469            snapToScreen(mDefaultScreen);
1470        } else {
1471            setCurrentScreen(mDefaultScreen);
1472        }
1473        getChildAt(mDefaultScreen).requestFocus();
1474    }
1475
1476    void setIndicators(Drawable previous, Drawable next) {
1477        mPreviousIndicator = previous;
1478        mNextIndicator = next;
1479        previous.setLevel(mCurrentScreen);
1480        next.setLevel(mCurrentScreen);
1481    }
1482
1483    public static class SavedState extends BaseSavedState {
1484        int currentScreen = -1;
1485
1486        SavedState(Parcelable superState) {
1487            super(superState);
1488        }
1489
1490        private SavedState(Parcel in) {
1491            super(in);
1492            currentScreen = in.readInt();
1493        }
1494
1495        @Override
1496        public void writeToParcel(Parcel out, int flags) {
1497            super.writeToParcel(out, flags);
1498            out.writeInt(currentScreen);
1499        }
1500
1501        public static final Parcelable.Creator<SavedState> CREATOR =
1502                new Parcelable.Creator<SavedState>() {
1503            public SavedState createFromParcel(Parcel in) {
1504                return new SavedState(in);
1505            }
1506
1507            public SavedState[] newArray(int size) {
1508                return new SavedState[size];
1509            }
1510        };
1511    }
1512}
1513