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