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