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