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