Workspace.java revision d22015cd37ea6ef53762eca5be57daca123ff607
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.LayoutParams;
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.TypedArray;
35import android.graphics.Canvas;
36import android.graphics.Paint;
37import android.graphics.Rect;
38import android.graphics.drawable.Drawable;
39import android.net.Uri;
40import android.os.IBinder;
41import android.os.Parcel;
42import android.os.Parcelable;
43import android.util.AttributeSet;
44import android.util.Log;
45import android.view.MotionEvent;
46import android.view.VelocityTracker;
47import android.view.View;
48import android.view.ViewConfiguration;
49import android.view.ViewDebug;
50import android.view.ViewGroup;
51import android.view.ViewParent;
52import android.view.animation.Animation;
53import android.view.animation.AnimationUtils;
54import android.view.animation.Interpolator;
55import android.view.animation.RotateAnimation;
56import android.view.animation.Animation.AnimationListener;
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        final int screenCount = getChildCount();
638        if (mFirstLayout) {
639            final int width = getWidth();
640            for (int i = 0; i < screenCount; i++) {
641                getChildAt(i).setX(i*width);
642            }
643            mFirstLayout = false;
644        }
645
646        for (int i = 0; i < screenCount; i++) {
647            final View child = getChildAt(i);
648            if (child.getVisibility() != View.GONE) {
649                final int childX = (int)child.getX();
650                final int childY = (int)child.getY();
651                child.layout(childX, childY,
652                        childX + child.getMeasuredWidth(), childY + child.getMeasuredHeight());
653            }
654        }
655
656        // if shrinkToBottom() is called on initialization, it has to be deferred
657        // until after the first call to onLayout so that it has the correct width
658        if (mWaitingToShrinkToBottom) {
659            shrinkToBottom(false);
660            mWaitingToShrinkToBottom = false;
661        }
662
663        if (LauncherApplication.isInPlaceRotationEnabled()) {
664            // When the device is rotated, the scroll position of the current screen
665            // needs to be refreshed
666            setCurrentScreen(getCurrentScreen());
667        }
668    }
669
670    @Override
671    public boolean requestChildRectangleOnScreen(View child, Rect rectangle, boolean immediate) {
672        int screen = indexOfChild(child);
673        if (screen != mCurrentScreen || !mScroller.isFinished()) {
674            if (!mLauncher.isWorkspaceLocked()) {
675                snapToScreen(screen);
676            }
677            return true;
678        }
679        return false;
680    }
681
682    @Override
683    protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
684        if (!mLauncher.isAllAppsVisible()) {
685            final Folder openFolder = getOpenFolder();
686            if (openFolder != null) {
687                return openFolder.requestFocus(direction, previouslyFocusedRect);
688            } else {
689                int focusableScreen;
690                if (mNextScreen != INVALID_SCREEN) {
691                    focusableScreen = mNextScreen;
692                } else {
693                    focusableScreen = mCurrentScreen;
694                }
695                getChildAt(focusableScreen).requestFocus(direction, previouslyFocusedRect);
696            }
697        }
698        return false;
699    }
700
701    @Override
702    public boolean dispatchUnhandledMove(View focused, int direction) {
703        if (direction == View.FOCUS_LEFT) {
704            if (getCurrentScreen() > 0) {
705                snapToScreen(getCurrentScreen() - 1);
706                return true;
707            }
708        } else if (direction == View.FOCUS_RIGHT) {
709            if (getCurrentScreen() < getChildCount() - 1) {
710                snapToScreen(getCurrentScreen() + 1);
711                return true;
712            }
713        }
714        return super.dispatchUnhandledMove(focused, direction);
715    }
716
717    @Override
718    public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
719        if (!mLauncher.isAllAppsVisible()) {
720            final Folder openFolder = getOpenFolder();
721            if (openFolder == null) {
722                getChildAt(mCurrentScreen).addFocusables(views, direction);
723                if (direction == View.FOCUS_LEFT) {
724                    if (mCurrentScreen > 0) {
725                        getChildAt(mCurrentScreen - 1).addFocusables(views, direction);
726                    }
727                } else if (direction == View.FOCUS_RIGHT) {
728                    if (mCurrentScreen < getChildCount() - 1) {
729                        getChildAt(mCurrentScreen + 1).addFocusables(views, direction);
730                    }
731                }
732            } else {
733                openFolder.addFocusables(views, direction);
734            }
735        }
736    }
737
738    @Override
739    public boolean dispatchTouchEvent(MotionEvent ev) {
740        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
741            // (In XLarge mode, the workspace is shrunken below all apps, and responds to taps
742            // ie when you click on a mini-screen, it zooms back to that screen)
743            if (mLauncher.isWorkspaceLocked() ||
744                    (!LauncherApplication.isScreenXLarge() && mLauncher.isAllAppsVisible())) {
745                return false;
746            }
747        }
748        return super.dispatchTouchEvent(ev);
749    }
750
751    @Override
752    public boolean onInterceptTouchEvent(MotionEvent ev) {
753        final boolean workspaceLocked = mLauncher.isWorkspaceLocked();
754        final boolean allAppsVisible = mLauncher.isAllAppsVisible();
755
756        // (In XLarge mode, the workspace is shrunken below all apps, and responds to taps
757        // ie when you click on a mini-screen, it zooms back to that screen)
758        if (workspaceLocked || (!LauncherApplication.isScreenXLarge() && allAppsVisible)) {
759            return false; // We don't want the events.  Let them fall through to the all apps view.
760        }
761
762        /*
763         * This method JUST determines whether we want to intercept the motion.
764         * If we return true, onTouchEvent will be called and we do the actual
765         * scrolling there.
766         */
767
768        /*
769         * Shortcut the most recurring case: the user is in the dragging
770         * state and he is moving his finger.  We want to intercept this
771         * motion.
772         */
773        final int action = ev.getAction();
774        if ((action == MotionEvent.ACTION_MOVE) && (mTouchState != TOUCH_STATE_REST)) {
775            return true;
776        }
777
778        if (mVelocityTracker == null) {
779            mVelocityTracker = VelocityTracker.obtain();
780        }
781        mVelocityTracker.addMovement(ev);
782
783        switch (action & MotionEvent.ACTION_MASK) {
784            case MotionEvent.ACTION_MOVE: {
785                /*
786                 * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
787                 * whether the user has moved far enough from his original down touch.
788                 */
789
790                /*
791                 * Locally do absolute value. mLastMotionX is set to the y value
792                 * of the down event.
793                 */
794                final int pointerIndex = ev.findPointerIndex(mActivePointerId);
795                final float x = ev.getX(pointerIndex);
796                final float y = ev.getY(pointerIndex);
797                final int xDiff = (int) Math.abs(x - mLastMotionX);
798                final int yDiff = (int) Math.abs(y - mLastMotionY);
799
800                final int touchSlop = mTouchSlop;
801                boolean xMoved = xDiff > touchSlop;
802                boolean yMoved = yDiff > touchSlop;
803
804                if (xMoved || yMoved) {
805
806                    if (xMoved) {
807                        // Scroll if the user moved far enough along the X axis
808                        mTouchState = TOUCH_STATE_SCROLLING;
809                        mLastMotionX = x;
810                        mTouchX = mScrollX;
811                        mSmoothingTime = System.nanoTime() / NANOTIME_DIV;
812                        enableChildrenCache(mCurrentScreen - 1, mCurrentScreen + 1);
813                    }
814                    // Either way, cancel any pending longpress
815                    if (mAllowLongPress) {
816                        mAllowLongPress = false;
817                        // Try canceling the long press. It could also have been scheduled
818                        // by a distant descendant, so use the mAllowLongPress flag to block
819                        // everything
820                        final View currentScreen = getChildAt(mCurrentScreen);
821                        currentScreen.cancelLongPress();
822                    }
823                }
824                break;
825            }
826
827        case MotionEvent.ACTION_DOWN: {
828            final float x = ev.getX();
829            final float y = ev.getY();
830            // Remember location of down touch
831            mLastMotionX = x;
832            mLastMotionY = y;
833            mActivePointerId = ev.getPointerId(0);
834            mAllowLongPress = true;
835
836                /*
837                 * If being flinged and user touches the screen, initiate drag;
838                 * otherwise don't.  mScroller.isFinished should be false when
839                 * being flinged.
840                 */
841                mTouchState = mScroller.isFinished() ? TOUCH_STATE_REST : TOUCH_STATE_SCROLLING;
842                break;
843            }
844
845            case MotionEvent.ACTION_CANCEL:
846            case MotionEvent.ACTION_UP:
847
848                if (mTouchState != TOUCH_STATE_SCROLLING) {
849                    final CellLayout currentScreen = (CellLayout)getChildAt(mCurrentScreen);
850                    if (!currentScreen.lastDownOnOccupiedCell()) {
851                        getLocationOnScreen(mTempCell);
852                        // Send a tap to the wallpaper if the last down was on empty space
853                        final int pointerIndex = ev.findPointerIndex(mActivePointerId);
854                        mWallpaperManager.sendWallpaperCommand(getWindowToken(),
855                                "android.wallpaper.tap",
856                                mTempCell[0] + (int) ev.getX(pointerIndex),
857                                mTempCell[1] + (int) ev.getY(pointerIndex), 0, null);
858                    }
859                }
860
861                // Release the drag
862                clearChildrenCache();
863                mTouchState = TOUCH_STATE_REST;
864                mActivePointerId = INVALID_POINTER;
865                mAllowLongPress = false;
866
867                if (mVelocityTracker != null) {
868                    mVelocityTracker.recycle();
869                    mVelocityTracker = null;
870                }
871
872            break;
873
874        case MotionEvent.ACTION_POINTER_UP:
875            onSecondaryPointerUp(ev);
876            break;
877        }
878
879        /*
880         * The only time we want to intercept motion events is if we are in the
881         * drag mode.
882         */
883        return mTouchState != TOUCH_STATE_REST;
884    }
885
886    private void onSecondaryPointerUp(MotionEvent ev) {
887        final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >>
888                MotionEvent.ACTION_POINTER_INDEX_SHIFT;
889        final int pointerId = ev.getPointerId(pointerIndex);
890        if (pointerId == mActivePointerId) {
891            // This was our active pointer going up. Choose a new
892            // active pointer and adjust accordingly.
893            // TODO: Make this decision more intelligent.
894            final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
895            mLastMotionX = ev.getX(newPointerIndex);
896            mLastMotionY = ev.getY(newPointerIndex);
897            mActivePointerId = ev.getPointerId(newPointerIndex);
898            if (mVelocityTracker != null) {
899                mVelocityTracker.clear();
900            }
901        }
902    }
903
904    /**
905     * If one of our descendant views decides that it could be focused now, only
906     * pass that along if it's on the current screen.
907     *
908     * This happens when live folders requery, and if they're off screen, they
909     * end up calling requestFocus, which pulls it on screen.
910     */
911    @Override
912    public void focusableViewAvailable(View focused) {
913        View current = getChildAt(mCurrentScreen);
914        View v = focused;
915        while (true) {
916            if (v == current) {
917                super.focusableViewAvailable(focused);
918                return;
919            }
920            if (v == this) {
921                return;
922            }
923            ViewParent parent = v.getParent();
924            if (parent instanceof View) {
925                v = (View) v.getParent();
926            } else {
927                return;
928            }
929        }
930    }
931
932    void enableChildrenCache(int fromScreen, int toScreen) {
933        if (fromScreen > toScreen) {
934            final int temp = fromScreen;
935            fromScreen = toScreen;
936            toScreen = temp;
937        }
938
939        final int screenCount = getChildCount();
940
941        fromScreen = Math.max(fromScreen, 0);
942        toScreen = Math.min(toScreen, screenCount - 1);
943
944        for (int i = fromScreen; i <= toScreen; i++) {
945            final CellLayout layout = (CellLayout) getChildAt(i);
946            layout.setChildrenDrawnWithCacheEnabled(true);
947            layout.setChildrenDrawingCacheEnabled(true);
948        }
949    }
950
951    void clearChildrenCache() {
952        final int screenCount = getChildCount();
953        for (int i = 0; i < screenCount; i++) {
954            final CellLayout layout = (CellLayout) getChildAt(i);
955            layout.setChildrenDrawnWithCacheEnabled(false);
956        }
957    }
958
959    @Override
960    public boolean onTouchEvent(MotionEvent ev) {
961
962        if (mLauncher.isWorkspaceLocked()) {
963            return false; // We don't want the events.  Let them fall through to the all apps view.
964        }
965        if (mLauncher.isAllAppsVisible()) {
966            // Cancel any scrolling that is in progress.
967            if (!mScroller.isFinished()) {
968                mScroller.abortAnimation();
969            }
970            snapToScreen(mCurrentScreen);
971            return false; // We don't want the events.  Let them fall through to the all apps view.
972        }
973
974        if (mVelocityTracker == null) {
975            mVelocityTracker = VelocityTracker.obtain();
976        }
977        mVelocityTracker.addMovement(ev);
978
979        final int action = ev.getAction();
980
981        switch (action & MotionEvent.ACTION_MASK) {
982        case MotionEvent.ACTION_DOWN:
983            /*
984             * If being flinged and user touches, stop the fling. isFinished
985             * will be false if being flinged.
986             */
987            if (!mScroller.isFinished()) {
988                mScroller.abortAnimation();
989            }
990
991            // Remember where the motion event started
992            mLastMotionX = ev.getX();
993            mActivePointerId = ev.getPointerId(0);
994            if (mTouchState == TOUCH_STATE_SCROLLING) {
995                enableChildrenCache(mCurrentScreen - 1, mCurrentScreen + 1);
996            }
997            break;
998        case MotionEvent.ACTION_MOVE:
999            if (mTouchState == TOUCH_STATE_SCROLLING) {
1000                // Scroll to follow the motion event
1001                final int pointerIndex = ev.findPointerIndex(mActivePointerId);
1002                final float x = ev.getX(pointerIndex);
1003                final float deltaX = mLastMotionX - x;
1004                mLastMotionX = x;
1005
1006                if (deltaX < 0) {
1007                    if (mTouchX > 0) {
1008                        mTouchX += Math.max(-mTouchX, deltaX);
1009                        mSmoothingTime = System.nanoTime() / NANOTIME_DIV;
1010                        invalidate();
1011                    }
1012                } else if (deltaX > 0) {
1013                    final float availableToScroll = getChildAt(getChildCount() - 1).getRight() -
1014                            mTouchX - getWidth();
1015                    if (availableToScroll > 0) {
1016                        mTouchX += Math.min(availableToScroll, deltaX);
1017                        mSmoothingTime = System.nanoTime() / NANOTIME_DIV;
1018                        invalidate();
1019                    }
1020                } else {
1021                    awakenScrollBars();
1022                }
1023            }
1024            break;
1025        case MotionEvent.ACTION_UP:
1026            if (mTouchState == TOUCH_STATE_SCROLLING) {
1027                final VelocityTracker velocityTracker = mVelocityTracker;
1028                velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
1029                final int velocityX = (int) velocityTracker.getXVelocity(mActivePointerId);
1030
1031                final int screenWidth = getWidth();
1032                final int whichScreen = (mScrollX + (screenWidth / 2)) / screenWidth;
1033                final float scrolledPos = (float) mScrollX / screenWidth;
1034
1035                if (velocityX > SNAP_VELOCITY && mCurrentScreen > 0) {
1036                    // Fling hard enough to move left.
1037                    // Don't fling across more than one screen at a time.
1038                    final int bound = scrolledPos < whichScreen ?
1039                            mCurrentScreen - 1 : mCurrentScreen;
1040                    snapToScreen(Math.min(whichScreen, bound), velocityX, true);
1041                } else if (velocityX < -SNAP_VELOCITY && mCurrentScreen < getChildCount() - 1) {
1042                    // Fling hard enough to move right
1043                    // Don't fling across more than one screen at a time.
1044                    final int bound = scrolledPos > whichScreen ?
1045                            mCurrentScreen + 1 : mCurrentScreen;
1046                    snapToScreen(Math.max(whichScreen, bound), velocityX, true);
1047                } else {
1048                    snapToScreen(whichScreen, 0, true);
1049                }
1050
1051                if (mVelocityTracker != null) {
1052                    mVelocityTracker.recycle();
1053                    mVelocityTracker = null;
1054                }
1055            }
1056            mTouchState = TOUCH_STATE_REST;
1057            mActivePointerId = INVALID_POINTER;
1058            break;
1059        case MotionEvent.ACTION_CANCEL:
1060            mTouchState = TOUCH_STATE_REST;
1061            mActivePointerId = INVALID_POINTER;
1062            break;
1063        case MotionEvent.ACTION_POINTER_UP:
1064            onSecondaryPointerUp(ev);
1065            break;
1066        }
1067
1068        return true;
1069    }
1070
1071    void shrinkToTop() {
1072        shrink(true, true);
1073    }
1074
1075    void shrinkToBottom() {
1076        shrinkToBottom(true);
1077    }
1078
1079    void shrinkToBottom(boolean animated) {
1080        if (mFirstLayout) {
1081            // (mFirstLayout == "first layout has not happened yet")
1082            // if we get a call to shrink() as part of our initialization (for example, if
1083            // Launcher is started in All Apps mode) then we need to wait for a layout call
1084            // to get our width so we can layout the mini-screen views correctly
1085            mWaitingToShrinkToBottom = true;
1086        } else {
1087            shrink(false, animated);
1088        }
1089    }
1090
1091    // we use this to shrink the workspace for the all apps view and the customize view
1092    private void shrink(boolean shrinkToTop, boolean animated) {
1093        mIsSmall = true;
1094        final int screenWidth = getWidth();
1095        final int screenHeight = getHeight();
1096        final int scaledScreenWidth = (int)(SHRINK_FACTOR*screenWidth);
1097        final int scaledScreenHeight = (int)(SHRINK_FACTOR*screenHeight);
1098        final float scaledSpacing = getResources().getDimension(R.dimen.smallScreenSpacing);
1099
1100        final int screenCount = getChildCount();
1101        float totalWidth = screenCount * scaledScreenWidth + (screenCount - 1) * scaledSpacing;
1102
1103        float newY = getResources().getDimension(R.dimen.smallScreenVerticalMargin);
1104        if (!shrinkToTop) {
1105            newY = screenHeight - newY - scaledScreenHeight;
1106        }
1107
1108        // We animate all the screens to the centered position in workspace
1109        // At the same time, the screens become greyed/dimmed
1110
1111        // newX is initialized to the left-most position of the centered screens
1112        float newX = (mCurrentScreen + 1) * screenWidth - screenWidth / 2 - totalWidth / 2;
1113        Sequencer s = new Sequencer();
1114        for (int i = 0; i < screenCount; i++) {
1115            CellLayout cl = (CellLayout) getChildAt(i);
1116            if (animated) {
1117                PropertyAnimator translateX = new PropertyAnimator(
1118                        500, cl, "x", cl.getX(), (int) newX);
1119                PropertyAnimator translateY = new PropertyAnimator(
1120                        500, cl, "y", cl.getY(), (int) newY);
1121                PropertyAnimator scaleX = new PropertyAnimator(
1122                        500, cl, "scaleX", cl.getScaleX(), SHRINK_FACTOR);
1123                PropertyAnimator scaleY = new PropertyAnimator(
1124                        500, cl, "scaleY", cl.getScaleY(), SHRINK_FACTOR);
1125                PropertyAnimator alpha = new PropertyAnimator(
1126                        500, cl, "dimmedBitmapAlpha", cl.getDimmedBitmapAlpha(), 1.0f);
1127                Sequencer.Builder b = s.play(translateX);
1128                b.with(translateY);
1129                b.with(scaleX);
1130                b.with(scaleY);
1131                b.with(alpha);
1132            } else {
1133                cl.setX((int)newX);
1134                cl.setY((int)newY);
1135                cl.setScaleX(SHRINK_FACTOR);
1136                cl.setScaleY(SHRINK_FACTOR);
1137                cl.setDimmedBitmapAlpha(1.0f);
1138            }
1139            // increment newX for the next screen
1140            newX += scaledScreenWidth + scaledSpacing;
1141            cl.setOnInterceptTouchListener(this);
1142        }
1143        setChildrenDrawnWithCacheEnabled(true);
1144        s.start();
1145    }
1146
1147    // We call this when we trigger an unshrink by clicking on the CellLayout cl
1148    private void unshrink(CellLayout clThatWasClicked) {
1149        if (mIsSmall) {
1150            int newCurrentScreen = mCurrentScreen;
1151            final int screenCount = getChildCount();
1152            for (int i = 0; i < screenCount; i++) {
1153                if (getChildAt(i) == clThatWasClicked) {
1154                    newCurrentScreen = i;
1155                }
1156            }
1157            final int delta = (newCurrentScreen - mCurrentScreen)*getWidth();
1158            for (int i = 0; i < screenCount; i++) {
1159                CellLayout cl = (CellLayout) getChildAt(i);
1160                cl.setX(cl.getX() + delta);
1161            }
1162            mScrollX = newCurrentScreen * getWidth();
1163
1164            unshrink();
1165            setCurrentScreen(newCurrentScreen);
1166        }
1167    }
1168
1169    public void unshrink() {
1170        if (mIsSmall) {
1171            final int screenWidth = getWidth();
1172            Sequencer s = new Sequencer();
1173            final int screenCount = getChildCount();
1174            for (int i = 0; i < screenCount; i++) {
1175                CellLayout cl = (CellLayout)getChildAt(i);
1176                int x = screenWidth * i;
1177
1178                PropertyAnimator translateX = new PropertyAnimator(500, cl, "x", cl.getX(), x);
1179                PropertyAnimator translateY = new PropertyAnimator(500, cl, "y", cl.getY(), 0);
1180                PropertyAnimator scaleX = new PropertyAnimator(500, cl, "scaleX", cl.getScaleX(), 1.0f);
1181                PropertyAnimator scaleY = new PropertyAnimator(500, cl, "scaleY", cl.getScaleY(), 1.0f);
1182                PropertyAnimator alpha = new PropertyAnimator(
1183                        500, cl, "dimmedBitmapAlpha", cl.getDimmedBitmapAlpha(), 0.0f);
1184                Sequencer.Builder b = s.play(translateX);
1185                b.with(translateY);
1186                b.with(scaleX);
1187                b.with(scaleY);
1188                b.with(alpha);
1189            }
1190            s.addListener(mUnshrinkAnimationListener);
1191            s.start();
1192        }
1193    }
1194
1195    void snapToScreen(int whichScreen) {
1196        snapToScreen(whichScreen, 0, false);
1197    }
1198
1199    private void snapToScreen(int whichScreen, int velocity, boolean settle) {
1200        // if (!mScroller.isFinished()) return;
1201
1202        whichScreen = Math.max(0, Math.min(whichScreen, getChildCount() - 1));
1203
1204        clearVacantCache();
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        clearVacantCache();
1334    }
1335
1336    public DropTarget getDropTargetDelegate(DragSource source, int x, int y, int xOffset, int yOffset,
1337            DragView dragView, Object dragInfo) {
1338
1339        // We may need to delegate the drag to a child view. If a 1x1 item
1340        // would land in a cell occupied by a DragTarget (e.g. a Folder),
1341        // then drag events should be handled by that child.
1342
1343        ItemInfo item = (ItemInfo)dragInfo;
1344        CellLayout currentLayout = getCurrentDropLayout();
1345
1346        int dragPointX, dragPointY;
1347        if (item.spanX == 1 && item.spanY == 1) {
1348            // For a 1x1, calculate the drop cell exactly as in onDragOver
1349            dragPointX = x - xOffset;
1350            dragPointY = y - yOffset;
1351        } else {
1352            // Otherwise, use the exact drag coordinates
1353            dragPointX = x;
1354            dragPointY = y;
1355        }
1356
1357        // If we are dragging over a cell that contains a DropTarget that will
1358        // accept the drop, delegate to that DropTarget.
1359        final int[] cellXY = mTempCell;
1360        currentLayout.estimateDropCell(dragPointX, dragPointY, item.spanX, item.spanY, cellXY);
1361        View child = currentLayout.getChildAt(cellXY[0], cellXY[1]);
1362        if (child instanceof DropTarget) {
1363            DropTarget target = (DropTarget)child;
1364            if (target.acceptDrop(source, x, y, xOffset, yOffset, dragView, dragInfo)) {
1365                return target;
1366            }
1367        }
1368        return null;
1369    }
1370
1371    public void onDragOver(DragSource source, int x, int y, int xOffset, int yOffset,
1372            DragView dragView, Object dragInfo) {
1373
1374        ItemInfo item = (ItemInfo)dragInfo;
1375        CellLayout currentLayout = getCurrentDropLayout();
1376
1377        if (dragInfo instanceof LauncherAppWidgetInfo) {
1378            LauncherAppWidgetInfo widgetInfo = (LauncherAppWidgetInfo)dragInfo;
1379
1380            if (widgetInfo.spanX == -1) {
1381                // Calculate the grid spans needed to fit this widget
1382                int[] spans = currentLayout.rectToCell(widgetInfo.minWidth, widgetInfo.minHeight, null);
1383                item.spanX = spans[0];
1384                item.spanY = spans[1];
1385            }
1386        }
1387        if (currentLayout != mDragTargetLayout) {
1388            if (mDragTargetLayout != null) {
1389                mDragTargetLayout.onDragComplete();
1390            }
1391            mDragTargetLayout = currentLayout;
1392        }
1393
1394        // Find the top left corner of the item
1395        int originX = x - xOffset;
1396        int originY = y - yOffset;
1397
1398        final View child = (mDragInfo == null) ? null : mDragInfo.cell;
1399        currentLayout.visualizeDropLocation(child, originX, originY, item.spanX, item.spanY);
1400    }
1401
1402    public void onDragExit(DragSource source, int x, int y, int xOffset,
1403            int yOffset, DragView dragView, Object dragInfo) {
1404        clearVacantCache();
1405        if (mDragTargetLayout != null) {
1406            mDragTargetLayout.onDragComplete();
1407            mDragTargetLayout = null;
1408        }
1409    }
1410
1411    private void onDropExternal(int x, int y, Object dragInfo,
1412            CellLayout cellLayout) {
1413        onDropExternal(x, y, dragInfo, cellLayout, false);
1414    }
1415
1416    private void onDropExternal(int x, int y, Object dragInfo,
1417            CellLayout cellLayout, boolean insertAtFirst) {
1418        // Drag from somewhere else
1419        ItemInfo info = (ItemInfo) dragInfo;
1420
1421        View view = null;
1422
1423        switch (info.itemType) {
1424        case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
1425        case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
1426            if (info.container == NO_ID && info instanceof ApplicationInfo) {
1427                // Came from all apps -- make a copy
1428                info = new ShortcutInfo((ApplicationInfo) info);
1429            }
1430            view = mLauncher.createShortcut(R.layout.application, cellLayout,
1431                    (ShortcutInfo) info);
1432            break;
1433        case LauncherSettings.Favorites.ITEM_TYPE_USER_FOLDER:
1434            view = FolderIcon.fromXml(R.layout.folder_icon, mLauncher,
1435                    (ViewGroup) getChildAt(mCurrentScreen),
1436                    ((UserFolderInfo) info));
1437            break;
1438        case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
1439            cellLayout.setTagToCellInfoForPoint(x, y);
1440            mLauncher.addAppWidgetFromDrop(((LauncherAppWidgetInfo)dragInfo).providerName, cellLayout.getTag());
1441            break;
1442        default:
1443            throw new IllegalStateException("Unknown item type: "
1444                    + info.itemType);
1445        }
1446
1447        // If the view is null, it has already been added.
1448        if (view == null) {
1449            cellLayout.onDragComplete();
1450        } else {
1451            mTargetCell = estimateDropCell(x, y, 1, 1, view, cellLayout, mTargetCell);
1452            addInScreen(view, indexOfChild(cellLayout), mTargetCell[0],
1453                    mTargetCell[1], info.spanX, info.spanY, insertAtFirst);
1454            cellLayout.onDropChild(view);
1455            CellLayout.LayoutParams lp = (CellLayout.LayoutParams) view.getLayoutParams();
1456
1457            LauncherModel.addOrMoveItemInDatabase(mLauncher, info,
1458                    LauncherSettings.Favorites.CONTAINER_DESKTOP, mCurrentScreen,
1459                    lp.cellX, lp.cellY);
1460        }
1461    }
1462
1463    /**
1464     * Return the current {@link CellLayout}, correctly picking the destination
1465     * screen while a scroll is in progress.
1466     */
1467    private CellLayout getCurrentDropLayout() {
1468        int index = mScroller.isFinished() ? mCurrentScreen : mNextScreen;
1469        return (CellLayout) getChildAt(index);
1470    }
1471
1472    /**
1473     * {@inheritDoc}
1474     */
1475    public boolean acceptDrop(DragSource source, int x, int y,
1476            int xOffset, int yOffset, DragView dragView, Object dragInfo) {
1477        final CellLayout layout = getCurrentDropLayout();
1478        final CellLayout.CellInfo cellInfo = mDragInfo;
1479        final int spanX = cellInfo == null ? 1 : cellInfo.spanX;
1480        final int spanY = cellInfo == null ? 1 : cellInfo.spanY;
1481
1482        if (mVacantCache == null) {
1483            final View ignoreView = cellInfo == null ? null : cellInfo.cell;
1484            mVacantCache = layout.findAllVacantCells(null, ignoreView);
1485        }
1486
1487        if (mVacantCache.findCellForSpan(mTempEstimate, spanX, spanY, false)) {
1488            return true;
1489        } else {
1490            Toast.makeText(getContext(), getContext().getString(R.string.out_of_space), Toast.LENGTH_SHORT).show();
1491            return false;
1492        }
1493    }
1494
1495    /**
1496     * {@inheritDoc}
1497     */
1498    public Rect estimateDropLocation(DragSource source, int x, int y,
1499            int xOffset, int yOffset, DragView dragView, Object dragInfo, Rect recycle) {
1500        final CellLayout layout = getCurrentDropLayout();
1501
1502        final CellLayout.CellInfo cellInfo = mDragInfo;
1503        final int spanX = cellInfo == null ? 1 : cellInfo.spanX;
1504        final int spanY = cellInfo == null ? 1 : cellInfo.spanY;
1505        final View ignoreView = cellInfo == null ? null : cellInfo.cell;
1506
1507        final Rect location = recycle != null ? recycle : new Rect();
1508
1509        // Find drop cell and convert into rectangle
1510        int[] dropCell = estimateDropCell(x - xOffset, y - yOffset, spanX,
1511                spanY, ignoreView, layout, mTempCell);
1512
1513        if (dropCell == null) {
1514            return null;
1515        }
1516
1517        layout.cellToPoint(dropCell[0], dropCell[1], mTempEstimate);
1518        location.left = mTempEstimate[0];
1519        location.top = mTempEstimate[1];
1520
1521        layout.cellToPoint(dropCell[0] + spanX, dropCell[1] + spanY, mTempEstimate);
1522        location.right = mTempEstimate[0];
1523        location.bottom = mTempEstimate[1];
1524
1525        return location;
1526    }
1527
1528    /**
1529     * Calculate the nearest cell where the given object would be dropped.
1530     */
1531    private int[] estimateDropCell(int pixelX, int pixelY,
1532            int spanX, int spanY, View ignoreView, CellLayout layout, int[] recycle) {
1533
1534        final int[] cellXY = mTempCell;
1535        layout.estimateDropCell(pixelX, pixelY, spanX, spanY, cellXY);
1536        layout.cellToPoint(cellXY[0], cellXY[1], mTempEstimate);
1537
1538        // Create vacant cell cache if none exists
1539        if (mVacantCache == null) {
1540            mVacantCache = layout.findAllVacantCells(null, ignoreView);
1541        }
1542
1543        // Find the best target drop location
1544        return layout.findNearestVacantArea(mTempEstimate[0], mTempEstimate[1], spanX, spanY, mVacantCache, recycle);
1545    }
1546
1547    /**
1548     * Estimate the size that a child with the given dimensions will take in the current screen.
1549     */
1550    void estimateChildSize(int minWidth, int minHeight, int[] result) {
1551        ((CellLayout)getChildAt(mCurrentScreen)).estimateChildSize(minWidth, minHeight, result);
1552    }
1553
1554    void setLauncher(Launcher launcher) {
1555        mLauncher = launcher;
1556    }
1557
1558    public void setDragController(DragController dragController) {
1559        mDragController = dragController;
1560    }
1561
1562    public void onDropCompleted(View target, boolean success) {
1563        clearVacantCache();
1564
1565        if (success) {
1566            if (target != this && mDragInfo != null) {
1567                final CellLayout cellLayout = (CellLayout) getChildAt(mDragInfo.screen);
1568                cellLayout.removeView(mDragInfo.cell);
1569                if (mDragInfo.cell instanceof DropTarget) {
1570                    mDragController.removeDropTarget((DropTarget)mDragInfo.cell);
1571                }
1572                // final Object tag = mDragInfo.cell.getTag();
1573            }
1574        } else {
1575            if (mDragInfo != null) {
1576                final CellLayout cellLayout = (CellLayout) getChildAt(mDragInfo.screen);
1577                cellLayout.onDropAborted(mDragInfo.cell);
1578            }
1579        }
1580
1581        mDragInfo = null;
1582    }
1583
1584    public void scrollLeft() {
1585        clearVacantCache();
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        clearVacantCache();
1597        if (mScroller.isFinished()) {
1598            if (mCurrentScreen < getChildCount() - 1)
1599                snapToScreen(mCurrentScreen + 1);
1600        } else {
1601            if (mNextScreen < getChildCount() - 1)
1602                snapToScreen(mNextScreen + 1);
1603        }
1604    }
1605
1606    public int getScreenForView(View v) {
1607        int result = -1;
1608        if (v != null) {
1609            ViewParent vp = v.getParent();
1610            final int screenCount = getChildCount();
1611            for (int i = 0; i < screenCount; i++) {
1612                if (vp == getChildAt(i)) {
1613                    return i;
1614                }
1615            }
1616        }
1617        return result;
1618    }
1619
1620    public Folder getFolderForTag(Object tag) {
1621        final int screenCount = getChildCount();
1622        for (int screen = 0; screen < screenCount; screen++) {
1623            CellLayout currentScreen = ((CellLayout) getChildAt(screen));
1624            int count = currentScreen.getChildCount();
1625            for (int i = 0; i < count; i++) {
1626                View child = currentScreen.getChildAt(i);
1627                CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
1628                if (lp.cellHSpan == 4 && lp.cellVSpan == 4 && child instanceof Folder) {
1629                    Folder f = (Folder) child;
1630                    if (f.getInfo() == tag && f.getInfo().opened) {
1631                        return f;
1632                    }
1633                }
1634            }
1635        }
1636        return null;
1637    }
1638
1639    public View getViewForTag(Object tag) {
1640        int screenCount = getChildCount();
1641        for (int screen = 0; screen < screenCount; screen++) {
1642            CellLayout currentScreen = ((CellLayout) getChildAt(screen));
1643            int count = currentScreen.getChildCount();
1644            for (int i = 0; i < count; i++) {
1645                View child = currentScreen.getChildAt(i);
1646                if (child.getTag() == tag) {
1647                    return child;
1648                }
1649            }
1650        }
1651        return null;
1652    }
1653
1654    /**
1655     * @return True is long presses are still allowed for the current touch
1656     */
1657    public boolean allowLongPress() {
1658        return mAllowLongPress;
1659    }
1660
1661    /**
1662     * Set true to allow long-press events to be triggered, usually checked by
1663     * {@link Launcher} to accept or block dpad-initiated long-presses.
1664     */
1665    public void setAllowLongPress(boolean allowLongPress) {
1666        mAllowLongPress = allowLongPress;
1667    }
1668
1669    void removeItems(final ArrayList<ApplicationInfo> apps) {
1670        final int screenCount = getChildCount();
1671        final PackageManager manager = getContext().getPackageManager();
1672        final AppWidgetManager widgets = AppWidgetManager.getInstance(getContext());
1673
1674        final HashSet<String> packageNames = new HashSet<String>();
1675        final int appCount = apps.size();
1676        for (int i = 0; i < appCount; i++) {
1677            packageNames.add(apps.get(i).componentName.getPackageName());
1678        }
1679
1680        for (int i = 0; i < screenCount; i++) {
1681            final CellLayout layout = (CellLayout) getChildAt(i);
1682
1683            // Avoid ANRs by treating each screen separately
1684            post(new Runnable() {
1685                public void run() {
1686                    final ArrayList<View> childrenToRemove = new ArrayList<View>();
1687                    childrenToRemove.clear();
1688
1689                    int childCount = layout.getChildCount();
1690                    for (int j = 0; j < childCount; j++) {
1691                        final View view = layout.getChildAt(j);
1692                        Object tag = view.getTag();
1693
1694                        if (tag instanceof ShortcutInfo) {
1695                            final ShortcutInfo info = (ShortcutInfo) tag;
1696                            final Intent intent = info.intent;
1697                            final ComponentName name = intent.getComponent();
1698
1699                            if (Intent.ACTION_MAIN.equals(intent.getAction()) && name != null) {
1700                                for (String packageName: packageNames) {
1701                                    if (packageName.equals(name.getPackageName())) {
1702                                        // TODO: This should probably be done on a worker thread
1703                                        LauncherModel.deleteItemFromDatabase(mLauncher, info);
1704                                        childrenToRemove.add(view);
1705                                    }
1706                                }
1707                            }
1708                        } else if (tag instanceof UserFolderInfo) {
1709                            final UserFolderInfo info = (UserFolderInfo) tag;
1710                            final ArrayList<ShortcutInfo> contents = info.contents;
1711                            final ArrayList<ShortcutInfo> toRemove = new ArrayList<ShortcutInfo>(1);
1712                            final int contentsCount = contents.size();
1713                            boolean removedFromFolder = false;
1714
1715                            for (int k = 0; k < contentsCount; k++) {
1716                                final ShortcutInfo appInfo = contents.get(k);
1717                                final Intent intent = appInfo.intent;
1718                                final ComponentName name = intent.getComponent();
1719
1720                                if (Intent.ACTION_MAIN.equals(intent.getAction()) && name != null) {
1721                                    for (String packageName: packageNames) {
1722                                        if (packageName.equals(name.getPackageName())) {
1723                                            toRemove.add(appInfo);
1724                                            // TODO: This should probably be done on a worker thread
1725                                            LauncherModel.deleteItemFromDatabase(
1726                                                    mLauncher, appInfo);
1727                                            removedFromFolder = true;
1728                                        }
1729                                    }
1730                                }
1731                            }
1732
1733                            contents.removeAll(toRemove);
1734                            if (removedFromFolder) {
1735                                final Folder folder = getOpenFolder();
1736                                if (folder != null)
1737                                    folder.notifyDataSetChanged();
1738                            }
1739                        } else if (tag instanceof LiveFolderInfo) {
1740                            final LiveFolderInfo info = (LiveFolderInfo) tag;
1741                            final Uri uri = info.uri;
1742                            final ProviderInfo providerInfo = manager.resolveContentProvider(
1743                                    uri.getAuthority(), 0);
1744
1745                            if (providerInfo != null) {
1746                                for (String packageName: packageNames) {
1747                                    if (packageName.equals(providerInfo.packageName)) {
1748                                        // TODO: This should probably be done on a worker thread
1749                                        LauncherModel.deleteItemFromDatabase(mLauncher, info);
1750                                        childrenToRemove.add(view);
1751                                    }
1752                                }
1753                            }
1754                        } else if (tag instanceof LauncherAppWidgetInfo) {
1755                            final LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) tag;
1756                            final AppWidgetProviderInfo provider =
1757                                    widgets.getAppWidgetInfo(info.appWidgetId);
1758                            if (provider != null) {
1759                                for (String packageName: packageNames) {
1760                                    if (packageName.equals(provider.provider.getPackageName())) {
1761                                        // TODO: This should probably be done on a worker thread
1762                                        LauncherModel.deleteItemFromDatabase(mLauncher, info);
1763                                        childrenToRemove.add(view);
1764                                    }
1765                                }
1766                            }
1767                        }
1768                    }
1769
1770                    childCount = childrenToRemove.size();
1771                    for (int j = 0; j < childCount; j++) {
1772                        View child = childrenToRemove.get(j);
1773                        layout.removeViewInLayout(child);
1774                        if (child instanceof DropTarget) {
1775                            mDragController.removeDropTarget((DropTarget)child);
1776                        }
1777                    }
1778
1779                    if (childCount > 0) {
1780                        layout.requestLayout();
1781                        layout.invalidate();
1782                    }
1783                }
1784            });
1785        }
1786    }
1787
1788    void updateShortcuts(ArrayList<ApplicationInfo> apps) {
1789        final PackageManager pm = mLauncher.getPackageManager();
1790
1791        final int screenCount = getChildCount();
1792        for (int i = 0; i < screenCount; i++) {
1793            final CellLayout layout = (CellLayout) getChildAt(i);
1794            int childCount = layout.getChildCount();
1795            for (int j = 0; j < childCount; j++) {
1796                final View view = layout.getChildAt(j);
1797                Object tag = view.getTag();
1798                if (tag instanceof ShortcutInfo) {
1799                    ShortcutInfo info = (ShortcutInfo)tag;
1800                    // We need to check for ACTION_MAIN otherwise getComponent() might
1801                    // return null for some shortcuts (for instance, for shortcuts to
1802                    // web pages.)
1803                    final Intent intent = info.intent;
1804                    final ComponentName name = intent.getComponent();
1805                    if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION &&
1806                            Intent.ACTION_MAIN.equals(intent.getAction()) && name != null) {
1807                        final int appCount = apps.size();
1808                        for (int k = 0; k < appCount; k++) {
1809                            ApplicationInfo app = apps.get(k);
1810                            if (app.componentName.equals(name)) {
1811                                info.setIcon(mIconCache.getIcon(info.intent));
1812                                ((TextView)view).setCompoundDrawablesWithIntrinsicBounds(null,
1813                                        new FastBitmapDrawable(info.getIcon(mIconCache)),
1814                                        null, null);
1815                                }
1816                        }
1817                    }
1818                }
1819            }
1820        }
1821    }
1822
1823    void moveToDefaultScreen(boolean animate) {
1824        if (animate) {
1825            snapToScreen(mDefaultScreen);
1826        } else {
1827            setCurrentScreen(mDefaultScreen);
1828        }
1829        getChildAt(mDefaultScreen).requestFocus();
1830    }
1831
1832    void setIndicators(Drawable previous, Drawable next) {
1833        mPreviousIndicator = previous;
1834        mNextIndicator = next;
1835        previous.setLevel(mCurrentScreen);
1836        next.setLevel(mCurrentScreen);
1837    }
1838
1839    public static class SavedState extends BaseSavedState {
1840        int currentScreen = -1;
1841
1842        SavedState(Parcelable superState) {
1843            super(superState);
1844        }
1845
1846        private SavedState(Parcel in) {
1847            super(in);
1848            currentScreen = in.readInt();
1849        }
1850
1851        @Override
1852        public void writeToParcel(Parcel out, int flags) {
1853            super.writeToParcel(out, flags);
1854            out.writeInt(currentScreen);
1855        }
1856
1857        public static final Parcelable.Creator<SavedState> CREATOR =
1858                new Parcelable.Creator<SavedState>() {
1859            public SavedState createFromParcel(Parcel in) {
1860                return new SavedState(in);
1861            }
1862
1863            public SavedState[] newArray(int size) {
1864                return new SavedState[size];
1865            }
1866        };
1867    }
1868}
1869