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