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