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