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