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