Workspace.java revision 59d5245b0fb8014fc16d4791fb3a031cc29799aa
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 android.app.WallpaperManager;
20import android.appwidget.AppWidgetManager;
21import android.appwidget.AppWidgetProviderInfo;
22import android.content.Context;
23import android.content.Intent;
24import android.content.ComponentName;
25import android.content.pm.ProviderInfo;
26import android.content.res.TypedArray;
27import android.content.pm.PackageManager;
28import android.graphics.Canvas;
29import android.graphics.Rect;
30import android.graphics.drawable.Drawable;
31import android.net.Uri;
32import android.os.Parcelable;
33import android.os.Parcel;
34import android.util.AttributeSet;
35import android.view.MotionEvent;
36import android.view.VelocityTracker;
37import android.view.View;
38import android.view.ViewConfiguration;
39import android.view.ViewGroup;
40import android.view.ViewParent;
41import android.widget.Scroller;
42import android.widget.TextView;
43
44import java.util.ArrayList;
45
46/**
47 * The workspace is a wide area with a wallpaper and a finite number of screens. Each
48 * screen contains a number of icons, folders or widgets the user can interact with.
49 * A workspace is meant to be used with a fixed width only.
50 */
51public class Workspace extends ViewGroup implements DropTarget, DragSource, DragScroller {
52    @SuppressWarnings({"UnusedDeclaration"})
53    private static final String TAG = "Launcher.Workspace";
54    private static final int INVALID_SCREEN = -1;
55
56    /**
57     * The velocity at which a fling gesture will cause us to snap to the next screen
58     */
59    private static final int SNAP_VELOCITY = 1000;
60
61    private final WallpaperManager mWallpaperManager;
62
63    private int mDefaultScreen;
64
65    private boolean mFirstLayout = true;
66
67    private int mCurrentScreen;
68    private int mNextScreen = INVALID_SCREEN;
69    private Scroller mScroller;
70    private VelocityTracker mVelocityTracker;
71
72    /**
73     * CellInfo for the cell that is currently being dragged
74     */
75    private CellLayout.CellInfo mDragInfo;
76
77    /**
78     * Target drop area calculated during last acceptDrop call.
79     */
80    private int[] mTargetCell = null;
81
82    private float mLastMotionX;
83    private float mLastMotionY;
84
85    private final static int TOUCH_STATE_REST = 0;
86    private final static int TOUCH_STATE_SCROLLING = 1;
87
88    private int mTouchState = TOUCH_STATE_REST;
89
90    private OnLongClickListener mLongClickListener;
91
92    private Launcher mLauncher;
93    private IconCache mIconCache;
94    private DragController mDragController;
95
96    /**
97     * Cache of vacant cells, used during drag events and invalidated as needed.
98     */
99    private CellLayout.CellInfo mVacantCache = null;
100
101    private int[] mTempCell = new int[2];
102    private int[] mTempEstimate = new int[2];
103
104    private boolean mAllowLongPress = true;
105
106    private int mTouchSlop;
107    private int mMaximumVelocity;
108
109    private Drawable mPreviousIndicator;
110    private Drawable mNextIndicator;
111
112    /**
113     * Used to inflate the Workspace from XML.
114     *
115     * @param context The application's context.
116     * @param attrs The attribtues set containing the Workspace's customization values.
117     */
118    public Workspace(Context context, AttributeSet attrs) {
119        this(context, attrs, 0);
120    }
121
122    /**
123     * Used to inflate the Workspace from XML.
124     *
125     * @param context The application's context.
126     * @param attrs The attribtues set containing the Workspace's customization values.
127     * @param defStyle Unused.
128     */
129    public Workspace(Context context, AttributeSet attrs, int defStyle) {
130        super(context, attrs, defStyle);
131
132        mWallpaperManager = WallpaperManager.getInstance(context);
133
134        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Workspace, defStyle, 0);
135        mDefaultScreen = a.getInt(R.styleable.Workspace_defaultScreen, 1);
136        a.recycle();
137
138        setHapticFeedbackEnabled(false);
139        initWorkspace();
140    }
141
142    /**
143     * Initializes various states for this workspace.
144     */
145    private void initWorkspace() {
146        Context context = getContext();
147        mScroller = new Scroller(context);
148        mCurrentScreen = mDefaultScreen;
149        Launcher.setScreen(mCurrentScreen);
150        LauncherApplication app = (LauncherApplication)context.getApplicationContext();
151        mIconCache = app.getIconCache();
152
153        final ViewConfiguration configuration = ViewConfiguration.get(getContext());
154        mTouchSlop = configuration.getScaledTouchSlop();
155        mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
156    }
157
158    @Override
159    public void addView(View child, int index, LayoutParams params) {
160        if (!(child instanceof CellLayout)) {
161            throw new IllegalArgumentException("A Workspace can only have CellLayout children.");
162        }
163        super.addView(child, index, params);
164    }
165
166    @Override
167    public void addView(View child) {
168        if (!(child instanceof CellLayout)) {
169            throw new IllegalArgumentException("A Workspace can only have CellLayout children.");
170        }
171        super.addView(child);
172    }
173
174    @Override
175    public void addView(View child, int index) {
176        if (!(child instanceof CellLayout)) {
177            throw new IllegalArgumentException("A Workspace can only have CellLayout children.");
178        }
179        super.addView(child, index);
180    }
181
182    @Override
183    public void addView(View child, int width, int height) {
184        if (!(child instanceof CellLayout)) {
185            throw new IllegalArgumentException("A Workspace can only have CellLayout children.");
186        }
187        super.addView(child, width, height);
188    }
189
190    @Override
191    public void addView(View child, LayoutParams params) {
192        if (!(child instanceof CellLayout)) {
193            throw new IllegalArgumentException("A Workspace can only have CellLayout children.");
194        }
195        super.addView(child, params);
196    }
197
198    /**
199     * @return The open folder on the current screen, or null if there is none
200     */
201    Folder getOpenFolder() {
202        CellLayout currentScreen = (CellLayout) getChildAt(mCurrentScreen);
203        int count = currentScreen.getChildCount();
204        for (int i = 0; i < count; i++) {
205            View child = currentScreen.getChildAt(i);
206            CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
207            if (lp.cellHSpan == 4 && lp.cellVSpan == 4 && child instanceof Folder) {
208                return (Folder) child;
209            }
210        }
211        return null;
212    }
213
214    ArrayList<Folder> getOpenFolders() {
215        final int screens = getChildCount();
216        ArrayList<Folder> folders = new ArrayList<Folder>(screens);
217
218        for (int screen = 0; screen < screens; screen++) {
219            CellLayout currentScreen = (CellLayout) getChildAt(screen);
220            int count = currentScreen.getChildCount();
221            for (int i = 0; i < count; i++) {
222                View child = currentScreen.getChildAt(i);
223                CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
224                if (lp.cellHSpan == 4 && lp.cellVSpan == 4 && child instanceof Folder) {
225                    folders.add((Folder) child);
226                    break;
227                }
228            }
229        }
230
231        return folders;
232    }
233
234    boolean isDefaultScreenShowing() {
235        return mCurrentScreen == mDefaultScreen;
236    }
237
238    /**
239     * Returns the index of the currently displayed screen.
240     *
241     * @return The index of the currently displayed screen.
242     */
243    int getCurrentScreen() {
244        return mCurrentScreen;
245    }
246
247    /**
248     * Sets the current screen.
249     *
250     * @param currentScreen
251     */
252    void setCurrentScreen(int currentScreen) {
253        if (!mScroller.isFinished()) mScroller.abortAnimation();
254        clearVacantCache();
255        mCurrentScreen = Math.max(0, Math.min(currentScreen, getChildCount() - 1));
256        mPreviousIndicator.setLevel(mCurrentScreen);
257        mNextIndicator.setLevel(mCurrentScreen);
258        scrollTo(mCurrentScreen * getWidth(), 0);
259        invalidate();
260    }
261
262    /**
263     * Adds the specified child in the current screen. The position and dimension of
264     * the child are defined by x, y, spanX and spanY.
265     *
266     * @param child The child to add in one of the workspace's screens.
267     * @param x The X position of the child in the screen's grid.
268     * @param y The Y position of the child in the screen's grid.
269     * @param spanX The number of cells spanned horizontally by the child.
270     * @param spanY The number of cells spanned vertically by the child.
271     */
272    void addInCurrentScreen(View child, int x, int y, int spanX, int spanY) {
273        addInScreen(child, mCurrentScreen, x, y, spanX, spanY, false);
274    }
275
276    /**
277     * Adds the specified child in the current screen. The position and dimension of
278     * the child are defined by x, y, spanX and spanY.
279     *
280     * @param child The child to add in one of the workspace's screens.
281     * @param x The X position of the child in the screen's grid.
282     * @param y The Y position of the child in the screen's grid.
283     * @param spanX The number of cells spanned horizontally by the child.
284     * @param spanY The number of cells spanned vertically by the child.
285     * @param insert When true, the child is inserted at the beginning of the children list.
286     */
287    void addInCurrentScreen(View child, int x, int y, int spanX, int spanY, boolean insert) {
288        addInScreen(child, mCurrentScreen, x, y, spanX, spanY, insert);
289    }
290
291    /**
292     * Adds the specified child in the specified screen. The position and dimension of
293     * the child are defined by x, y, spanX and spanY.
294     *
295     * @param child The child to add in one of the workspace's screens.
296     * @param screen The screen in which to add the child.
297     * @param x The X position of the child in the screen's grid.
298     * @param y The Y position of the child in the screen's grid.
299     * @param spanX The number of cells spanned horizontally by the child.
300     * @param spanY The number of cells spanned vertically by the child.
301     */
302    void addInScreen(View child, int screen, int x, int y, int spanX, int spanY) {
303        addInScreen(child, screen, x, y, spanX, spanY, false);
304    }
305
306    /**
307     * Adds the specified child in the specified screen. The position and dimension of
308     * the child are defined by x, y, spanX and spanY.
309     *
310     * @param child The child to add in one of the workspace's screens.
311     * @param screen The screen in which to add the child.
312     * @param x The X position of the child in the screen's grid.
313     * @param y The Y position of the child in the screen's grid.
314     * @param spanX The number of cells spanned horizontally by the child.
315     * @param spanY The number of cells spanned vertically by the child.
316     * @param insert When true, the child is inserted at the beginning of the children list.
317     */
318    void addInScreen(View child, int screen, int x, int y, int spanX, int spanY, boolean insert) {
319        if (screen < 0 || screen >= getChildCount()) {
320            throw new IllegalStateException("The screen must be >= 0 and < " + getChildCount());
321        }
322
323        clearVacantCache();
324
325        final CellLayout group = (CellLayout) getChildAt(screen);
326        CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
327        if (lp == null) {
328            lp = new CellLayout.LayoutParams(x, y, spanX, spanY);
329        } else {
330            lp.cellX = x;
331            lp.cellY = y;
332            lp.cellHSpan = spanX;
333            lp.cellVSpan = spanY;
334        }
335        group.addView(child, insert ? 0 : -1, lp);
336        if (!(child instanceof Folder)) {
337            child.setHapticFeedbackEnabled(false);
338            child.setOnLongClickListener(mLongClickListener);
339        }
340        if (child instanceof DropTarget) {
341            mDragController.addDropTarget((DropTarget)child);
342        }
343    }
344
345    CellLayout.CellInfo findAllVacantCells(boolean[] occupied) {
346        CellLayout group = (CellLayout) getChildAt(mCurrentScreen);
347        if (group != null) {
348            return group.findAllVacantCells(occupied, null);
349        }
350        return null;
351    }
352
353    private void clearVacantCache() {
354        if (mVacantCache != null) {
355            mVacantCache.clearVacantCells();
356            mVacantCache = null;
357        }
358    }
359
360    /**
361     * Registers the specified listener on each screen contained in this workspace.
362     *
363     * @param l The listener used to respond to long clicks.
364     */
365    @Override
366    public void setOnLongClickListener(OnLongClickListener l) {
367        mLongClickListener = l;
368        final int count = getChildCount();
369        for (int i = 0; i < count; i++) {
370            getChildAt(i).setOnLongClickListener(l);
371        }
372    }
373
374    private void updateWallpaperOffset() {
375        updateWallpaperOffset(getChildAt(getChildCount() - 1).getRight() - (mRight - mLeft));
376    }
377
378    private void updateWallpaperOffset(int scrollRange) {
379        mWallpaperManager.setWallpaperOffsetSteps(1.0f / (getChildCount() - 1), 0 );
380        mWallpaperManager.setWallpaperOffsets(getWindowToken(), mScrollX / (float) scrollRange, 0);
381    }
382
383    @Override
384    public void computeScroll() {
385        if (mScroller.computeScrollOffset()) {
386            mScrollX = mScroller.getCurrX();
387            mScrollY = mScroller.getCurrY();
388            updateWallpaperOffset();
389            postInvalidate();
390        } else if (mNextScreen != INVALID_SCREEN) {
391            mCurrentScreen = Math.max(0, Math.min(mNextScreen, getChildCount() - 1));
392            mPreviousIndicator.setLevel(mCurrentScreen);
393            mNextIndicator.setLevel(mCurrentScreen);
394            Launcher.setScreen(mCurrentScreen);
395            mNextScreen = INVALID_SCREEN;
396            clearChildrenCache();
397        }
398    }
399
400    @Override
401    protected void dispatchDraw(Canvas canvas) {
402        boolean restore = false;
403        int restoreCount = 0;
404
405        // ViewGroup.dispatchDraw() supports many features we don't need:
406        // clip to padding, layout animation, animation listener, disappearing
407        // children, etc. The following implementation attempts to fast-track
408        // the drawing dispatch by drawing only what we know needs to be drawn.
409
410        boolean fastDraw = mTouchState != TOUCH_STATE_SCROLLING && mNextScreen == INVALID_SCREEN;
411        // If we are not scrolling or flinging, draw only the current screen
412        if (fastDraw) {
413            drawChild(canvas, getChildAt(mCurrentScreen), getDrawingTime());
414        } else {
415            final long drawingTime = getDrawingTime();
416            // If we are flinging, draw only the current screen and the target screen
417            if (mNextScreen >= 0 && mNextScreen < getChildCount() &&
418                    Math.abs(mCurrentScreen - mNextScreen) == 1) {
419                drawChild(canvas, getChildAt(mCurrentScreen), drawingTime);
420                drawChild(canvas, getChildAt(mNextScreen), drawingTime);
421            } else {
422                // If we are scrolling, draw all of our children
423                final int count = getChildCount();
424                for (int i = 0; i < count; i++) {
425                    drawChild(canvas, getChildAt(i), drawingTime);
426                }
427            }
428        }
429
430        if (restore) {
431            canvas.restoreToCount(restoreCount);
432        }
433    }
434
435    protected void onAttachedToWindow() {
436        super.onAttachedToWindow();
437        mDragController.setWindowToken(getWindowToken());
438    }
439
440    @Override
441    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
442        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
443
444        final int width = MeasureSpec.getSize(widthMeasureSpec);
445        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
446        if (widthMode != MeasureSpec.EXACTLY) {
447            throw new IllegalStateException("Workspace can only be used in EXACTLY mode.");
448        }
449
450        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
451        if (heightMode != MeasureSpec.EXACTLY) {
452            throw new IllegalStateException("Workspace can only be used in EXACTLY mode.");
453        }
454
455        // The children are given the same width and height as the workspace
456        final int count = getChildCount();
457        for (int i = 0; i < count; i++) {
458            getChildAt(i).measure(widthMeasureSpec, heightMeasureSpec);
459        }
460
461
462        if (mFirstLayout) {
463            setHorizontalScrollBarEnabled(false);
464            scrollTo(mCurrentScreen * width, 0);
465            setHorizontalScrollBarEnabled(true);
466            updateWallpaperOffset(width * (getChildCount() - 1));
467            mFirstLayout = false;
468        }
469    }
470
471    @Override
472    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
473        int childLeft = 0;
474
475        final int count = getChildCount();
476        for (int i = 0; i < count; i++) {
477            final View child = getChildAt(i);
478            if (child.getVisibility() != View.GONE) {
479                final int childWidth = child.getMeasuredWidth();
480                child.layout(childLeft, 0, childLeft + childWidth, child.getMeasuredHeight());
481                childLeft += childWidth;
482            }
483        }
484    }
485
486    @Override
487    public boolean requestChildRectangleOnScreen(View child, Rect rectangle, boolean immediate) {
488        int screen = indexOfChild(child);
489        if (screen != mCurrentScreen || !mScroller.isFinished()) {
490            if (!mLauncher.isWorkspaceLocked()) {
491                snapToScreen(screen);
492            }
493            return true;
494        }
495        return false;
496    }
497
498    @Override
499    protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
500        if (!mLauncher.isAllAppsVisible()) {
501            final Folder openFolder = getOpenFolder();
502            if (openFolder != null) {
503                return openFolder.requestFocus(direction, previouslyFocusedRect);
504            } else {
505                int focusableScreen;
506                if (mNextScreen != INVALID_SCREEN) {
507                    focusableScreen = mNextScreen;
508                } else {
509                    focusableScreen = mCurrentScreen;
510                }
511                getChildAt(focusableScreen).requestFocus(direction, previouslyFocusedRect);
512            }
513        }
514        return false;
515    }
516
517    @Override
518    public boolean dispatchUnhandledMove(View focused, int direction) {
519        if (direction == View.FOCUS_LEFT) {
520            if (getCurrentScreen() > 0) {
521                snapToScreen(getCurrentScreen() - 1);
522                return true;
523            }
524        } else if (direction == View.FOCUS_RIGHT) {
525            if (getCurrentScreen() < getChildCount() - 1) {
526                snapToScreen(getCurrentScreen() + 1);
527                return true;
528            }
529        }
530        return super.dispatchUnhandledMove(focused, direction);
531    }
532
533    @Override
534    public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
535        if (!mLauncher.isAllAppsVisible()) {
536            final Folder openFolder = getOpenFolder();
537            if (openFolder == null) {
538                getChildAt(mCurrentScreen).addFocusables(views, direction);
539                if (direction == View.FOCUS_LEFT) {
540                    if (mCurrentScreen > 0) {
541                        getChildAt(mCurrentScreen - 1).addFocusables(views, direction);
542                    }
543                } else if (direction == View.FOCUS_RIGHT){
544                    if (mCurrentScreen < getChildCount() - 1) {
545                        getChildAt(mCurrentScreen + 1).addFocusables(views, direction);
546                    }
547                }
548            } else {
549                openFolder.addFocusables(views, direction);
550            }
551        }
552    }
553
554    @Override
555    public boolean dispatchTouchEvent(MotionEvent ev) {
556        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
557            if (mLauncher.isWorkspaceLocked() || mLauncher.isAllAppsVisible()) {
558                return false;
559            }
560        }
561        return super.dispatchTouchEvent(ev);
562    }
563
564    @Override
565    public boolean onInterceptTouchEvent(MotionEvent ev) {
566        final boolean workspaceLocked = mLauncher.isWorkspaceLocked();
567        final boolean allAppsVisible = mLauncher.isAllAppsVisible();
568        if (workspaceLocked || allAppsVisible) {
569            return false; // We don't want the events.  Let them fall through to the all apps view.
570        }
571
572        /*
573         * This method JUST determines whether we want to intercept the motion.
574         * If we return true, onTouchEvent will be called and we do the actual
575         * scrolling there.
576         */
577
578        /*
579         * Shortcut the most recurring case: the user is in the dragging
580         * state and he is moving his finger.  We want to intercept this
581         * motion.
582         */
583        final int action = ev.getAction();
584        if ((action == MotionEvent.ACTION_MOVE) && (mTouchState != TOUCH_STATE_REST)) {
585            return true;
586        }
587
588        final float x = ev.getX();
589        final float y = ev.getY();
590
591        switch (action) {
592            case MotionEvent.ACTION_MOVE:
593                /*
594                 * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
595                 * whether the user has moved far enough from his original down touch.
596                 */
597
598                /*
599                 * Locally do absolute value. mLastMotionX is set to the y value
600                 * of the down event.
601                 */
602                final int xDiff = (int) Math.abs(x - mLastMotionX);
603                final int yDiff = (int) Math.abs(y - mLastMotionY);
604
605                final int touchSlop = mTouchSlop;
606                boolean xMoved = xDiff > touchSlop;
607                boolean yMoved = yDiff > touchSlop;
608
609                if (xMoved || yMoved) {
610
611                    if (xMoved) {
612                        // Scroll if the user moved far enough along the X axis
613                        mTouchState = TOUCH_STATE_SCROLLING;
614                        enableChildrenCache(mCurrentScreen - 1, mCurrentScreen + 1);
615                    }
616                    // Either way, cancel any pending longpress
617                    if (mAllowLongPress) {
618                        mAllowLongPress = false;
619                        // Try canceling the long press. It could also have been scheduled
620                        // by a distant descendant, so use the mAllowLongPress flag to block
621                        // everything
622                        final View currentScreen = getChildAt(mCurrentScreen);
623                        currentScreen.cancelLongPress();
624                    }
625                }
626                break;
627
628            case MotionEvent.ACTION_DOWN:
629                // Remember location of down touch
630                mLastMotionX = x;
631                mLastMotionY = y;
632                mAllowLongPress = true;
633
634                /*
635                 * If being flinged and user touches the screen, initiate drag;
636                 * otherwise don't.  mScroller.isFinished should be false when
637                 * being flinged.
638                 */
639                mTouchState = mScroller.isFinished() ? TOUCH_STATE_REST : TOUCH_STATE_SCROLLING;
640                break;
641
642            case MotionEvent.ACTION_CANCEL:
643            case MotionEvent.ACTION_UP:
644
645                if (mTouchState != TOUCH_STATE_SCROLLING) {
646                    final CellLayout currentScreen = (CellLayout)getChildAt(mCurrentScreen);
647                    if (!currentScreen.lastDownOnOccupiedCell()) {
648                        getLocationOnScreen(mTempCell);
649                        // Send a tap to the wallpaper if the last down was on empty space
650                        mWallpaperManager.sendWallpaperCommand(getWindowToken(),
651                                "android.wallpaper.tap",
652                                mTempCell[0] + (int) ev.getX(),
653                                mTempCell[1] + (int) ev.getY(), 0, null);
654                    }
655                }
656
657                // Release the drag
658                clearChildrenCache();
659                mTouchState = TOUCH_STATE_REST;
660                mAllowLongPress = false;
661
662                break;
663        }
664
665        /*
666         * The only time we want to intercept motion events is if we are in the
667         * drag mode.
668         */
669        return mTouchState != TOUCH_STATE_REST;
670    }
671
672    /**
673     * If one of our descendant views decides that it could be focused now, only
674     * pass that along if it's on the current screen.
675     *
676     * This happens when live folders requery, and if they're off screen, they
677     * end up calling requestFocus, which pulls it on screen.
678     */
679    @Override
680    public void focusableViewAvailable(View focused) {
681        View current = getChildAt(mCurrentScreen);
682        View v = focused;
683        while (true) {
684            if (v == current) {
685                super.focusableViewAvailable(focused);
686                return;
687            }
688            if (v == this) {
689                return;
690            }
691            ViewParent parent = v.getParent();
692            if (parent instanceof View) {
693                v = (View)v.getParent();
694            } else {
695                return;
696            }
697        }
698    }
699
700    void enableChildrenCache(int fromScreen, int toScreen) {
701        if (fromScreen > toScreen) {
702            fromScreen = toScreen;
703            toScreen = fromScreen;
704        }
705
706        final int count = getChildCount();
707
708        fromScreen = Math.max(fromScreen, 0);
709        toScreen = Math.min(toScreen, count - 1);
710
711        for (int i = fromScreen; i <= toScreen; i++) {
712            final CellLayout layout = (CellLayout) getChildAt(i);
713            layout.setChildrenDrawnWithCacheEnabled(true);
714            layout.setChildrenDrawingCacheEnabled(true);
715        }
716    }
717
718    void clearChildrenCache() {
719        final int count = getChildCount();
720        for (int i = 0; i < count; i++) {
721            final CellLayout layout = (CellLayout) getChildAt(i);
722            layout.setChildrenDrawnWithCacheEnabled(false);
723        }
724    }
725
726    @Override
727    public boolean onTouchEvent(MotionEvent ev) {
728
729        if (mLauncher.isWorkspaceLocked()) {
730            return false; // We don't want the events.  Let them fall through to the all apps view.
731        }
732        if (mLauncher.isAllAppsVisible()) {
733            // Cancel any scrolling that is in progress.
734            if (!mScroller.isFinished()) {
735                mScroller.abortAnimation();
736            }
737            snapToScreen(mCurrentScreen);
738            return false; // We don't want the events.  Let them fall through to the all apps view.
739        }
740
741        if (mVelocityTracker == null) {
742            mVelocityTracker = VelocityTracker.obtain();
743        }
744        mVelocityTracker.addMovement(ev);
745
746        final int action = ev.getAction();
747        final float x = ev.getX();
748
749        switch (action) {
750        case MotionEvent.ACTION_DOWN:
751            /*
752             * If being flinged and user touches, stop the fling. isFinished
753             * will be false if being flinged.
754             */
755            if (!mScroller.isFinished()) {
756                mScroller.abortAnimation();
757            }
758
759            // Remember where the motion event started
760            mLastMotionX = x;
761            break;
762        case MotionEvent.ACTION_MOVE:
763            if (mTouchState == TOUCH_STATE_SCROLLING) {
764                // Scroll to follow the motion event
765                final int deltaX = (int) (mLastMotionX - x);
766                mLastMotionX = x;
767
768                if (deltaX < 0) {
769                    if (mScrollX > 0) {
770                        scrollBy(Math.max(-mScrollX, deltaX), 0);
771                        updateWallpaperOffset();
772                    }
773                } else if (deltaX > 0) {
774                    final int availableToScroll = getChildAt(getChildCount() - 1).getRight() -
775                            mScrollX - getWidth();
776                    if (availableToScroll > 0) {
777                        scrollBy(Math.min(availableToScroll, deltaX), 0);
778                        updateWallpaperOffset();
779                    }
780                } else {
781                    awakenScrollBars();
782                }
783            }
784            break;
785        case MotionEvent.ACTION_UP:
786            if (mTouchState == TOUCH_STATE_SCROLLING) {
787                final VelocityTracker velocityTracker = mVelocityTracker;
788                velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
789                int velocityX = (int) velocityTracker.getXVelocity();
790
791                if (velocityX > SNAP_VELOCITY && mCurrentScreen > 0) {
792                    // Fling hard enough to move left
793                    snapToScreen(mCurrentScreen - 1);
794                } else if (velocityX < -SNAP_VELOCITY && mCurrentScreen < getChildCount() - 1) {
795                    // Fling hard enough to move right
796                    snapToScreen(mCurrentScreen + 1);
797                } else {
798                    snapToDestination();
799                }
800
801                if (mVelocityTracker != null) {
802                    mVelocityTracker.recycle();
803                    mVelocityTracker = null;
804                }
805            }
806            mTouchState = TOUCH_STATE_REST;
807            break;
808        case MotionEvent.ACTION_CANCEL:
809            mTouchState = TOUCH_STATE_REST;
810        }
811
812        return true;
813    }
814
815    private void snapToDestination() {
816        final int screenWidth = getWidth();
817        final int whichScreen = (mScrollX + (screenWidth / 2)) / screenWidth;
818
819        snapToScreen(whichScreen);
820    }
821
822    void snapToScreen(int whichScreen) {
823        //if (!mScroller.isFinished()) return;
824
825        whichScreen = Math.max(0, Math.min(whichScreen, getChildCount() - 1));
826
827        clearVacantCache();
828        enableChildrenCache(mCurrentScreen, whichScreen);
829
830        final int screenDelta = Math.abs(whichScreen - mCurrentScreen);
831
832        mNextScreen = whichScreen;
833
834        mPreviousIndicator.setLevel(mNextScreen);
835        mNextIndicator.setLevel(mNextScreen);
836
837        View focusedChild = getFocusedChild();
838        if (focusedChild != null && screenDelta != 0 && focusedChild == getChildAt(mCurrentScreen)) {
839            focusedChild.clearFocus();
840        }
841
842        final int newX = whichScreen * getWidth();
843        final int delta = newX - mScrollX;
844        final int duration = screenDelta * 300;
845        awakenScrollBars(duration);
846
847        if (!mScroller.isFinished()) mScroller.abortAnimation();
848        mScroller.startScroll(mScrollX, 0, delta, 0, duration);
849        invalidate();
850    }
851
852    void startDrag(CellLayout.CellInfo cellInfo) {
853        View child = cellInfo.cell;
854
855        // Make sure the drag was started by a long press as opposed to a long click.
856        if (!child.isInTouchMode()) {
857            return;
858        }
859
860        mDragInfo = cellInfo;
861        mDragInfo.screen = mCurrentScreen;
862
863        CellLayout current = ((CellLayout) getChildAt(mCurrentScreen));
864
865        current.onDragChild(child);
866        mDragController.startDrag(child, this, child.getTag(), DragController.DRAG_ACTION_MOVE);
867        invalidate();
868    }
869
870    @Override
871    protected Parcelable onSaveInstanceState() {
872        final SavedState state = new SavedState(super.onSaveInstanceState());
873        state.currentScreen = mCurrentScreen;
874        return state;
875    }
876
877    @Override
878    protected void onRestoreInstanceState(Parcelable state) {
879        SavedState savedState = (SavedState) state;
880        super.onRestoreInstanceState(savedState.getSuperState());
881        if (savedState.currentScreen != -1) {
882            mCurrentScreen = savedState.currentScreen;
883            Launcher.setScreen(mCurrentScreen);
884        }
885    }
886
887    void addApplicationShortcut(ShortcutInfo info, CellLayout.CellInfo cellInfo) {
888        addApplicationShortcut(info, cellInfo, false);
889    }
890
891    void addApplicationShortcut(ShortcutInfo info, CellLayout.CellInfo cellInfo,
892            boolean insertAtFirst) {
893        final CellLayout layout = (CellLayout) getChildAt(cellInfo.screen);
894        final int[] result = new int[2];
895
896        layout.cellToPoint(cellInfo.cellX, cellInfo.cellY, result);
897        onDropExternal(result[0], result[1], info, layout, insertAtFirst);
898    }
899
900    public void onDrop(DragSource source, int x, int y, int xOffset, int yOffset,
901            DragView dragView, Object dragInfo) {
902        final CellLayout cellLayout = getCurrentDropLayout();
903        if (source != this) {
904            onDropExternal(x - xOffset, y - yOffset, dragInfo, cellLayout);
905        } else {
906            // Move internally
907            if (mDragInfo != null) {
908                final View cell = mDragInfo.cell;
909                int index = mScroller.isFinished() ? mCurrentScreen : mNextScreen;
910                if (index != mDragInfo.screen) {
911                    final CellLayout originalCellLayout = (CellLayout) getChildAt(mDragInfo.screen);
912                    originalCellLayout.removeView(cell);
913                    cellLayout.addView(cell);
914                }
915                mTargetCell = estimateDropCell(x - xOffset, y - yOffset,
916                        mDragInfo.spanX, mDragInfo.spanY, cell, cellLayout, mTargetCell);
917                cellLayout.onDropChild(cell, mTargetCell);
918
919                final ItemInfo info = (ItemInfo) cell.getTag();
920                CellLayout.LayoutParams lp = (CellLayout.LayoutParams) cell.getLayoutParams();
921                LauncherModel.moveItemInDatabase(mLauncher, info,
922                        LauncherSettings.Favorites.CONTAINER_DESKTOP, index, lp.cellX, lp.cellY);
923            }
924        }
925    }
926
927    public void onDragEnter(DragSource source, int x, int y, int xOffset, int yOffset,
928            DragView dragView, Object dragInfo) {
929        clearVacantCache();
930    }
931
932    public void onDragOver(DragSource source, int x, int y, int xOffset, int yOffset,
933            DragView dragView, Object dragInfo) {
934    }
935
936    public void onDragExit(DragSource source, int x, int y, int xOffset, int yOffset,
937            DragView dragView, Object dragInfo) {
938        clearVacantCache();
939    }
940
941    private void onDropExternal(int x, int y, Object dragInfo, CellLayout cellLayout) {
942        onDropExternal(x, y, dragInfo, cellLayout, false);
943    }
944
945    private void onDropExternal(int x, int y, Object dragInfo, CellLayout cellLayout,
946            boolean insertAtFirst) {
947        // Drag from somewhere else
948        ItemInfo info = (ItemInfo) dragInfo;
949
950        View view;
951
952        switch (info.itemType) {
953        case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
954        case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
955            if (info.container == NO_ID) {
956                // Came from all apps -- make a copy
957                info = new ShortcutInfo((ApplicationInfo)info);
958            }
959            view = mLauncher.createShortcut(R.layout.application, cellLayout, (ShortcutInfo)info);
960            break;
961        case LauncherSettings.Favorites.ITEM_TYPE_USER_FOLDER:
962            view = FolderIcon.fromXml(R.layout.folder_icon, mLauncher,
963                    (ViewGroup) getChildAt(mCurrentScreen), ((UserFolderInfo) info));
964            break;
965        default:
966            throw new IllegalStateException("Unknown item type: " + info.itemType);
967        }
968
969        cellLayout.addView(view, insertAtFirst ? 0 : -1);
970        view.setHapticFeedbackEnabled(false);
971        view.setOnLongClickListener(mLongClickListener);
972        if (view instanceof DropTarget) {
973            mDragController.addDropTarget((DropTarget) view);
974        }
975
976        mTargetCell = estimateDropCell(x, y, 1, 1, view, cellLayout, mTargetCell);
977        cellLayout.onDropChild(view, mTargetCell);
978        CellLayout.LayoutParams lp = (CellLayout.LayoutParams) view.getLayoutParams();
979
980        LauncherModel.addOrMoveItemInDatabase(mLauncher, info,
981                LauncherSettings.Favorites.CONTAINER_DESKTOP, mCurrentScreen, lp.cellX, lp.cellY);
982    }
983
984    /**
985     * Return the current {@link CellLayout}, correctly picking the destination
986     * screen while a scroll is in progress.
987     */
988    private CellLayout getCurrentDropLayout() {
989        int index = mScroller.isFinished() ? mCurrentScreen : mNextScreen;
990        return (CellLayout) getChildAt(index);
991    }
992
993    /**
994     * {@inheritDoc}
995     */
996    public boolean acceptDrop(DragSource source, int x, int y,
997            int xOffset, int yOffset, DragView dragView, Object dragInfo) {
998        final CellLayout layout = getCurrentDropLayout();
999        final CellLayout.CellInfo cellInfo = mDragInfo;
1000        final int spanX = cellInfo == null ? 1 : cellInfo.spanX;
1001        final int spanY = cellInfo == null ? 1 : cellInfo.spanY;
1002
1003        if (mVacantCache == null) {
1004            final View ignoreView = cellInfo == null ? null : cellInfo.cell;
1005            mVacantCache = layout.findAllVacantCells(null, ignoreView);
1006        }
1007
1008        return mVacantCache.findCellForSpan(mTempEstimate, spanX, spanY, false);
1009    }
1010
1011    /**
1012     * {@inheritDoc}
1013     */
1014    public Rect estimateDropLocation(DragSource source, int x, int y,
1015            int xOffset, int yOffset, DragView dragView, Object dragInfo, Rect recycle) {
1016        final CellLayout layout = getCurrentDropLayout();
1017
1018        final CellLayout.CellInfo cellInfo = mDragInfo;
1019        final int spanX = cellInfo == null ? 1 : cellInfo.spanX;
1020        final int spanY = cellInfo == null ? 1 : cellInfo.spanY;
1021        final View ignoreView = cellInfo == null ? null : cellInfo.cell;
1022
1023        final Rect location = recycle != null ? recycle : new Rect();
1024
1025        // Find drop cell and convert into rectangle
1026        int[] dropCell = estimateDropCell(x - xOffset, y - yOffset,
1027                spanX, spanY, ignoreView, layout, mTempCell);
1028
1029        if (dropCell == null) {
1030            return null;
1031        }
1032
1033        layout.cellToPoint(dropCell[0], dropCell[1], mTempEstimate);
1034        location.left = mTempEstimate[0];
1035        location.top = mTempEstimate[1];
1036
1037        layout.cellToPoint(dropCell[0] + spanX, dropCell[1] + spanY, mTempEstimate);
1038        location.right = mTempEstimate[0];
1039        location.bottom = mTempEstimate[1];
1040
1041        return location;
1042    }
1043
1044    /**
1045     * Calculate the nearest cell where the given object would be dropped.
1046     */
1047    private int[] estimateDropCell(int pixelX, int pixelY,
1048            int spanX, int spanY, View ignoreView, CellLayout layout, int[] recycle) {
1049        // Create vacant cell cache if none exists
1050        if (mVacantCache == null) {
1051            mVacantCache = layout.findAllVacantCells(null, ignoreView);
1052        }
1053
1054        // Find the best target drop location
1055        return layout.findNearestVacantArea(pixelX, pixelY,
1056                spanX, spanY, mVacantCache, recycle);
1057    }
1058
1059    void setLauncher(Launcher launcher) {
1060        mLauncher = launcher;
1061    }
1062
1063    public void setDragController(DragController dragController) {
1064        mDragController = dragController;
1065    }
1066
1067    public void onDropCompleted(View target, boolean success) {
1068        clearVacantCache();
1069
1070        if (success){
1071            if (target != this && mDragInfo != null) {
1072                final CellLayout cellLayout = (CellLayout) getChildAt(mDragInfo.screen);
1073                cellLayout.removeView(mDragInfo.cell);
1074                if (mDragInfo.cell instanceof DropTarget) {
1075                    mDragController.removeDropTarget((DropTarget)mDragInfo.cell);
1076                }
1077                //final Object tag = mDragInfo.cell.getTag();
1078            }
1079        } else {
1080            if (mDragInfo != null) {
1081                final CellLayout cellLayout = (CellLayout) getChildAt(mDragInfo.screen);
1082                cellLayout.onDropAborted(mDragInfo.cell);
1083            }
1084        }
1085
1086        mDragInfo = null;
1087    }
1088
1089    public void scrollLeft() {
1090        clearVacantCache();
1091        if (mScroller.isFinished()) {
1092            if (mCurrentScreen > 0) snapToScreen(mCurrentScreen - 1);
1093        } else {
1094            if (mNextScreen > 0) snapToScreen(mNextScreen - 1);
1095        }
1096    }
1097
1098    public void scrollRight() {
1099        clearVacantCache();
1100        if (mScroller.isFinished()) {
1101            if (mCurrentScreen < getChildCount() -1) snapToScreen(mCurrentScreen + 1);
1102        } else {
1103            if (mNextScreen < getChildCount() -1) snapToScreen(mNextScreen + 1);
1104        }
1105    }
1106
1107    public int getScreenForView(View v) {
1108        int result = -1;
1109        if (v != null) {
1110            ViewParent vp = v.getParent();
1111            int count = getChildCount();
1112            for (int i = 0; i < count; i++) {
1113                if (vp == getChildAt(i)) {
1114                    return i;
1115                }
1116            }
1117        }
1118        return result;
1119    }
1120
1121    public Folder getFolderForTag(Object tag) {
1122        int screenCount = getChildCount();
1123        for (int screen = 0; screen < screenCount; screen++) {
1124            CellLayout currentScreen = ((CellLayout) getChildAt(screen));
1125            int count = currentScreen.getChildCount();
1126            for (int i = 0; i < count; i++) {
1127                View child = currentScreen.getChildAt(i);
1128                CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
1129                if (lp.cellHSpan == 4 && lp.cellVSpan == 4 && child instanceof Folder) {
1130                    Folder f = (Folder) child;
1131                    if (f.getInfo() == tag) {
1132                        return f;
1133                    }
1134                }
1135            }
1136        }
1137        return null;
1138    }
1139
1140    public View getViewForTag(Object tag) {
1141        int screenCount = getChildCount();
1142        for (int screen = 0; screen < screenCount; screen++) {
1143            CellLayout currentScreen = ((CellLayout) getChildAt(screen));
1144            int count = currentScreen.getChildCount();
1145            for (int i = 0; i < count; i++) {
1146                View child = currentScreen.getChildAt(i);
1147                if (child.getTag() == tag) {
1148                    return child;
1149                }
1150            }
1151        }
1152        return null;
1153    }
1154
1155    /**
1156     * @return True is long presses are still allowed for the current touch
1157     */
1158    public boolean allowLongPress() {
1159        return mAllowLongPress;
1160    }
1161
1162    /**
1163     * Set true to allow long-press events to be triggered, usually checked by
1164     * {@link Launcher} to accept or block dpad-initiated long-presses.
1165     */
1166    public void setAllowLongPress(boolean allowLongPress) {
1167        mAllowLongPress = allowLongPress;
1168    }
1169
1170    void removeItemsForPackage(final String packageName) {
1171        final int count = getChildCount();
1172        final PackageManager manager = getContext().getPackageManager();
1173        final AppWidgetManager widgets = AppWidgetManager.getInstance(getContext());
1174
1175        for (int i = 0; i < count; i++) {
1176            final CellLayout layout = (CellLayout) getChildAt(i);
1177
1178            // Avoid ANRs by treating each screen separately
1179            post(new Runnable() {
1180                public void run() {
1181                    final ArrayList<View> childrenToRemove = new ArrayList<View>();
1182                    childrenToRemove.clear();
1183
1184                    int childCount = layout.getChildCount();
1185                    for (int j = 0; j < childCount; j++) {
1186                        final View view = layout.getChildAt(j);
1187                        Object tag = view.getTag();
1188
1189                        if (tag instanceof ShortcutInfo) {
1190                            final ShortcutInfo info = (ShortcutInfo) tag;
1191                            // We need to check for ACTION_MAIN otherwise getComponent() might
1192                            // return null for some shortcuts (for instance, for shortcuts to
1193                            // web pages.)
1194                            final Intent intent = info.intent;
1195                            final ComponentName name = intent.getComponent();
1196
1197                            if (Intent.ACTION_MAIN.equals(intent.getAction()) &&
1198                                    name != null && packageName.equals(name.getPackageName())) {
1199                                // TODO: This should probably be done on a worker thread
1200                                LauncherModel.deleteItemFromDatabase(mLauncher, info);
1201                                childrenToRemove.add(view);
1202                            }
1203                        } else if (tag instanceof UserFolderInfo) {
1204                            final UserFolderInfo info = (UserFolderInfo) tag;
1205                            final ArrayList<ShortcutInfo> contents = info.contents;
1206                            final ArrayList<ShortcutInfo> toRemove = new ArrayList<ShortcutInfo>(1);
1207                            final int contentsCount = contents.size();
1208                            boolean removedFromFolder = false;
1209
1210                            for (int k = 0; k < contentsCount; k++) {
1211                                final ShortcutInfo appInfo = contents.get(k);
1212                                final Intent intent = appInfo.intent;
1213                                final ComponentName name = intent.getComponent();
1214
1215                                if (Intent.ACTION_MAIN.equals(intent.getAction()) &&
1216                                        name != null && packageName.equals(name.getPackageName())) {
1217                                    toRemove.add(appInfo);
1218                                    // TODO: This should probably be done on a worker thread
1219                                    LauncherModel.deleteItemFromDatabase(mLauncher, appInfo);
1220                                    removedFromFolder = true;
1221                                }
1222                            }
1223
1224                            contents.removeAll(toRemove);
1225                            if (removedFromFolder) {
1226                                final Folder folder = getOpenFolder();
1227                                if (folder != null) folder.notifyDataSetChanged();
1228                            }
1229                        } else if (tag instanceof LiveFolderInfo) {
1230                            final LiveFolderInfo info = (LiveFolderInfo) tag;
1231                            final Uri uri = info.uri;
1232                            final ProviderInfo providerInfo = manager.resolveContentProvider(
1233                                    uri.getAuthority(), 0);
1234
1235                            if (providerInfo == null ||
1236                                    packageName.equals(providerInfo.packageName)) {
1237                                // TODO: This should probably be done on a worker thread
1238                                LauncherModel.deleteItemFromDatabase(mLauncher, info);
1239                                childrenToRemove.add(view);
1240                            }
1241                        } else if (tag instanceof LauncherAppWidgetInfo) {
1242                            final LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) tag;
1243                            final AppWidgetProviderInfo provider =
1244                                    widgets.getAppWidgetInfo(info.appWidgetId);
1245                            if (provider == null ||
1246                                    packageName.equals(provider.provider.getPackageName())) {
1247                                // TODO: This should probably be done on a worker thread
1248                                LauncherModel.deleteItemFromDatabase(mLauncher, info);
1249                                childrenToRemove.add(view);
1250                            }
1251                        }
1252                    }
1253
1254                    childCount = childrenToRemove.size();
1255                    for (int j = 0; j < childCount; j++) {
1256                        View child = childrenToRemove.get(j);
1257                        layout.removeViewInLayout(child);
1258                        if (child instanceof DropTarget) {
1259                            mDragController.removeDropTarget((DropTarget)child);
1260                        }
1261                    }
1262
1263                    if (childCount > 0) {
1264                        layout.requestLayout();
1265                        layout.invalidate();
1266                    }
1267                }
1268            });
1269        }
1270    }
1271
1272    void updateShortcutsForPackage(String packageName) {
1273        final PackageManager pm = mLauncher.getPackageManager();
1274
1275        final int count = getChildCount();
1276        for (int i = 0; i < count; i++) {
1277            final CellLayout layout = (CellLayout) getChildAt(i);
1278            int childCount = layout.getChildCount();
1279            for (int j = 0; j < childCount; j++) {
1280                final View view = layout.getChildAt(j);
1281                Object tag = view.getTag();
1282                if (tag instanceof ShortcutInfo) {
1283                    ShortcutInfo info = (ShortcutInfo)tag;
1284                    // We need to check for ACTION_MAIN otherwise getComponent() might
1285                    // return null for some shortcuts (for instance, for shortcuts to
1286                    // web pages.)
1287                    final Intent intent = info.intent;
1288                    final ComponentName name = intent.getComponent();
1289                    if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION &&
1290                            Intent.ACTION_MAIN.equals(intent.getAction()) && name != null &&
1291                            packageName.equals(name.getPackageName())) {
1292
1293                        info.setIcon(mIconCache.getIcon(info.intent));
1294                        ((TextView) view).setCompoundDrawablesWithIntrinsicBounds(null,
1295                                new FastBitmapDrawable(info.getIcon(mIconCache)), null, null);
1296                    }
1297                }
1298            }
1299        }
1300    }
1301
1302    void moveToDefaultScreen(boolean animate) {
1303        if (animate) {
1304            snapToScreen(mDefaultScreen);
1305        } else {
1306            setCurrentScreen(mDefaultScreen);
1307        }
1308        getChildAt(mDefaultScreen).requestFocus();
1309    }
1310
1311    void setIndicators(Drawable previous, Drawable next) {
1312        mPreviousIndicator = previous;
1313        mNextIndicator = next;
1314        previous.setLevel(mCurrentScreen);
1315        next.setLevel(mCurrentScreen);
1316    }
1317
1318    public static class SavedState extends BaseSavedState {
1319        int currentScreen = -1;
1320
1321        SavedState(Parcelable superState) {
1322            super(superState);
1323        }
1324
1325        private SavedState(Parcel in) {
1326            super(in);
1327            currentScreen = in.readInt();
1328        }
1329
1330        @Override
1331        public void writeToParcel(Parcel out, int flags) {
1332            super.writeToParcel(out, flags);
1333            out.writeInt(currentScreen);
1334        }
1335
1336        public static final Parcelable.Creator<SavedState> CREATOR =
1337                new Parcelable.Creator<SavedState>() {
1338            public SavedState createFromParcel(Parcel in) {
1339                return new SavedState(in);
1340            }
1341
1342            public SavedState[] newArray(int size) {
1343                return new SavedState[size];
1344            }
1345        };
1346    }
1347}
1348