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