Workspace.java revision a63c452f5bd491ba9b28c332ccedc6c6c7e2f3cc
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 com.android.launcher.R;
20import com.android.launcher2.CellLayout.CellInfo;
21
22import android.animation.Animatable;
23import android.animation.PropertyAnimator;
24import android.animation.Sequencer;
25import android.animation.Animatable.AnimatableListener;
26import android.app.WallpaperManager;
27import android.appwidget.AppWidgetManager;
28import android.appwidget.AppWidgetProviderInfo;
29import android.content.ComponentName;
30import android.content.Context;
31import android.content.Intent;
32import android.content.pm.PackageManager;
33import android.content.pm.ProviderInfo;
34import android.content.res.Resources;
35import android.content.res.TypedArray;
36import android.graphics.Canvas;
37import android.graphics.Matrix;
38import android.graphics.Paint;
39import android.graphics.Rect;
40import android.graphics.drawable.Drawable;
41import android.net.Uri;
42import android.os.IBinder;
43import android.os.Parcel;
44import android.os.Parcelable;
45import android.util.AttributeSet;
46import android.util.Log;
47import android.view.MotionEvent;
48import android.view.VelocityTracker;
49import android.view.View;
50import android.view.ViewConfiguration;
51import android.view.ViewGroup;
52import android.view.ViewParent;
53import android.view.animation.Interpolator;
54import android.widget.Scroller;
55import android.widget.TextView;
56import android.widget.Toast;
57
58import java.util.ArrayList;
59import java.util.HashSet;
60
61/**
62 * The workspace is a wide area with a wallpaper and a finite number of screens.
63 * Each screen contains a number of icons, folders or widgets the user can
64 * interact with. A workspace is meant to be used with a fixed width only.
65 */
66public class Workspace extends ViewGroup
67        implements DropTarget, DragSource, DragScroller, View.OnTouchListener {
68    @SuppressWarnings({"UnusedDeclaration"})
69    private static final String TAG = "Launcher.Workspace";
70    private static final int INVALID_SCREEN = -1;
71    // This is how much the workspace shrinks when we enter all apps or
72    // customization mode
73    private static final float SHRINK_FACTOR = 0.16f;
74    private static final int SHRINK_TO_TOP = 0;
75    private static final int SHRINK_TO_MIDDLE = 1;
76    private static final int SHRINK_TO_BOTTOM = 2;
77
78    /**
79     * The velocity at which a fling gesture will cause us to snap to the next
80     * screen
81     */
82    private static final int SNAP_VELOCITY = 600;
83
84    private final WallpaperManager mWallpaperManager;
85
86    private int mDefaultScreen;
87
88    private boolean mFirstLayout = true;
89    private boolean mWaitingToShrinkToBottom = false;
90
91    private int mCurrentScreen;
92    private int mNextScreen = INVALID_SCREEN;
93    private Scroller mScroller;
94    private VelocityTracker mVelocityTracker;
95
96    /**
97     * CellInfo for the cell that is currently being dragged
98     */
99    private CellLayout.CellInfo mDragInfo;
100
101    /**
102     * Target drop area calculated during last acceptDrop call.
103     */
104    private int[] mTargetCell = null;
105
106    /**
107     * The CellLayout that is currently being dragged over
108     */
109    private CellLayout mDragTargetLayout = null;
110
111    private float mLastMotionX;
112    private float mLastMotionY;
113
114    private final static int TOUCH_STATE_REST = 0;
115    private final static int TOUCH_STATE_SCROLLING = 1;
116
117    private int mTouchState = TOUCH_STATE_REST;
118
119    private OnLongClickListener mLongClickListener;
120
121    private Launcher mLauncher;
122    private IconCache mIconCache;
123    private DragController mDragController;
124
125
126    private int[] mTempCell = new int[2];
127    private int[] mTempEstimate = new int[2];
128    private float[] mTempDragCoordinates = new float[2];
129    private float[] mTempDragBottomRightCoordinates = new float[2];
130
131    private boolean mAllowLongPress = true;
132
133    private int mTouchSlop;
134    private int mMaximumVelocity;
135
136    private static final int INVALID_POINTER = -1;
137    private static final int DEFAULT_CELL_COUNT_X = 4;
138    private static final int DEFAULT_CELL_COUNT_Y = 4;
139
140    private int mActivePointerId = INVALID_POINTER;
141
142    private Drawable mPreviousIndicator;
143    private Drawable mNextIndicator;
144
145    private static final float NANOTIME_DIV = 1000000000.0f;
146    private static final float SMOOTHING_SPEED = 0.75f;
147    private static final float SMOOTHING_CONSTANT = (float) (0.016 / Math.log(SMOOTHING_SPEED));
148    private float mSmoothingTime;
149    private float mTouchX;
150
151    private WorkspaceOvershootInterpolator mScrollInterpolator;
152
153    private static final float BASELINE_FLING_VELOCITY = 2500.f;
154    private static final float FLING_VELOCITY_INFLUENCE = 0.4f;
155
156    private Paint mDropIndicatorPaint;
157
158    // State variable that indicated whether the screens are small (ie when you're
159    // in all apps or customize mode)
160    private boolean mIsSmall;
161    private AnimatableListener mUnshrinkAnimationListener;
162
163    private static class WorkspaceOvershootInterpolator implements Interpolator {
164        private static final float DEFAULT_TENSION = 1.3f;
165        private float mTension;
166
167        public WorkspaceOvershootInterpolator() {
168            mTension = DEFAULT_TENSION;
169        }
170
171        public void setDistance(int distance) {
172            mTension = distance > 0 ? DEFAULT_TENSION / distance : DEFAULT_TENSION;
173        }
174
175        public void disableSettle() {
176            mTension = 0.f;
177        }
178
179        public float getInterpolation(float t) {
180            // _o(t) = t * t * ((tension + 1) * t + tension)
181            // o(t) = _o(t - 1) + 1
182            t -= 1.0f;
183            return t * t * ((mTension + 1) * t + mTension) + 1.0f;
184        }
185    }
186
187    /**
188     * Used to inflate the Workspace from XML.
189     *
190     * @param context The application's context.
191     * @param attrs The attribtues set containing the Workspace's customization values.
192     */
193    public Workspace(Context context, AttributeSet attrs) {
194        this(context, attrs, 0);
195    }
196
197    /**
198     * Used to inflate the Workspace from XML.
199     *
200     * @param context The application's context.
201     * @param attrs The attribtues set containing the Workspace's customization values.
202     * @param defStyle Unused.
203     */
204    public Workspace(Context context, AttributeSet attrs, int defStyle) {
205        super(context, attrs, defStyle);
206
207        mWallpaperManager = WallpaperManager.getInstance(context);
208
209        TypedArray a = context.obtainStyledAttributes(attrs,
210                R.styleable.Workspace, defStyle, 0);
211        int cellCountX = a.getInt(R.styleable.Workspace_cellCountX, DEFAULT_CELL_COUNT_X);
212        int cellCountY = a.getInt(R.styleable.Workspace_cellCountY, DEFAULT_CELL_COUNT_Y);
213        mDefaultScreen = a.getInt(R.styleable.Workspace_defaultScreen, 1);
214        a.recycle();
215
216        LauncherModel.updateWorkspaceLayoutCells(cellCountX, cellCountY);
217        setHapticFeedbackEnabled(false);
218        initWorkspace();
219    }
220
221    /**
222     * Initializes various states for this workspace.
223     */
224    private void initWorkspace() {
225        Context context = getContext();
226        mScrollInterpolator = new WorkspaceOvershootInterpolator();
227        mScroller = new Scroller(context, mScrollInterpolator);
228        mCurrentScreen = mDefaultScreen;
229        Launcher.setScreen(mCurrentScreen);
230        LauncherApplication app = (LauncherApplication)context.getApplicationContext();
231        mIconCache = app.getIconCache();
232
233        final ViewConfiguration configuration = ViewConfiguration.get(getContext());
234        mTouchSlop = configuration.getScaledTouchSlop();
235        mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
236        mUnshrinkAnimationListener = new AnimatableListener() {
237            public void onAnimationStart(Animatable animation) {}
238            public void onAnimationEnd(Animatable animation) {
239                mIsSmall = false;
240            }
241            public void onAnimationCancel(Animatable animation) {}
242            public void onAnimationRepeat(Animatable animation) {}
243        };
244    }
245
246    @Override
247    public void addView(View child, int index, LayoutParams params) {
248        if (!(child instanceof CellLayout)) {
249            throw new IllegalArgumentException("A Workspace can only have CellLayout children.");
250        }
251        super.addView(child, index, params);
252    }
253
254    @Override
255    public void addView(View child) {
256        if (!(child instanceof CellLayout)) {
257            throw new IllegalArgumentException("A Workspace can only have CellLayout children.");
258        }
259        super.addView(child);
260    }
261
262    @Override
263    public void addView(View child, int index) {
264        if (!(child instanceof CellLayout)) {
265            throw new IllegalArgumentException("A Workspace can only have CellLayout children.");
266        }
267        super.addView(child, index);
268    }
269
270    @Override
271    public void addView(View child, int width, int height) {
272        if (!(child instanceof CellLayout)) {
273            throw new IllegalArgumentException("A Workspace can only have CellLayout children.");
274        }
275        super.addView(child, width, height);
276    }
277
278    @Override
279    public void addView(View child, LayoutParams params) {
280        if (!(child instanceof CellLayout)) {
281            throw new IllegalArgumentException("A Workspace can only have CellLayout children.");
282        }
283        super.addView(child, params);
284    }
285
286    /**
287     * @return The open folder on the current screen, or null if there is none
288     */
289    Folder getOpenFolder() {
290        CellLayout currentScreen = (CellLayout) getChildAt(mCurrentScreen);
291        int count = currentScreen.getChildCount();
292        for (int i = 0; i < count; i++) {
293            View child = currentScreen.getChildAt(i);
294            if (child instanceof Folder) {
295                Folder folder = (Folder) child;
296                if (folder.getInfo().opened)
297                    return folder;
298            }
299        }
300        return null;
301    }
302
303    ArrayList<Folder> getOpenFolders() {
304        final int screenCount = getChildCount();
305        ArrayList<Folder> folders = new ArrayList<Folder>(screenCount);
306
307        for (int screen = 0; screen < screenCount; screen++) {
308            CellLayout currentScreen = (CellLayout) getChildAt(screen);
309            int count = currentScreen.getChildCount();
310            for (int i = 0; i < count; i++) {
311                View child = currentScreen.getChildAt(i);
312                CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child
313                        .getLayoutParams();
314                if (child instanceof Folder) {
315                    Folder folder = (Folder) child;
316                    if (folder.getInfo().opened)
317                        folders.add(folder);
318                    break;
319                }
320            }
321        }
322
323        return folders;
324    }
325
326    boolean isDefaultScreenShowing() {
327        return mCurrentScreen == mDefaultScreen;
328    }
329
330    /**
331     * Returns the index of the currently displayed screen.
332     *
333     * @return The index of the currently displayed screen.
334     */
335    int getCurrentScreen() {
336        return mCurrentScreen;
337    }
338
339    /**
340     * Sets the current screen.
341     *
342     * @param currentScreen
343     */
344    void setCurrentScreen(int currentScreen) {
345        setCurrentScreen(currentScreen, true);
346    }
347
348    void setCurrentScreen(int currentScreen, boolean animateScrolling) {
349        setCurrentScreen(currentScreen, animateScrolling, getWidth());
350    }
351
352    void setCurrentScreen(int currentScreen, boolean animateScrolling, int screenWidth) {
353        if (!mScroller.isFinished())
354            mScroller.abortAnimation();
355        mCurrentScreen = Math.max(0, Math.min(currentScreen, getChildCount() - 1));
356        if (mPreviousIndicator != null) {
357            mPreviousIndicator.setLevel(mCurrentScreen);
358            mNextIndicator.setLevel(mCurrentScreen);
359        }
360        if (animateScrolling) {
361            scrollTo(mCurrentScreen * screenWidth, 0);
362        } else {
363            mScrollX = mCurrentScreen * screenWidth;
364        }
365        updateWallpaperOffset(screenWidth * (getChildCount() - 1));
366        invalidate();
367    }
368
369    /**
370     * Adds the specified child in the current screen. The position and dimension of
371     * the child are defined by x, y, spanX and spanY.
372     *
373     * @param child The child to add in one of the workspace's screens.
374     * @param x The X position of the child in the screen's grid.
375     * @param y The Y position of the child in the screen's grid.
376     * @param spanX The number of cells spanned horizontally by the child.
377     * @param spanY The number of cells spanned vertically by the child.
378     */
379    void addInCurrentScreen(View child, int x, int y, int spanX, int spanY) {
380        addInScreen(child, mCurrentScreen, x, y, spanX, spanY, false);
381    }
382
383    /**
384     * Adds the specified child in the current screen. The position and dimension of
385     * the child are defined by x, y, spanX and spanY.
386     *
387     * @param child The child to add in one of the workspace's screens.
388     * @param x The X position of the child in the screen's grid.
389     * @param y The Y position of the child in the screen's grid.
390     * @param spanX The number of cells spanned horizontally by the child.
391     * @param spanY The number of cells spanned vertically by the child.
392     * @param insert When true, the child is inserted at the beginning of the children list.
393     */
394    void addInCurrentScreen(View child, int x, int y, int spanX, int spanY, boolean insert) {
395        addInScreen(child, mCurrentScreen, x, y, spanX, spanY, insert);
396    }
397
398    /**
399     * Adds the specified child in the specified screen. The position and dimension of
400     * the child are defined by x, y, spanX and spanY.
401     *
402     * @param child The child to add in one of the workspace's screens.
403     * @param screen The screen in which to add the child.
404     * @param x The X position of the child in the screen's grid.
405     * @param y The Y position of the child in the screen's grid.
406     * @param spanX The number of cells spanned horizontally by the child.
407     * @param spanY The number of cells spanned vertically by the child.
408     */
409    void addInScreen(View child, int screen, int x, int y, int spanX, int spanY) {
410        addInScreen(child, screen, x, y, spanX, spanY, false);
411    }
412
413    void addInFullScreen(View child, int screen) {
414        addInScreen(child, screen, 0, 0, -1, -1);
415    }
416
417    /**
418     * Adds the specified child in the specified screen. The position and dimension of
419     * the child are defined by x, y, spanX and spanY.
420     *
421     * @param child The child to add in one of the workspace's screens.
422     * @param screen The screen in which to add the child.
423     * @param x The X position of the child in the screen's grid.
424     * @param y The Y position of the child in the screen's grid.
425     * @param spanX The number of cells spanned horizontally by the child.
426     * @param spanY The number of cells spanned vertically by the child.
427     * @param insert When true, the child is inserted at the beginning of the children list.
428     */
429    void addInScreen(View child, int screen, int x, int y, int spanX, int spanY, boolean insert) {
430        if (screen < 0 || screen >= getChildCount()) {
431            Log.e(TAG, "The screen must be >= 0 and < " + getChildCount()
432                + " (was " + screen + "); skipping child");
433            return;
434        }
435
436        final CellLayout group = (CellLayout) getChildAt(screen);
437        CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
438        if (lp == null) {
439            lp = new CellLayout.LayoutParams(x, y, spanX, spanY);
440        } else {
441            lp.cellX = x;
442            lp.cellY = y;
443            lp.cellHSpan = spanX;
444            lp.cellVSpan = spanY;
445        }
446
447        // Get the canonical child id to uniquely represent this view in this screen
448        int childId = LauncherModel.getCellLayoutChildId(child.getId(), screen, x, y, spanX, spanY);
449        if (!group.addViewToCellLayout(child, insert ? 0 : -1, childId, lp)) {
450            // TODO: This branch occurs when the workspace is adding views
451            // outside of the defined grid
452            // maybe we should be deleting these items from the LauncherModel?
453            Log.w(TAG, "Failed to add to item at (" + lp.cellX + "," + lp.cellY + ") to CellLayout");
454        }
455
456        if (!(child instanceof Folder)) {
457            child.setHapticFeedbackEnabled(false);
458            child.setOnLongClickListener(mLongClickListener);
459        }
460        if (child instanceof DropTarget) {
461            mDragController.addDropTarget((DropTarget) child);
462        }
463    }
464
465    CellLayout.CellInfo updateOccupiedCellsForCurrentScreen(boolean[] occupied) {
466        CellLayout group = (CellLayout) getChildAt(mCurrentScreen);
467        if (group != null) {
468            return group.updateOccupiedCells(occupied, null);
469        }
470        return null;
471    }
472
473    public boolean onTouch(View v, MotionEvent event) {
474        // this is an intercepted event being forwarded from a cell layout
475        if (mIsSmall) {
476            unshrink((CellLayout)v);
477            mLauncher.onWorkspaceUnshrink();
478            return true;
479        }
480        return false;
481    }
482
483    /**
484     * Registers the specified listener on each screen contained in this workspace.
485     *
486     * @param l The listener used to respond to long clicks.
487     */
488    @Override
489    public void setOnLongClickListener(OnLongClickListener l) {
490        mLongClickListener = l;
491        final int screenCount = getChildCount();
492        for (int i = 0; i < screenCount; i++) {
493            getChildAt(i).setOnLongClickListener(l);
494        }
495    }
496
497    private void updateWallpaperOffset() {
498        updateWallpaperOffset(getChildAt(getChildCount() - 1).getRight() - (mRight - mLeft));
499    }
500
501    private void updateWallpaperOffset(int scrollRange) {
502        IBinder token = getWindowToken();
503        if (token != null) {
504            mWallpaperManager.setWallpaperOffsetSteps(1.0f / (getChildCount() - 1), 0 );
505            mWallpaperManager.setWallpaperOffsets(getWindowToken(),
506                    Math.max(0.f, Math.min(mScrollX/(float)scrollRange, 1.f)), 0);
507        }
508    }
509
510    @Override
511    public void scrollTo(int x, int y) {
512        super.scrollTo(x, y);
513        mTouchX = x;
514        mSmoothingTime = System.nanoTime() / NANOTIME_DIV;
515    }
516
517    @Override
518    public void computeScroll() {
519        if (mScroller.computeScrollOffset()) {
520            mTouchX = mScrollX = mScroller.getCurrX();
521            mSmoothingTime = System.nanoTime() / NANOTIME_DIV;
522            mScrollY = mScroller.getCurrY();
523            updateWallpaperOffset();
524            postInvalidate();
525        } else if (mNextScreen != INVALID_SCREEN) {
526            mCurrentScreen = Math.max(0, Math.min(mNextScreen, getChildCount() - 1));
527            if (mPreviousIndicator != null) {
528                mPreviousIndicator.setLevel(mCurrentScreen);
529                mNextIndicator.setLevel(mCurrentScreen);
530            }
531            Launcher.setScreen(mCurrentScreen);
532            mNextScreen = INVALID_SCREEN;
533            clearChildrenCache();
534        } else if (mTouchState == TOUCH_STATE_SCROLLING) {
535            final float now = System.nanoTime() / NANOTIME_DIV;
536            final float e = (float) Math.exp((now - mSmoothingTime) / SMOOTHING_CONSTANT);
537            final float dx = mTouchX - mScrollX;
538            mScrollX += dx * e;
539            mSmoothingTime = now;
540
541            // Keep generating points as long as we're more than 1px away from the target
542            if (dx > 1.f || dx < -1.f) {
543                updateWallpaperOffset();
544                postInvalidate();
545            }
546        }
547    }
548
549    @Override
550    protected void dispatchDraw(Canvas canvas) {
551        boolean restore = false;
552        int restoreCount = 0;
553
554        // ViewGroup.dispatchDraw() supports many features we don't need:
555        // clip to padding, layout animation, animation listener, disappearing
556        // children, etc. The following implementation attempts to fast-track
557        // the drawing dispatch by drawing only what we know needs to be drawn.
558
559        boolean fastDraw = mTouchState != TOUCH_STATE_SCROLLING && mNextScreen == INVALID_SCREEN;
560
561        // if the screens are all small, we need to draw all the screens since
562        // they're most likely all visible
563        if (mIsSmall) {
564            final int screenCount = getChildCount();
565            for (int i = 0; i < screenCount; i++) {
566                CellLayout cl = (CellLayout)getChildAt(i);
567                drawChild(canvas, cl, getDrawingTime());
568            }
569        } else if (fastDraw) {
570            // If we are not scrolling or flinging, draw only the current screen
571            drawChild(canvas, getChildAt(mCurrentScreen), getDrawingTime());
572        } else {
573            final long drawingTime = getDrawingTime();
574            final float scrollPos = (float) mScrollX / getWidth();
575            final int leftScreen = (int) scrollPos;
576            final int rightScreen = leftScreen + 1;
577            if (leftScreen >= 0) {
578                drawChild(canvas, getChildAt(leftScreen), drawingTime);
579            }
580            if (scrollPos != leftScreen && rightScreen < getChildCount()) {
581                drawChild(canvas, getChildAt(rightScreen), drawingTime);
582            }
583        }
584
585        if (restore) {
586            canvas.restoreToCount(restoreCount);
587        }
588    }
589
590    protected void onAttachedToWindow() {
591        super.onAttachedToWindow();
592        computeScroll();
593        mDragController.setWindowToken(getWindowToken());
594    }
595
596    @Override
597    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
598        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
599
600        final int width = MeasureSpec.getSize(widthMeasureSpec);
601        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
602        if (widthMode != MeasureSpec.EXACTLY) {
603            throw new IllegalStateException("Workspace can only be used in EXACTLY mode.");
604        }
605
606        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
607        if (heightMode != MeasureSpec.EXACTLY) {
608            throw new IllegalStateException("Workspace can only be used in EXACTLY mode.");
609        }
610
611        // The children are given the same width and height as the workspace
612        final int screenCount = getChildCount();
613        for (int i = 0; i < screenCount; i++) {
614            getChildAt(i).measure(widthMeasureSpec, heightMeasureSpec);
615        }
616
617        if (mFirstLayout) {
618            setHorizontalScrollBarEnabled(false);
619            setCurrentScreen(mCurrentScreen, false, width);
620            setHorizontalScrollBarEnabled(true);
621        }
622    }
623
624    @Override
625    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
626        if (mFirstLayout) {
627            mFirstLayout = false;
628        }
629        int childLeft = 0;
630        final int screenCount = getChildCount();
631        for (int i = 0; i < screenCount; i++) {
632            final View child = getChildAt(i);
633            if (child.getVisibility() != View.GONE) {
634                final int childWidth = child.getMeasuredWidth();
635                child.layout(childLeft, 0,
636                        childLeft + childWidth, child.getMeasuredHeight());
637                childLeft += childWidth;
638            }
639        }
640
641        // if shrinkToBottom() is called on initialization, it has to be deferred
642        // until after the first call to onLayout so that it has the correct width
643        if (mWaitingToShrinkToBottom) {
644            shrinkToBottom(false);
645            mWaitingToShrinkToBottom = false;
646        }
647
648        if (LauncherApplication.isInPlaceRotationEnabled()) {
649            // When the device is rotated, the scroll position of the current screen
650            // needs to be refreshed
651            setCurrentScreen(getCurrentScreen());
652        }
653    }
654
655    @Override
656    public boolean requestChildRectangleOnScreen(View child, Rect rectangle, boolean immediate) {
657        int screen = indexOfChild(child);
658        if (screen != mCurrentScreen || !mScroller.isFinished()) {
659            if (!mLauncher.isWorkspaceLocked()) {
660                snapToScreen(screen);
661            }
662            return true;
663        }
664        return false;
665    }
666
667    @Override
668    protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
669        if (!mLauncher.isAllAppsVisible()) {
670            final Folder openFolder = getOpenFolder();
671            if (openFolder != null) {
672                return openFolder.requestFocus(direction, previouslyFocusedRect);
673            } else {
674                int focusableScreen;
675                if (mNextScreen != INVALID_SCREEN) {
676                    focusableScreen = mNextScreen;
677                } else {
678                    focusableScreen = mCurrentScreen;
679                }
680                getChildAt(focusableScreen).requestFocus(direction, previouslyFocusedRect);
681            }
682        }
683        return false;
684    }
685
686    @Override
687    public boolean dispatchUnhandledMove(View focused, int direction) {
688        if (direction == View.FOCUS_LEFT) {
689            if (getCurrentScreen() > 0) {
690                snapToScreen(getCurrentScreen() - 1);
691                return true;
692            }
693        } else if (direction == View.FOCUS_RIGHT) {
694            if (getCurrentScreen() < getChildCount() - 1) {
695                snapToScreen(getCurrentScreen() + 1);
696                return true;
697            }
698        }
699        return super.dispatchUnhandledMove(focused, direction);
700    }
701
702    @Override
703    public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
704        if (!mLauncher.isAllAppsVisible()) {
705            final Folder openFolder = getOpenFolder();
706            if (openFolder == null) {
707                getChildAt(mCurrentScreen).addFocusables(views, direction);
708                if (direction == View.FOCUS_LEFT) {
709                    if (mCurrentScreen > 0) {
710                        getChildAt(mCurrentScreen - 1).addFocusables(views, direction);
711                    }
712                } else if (direction == View.FOCUS_RIGHT) {
713                    if (mCurrentScreen < getChildCount() - 1) {
714                        getChildAt(mCurrentScreen + 1).addFocusables(views, direction);
715                    }
716                }
717            } else {
718                openFolder.addFocusables(views, direction);
719            }
720        }
721    }
722
723    @Override
724    public boolean dispatchTouchEvent(MotionEvent ev) {
725        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
726            // (In XLarge mode, the workspace is shrunken below all apps, and responds to taps
727            // ie when you click on a mini-screen, it zooms back to that screen)
728            if (mLauncher.isWorkspaceLocked() ||
729                    (!LauncherApplication.isScreenXLarge() && mLauncher.isAllAppsVisible())) {
730                return false;
731            }
732        }
733        return super.dispatchTouchEvent(ev);
734    }
735
736    /**
737     * {@inheritDoc}
738     */
739    @Override
740    public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
741        if (disallowIntercept) {
742            // We need to make sure to cancel our long press if
743            // a scrollable widget takes over touch events
744            final View currentScreen = getChildAt(mCurrentScreen);
745            currentScreen.cancelLongPress();
746        }
747        super.requestDisallowInterceptTouchEvent(disallowIntercept);
748    }
749
750    @Override
751    public boolean onInterceptTouchEvent(MotionEvent ev) {
752        final boolean workspaceLocked = mLauncher.isWorkspaceLocked();
753        final boolean allAppsVisible = mLauncher.isAllAppsVisible();
754
755        // (In XLarge mode, the workspace is shrunken below all apps, and responds to taps
756        // ie when you click on a mini-screen, it zooms back to that screen)
757        if (workspaceLocked || (!LauncherApplication.isScreenXLarge() && allAppsVisible)) {
758            return false; // We don't want the events.  Let them fall through to the all apps view.
759        }
760
761        /*
762         * This method JUST determines whether we want to intercept the motion.
763         * If we return true, onTouchEvent will be called and we do the actual
764         * scrolling there.
765         */
766
767        /*
768         * Shortcut the most recurring case: the user is in the dragging
769         * state and he is moving his finger.  We want to intercept this
770         * motion.
771         */
772        final int action = ev.getAction();
773        if ((action == MotionEvent.ACTION_MOVE) && (mTouchState != TOUCH_STATE_REST)) {
774            return true;
775        }
776
777        if (mVelocityTracker == null) {
778            mVelocityTracker = VelocityTracker.obtain();
779        }
780        mVelocityTracker.addMovement(ev);
781
782        switch (action & MotionEvent.ACTION_MASK) {
783            case MotionEvent.ACTION_MOVE: {
784                /*
785                 * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
786                 * whether the user has moved far enough from his original down touch.
787                 */
788
789                /*
790                 * Locally do absolute value. mLastMotionX is set to the y value
791                 * of the down event.
792                 */
793                final int pointerIndex = ev.findPointerIndex(mActivePointerId);
794                final float x = ev.getX(pointerIndex);
795                final float y = ev.getY(pointerIndex);
796                final int xDiff = (int) Math.abs(x - mLastMotionX);
797                final int yDiff = (int) Math.abs(y - mLastMotionY);
798
799                final int touchSlop = mTouchSlop;
800                boolean xMoved = xDiff > touchSlop;
801                boolean yMoved = yDiff > touchSlop;
802
803                if (xMoved || yMoved) {
804
805                    if (xMoved) {
806                        // Scroll if the user moved far enough along the X axis
807                        mTouchState = TOUCH_STATE_SCROLLING;
808                        mLastMotionX = x;
809                        mTouchX = mScrollX;
810                        mSmoothingTime = System.nanoTime() / NANOTIME_DIV;
811                        enableChildrenCache(mCurrentScreen - 1, mCurrentScreen + 1);
812                    }
813                    // Either way, cancel any pending longpress
814                    if (mAllowLongPress) {
815                        mAllowLongPress = false;
816                        // Try canceling the long press. It could also have been scheduled
817                        // by a distant descendant, so use the mAllowLongPress flag to block
818                        // everything
819                        final View currentScreen = getChildAt(mCurrentScreen);
820                        currentScreen.cancelLongPress();
821                    }
822                }
823                break;
824            }
825
826        case MotionEvent.ACTION_DOWN: {
827            final float x = ev.getX();
828            final float y = ev.getY();
829            // Remember location of down touch
830            mLastMotionX = x;
831            mLastMotionY = y;
832            mActivePointerId = ev.getPointerId(0);
833            mAllowLongPress = true;
834
835                /*
836                 * If being flinged and user touches the screen, initiate drag;
837                 * otherwise don't.  mScroller.isFinished should be false when
838                 * being flinged.
839                 */
840                mTouchState = mScroller.isFinished() ? TOUCH_STATE_REST : TOUCH_STATE_SCROLLING;
841                break;
842            }
843
844            case MotionEvent.ACTION_CANCEL:
845            case MotionEvent.ACTION_UP:
846
847                if (mTouchState != TOUCH_STATE_SCROLLING) {
848                    final CellLayout currentScreen = (CellLayout)getChildAt(mCurrentScreen);
849                    if (!currentScreen.lastDownOnOccupiedCell()) {
850                        getLocationOnScreen(mTempCell);
851                        // Send a tap to the wallpaper if the last down was on empty space
852                        final int pointerIndex = ev.findPointerIndex(mActivePointerId);
853                        mWallpaperManager.sendWallpaperCommand(getWindowToken(),
854                                "android.wallpaper.tap",
855                                mTempCell[0] + (int) ev.getX(pointerIndex),
856                                mTempCell[1] + (int) ev.getY(pointerIndex), 0, null);
857                    }
858                }
859
860                // Release the drag
861                clearChildrenCache();
862                mTouchState = TOUCH_STATE_REST;
863                mActivePointerId = INVALID_POINTER;
864                mAllowLongPress = false;
865
866                if (mVelocityTracker != null) {
867                    mVelocityTracker.recycle();
868                    mVelocityTracker = null;
869                }
870
871            break;
872
873        case MotionEvent.ACTION_POINTER_UP:
874            onSecondaryPointerUp(ev);
875            break;
876        }
877
878        /*
879         * The only time we want to intercept motion events is if we are in the
880         * drag mode.
881         */
882        return mTouchState != TOUCH_STATE_REST;
883    }
884
885    private void onSecondaryPointerUp(MotionEvent ev) {
886        final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >>
887                MotionEvent.ACTION_POINTER_INDEX_SHIFT;
888        final int pointerId = ev.getPointerId(pointerIndex);
889        if (pointerId == mActivePointerId) {
890            // This was our active pointer going up. Choose a new
891            // active pointer and adjust accordingly.
892            // TODO: Make this decision more intelligent.
893            final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
894            mLastMotionX = ev.getX(newPointerIndex);
895            mLastMotionY = ev.getY(newPointerIndex);
896            mActivePointerId = ev.getPointerId(newPointerIndex);
897            if (mVelocityTracker != null) {
898                mVelocityTracker.clear();
899            }
900        }
901    }
902
903    /**
904     * If one of our descendant views decides that it could be focused now, only
905     * pass that along if it's on the current screen.
906     *
907     * This happens when live folders requery, and if they're off screen, they
908     * end up calling requestFocus, which pulls it on screen.
909     */
910    @Override
911    public void focusableViewAvailable(View focused) {
912        View current = getChildAt(mCurrentScreen);
913        View v = focused;
914        while (true) {
915            if (v == current) {
916                super.focusableViewAvailable(focused);
917                return;
918            }
919            if (v == this) {
920                return;
921            }
922            ViewParent parent = v.getParent();
923            if (parent instanceof View) {
924                v = (View) v.getParent();
925            } else {
926                return;
927            }
928        }
929    }
930
931    void enableChildrenCache(int fromScreen, int toScreen) {
932        if (fromScreen > toScreen) {
933            final int temp = fromScreen;
934            fromScreen = toScreen;
935            toScreen = temp;
936        }
937
938        final int screenCount = getChildCount();
939
940        fromScreen = Math.max(fromScreen, 0);
941        toScreen = Math.min(toScreen, screenCount - 1);
942
943        for (int i = fromScreen; i <= toScreen; i++) {
944            final CellLayout layout = (CellLayout) getChildAt(i);
945            layout.setChildrenDrawnWithCacheEnabled(true);
946            layout.setChildrenDrawingCacheEnabled(true);
947        }
948    }
949
950    void clearChildrenCache() {
951        final int screenCount = getChildCount();
952        for (int i = 0; i < screenCount; i++) {
953            final CellLayout layout = (CellLayout) getChildAt(i);
954            layout.setChildrenDrawnWithCacheEnabled(false);
955        }
956    }
957
958    @Override
959    public boolean onTouchEvent(MotionEvent ev) {
960
961        if (mLauncher.isWorkspaceLocked()) {
962            return false; // We don't want the events.  Let them fall through to the all apps view.
963        }
964        if (mLauncher.isAllAppsVisible()) {
965            // Cancel any scrolling that is in progress.
966            if (!mScroller.isFinished()) {
967                mScroller.abortAnimation();
968            }
969            snapToScreen(mCurrentScreen);
970            return false; // We don't want the events.  Let them fall through to the all apps view.
971        }
972
973        if (mVelocityTracker == null) {
974            mVelocityTracker = VelocityTracker.obtain();
975        }
976        mVelocityTracker.addMovement(ev);
977
978        final int action = ev.getAction();
979
980        switch (action & MotionEvent.ACTION_MASK) {
981        case MotionEvent.ACTION_DOWN:
982            /*
983             * If being flinged and user touches, stop the fling. isFinished
984             * will be false if being flinged.
985             */
986            if (!mScroller.isFinished()) {
987                mScroller.abortAnimation();
988            }
989
990            // Remember where the motion event started
991            mLastMotionX = ev.getX();
992            mActivePointerId = ev.getPointerId(0);
993            if (mTouchState == TOUCH_STATE_SCROLLING) {
994                enableChildrenCache(mCurrentScreen - 1, mCurrentScreen + 1);
995            }
996            break;
997        case MotionEvent.ACTION_MOVE:
998            if (mTouchState == TOUCH_STATE_SCROLLING) {
999                // Scroll to follow the motion event
1000                final int pointerIndex = ev.findPointerIndex(mActivePointerId);
1001                final float x = ev.getX(pointerIndex);
1002                final float deltaX = mLastMotionX - x;
1003                mLastMotionX = x;
1004
1005                if (deltaX < 0) {
1006                    if (mTouchX > 0) {
1007                        mTouchX += Math.max(-mTouchX, deltaX);
1008                        mSmoothingTime = System.nanoTime() / NANOTIME_DIV;
1009                        invalidate();
1010                    }
1011                } else if (deltaX > 0) {
1012                    final float availableToScroll = getChildAt(getChildCount() - 1).getRight() -
1013                            mTouchX - getWidth();
1014                    if (availableToScroll > 0) {
1015                        mTouchX += Math.min(availableToScroll, deltaX);
1016                        mSmoothingTime = System.nanoTime() / NANOTIME_DIV;
1017                        invalidate();
1018                    }
1019                } else {
1020                    awakenScrollBars();
1021                }
1022            }
1023            break;
1024        case MotionEvent.ACTION_UP:
1025            if (mTouchState == TOUCH_STATE_SCROLLING) {
1026                final VelocityTracker velocityTracker = mVelocityTracker;
1027                velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
1028                final int velocityX = (int) velocityTracker.getXVelocity(mActivePointerId);
1029
1030                final int screenWidth = getWidth();
1031                final int whichScreen = (mScrollX + (screenWidth / 2)) / screenWidth;
1032                final float scrolledPos = (float) mScrollX / screenWidth;
1033
1034                if (velocityX > SNAP_VELOCITY && mCurrentScreen > 0) {
1035                    // Fling hard enough to move left.
1036                    // Don't fling across more than one screen at a time.
1037                    final int bound = scrolledPos < whichScreen ?
1038                            mCurrentScreen - 1 : mCurrentScreen;
1039                    snapToScreen(Math.min(whichScreen, bound), velocityX, true);
1040                } else if (velocityX < -SNAP_VELOCITY && mCurrentScreen < getChildCount() - 1) {
1041                    // Fling hard enough to move right
1042                    // Don't fling across more than one screen at a time.
1043                    final int bound = scrolledPos > whichScreen ?
1044                            mCurrentScreen + 1 : mCurrentScreen;
1045                    snapToScreen(Math.max(whichScreen, bound), velocityX, true);
1046                } else {
1047                    snapToScreen(whichScreen, 0, true);
1048                }
1049
1050                if (mVelocityTracker != null) {
1051                    mVelocityTracker.recycle();
1052                    mVelocityTracker = null;
1053                }
1054            }
1055            mTouchState = TOUCH_STATE_REST;
1056            mActivePointerId = INVALID_POINTER;
1057            break;
1058        case MotionEvent.ACTION_CANCEL:
1059            mTouchState = TOUCH_STATE_REST;
1060            mActivePointerId = INVALID_POINTER;
1061            break;
1062        case MotionEvent.ACTION_POINTER_UP:
1063            onSecondaryPointerUp(ev);
1064            break;
1065        }
1066
1067        return true;
1068    }
1069
1070    public boolean isSmall() {
1071        return mIsSmall;
1072    }
1073
1074    void shrinkToTop() {
1075        shrink(SHRINK_TO_TOP, true);
1076    }
1077
1078    void shrinkToMiddle() {
1079        shrink(SHRINK_TO_MIDDLE, true);
1080    }
1081
1082    void shrinkToBottom() {
1083        shrinkToBottom(true);
1084    }
1085
1086    void shrinkToBottom(boolean animated) {
1087        if (mFirstLayout) {
1088            // (mFirstLayout == "first layout has not happened yet")
1089            // if we get a call to shrink() as part of our initialization (for example, if
1090            // Launcher is started in All Apps mode) then we need to wait for a layout call
1091            // to get our width so we can layout the mini-screen views correctly
1092            mWaitingToShrinkToBottom = true;
1093        } else {
1094            shrink(SHRINK_TO_BOTTOM, animated);
1095        }
1096    }
1097
1098    // we use this to shrink the workspace for the all apps view and the customize view
1099    private void shrink(int shrinkPosition, boolean animated) {
1100        mIsSmall = true;
1101        final Resources res = getResources();
1102        final int screenWidth = getWidth();
1103        final int screenHeight = getHeight();
1104        final int scaledScreenWidth = (int) (SHRINK_FACTOR * screenWidth);
1105        final int scaledScreenHeight = (int) (SHRINK_FACTOR * screenHeight);
1106        final float scaledSpacing = res.getDimension(R.dimen.smallScreenSpacing);
1107
1108        final int screenCount = getChildCount();
1109        float totalWidth = screenCount * scaledScreenWidth + (screenCount - 1) * scaledSpacing;
1110
1111        float newY = getResources().getDimension(R.dimen.smallScreenVerticalMargin);
1112        if (shrinkPosition == SHRINK_TO_BOTTOM) {
1113            newY = screenHeight - newY - scaledScreenHeight;
1114        } else if (shrinkPosition == SHRINK_TO_MIDDLE) {
1115            newY = screenHeight / 2 - scaledScreenHeight / 2;
1116        }
1117
1118        // We animate all the screens to the centered position in workspace
1119        // At the same time, the screens become greyed/dimmed
1120
1121        // newX is initialized to the left-most position of the centered screens
1122        float newX = (mCurrentScreen + 1) * screenWidth - screenWidth / 2 - totalWidth / 2;
1123        Sequencer s = new Sequencer();
1124        for (int i = 0; i < screenCount; i++) {
1125            CellLayout cl = (CellLayout) getChildAt(i);
1126            cl.setPivotX(0.0f);
1127            cl.setPivotY(0.0f);
1128            if (animated) {
1129                final int duration = res.getInteger(R.integer.config_workspaceShrinkTime);
1130                s.playTogether(
1131                        new PropertyAnimator(duration, cl, "x", newX),
1132                        new PropertyAnimator(duration, cl, "y", newY),
1133                        new PropertyAnimator(duration, cl, "scaleX", SHRINK_FACTOR),
1134                        new PropertyAnimator(duration, cl, "scaleY", SHRINK_FACTOR),
1135                        new PropertyAnimator(duration, cl, "dimmedBitmapAlpha", 1.0f));
1136            } else {
1137                cl.setX((int)newX);
1138                cl.setY((int)newY);
1139                cl.setScaleX(SHRINK_FACTOR);
1140                cl.setScaleY(SHRINK_FACTOR);
1141                cl.setDimmedBitmapAlpha(1.0f);
1142            }
1143            // increment newX for the next screen
1144            newX += scaledScreenWidth + scaledSpacing;
1145            cl.setOnInterceptTouchListener(this);
1146        }
1147        setChildrenDrawnWithCacheEnabled(true);
1148        if (animated) s.start();
1149    }
1150
1151    // We call this when we trigger an unshrink by clicking on the CellLayout cl
1152    private void unshrink(CellLayout clThatWasClicked) {
1153        int newCurrentScreen = mCurrentScreen;
1154        final int screenCount = getChildCount();
1155        for (int i = 0; i < screenCount; i++) {
1156            if (getChildAt(i) == clThatWasClicked) {
1157                newCurrentScreen = i;
1158            }
1159        }
1160        unshrink(newCurrentScreen);
1161    }
1162
1163    private void unshrink(int newCurrentScreen) {
1164        if (mIsSmall) {
1165            int delta = (newCurrentScreen - mCurrentScreen)*getWidth();
1166
1167            final int screenCount = getChildCount();
1168            for (int i = 0; i < screenCount; i++) {
1169                CellLayout cl = (CellLayout) getChildAt(i);
1170                cl.setX(cl.getX() + delta);
1171            }
1172            mScrollX = newCurrentScreen * getWidth();
1173
1174            unshrink();
1175            setCurrentScreen(newCurrentScreen);
1176        }
1177    }
1178
1179    void unshrink() {
1180        unshrink(true);
1181    }
1182
1183    void unshrink(boolean animated) {
1184        if (mIsSmall) {
1185            Sequencer s = new Sequencer();
1186            final int screenCount = getChildCount();
1187
1188            final int duration = getResources().getInteger(R.integer.config_workspaceUnshrinkTime);
1189            for (int i = 0; i < screenCount; i++) {
1190                final CellLayout cl = (CellLayout)getChildAt(i);
1191                cl.setPivotX(0.0f);
1192                cl.setPivotY(0.0f);
1193                if (animated) {
1194                    s.playTogether(
1195                            new PropertyAnimator(duration, cl, "translationX", 0.0f),
1196                            new PropertyAnimator(duration, cl, "translationY", 0.0f),
1197                            new PropertyAnimator(duration, cl, "scaleX", 1.0f),
1198                            new PropertyAnimator(duration, cl, "scaleY", 1.0f),
1199                            new PropertyAnimator(duration, cl, "dimmedBitmapAlpha", 0.0f));
1200                } else {
1201                    cl.setTranslationX(0.0f);
1202                    cl.setTranslationY(0.0f);
1203                    cl.setScaleX(1.0f);
1204                    cl.setScaleY(1.0f);
1205                    cl.setDimmedBitmapAlpha(0.0f);
1206                }
1207            }
1208            s.addListener(mUnshrinkAnimationListener);
1209            s.start();
1210        }
1211    }
1212
1213    void snapToScreen(int whichScreen) {
1214        snapToScreen(whichScreen, 0, false);
1215    }
1216
1217    private void snapToScreen(int whichScreen, int velocity, boolean settle) {
1218        // if (!mScroller.isFinished()) return;
1219
1220        whichScreen = Math.max(0, Math.min(whichScreen, getChildCount() - 1));
1221
1222        enableChildrenCache(mCurrentScreen, whichScreen);
1223
1224        mNextScreen = whichScreen;
1225
1226        if (mPreviousIndicator != null) {
1227            mPreviousIndicator.setLevel(mNextScreen);
1228            mNextIndicator.setLevel(mNextScreen);
1229        }
1230
1231        View focusedChild = getFocusedChild();
1232        if (focusedChild != null && whichScreen != mCurrentScreen &&
1233                focusedChild == getChildAt(mCurrentScreen)) {
1234            focusedChild.clearFocus();
1235        }
1236
1237        final int screenDelta = Math.max(1, Math.abs(whichScreen - mCurrentScreen));
1238        final int newX = whichScreen * getWidth();
1239        final int delta = newX - mScrollX;
1240        int duration = (screenDelta + 1) * 100;
1241
1242        if (!mScroller.isFinished()) {
1243            mScroller.abortAnimation();
1244        }
1245
1246        if (settle) {
1247            mScrollInterpolator.setDistance(screenDelta);
1248        } else {
1249            mScrollInterpolator.disableSettle();
1250        }
1251
1252        velocity = Math.abs(velocity);
1253        if (velocity > 0) {
1254            duration += (duration / (velocity / BASELINE_FLING_VELOCITY))
1255                    * FLING_VELOCITY_INFLUENCE;
1256        } else {
1257            duration += 100;
1258        }
1259
1260        awakenScrollBars(duration);
1261        mScroller.startScroll(mScrollX, 0, delta, 0, duration);
1262        invalidate();
1263    }
1264
1265    void startDrag(CellLayout.CellInfo cellInfo) {
1266        View child = cellInfo.cell;
1267
1268        // Make sure the drag was started by a long press as opposed to a long click.
1269        if (!child.isInTouchMode()) {
1270            return;
1271        }
1272
1273        mDragInfo = cellInfo;
1274        mDragInfo.screen = mCurrentScreen;
1275
1276        CellLayout current = ((CellLayout) getChildAt(mCurrentScreen));
1277
1278        current.onDragChild(child);
1279        mDragController.startDrag(child, this, child.getTag(), DragController.DRAG_ACTION_MOVE);
1280        invalidate();
1281    }
1282
1283    @Override
1284    protected Parcelable onSaveInstanceState() {
1285        final SavedState state = new SavedState(super.onSaveInstanceState());
1286        state.currentScreen = mCurrentScreen;
1287        return state;
1288    }
1289
1290    @Override
1291    protected void onRestoreInstanceState(Parcelable state) {
1292        SavedState savedState = (SavedState) state;
1293        super.onRestoreInstanceState(savedState.getSuperState());
1294        if (savedState.currentScreen != -1) {
1295            setCurrentScreen(savedState.currentScreen, false);
1296            Launcher.setScreen(mCurrentScreen);
1297        }
1298    }
1299
1300    void addApplicationShortcut(ShortcutInfo info, CellLayout.CellInfo cellInfo) {
1301        addApplicationShortcut(info, cellInfo, false);
1302    }
1303
1304    void addApplicationShortcut(ShortcutInfo info, CellLayout.CellInfo cellInfo,
1305            boolean insertAtFirst) {
1306        final CellLayout layout = (CellLayout) getChildAt(cellInfo.screen);
1307        final int[] result = new int[2];
1308
1309        layout.cellToPoint(cellInfo.cellX, cellInfo.cellY, result);
1310        onDropExternal(result[0], result[1], info, layout, insertAtFirst);
1311    }
1312
1313    public void onDrop(DragSource source, int x, int y, int xOffset, int yOffset,
1314            DragView dragView, Object dragInfo) {
1315        CellLayout cellLayout = getCurrentDropLayout();
1316        int originX = x - xOffset;
1317        int originY = y - yOffset;
1318        if (mIsSmall) {
1319            // find out which target layout is over
1320            final float[] localXY = mTempDragCoordinates;
1321            localXY[0] = originX;
1322            localXY[1] = originY;
1323            final float[] localBottomRightXY = mTempDragBottomRightCoordinates;
1324            // we need to subtract left/top here because DragController already adds
1325            // dragRegionLeft/Top to xOffset and yOffset
1326            localBottomRightXY[0] = originX + dragView.getDragRegionWidth();
1327            localBottomRightXY[1] = originY + dragView.getDragRegionHeight();
1328            cellLayout =  findMatchingScreenForDragOver(localXY, localBottomRightXY);
1329            if (cellLayout == null) {
1330                // cancel the drag if we're not over a mini-screen at time of drop
1331                // TODO: maybe add a nice fade here?
1332                return;
1333            }
1334            // localXY will be transformed into the local screen's coordinate space; save that info
1335            originX = (int)localXY[0];
1336            originY = (int)localXY[1];
1337        }
1338        if (source != this) {
1339            onDropExternal(originX, originY, dragInfo, cellLayout);
1340        } else {
1341            // Move internally
1342            if (mDragInfo != null) {
1343                final View cell = mDragInfo.cell;
1344                int index = mScroller.isFinished() ? mCurrentScreen : mNextScreen;
1345                if (index != mDragInfo.screen) {
1346                    final CellLayout originalCellLayout = (CellLayout) getChildAt(mDragInfo.screen);
1347                    originalCellLayout.removeView(cell);
1348                    addInScreen(cell, index, mDragInfo.cellX, mDragInfo.cellY,
1349                            mDragInfo.spanX, mDragInfo.spanY);
1350                }
1351
1352                mTargetCell = estimateDropCell(originX, originY,
1353                        mDragInfo.spanX, mDragInfo.spanY, cell, cellLayout,
1354                        mTargetCell);
1355                cellLayout.onDropChild(cell);
1356
1357                // update the item's position after drop
1358                final ItemInfo info = (ItemInfo) cell.getTag();
1359                CellLayout.LayoutParams lp = (CellLayout.LayoutParams) cell
1360                        .getLayoutParams();
1361                lp.cellX = mTargetCell[0];
1362                lp.cellY = mTargetCell[1];
1363
1364                LauncherModel.moveItemInDatabase(mLauncher, info,
1365                        LauncherSettings.Favorites.CONTAINER_DESKTOP, index,
1366                        lp.cellX, lp.cellY);
1367            }
1368        }
1369    }
1370
1371    public void onDragEnter(DragSource source, int x, int y, int xOffset,
1372            int yOffset, DragView dragView, Object dragInfo) {
1373    }
1374
1375    public DropTarget getDropTargetDelegate(DragSource source, int x, int y, int xOffset, int yOffset,
1376            DragView dragView, Object dragInfo) {
1377
1378        // We may need to delegate the drag to a child view. If a 1x1 item
1379        // would land in a cell occupied by a DragTarget (e.g. a Folder),
1380        // then drag events should be handled by that child.
1381
1382        ItemInfo item = (ItemInfo)dragInfo;
1383        CellLayout currentLayout = getCurrentDropLayout();
1384
1385        int dragPointX, dragPointY;
1386        if (item.spanX == 1 && item.spanY == 1) {
1387            // For a 1x1, calculate the drop cell exactly as in onDragOver
1388            dragPointX = x - xOffset;
1389            dragPointY = y - yOffset;
1390        } else {
1391            // Otherwise, use the exact drag coordinates
1392            dragPointX = x;
1393            dragPointY = y;
1394        }
1395
1396        // If we are dragging over a cell that contains a DropTarget that will
1397        // accept the drop, delegate to that DropTarget.
1398        final int[] cellXY = mTempCell;
1399        currentLayout.estimateDropCell(dragPointX, dragPointY, item.spanX, item.spanY, cellXY);
1400        View child = currentLayout.getChildAt(cellXY[0], cellXY[1]);
1401        if (child instanceof DropTarget) {
1402            DropTarget target = (DropTarget)child;
1403            if (target.acceptDrop(source, x, y, xOffset, yOffset, dragView, dragInfo)) {
1404                return target;
1405            }
1406        }
1407        return null;
1408    }
1409
1410    // xy = upper left corner of item being dragged
1411    // bottomRightXy = lower right corner of item being dragged
1412    // This method will see which mini-screen is most overlapped by the item being dragged, and
1413    // return it. It will also transform the parameters xy and bottomRightXy into the local
1414    // coordinate space of the returned screen
1415    private CellLayout findMatchingScreenForDragOver(float[] xy, float[] bottomRightXy) {
1416        float x = xy[0];
1417        float y = xy[1];
1418        float right = bottomRightXy[0];
1419        float bottom = bottomRightXy[1];
1420
1421        float bestX = 0;
1422        float bestY = 0;
1423        float bestRight = 0;
1424        float bestBottom = 0;
1425
1426        Matrix inverseMatrix = new Matrix();
1427
1428        // We loop through all the screens (ie CellLayouts) and see which one overlaps the most
1429        // with the item being dragged.
1430        final int screenCount = getChildCount();
1431        CellLayout bestMatchingScreen = null;
1432        float bestOverlapSoFar = 0;
1433        for (int i = 0; i < screenCount; i++) {
1434            CellLayout cl = (CellLayout)getChildAt(i);
1435            // Transform the coordinates of the item being dragged to the CellLayout's coordinates
1436            float left = cl.getLeft();
1437            float top = cl.getTop();
1438            xy[0] = x + mScrollX - left;
1439            xy[1] = y + mScrollY - top;
1440            cl.getMatrix().invert(inverseMatrix);
1441
1442            bottomRightXy[0] = right + mScrollX - left;
1443            bottomRightXy[1] = bottom + mScrollY - top;
1444
1445            inverseMatrix.mapPoints(xy);
1446            inverseMatrix.mapPoints(bottomRightXy);
1447
1448            float dragRegionX = xy[0];
1449            float dragRegionY = xy[1];
1450            float dragRegionRight = bottomRightXy[0];
1451            float dragRegionBottom = bottomRightXy[1];
1452
1453            // Find the overlapping region
1454            float overlapLeft = Math.max(0f, dragRegionX);
1455            float overlapTop = Math.max(0f, dragRegionY);
1456            float overlapBottom = Math.min(cl.getHeight(), dragRegionBottom);
1457            float overlapRight = Math.min(cl.getWidth(), dragRegionRight);
1458
1459            if (overlapRight >= 0 && overlapLeft <= cl.getWidth() &&
1460                    overlapTop >= 0 && overlapBottom <= cl.getHeight()) {
1461                // Calculate the size of the overlapping region
1462                float overlap = (overlapRight - overlapLeft) * (overlapBottom - overlapTop);
1463                if (overlap > bestOverlapSoFar) {
1464                    bestOverlapSoFar = overlap;
1465                    bestMatchingScreen = cl;
1466                    bestX = xy[0];
1467                    bestY = xy[1];
1468                    bestRight = bottomRightXy[0];
1469                    bestBottom = bottomRightXy[1];
1470                }
1471             }
1472        }
1473        if (bestMatchingScreen != null && bestMatchingScreen != mDragTargetLayout) {
1474            if (mDragTargetLayout != null) {
1475                mDragTargetLayout.onDragComplete();
1476            }
1477            mDragTargetLayout = bestMatchingScreen;
1478        }
1479        xy[0] = bestX;
1480        xy[1] = bestY;
1481        bottomRightXy[0] = bestRight;
1482        bottomRightXy[1] = bestBottom;
1483        return bestMatchingScreen;
1484    }
1485
1486    public void onDragOver(DragSource source, int x, int y, int xOffset, int yOffset,
1487            DragView dragView, Object dragInfo) {
1488
1489        final ItemInfo item = (ItemInfo)dragInfo;
1490        CellLayout currentLayout = getCurrentDropLayout();
1491
1492        if (dragInfo instanceof LauncherAppWidgetInfo) {
1493            LauncherAppWidgetInfo widgetInfo = (LauncherAppWidgetInfo)dragInfo;
1494
1495            if (widgetInfo.spanX == -1) {
1496                // Calculate the grid spans needed to fit this widget
1497                int[] spans = currentLayout.rectToCell(widgetInfo.minWidth, widgetInfo.minHeight, null);
1498                item.spanX = spans[0];
1499                item.spanY = spans[1];
1500            }
1501        }
1502        int originX = x - xOffset;
1503        int originY = y - yOffset;
1504        if (mIsSmall) {
1505            // find out which mini screen the dragged item is over
1506            final float[] localXY = mTempDragCoordinates;
1507            localXY[0] = originX;
1508            localXY[1] = originY;
1509            final float[] localBottomRightXY = mTempDragBottomRightCoordinates;
1510
1511            localBottomRightXY[0] = originX + dragView.getDragRegionWidth();
1512            localBottomRightXY[1] = originY + dragView.getDragRegionHeight();
1513            currentLayout = findMatchingScreenForDragOver(localXY, localBottomRightXY);
1514            if (currentLayout != null) {
1515                currentLayout.setHover(true);
1516            }
1517
1518            originX = (int)localXY[0];
1519            originY = (int)localXY[1];
1520        }
1521
1522        if (source != this) {
1523            // This is a hack to fix the point used to determine which cell an icon from the all
1524            // apps screen is over
1525            if (item != null && item.spanX == 1 && currentLayout != null) {
1526                int dragRegionLeft = (dragView.getWidth() - currentLayout.getCellWidth()) / 2;
1527
1528                originX += dragRegionLeft - dragView.getDragRegionLeft();
1529                if (dragView.getDragRegionWidth() != currentLayout.getCellWidth()) {
1530                    dragView.setDragRegion(dragView.getDragRegionLeft(), dragView.getDragRegionTop(),
1531                            currentLayout.getCellWidth(), dragView.getDragRegionHeight());
1532                }
1533            }
1534        }
1535        if (currentLayout != mDragTargetLayout) {
1536            if (mDragTargetLayout != null) {
1537                mDragTargetLayout.onDragComplete();
1538            }
1539            mDragTargetLayout = currentLayout;
1540        }
1541
1542        // only visualize the drop locations for moving icons within the home screen on tablet
1543        // on phone, we also visualize icons dragged in from All Apps
1544        if ((!LauncherApplication.isScreenXLarge() || source == this)
1545                && mDragTargetLayout != null) {
1546            final View child = (mDragInfo == null) ? null : mDragInfo.cell;
1547            mDragTargetLayout.visualizeDropLocation(
1548                    child, originX, originY, item.spanX, item.spanY);
1549        }
1550    }
1551
1552    public void onDragExit(DragSource source, int x, int y, int xOffset,
1553            int yOffset, DragView dragView, Object dragInfo) {
1554        if (mDragTargetLayout != null) {
1555            mDragTargetLayout.onDragComplete();
1556            mDragTargetLayout = null;
1557        }
1558    }
1559
1560    private void onDropExternal(int x, int y, Object dragInfo,
1561            CellLayout cellLayout) {
1562        onDropExternal(x, y, dragInfo, cellLayout, false);
1563    }
1564
1565    private void onDropExternal(int x, int y, Object dragInfo,
1566            CellLayout cellLayout, boolean insertAtFirst) {
1567        // Drag from somewhere else
1568        ItemInfo info = (ItemInfo) dragInfo;
1569
1570        View view = null;
1571
1572        switch (info.itemType) {
1573        case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
1574        case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
1575            if (info.container == NO_ID && info instanceof ApplicationInfo) {
1576                // Came from all apps -- make a copy
1577                info = new ShortcutInfo((ApplicationInfo) info);
1578            }
1579            view = mLauncher.createShortcut(R.layout.application, cellLayout,
1580                    (ShortcutInfo) info);
1581            break;
1582        case LauncherSettings.Favorites.ITEM_TYPE_USER_FOLDER:
1583            view = FolderIcon.fromXml(R.layout.folder_icon, mLauncher,
1584                    (ViewGroup) getChildAt(mCurrentScreen),
1585                    ((UserFolderInfo) info));
1586            break;
1587        case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
1588            cellLayout.setTagToCellInfoForPoint(x, y);
1589            int[] position = new int[2];
1590            position[0] = x;
1591            position[1] = y;
1592            mLauncher.addAppWidgetFromDrop(((LauncherAppWidgetInfo)dragInfo).providerName,
1593                    cellLayout.getTag(), position);
1594            break;
1595        default:
1596            throw new IllegalStateException("Unknown item type: "
1597                    + info.itemType);
1598        }
1599
1600        // If the view is null, it has already been added.
1601        if (view == null) {
1602            cellLayout.onDragComplete();
1603        } else {
1604            mTargetCell = estimateDropCell(x, y, 1, 1, view, cellLayout, mTargetCell);
1605            addInScreen(view, indexOfChild(cellLayout), mTargetCell[0],
1606                    mTargetCell[1], info.spanX, info.spanY, insertAtFirst);
1607            cellLayout.onDropChild(view);
1608            CellLayout.LayoutParams lp = (CellLayout.LayoutParams) view.getLayoutParams();
1609
1610            LauncherModel.addOrMoveItemInDatabase(mLauncher, info,
1611                    LauncherSettings.Favorites.CONTAINER_DESKTOP, mCurrentScreen,
1612                    lp.cellX, lp.cellY);
1613        }
1614    }
1615
1616    /**
1617     * Return the current {@link CellLayout}, correctly picking the destination
1618     * screen while a scroll is in progress.
1619     */
1620    private CellLayout getCurrentDropLayout() {
1621        int index = mScroller.isFinished() ? mCurrentScreen : mNextScreen;
1622        return (CellLayout) getChildAt(index);
1623    }
1624
1625    /**
1626     * {@inheritDoc}
1627     */
1628    public boolean acceptDrop(DragSource source, int x, int y,
1629            int xOffset, int yOffset, DragView dragView, Object dragInfo) {
1630        final CellLayout layout = getCurrentDropLayout();
1631        final CellLayout.CellInfo dragCellInfo = mDragInfo;
1632        final int spanX = dragCellInfo == null ? 1 : dragCellInfo.spanX;
1633        final int spanY = dragCellInfo == null ? 1 : dragCellInfo.spanY;
1634
1635        final View ignoreView = dragCellInfo == null ? null : dragCellInfo.cell;
1636        final CellLayout.CellInfo cellInfo = layout.updateOccupiedCells(null, ignoreView);
1637
1638        if (cellInfo.findCellForSpan(mTempEstimate, spanX, spanY)) {
1639            return true;
1640        } else {
1641            Toast.makeText(getContext(), getContext().getString(R.string.out_of_space), Toast.LENGTH_SHORT).show();
1642            return false;
1643        }
1644    }
1645
1646    /**
1647     * {@inheritDoc}
1648     */
1649    public Rect estimateDropLocation(DragSource source, int x, int y,
1650            int xOffset, int yOffset, DragView dragView, Object dragInfo, Rect recycle) {
1651        final CellLayout layout = getCurrentDropLayout();
1652
1653        final CellLayout.CellInfo cellInfo = mDragInfo;
1654        final int spanX = cellInfo == null ? 1 : cellInfo.spanX;
1655        final int spanY = cellInfo == null ? 1 : cellInfo.spanY;
1656        final View ignoreView = cellInfo == null ? null : cellInfo.cell;
1657
1658        final Rect location = recycle != null ? recycle : new Rect();
1659
1660        // Find drop cell and convert into rectangle
1661        int[] dropCell = estimateDropCell(x - xOffset, y - yOffset, spanX,
1662                spanY, ignoreView, layout, mTempCell);
1663
1664        if (dropCell == null) {
1665            return null;
1666        }
1667
1668        layout.cellToPoint(dropCell[0], dropCell[1], mTempEstimate);
1669        location.left = mTempEstimate[0];
1670        location.top = mTempEstimate[1];
1671
1672        layout.cellToPoint(dropCell[0] + spanX, dropCell[1] + spanY, mTempEstimate);
1673        location.right = mTempEstimate[0];
1674        location.bottom = mTempEstimate[1];
1675
1676        return location;
1677    }
1678
1679    /**
1680     * Calculate the nearest cell where the given object would be dropped.
1681     */
1682    private int[] estimateDropCell(int pixelX, int pixelY,
1683            int spanX, int spanY, View ignoreView, CellLayout layout, int[] recycle) {
1684
1685        final int[] cellXY = mTempCell;
1686        layout.estimateDropCell(pixelX, pixelY, spanX, spanY, cellXY);
1687        layout.cellToPoint(cellXY[0], cellXY[1], mTempEstimate);
1688
1689        final CellLayout.CellInfo cellInfo = layout.updateOccupiedCells(null, ignoreView);
1690        // Find the best target drop location
1691        return layout.findNearestVacantArea(mTempEstimate[0], mTempEstimate[1], spanX, spanY, cellInfo, recycle);
1692    }
1693
1694    /**
1695     * Estimate the size that a child with the given dimensions will take in the current screen.
1696     */
1697    void estimateChildSize(int minWidth, int minHeight, int[] result) {
1698        ((CellLayout)getChildAt(mCurrentScreen)).estimateChildSize(minWidth, minHeight, result);
1699    }
1700
1701    void setLauncher(Launcher launcher) {
1702        mLauncher = launcher;
1703    }
1704
1705    public void setDragController(DragController dragController) {
1706        mDragController = dragController;
1707    }
1708
1709    public void onDropCompleted(View target, boolean success) {
1710        if (success) {
1711            if (target != this && mDragInfo != null) {
1712                final CellLayout cellLayout = (CellLayout) getChildAt(mDragInfo.screen);
1713                cellLayout.removeView(mDragInfo.cell);
1714                if (mDragInfo.cell instanceof DropTarget) {
1715                    mDragController.removeDropTarget((DropTarget)mDragInfo.cell);
1716                }
1717                // final Object tag = mDragInfo.cell.getTag();
1718            }
1719        } else {
1720            if (mDragInfo != null) {
1721                final CellLayout cellLayout = (CellLayout) getChildAt(mDragInfo.screen);
1722                cellLayout.onDropAborted(mDragInfo.cell);
1723            }
1724        }
1725
1726        mDragInfo = null;
1727    }
1728
1729    public void scrollLeft() {
1730        if (mScroller.isFinished()) {
1731            if (mCurrentScreen > 0)
1732                snapToScreen(mCurrentScreen - 1);
1733        } else {
1734            if (mNextScreen > 0)
1735                snapToScreen(mNextScreen - 1);
1736        }
1737    }
1738
1739    public void scrollRight() {
1740        if (mScroller.isFinished()) {
1741            if (mCurrentScreen < getChildCount() - 1)
1742                snapToScreen(mCurrentScreen + 1);
1743        } else {
1744            if (mNextScreen < getChildCount() - 1)
1745                snapToScreen(mNextScreen + 1);
1746        }
1747    }
1748
1749    public int getScreenForView(View v) {
1750        int result = -1;
1751        if (v != null) {
1752            ViewParent vp = v.getParent();
1753            final int screenCount = getChildCount();
1754            for (int i = 0; i < screenCount; i++) {
1755                if (vp == getChildAt(i)) {
1756                    return i;
1757                }
1758            }
1759        }
1760        return result;
1761    }
1762
1763    public Folder getFolderForTag(Object tag) {
1764        final int screenCount = getChildCount();
1765        for (int screen = 0; screen < screenCount; screen++) {
1766            CellLayout currentScreen = ((CellLayout) getChildAt(screen));
1767            int count = currentScreen.getChildCount();
1768            for (int i = 0; i < count; i++) {
1769                View child = currentScreen.getChildAt(i);
1770                CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
1771                if (lp.cellHSpan == 4 && lp.cellVSpan == 4 && child instanceof Folder) {
1772                    Folder f = (Folder) child;
1773                    if (f.getInfo() == tag && f.getInfo().opened) {
1774                        return f;
1775                    }
1776                }
1777            }
1778        }
1779        return null;
1780    }
1781
1782    public View getViewForTag(Object tag) {
1783        int screenCount = getChildCount();
1784        for (int screen = 0; screen < screenCount; screen++) {
1785            CellLayout currentScreen = ((CellLayout) getChildAt(screen));
1786            int count = currentScreen.getChildCount();
1787            for (int i = 0; i < count; i++) {
1788                View child = currentScreen.getChildAt(i);
1789                if (child.getTag() == tag) {
1790                    return child;
1791                }
1792            }
1793        }
1794        return null;
1795    }
1796
1797    /**
1798     * @return True is long presses are still allowed for the current touch
1799     */
1800    public boolean allowLongPress() {
1801        return mAllowLongPress;
1802    }
1803
1804    /**
1805     * Set true to allow long-press events to be triggered, usually checked by
1806     * {@link Launcher} to accept or block dpad-initiated long-presses.
1807     */
1808    public void setAllowLongPress(boolean allowLongPress) {
1809        mAllowLongPress = allowLongPress;
1810    }
1811
1812    void removeItems(final ArrayList<ApplicationInfo> apps) {
1813        final int screenCount = getChildCount();
1814        final PackageManager manager = getContext().getPackageManager();
1815        final AppWidgetManager widgets = AppWidgetManager.getInstance(getContext());
1816
1817        final HashSet<String> packageNames = new HashSet<String>();
1818        final int appCount = apps.size();
1819        for (int i = 0; i < appCount; i++) {
1820            packageNames.add(apps.get(i).componentName.getPackageName());
1821        }
1822
1823        for (int i = 0; i < screenCount; i++) {
1824            final CellLayout layout = (CellLayout) getChildAt(i);
1825
1826            // Avoid ANRs by treating each screen separately
1827            post(new Runnable() {
1828                public void run() {
1829                    final ArrayList<View> childrenToRemove = new ArrayList<View>();
1830                    childrenToRemove.clear();
1831
1832                    int childCount = layout.getChildCount();
1833                    for (int j = 0; j < childCount; j++) {
1834                        final View view = layout.getChildAt(j);
1835                        Object tag = view.getTag();
1836
1837                        if (tag instanceof ShortcutInfo) {
1838                            final ShortcutInfo info = (ShortcutInfo) tag;
1839                            final Intent intent = info.intent;
1840                            final ComponentName name = intent.getComponent();
1841
1842                            if (Intent.ACTION_MAIN.equals(intent.getAction()) && name != null) {
1843                                for (String packageName: packageNames) {
1844                                    if (packageName.equals(name.getPackageName())) {
1845                                        // TODO: This should probably be done on a worker thread
1846                                        LauncherModel.deleteItemFromDatabase(mLauncher, info);
1847                                        childrenToRemove.add(view);
1848                                    }
1849                                }
1850                            }
1851                        } else if (tag instanceof UserFolderInfo) {
1852                            final UserFolderInfo info = (UserFolderInfo) tag;
1853                            final ArrayList<ShortcutInfo> contents = info.contents;
1854                            final ArrayList<ShortcutInfo> toRemove = new ArrayList<ShortcutInfo>(1);
1855                            final int contentsCount = contents.size();
1856                            boolean removedFromFolder = false;
1857
1858                            for (int k = 0; k < contentsCount; k++) {
1859                                final ShortcutInfo appInfo = contents.get(k);
1860                                final Intent intent = appInfo.intent;
1861                                final ComponentName name = intent.getComponent();
1862
1863                                if (Intent.ACTION_MAIN.equals(intent.getAction()) && name != null) {
1864                                    for (String packageName: packageNames) {
1865                                        if (packageName.equals(name.getPackageName())) {
1866                                            toRemove.add(appInfo);
1867                                            // TODO: This should probably be done on a worker thread
1868                                            LauncherModel.deleteItemFromDatabase(
1869                                                    mLauncher, appInfo);
1870                                            removedFromFolder = true;
1871                                        }
1872                                    }
1873                                }
1874                            }
1875
1876                            contents.removeAll(toRemove);
1877                            if (removedFromFolder) {
1878                                final Folder folder = getOpenFolder();
1879                                if (folder != null)
1880                                    folder.notifyDataSetChanged();
1881                            }
1882                        } else if (tag instanceof LiveFolderInfo) {
1883                            final LiveFolderInfo info = (LiveFolderInfo) tag;
1884                            final Uri uri = info.uri;
1885                            final ProviderInfo providerInfo = manager.resolveContentProvider(
1886                                    uri.getAuthority(), 0);
1887
1888                            if (providerInfo != null) {
1889                                for (String packageName: packageNames) {
1890                                    if (packageName.equals(providerInfo.packageName)) {
1891                                        // TODO: This should probably be done on a worker thread
1892                                        LauncherModel.deleteItemFromDatabase(mLauncher, info);
1893                                        childrenToRemove.add(view);
1894                                    }
1895                                }
1896                            }
1897                        } else if (tag instanceof LauncherAppWidgetInfo) {
1898                            final LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) tag;
1899                            final AppWidgetProviderInfo provider =
1900                                    widgets.getAppWidgetInfo(info.appWidgetId);
1901                            if (provider != null) {
1902                                for (String packageName: packageNames) {
1903                                    if (packageName.equals(provider.provider.getPackageName())) {
1904                                        // TODO: This should probably be done on a worker thread
1905                                        LauncherModel.deleteItemFromDatabase(mLauncher, info);
1906                                        childrenToRemove.add(view);
1907                                    }
1908                                }
1909                            }
1910                        }
1911                    }
1912
1913                    childCount = childrenToRemove.size();
1914                    for (int j = 0; j < childCount; j++) {
1915                        View child = childrenToRemove.get(j);
1916                        layout.removeViewInLayout(child);
1917                        if (child instanceof DropTarget) {
1918                            mDragController.removeDropTarget((DropTarget)child);
1919                        }
1920                    }
1921
1922                    if (childCount > 0) {
1923                        layout.requestLayout();
1924                        layout.invalidate();
1925                    }
1926                }
1927            });
1928        }
1929    }
1930
1931    void updateShortcuts(ArrayList<ApplicationInfo> apps) {
1932        final PackageManager pm = mLauncher.getPackageManager();
1933
1934        final int screenCount = getChildCount();
1935        for (int i = 0; i < screenCount; i++) {
1936            final CellLayout layout = (CellLayout) getChildAt(i);
1937            int childCount = layout.getChildCount();
1938            for (int j = 0; j < childCount; j++) {
1939                final View view = layout.getChildAt(j);
1940                Object tag = view.getTag();
1941                if (tag instanceof ShortcutInfo) {
1942                    ShortcutInfo info = (ShortcutInfo)tag;
1943                    // We need to check for ACTION_MAIN otherwise getComponent() might
1944                    // return null for some shortcuts (for instance, for shortcuts to
1945                    // web pages.)
1946                    final Intent intent = info.intent;
1947                    final ComponentName name = intent.getComponent();
1948                    if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION &&
1949                            Intent.ACTION_MAIN.equals(intent.getAction()) && name != null) {
1950                        final int appCount = apps.size();
1951                        for (int k = 0; k < appCount; k++) {
1952                            ApplicationInfo app = apps.get(k);
1953                            if (app.componentName.equals(name)) {
1954                                info.setIcon(mIconCache.getIcon(info.intent));
1955                                ((TextView)view).setCompoundDrawablesWithIntrinsicBounds(null,
1956                                        new FastBitmapDrawable(info.getIcon(mIconCache)),
1957                                        null, null);
1958                                }
1959                        }
1960                    }
1961                }
1962            }
1963        }
1964    }
1965
1966    void moveToDefaultScreen(boolean animate) {
1967        if (animate) {
1968            if (mIsSmall) {
1969                unshrink(mDefaultScreen);
1970            } else {
1971                snapToScreen(mDefaultScreen);
1972            }
1973        } else {
1974            setCurrentScreen(mDefaultScreen);
1975        }
1976        getChildAt(mDefaultScreen).requestFocus();
1977    }
1978
1979    void setIndicators(Drawable previous, Drawable next) {
1980        mPreviousIndicator = previous;
1981        mNextIndicator = next;
1982        previous.setLevel(mCurrentScreen);
1983        next.setLevel(mCurrentScreen);
1984    }
1985
1986    public static class SavedState extends BaseSavedState {
1987        int currentScreen = -1;
1988
1989        SavedState(Parcelable superState) {
1990            super(superState);
1991        }
1992
1993        private SavedState(Parcel in) {
1994            super(in);
1995            currentScreen = in.readInt();
1996        }
1997
1998        @Override
1999        public void writeToParcel(Parcel out, int flags) {
2000            super.writeToParcel(out, flags);
2001            out.writeInt(currentScreen);
2002        }
2003
2004        public static final Parcelable.Creator<SavedState> CREATOR =
2005                new Parcelable.Creator<SavedState>() {
2006            public SavedState createFromParcel(Parcel in) {
2007                return new SavedState(in);
2008            }
2009
2010            public SavedState[] newArray(int size) {
2011                return new SavedState[size];
2012            }
2013        };
2014    }
2015}
2016