Workspace.java revision 96226223d9849842bb2a67af051acbae9e0677d5
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 com.android.launcher.R;
20import com.android.launcher2.CellLayout.CellInfo;
21
22import android.animation.Animatable;
23import android.animation.PropertyAnimator;
24import android.animation.Sequencer;
25import android.animation.Animatable.AnimatableListener;
26import android.app.WallpaperManager;
27import android.appwidget.AppWidgetManager;
28import android.appwidget.AppWidgetProviderInfo;
29import android.content.ComponentName;
30import android.content.Context;
31import android.content.Intent;
32import android.content.pm.PackageManager;
33import android.content.pm.ProviderInfo;
34import android.content.res.Resources;
35import android.content.res.TypedArray;
36import android.graphics.Canvas;
37import android.graphics.Paint;
38import android.graphics.Rect;
39import android.graphics.drawable.Drawable;
40import android.net.Uri;
41import android.os.IBinder;
42import android.os.Parcel;
43import android.os.Parcelable;
44import android.util.AttributeSet;
45import android.util.Log;
46import android.view.MotionEvent;
47import android.view.VelocityTracker;
48import android.view.View;
49import android.view.ViewConfiguration;
50import android.view.ViewGroup;
51import android.view.ViewParent;
52import android.view.animation.Interpolator;
53import android.widget.Scroller;
54import android.widget.TextView;
55import android.widget.Toast;
56
57import java.util.ArrayList;
58import java.util.HashSet;
59
60/**
61 * The workspace is a wide area with a wallpaper and a finite number of screens.
62 * Each screen contains a number of icons, folders or widgets the user can
63 * interact with. A workspace is meant to be used with a fixed width only.
64 */
65public class Workspace extends ViewGroup
66        implements DropTarget, DragSource, DragScroller, View.OnTouchListener {
67    @SuppressWarnings({"UnusedDeclaration"})
68    private static final String TAG = "Launcher.Workspace";
69    private static final int INVALID_SCREEN = -1;
70    // This is how much the workspace shrinks when we enter all apps or
71    // customization mode
72    private static final float SHRINK_FACTOR = 0.16f;
73
74    /**
75     * The velocity at which a fling gesture will cause us to snap to the next
76     * screen
77     */
78    private static final int SNAP_VELOCITY = 600;
79
80    private final WallpaperManager mWallpaperManager;
81
82    private int mDefaultScreen;
83
84    private boolean mFirstLayout = true;
85    private boolean mWaitingToShrinkToBottom = false;
86
87    private int mCurrentScreen;
88    private int mNextScreen = INVALID_SCREEN;
89    private Scroller mScroller;
90    private VelocityTracker mVelocityTracker;
91
92    /**
93     * CellInfo for the cell that is currently being dragged
94     */
95    private CellLayout.CellInfo mDragInfo;
96
97    /**
98     * Target drop area calculated during last acceptDrop call.
99     */
100    private int[] mTargetCell = null;
101
102    /**
103     * The CellLayout that is currently being dragged over
104     */
105    private CellLayout mDragTargetLayout = null;
106
107    private float mLastMotionX;
108    private float mLastMotionY;
109
110    private final static int TOUCH_STATE_REST = 0;
111    private final static int TOUCH_STATE_SCROLLING = 1;
112
113    private int mTouchState = TOUCH_STATE_REST;
114
115    private OnLongClickListener mLongClickListener;
116
117    private Launcher mLauncher;
118    private IconCache mIconCache;
119    private DragController mDragController;
120
121
122    private int[] mTempCell = new int[2];
123    private int[] mTempEstimate = new int[2];
124
125    private boolean mAllowLongPress = true;
126
127    private int mTouchSlop;
128    private int mMaximumVelocity;
129
130    private static final int INVALID_POINTER = -1;
131    private static final int DEFAULT_CELL_COUNT_X = 4;
132    private static final int DEFAULT_CELL_COUNT_Y = 4;
133
134    private int mActivePointerId = INVALID_POINTER;
135
136    private Drawable mPreviousIndicator;
137    private Drawable mNextIndicator;
138
139    private static final float NANOTIME_DIV = 1000000000.0f;
140    private static final float SMOOTHING_SPEED = 0.75f;
141    private static final float SMOOTHING_CONSTANT = (float) (0.016 / Math.log(SMOOTHING_SPEED));
142    private float mSmoothingTime;
143    private float mTouchX;
144
145    private WorkspaceOvershootInterpolator mScrollInterpolator;
146
147    private static final float BASELINE_FLING_VELOCITY = 2500.f;
148    private static final float FLING_VELOCITY_INFLUENCE = 0.4f;
149
150    private Paint mDropIndicatorPaint;
151
152    // State variable that indicated whether the screens are small (ie when you're
153    // in all apps or customize mode)
154    private boolean mIsSmall;
155    private AnimatableListener mUnshrinkAnimationListener;
156
157    private static class WorkspaceOvershootInterpolator implements Interpolator {
158        private static final float DEFAULT_TENSION = 1.3f;
159        private float mTension;
160
161        public WorkspaceOvershootInterpolator() {
162            mTension = DEFAULT_TENSION;
163        }
164
165        public void setDistance(int distance) {
166            mTension = distance > 0 ? DEFAULT_TENSION / distance : DEFAULT_TENSION;
167        }
168
169        public void disableSettle() {
170            mTension = 0.f;
171        }
172
173        public float getInterpolation(float t) {
174            // _o(t) = t * t * ((tension + 1) * t + tension)
175            // o(t) = _o(t - 1) + 1
176            t -= 1.0f;
177            return t * t * ((mTension + 1) * t + mTension) + 1.0f;
178        }
179    }
180
181    /**
182     * Used to inflate the Workspace from XML.
183     *
184     * @param context The application's context.
185     * @param attrs The attribtues set containing the Workspace's customization values.
186     */
187    public Workspace(Context context, AttributeSet attrs) {
188        this(context, attrs, 0);
189    }
190
191    /**
192     * Used to inflate the Workspace from XML.
193     *
194     * @param context The application's context.
195     * @param attrs The attribtues set containing the Workspace's customization values.
196     * @param defStyle Unused.
197     */
198    public Workspace(Context context, AttributeSet attrs, int defStyle) {
199        super(context, attrs, defStyle);
200
201        mWallpaperManager = WallpaperManager.getInstance(context);
202
203        TypedArray a = context.obtainStyledAttributes(attrs,
204                R.styleable.Workspace, defStyle, 0);
205        int cellCountX = a.getInt(R.styleable.Workspace_cellCountX, DEFAULT_CELL_COUNT_X);
206        int cellCountY = a.getInt(R.styleable.Workspace_cellCountY, DEFAULT_CELL_COUNT_Y);
207        mDefaultScreen = a.getInt(R.styleable.Workspace_defaultScreen, 1);
208        a.recycle();
209
210        LauncherModel.updateWorkspaceLayoutCells(cellCountX, cellCountY);
211        setHapticFeedbackEnabled(false);
212        initWorkspace();
213    }
214
215    /**
216     * Initializes various states for this workspace.
217     */
218    private void initWorkspace() {
219        Context context = getContext();
220        mScrollInterpolator = new WorkspaceOvershootInterpolator();
221        mScroller = new Scroller(context, mScrollInterpolator);
222        mCurrentScreen = mDefaultScreen;
223        Launcher.setScreen(mCurrentScreen);
224        LauncherApplication app = (LauncherApplication)context.getApplicationContext();
225        mIconCache = app.getIconCache();
226
227        final ViewConfiguration configuration = ViewConfiguration.get(getContext());
228        mTouchSlop = configuration.getScaledTouchSlop();
229        mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
230        mUnshrinkAnimationListener = new AnimatableListener() {
231            public void onAnimationStart(Animatable animation) {}
232            public void onAnimationEnd(Animatable animation) {
233                mIsSmall = false;
234            }
235            public void onAnimationCancel(Animatable animation) {}
236            public void onAnimationRepeat(Animatable animation) {}
237        };
238    }
239
240    @Override
241    public void addView(View child, int index, LayoutParams params) {
242        if (!(child instanceof CellLayout)) {
243            throw new IllegalArgumentException("A Workspace can only have CellLayout children.");
244        }
245        super.addView(child, index, params);
246    }
247
248    @Override
249    public void addView(View child) {
250        if (!(child instanceof CellLayout)) {
251            throw new IllegalArgumentException("A Workspace can only have CellLayout children.");
252        }
253        super.addView(child);
254    }
255
256    @Override
257    public void addView(View child, int index) {
258        if (!(child instanceof CellLayout)) {
259            throw new IllegalArgumentException("A Workspace can only have CellLayout children.");
260        }
261        super.addView(child, index);
262    }
263
264    @Override
265    public void addView(View child, int width, int height) {
266        if (!(child instanceof CellLayout)) {
267            throw new IllegalArgumentException("A Workspace can only have CellLayout children.");
268        }
269        super.addView(child, width, height);
270    }
271
272    @Override
273    public void addView(View child, LayoutParams params) {
274        if (!(child instanceof CellLayout)) {
275            throw new IllegalArgumentException("A Workspace can only have CellLayout children.");
276        }
277        super.addView(child, params);
278    }
279
280    /**
281     * @return The open folder on the current screen, or null if there is none
282     */
283    Folder getOpenFolder() {
284        CellLayout currentScreen = (CellLayout) getChildAt(mCurrentScreen);
285        int count = currentScreen.getChildCount();
286        for (int i = 0; i < count; i++) {
287            View child = currentScreen.getChildAt(i);
288            if (child instanceof Folder) {
289                Folder folder = (Folder) child;
290                if (folder.getInfo().opened)
291                    return folder;
292            }
293        }
294        return null;
295    }
296
297    ArrayList<Folder> getOpenFolders() {
298        final int screenCount = getChildCount();
299        ArrayList<Folder> folders = new ArrayList<Folder>(screenCount);
300
301        for (int screen = 0; screen < screenCount; screen++) {
302            CellLayout currentScreen = (CellLayout) getChildAt(screen);
303            int count = currentScreen.getChildCount();
304            for (int i = 0; i < count; i++) {
305                View child = currentScreen.getChildAt(i);
306                CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child
307                        .getLayoutParams();
308                if (child instanceof Folder) {
309                    Folder folder = (Folder) child;
310                    if (folder.getInfo().opened)
311                        folders.add(folder);
312                    break;
313                }
314            }
315        }
316
317        return folders;
318    }
319
320    boolean isDefaultScreenShowing() {
321        return mCurrentScreen == mDefaultScreen;
322    }
323
324    /**
325     * Returns the index of the currently displayed screen.
326     *
327     * @return The index of the currently displayed screen.
328     */
329    int getCurrentScreen() {
330        return mCurrentScreen;
331    }
332
333    /**
334     * Sets the current screen.
335     *
336     * @param currentScreen
337     */
338    void setCurrentScreen(int currentScreen) {
339        setCurrentScreen(currentScreen, true);
340    }
341
342    void setCurrentScreen(int currentScreen, boolean animateScrolling) {
343        setCurrentScreen(currentScreen, animateScrolling, getWidth());
344    }
345
346    void setCurrentScreen(int currentScreen, boolean animateScrolling, int screenWidth) {
347        if (!mScroller.isFinished())
348            mScroller.abortAnimation();
349        mCurrentScreen = Math.max(0, Math.min(currentScreen, getChildCount() - 1));
350        if (mPreviousIndicator != null) {
351            mPreviousIndicator.setLevel(mCurrentScreen);
352            mNextIndicator.setLevel(mCurrentScreen);
353        }
354        if (animateScrolling) {
355            scrollTo(mCurrentScreen * screenWidth, 0);
356        } else {
357            mScrollX = mCurrentScreen * screenWidth;
358        }
359        updateWallpaperOffset(screenWidth * (getChildCount() - 1));
360        invalidate();
361    }
362
363    /**
364     * Adds the specified child in the current screen. The position and dimension of
365     * the child are defined by x, y, spanX and spanY.
366     *
367     * @param child The child to add in one of the workspace's screens.
368     * @param x The X position of the child in the screen's grid.
369     * @param y The Y position of the child in the screen's grid.
370     * @param spanX The number of cells spanned horizontally by the child.
371     * @param spanY The number of cells spanned vertically by the child.
372     */
373    void addInCurrentScreen(View child, int x, int y, int spanX, int spanY) {
374        addInScreen(child, mCurrentScreen, x, y, spanX, spanY, false);
375    }
376
377    /**
378     * Adds the specified child in the current screen. The position and dimension of
379     * the child are defined by x, y, spanX and spanY.
380     *
381     * @param child The child to add in one of the workspace's screens.
382     * @param x The X position of the child in the screen's grid.
383     * @param y The Y position of the child in the screen's grid.
384     * @param spanX The number of cells spanned horizontally by the child.
385     * @param spanY The number of cells spanned vertically by the child.
386     * @param insert When true, the child is inserted at the beginning of the children list.
387     */
388    void addInCurrentScreen(View child, int x, int y, int spanX, int spanY, boolean insert) {
389        addInScreen(child, mCurrentScreen, x, y, spanX, spanY, insert);
390    }
391
392    /**
393     * Adds the specified child in the specified screen. The position and dimension of
394     * the child are defined by x, y, spanX and spanY.
395     *
396     * @param child The child to add in one of the workspace's screens.
397     * @param screen The screen in which to add the child.
398     * @param x The X position of the child in the screen's grid.
399     * @param y The Y position of the child in the screen's grid.
400     * @param spanX The number of cells spanned horizontally by the child.
401     * @param spanY The number of cells spanned vertically by the child.
402     */
403    void addInScreen(View child, int screen, int x, int y, int spanX, int spanY) {
404        addInScreen(child, screen, x, y, spanX, spanY, false);
405    }
406
407    void addInFullScreen(View child, int screen) {
408        addInScreen(child, screen, 0, 0, -1, -1);
409    }
410
411    /**
412     * Adds the specified child in the specified screen. The position and dimension of
413     * the child are defined by x, y, spanX and spanY.
414     *
415     * @param child The child to add in one of the workspace's screens.
416     * @param screen The screen in which to add the child.
417     * @param x The X position of the child in the screen's grid.
418     * @param y The Y position of the child in the screen's grid.
419     * @param spanX The number of cells spanned horizontally by the child.
420     * @param spanY The number of cells spanned vertically by the child.
421     * @param insert When true, the child is inserted at the beginning of the children list.
422     */
423    void addInScreen(View child, int screen, int x, int y, int spanX, int spanY, boolean insert) {
424        if (screen < 0 || screen >= getChildCount()) {
425            Log.e(TAG, "The screen must be >= 0 and < " + getChildCount()
426                + " (was " + screen + "); skipping child");
427            return;
428        }
429
430        final CellLayout group = (CellLayout) getChildAt(screen);
431        CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
432        if (lp == null) {
433            lp = new CellLayout.LayoutParams(x, y, spanX, spanY);
434        } else {
435            lp.cellX = x;
436            lp.cellY = y;
437            lp.cellHSpan = spanX;
438            lp.cellVSpan = spanY;
439        }
440
441        // Get the canonical child id to uniquely represent this view in this screen
442        int childId = LauncherModel.getCellLayoutChildId(child.getId(), screen, x, y, spanX, spanY);
443        if (!group.addViewToCellLayout(child, insert ? 0 : -1, childId, lp)) {
444            // TODO: This branch occurs when the workspace is adding views
445            // outside of the defined grid
446            // maybe we should be deleting these items from the LauncherModel?
447            Log.w(TAG, "Failed to add to item at (" + lp.cellX + "," + lp.cellY + ") to CellLayout");
448        }
449
450        if (!(child instanceof Folder)) {
451            child.setHapticFeedbackEnabled(false);
452            child.setOnLongClickListener(mLongClickListener);
453        }
454        if (child instanceof DropTarget) {
455            mDragController.addDropTarget((DropTarget) child);
456        }
457    }
458
459    CellLayout.CellInfo updateOccupiedCells(boolean[] occupied) {
460        CellLayout group = (CellLayout) getChildAt(mCurrentScreen);
461        if (group != null) {
462            return group.updateOccupiedCells(occupied, null);
463        }
464        return null;
465    }
466
467    public boolean onTouch(View v, MotionEvent event) {
468        // this is an intercepted event being forwarded from a cell layout
469        if (mIsSmall) {
470            unshrink((CellLayout)v);
471            mLauncher.onWorkspaceUnshrink();
472            return true;
473        }
474        return false;
475    }
476
477    /**
478     * Registers the specified listener on each screen contained in this workspace.
479     *
480     * @param l The listener used to respond to long clicks.
481     */
482    @Override
483    public void setOnLongClickListener(OnLongClickListener l) {
484        mLongClickListener = l;
485        final int screenCount = getChildCount();
486        for (int i = 0; i < screenCount; i++) {
487            getChildAt(i).setOnLongClickListener(l);
488        }
489    }
490
491    private void updateWallpaperOffset() {
492        updateWallpaperOffset(getChildAt(getChildCount() - 1).getRight() - (mRight - mLeft));
493    }
494
495    private void updateWallpaperOffset(int scrollRange) {
496        IBinder token = getWindowToken();
497        if (token != null) {
498            mWallpaperManager.setWallpaperOffsetSteps(1.0f / (getChildCount() - 1), 0 );
499            mWallpaperManager.setWallpaperOffsets(getWindowToken(),
500                    Math.max(0.f, Math.min(mScrollX/(float)scrollRange, 1.f)), 0);
501        }
502    }
503
504    @Override
505    public void scrollTo(int x, int y) {
506        super.scrollTo(x, y);
507        mTouchX = x;
508        mSmoothingTime = System.nanoTime() / NANOTIME_DIV;
509    }
510
511    @Override
512    public void computeScroll() {
513        if (mScroller.computeScrollOffset()) {
514            mTouchX = mScrollX = mScroller.getCurrX();
515            mSmoothingTime = System.nanoTime() / NANOTIME_DIV;
516            mScrollY = mScroller.getCurrY();
517            updateWallpaperOffset();
518            postInvalidate();
519        } else if (mNextScreen != INVALID_SCREEN) {
520            mCurrentScreen = Math.max(0, Math.min(mNextScreen, getChildCount() - 1));
521            if (mPreviousIndicator != null) {
522                mPreviousIndicator.setLevel(mCurrentScreen);
523                mNextIndicator.setLevel(mCurrentScreen);
524            }
525            Launcher.setScreen(mCurrentScreen);
526            mNextScreen = INVALID_SCREEN;
527            clearChildrenCache();
528        } else if (mTouchState == TOUCH_STATE_SCROLLING) {
529            final float now = System.nanoTime() / NANOTIME_DIV;
530            final float e = (float) Math.exp((now - mSmoothingTime) / SMOOTHING_CONSTANT);
531            final float dx = mTouchX - mScrollX;
532            mScrollX += dx * e;
533            mSmoothingTime = now;
534
535            // Keep generating points as long as we're more than 1px away from the target
536            if (dx > 1.f || dx < -1.f) {
537                updateWallpaperOffset();
538                postInvalidate();
539            }
540        }
541    }
542
543    @Override
544    protected void dispatchDraw(Canvas canvas) {
545        boolean restore = false;
546        int restoreCount = 0;
547
548        // ViewGroup.dispatchDraw() supports many features we don't need:
549        // clip to padding, layout animation, animation listener, disappearing
550        // children, etc. The following implementation attempts to fast-track
551        // the drawing dispatch by drawing only what we know needs to be drawn.
552
553        boolean fastDraw = mTouchState != TOUCH_STATE_SCROLLING && mNextScreen == INVALID_SCREEN;
554
555        // if the screens are all small, we need to draw all the screens since
556        // they're most likely all visible
557        if (mIsSmall) {
558            final int screenCount = getChildCount();
559            for (int i = 0; i < screenCount; i++) {
560                CellLayout cl = (CellLayout)getChildAt(i);
561                drawChild(canvas, cl, getDrawingTime());
562            }
563        } else if (fastDraw) {
564            // If we are not scrolling or flinging, draw only the current screen
565            drawChild(canvas, getChildAt(mCurrentScreen), getDrawingTime());
566        } else {
567            final long drawingTime = getDrawingTime();
568            final float scrollPos = (float) mScrollX / getWidth();
569            final int leftScreen = (int) scrollPos;
570            final int rightScreen = leftScreen + 1;
571            if (leftScreen >= 0) {
572                drawChild(canvas, getChildAt(leftScreen), drawingTime);
573            }
574            if (scrollPos != leftScreen && rightScreen < getChildCount()) {
575                drawChild(canvas, getChildAt(rightScreen), drawingTime);
576            }
577        }
578
579        if (restore) {
580            canvas.restoreToCount(restoreCount);
581        }
582    }
583
584    protected void onAttachedToWindow() {
585        super.onAttachedToWindow();
586        computeScroll();
587        mDragController.setWindowToken(getWindowToken());
588    }
589
590    @Override
591    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
592        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
593
594        final int width = MeasureSpec.getSize(widthMeasureSpec);
595        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
596        if (widthMode != MeasureSpec.EXACTLY) {
597            throw new IllegalStateException("Workspace can only be used in EXACTLY mode.");
598        }
599
600        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
601        if (heightMode != MeasureSpec.EXACTLY) {
602            throw new IllegalStateException("Workspace can only be used in EXACTLY mode.");
603        }
604
605        // The children are given the same width and height as the workspace
606        final int screenCount = getChildCount();
607        for (int i = 0; i < screenCount; i++) {
608            getChildAt(i).measure(widthMeasureSpec, heightMeasureSpec);
609        }
610
611        if (mFirstLayout) {
612            setHorizontalScrollBarEnabled(false);
613            setCurrentScreen(mCurrentScreen, false, width);
614            setHorizontalScrollBarEnabled(true);
615        }
616    }
617
618    @Override
619    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
620        if (mFirstLayout) {
621            mFirstLayout = false;
622        }
623        int childLeft = 0;
624        final int screenCount = getChildCount();
625        for (int i = 0; i < screenCount; i++) {
626            final View child = getChildAt(i);
627            if (child.getVisibility() != View.GONE) {
628                final int childWidth = child.getMeasuredWidth();
629                child.layout(childLeft, 0,
630                        childLeft + childWidth, child.getMeasuredHeight());
631                childLeft += childWidth;
632            }
633        }
634
635        // if shrinkToBottom() is called on initialization, it has to be deferred
636        // until after the first call to onLayout so that it has the correct width
637        if (mWaitingToShrinkToBottom) {
638            shrinkToBottom(false);
639            mWaitingToShrinkToBottom = false;
640        }
641
642        if (LauncherApplication.isInPlaceRotationEnabled()) {
643            // When the device is rotated, the scroll position of the current screen
644            // needs to be refreshed
645            setCurrentScreen(getCurrentScreen());
646        }
647    }
648
649    @Override
650    public boolean requestChildRectangleOnScreen(View child, Rect rectangle, boolean immediate) {
651        int screen = indexOfChild(child);
652        if (screen != mCurrentScreen || !mScroller.isFinished()) {
653            if (!mLauncher.isWorkspaceLocked()) {
654                snapToScreen(screen);
655            }
656            return true;
657        }
658        return false;
659    }
660
661    @Override
662    protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
663        if (!mLauncher.isAllAppsVisible()) {
664            final Folder openFolder = getOpenFolder();
665            if (openFolder != null) {
666                return openFolder.requestFocus(direction, previouslyFocusedRect);
667            } else {
668                int focusableScreen;
669                if (mNextScreen != INVALID_SCREEN) {
670                    focusableScreen = mNextScreen;
671                } else {
672                    focusableScreen = mCurrentScreen;
673                }
674                getChildAt(focusableScreen).requestFocus(direction, previouslyFocusedRect);
675            }
676        }
677        return false;
678    }
679
680    @Override
681    public boolean dispatchUnhandledMove(View focused, int direction) {
682        if (direction == View.FOCUS_LEFT) {
683            if (getCurrentScreen() > 0) {
684                snapToScreen(getCurrentScreen() - 1);
685                return true;
686            }
687        } else if (direction == View.FOCUS_RIGHT) {
688            if (getCurrentScreen() < getChildCount() - 1) {
689                snapToScreen(getCurrentScreen() + 1);
690                return true;
691            }
692        }
693        return super.dispatchUnhandledMove(focused, direction);
694    }
695
696    @Override
697    public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
698        if (!mLauncher.isAllAppsVisible()) {
699            final Folder openFolder = getOpenFolder();
700            if (openFolder == null) {
701                getChildAt(mCurrentScreen).addFocusables(views, direction);
702                if (direction == View.FOCUS_LEFT) {
703                    if (mCurrentScreen > 0) {
704                        getChildAt(mCurrentScreen - 1).addFocusables(views, direction);
705                    }
706                } else if (direction == View.FOCUS_RIGHT) {
707                    if (mCurrentScreen < getChildCount() - 1) {
708                        getChildAt(mCurrentScreen + 1).addFocusables(views, direction);
709                    }
710                }
711            } else {
712                openFolder.addFocusables(views, direction);
713            }
714        }
715    }
716
717    @Override
718    public boolean dispatchTouchEvent(MotionEvent ev) {
719        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
720            // (In XLarge mode, the workspace is shrunken below all apps, and responds to taps
721            // ie when you click on a mini-screen, it zooms back to that screen)
722            if (mLauncher.isWorkspaceLocked() ||
723                    (!LauncherApplication.isScreenXLarge() && mLauncher.isAllAppsVisible())) {
724                return false;
725            }
726        }
727        return super.dispatchTouchEvent(ev);
728    }
729
730    /**
731     * {@inheritDoc}
732     */
733    @Override
734    public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
735        if (disallowIntercept) {
736            // We need to make sure to cancel our long press if
737            // a scrollable widget takes over touch events
738            final View currentScreen = getChildAt(mCurrentScreen);
739            currentScreen.cancelLongPress();
740        }
741        super.requestDisallowInterceptTouchEvent(disallowIntercept);
742    }
743
744    @Override
745    public boolean onInterceptTouchEvent(MotionEvent ev) {
746        final boolean workspaceLocked = mLauncher.isWorkspaceLocked();
747        final boolean allAppsVisible = mLauncher.isAllAppsVisible();
748
749        // (In XLarge mode, the workspace is shrunken below all apps, and responds to taps
750        // ie when you click on a mini-screen, it zooms back to that screen)
751        if (workspaceLocked || (!LauncherApplication.isScreenXLarge() && allAppsVisible)) {
752            return false; // We don't want the events.  Let them fall through to the all apps view.
753        }
754
755        /*
756         * This method JUST determines whether we want to intercept the motion.
757         * If we return true, onTouchEvent will be called and we do the actual
758         * scrolling there.
759         */
760
761        /*
762         * Shortcut the most recurring case: the user is in the dragging
763         * state and he is moving his finger.  We want to intercept this
764         * motion.
765         */
766        final int action = ev.getAction();
767        if ((action == MotionEvent.ACTION_MOVE) && (mTouchState != TOUCH_STATE_REST)) {
768            return true;
769        }
770
771        if (mVelocityTracker == null) {
772            mVelocityTracker = VelocityTracker.obtain();
773        }
774        mVelocityTracker.addMovement(ev);
775
776        switch (action & MotionEvent.ACTION_MASK) {
777            case MotionEvent.ACTION_MOVE: {
778                /*
779                 * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
780                 * whether the user has moved far enough from his original down touch.
781                 */
782
783                /*
784                 * Locally do absolute value. mLastMotionX is set to the y value
785                 * of the down event.
786                 */
787                final int pointerIndex = ev.findPointerIndex(mActivePointerId);
788                final float x = ev.getX(pointerIndex);
789                final float y = ev.getY(pointerIndex);
790                final int xDiff = (int) Math.abs(x - mLastMotionX);
791                final int yDiff = (int) Math.abs(y - mLastMotionY);
792
793                final int touchSlop = mTouchSlop;
794                boolean xMoved = xDiff > touchSlop;
795                boolean yMoved = yDiff > touchSlop;
796
797                if (xMoved || yMoved) {
798
799                    if (xMoved) {
800                        // Scroll if the user moved far enough along the X axis
801                        mTouchState = TOUCH_STATE_SCROLLING;
802                        mLastMotionX = x;
803                        mTouchX = mScrollX;
804                        mSmoothingTime = System.nanoTime() / NANOTIME_DIV;
805                        enableChildrenCache(mCurrentScreen - 1, mCurrentScreen + 1);
806                    }
807                    // Either way, cancel any pending longpress
808                    if (mAllowLongPress) {
809                        mAllowLongPress = false;
810                        // Try canceling the long press. It could also have been scheduled
811                        // by a distant descendant, so use the mAllowLongPress flag to block
812                        // everything
813                        final View currentScreen = getChildAt(mCurrentScreen);
814                        currentScreen.cancelLongPress();
815                    }
816                }
817                break;
818            }
819
820        case MotionEvent.ACTION_DOWN: {
821            final float x = ev.getX();
822            final float y = ev.getY();
823            // Remember location of down touch
824            mLastMotionX = x;
825            mLastMotionY = y;
826            mActivePointerId = ev.getPointerId(0);
827            mAllowLongPress = true;
828
829                /*
830                 * If being flinged and user touches the screen, initiate drag;
831                 * otherwise don't.  mScroller.isFinished should be false when
832                 * being flinged.
833                 */
834                mTouchState = mScroller.isFinished() ? TOUCH_STATE_REST : TOUCH_STATE_SCROLLING;
835                break;
836            }
837
838            case MotionEvent.ACTION_CANCEL:
839            case MotionEvent.ACTION_UP:
840
841                if (mTouchState != TOUCH_STATE_SCROLLING) {
842                    final CellLayout currentScreen = (CellLayout)getChildAt(mCurrentScreen);
843                    if (!currentScreen.lastDownOnOccupiedCell()) {
844                        getLocationOnScreen(mTempCell);
845                        // Send a tap to the wallpaper if the last down was on empty space
846                        final int pointerIndex = ev.findPointerIndex(mActivePointerId);
847                        mWallpaperManager.sendWallpaperCommand(getWindowToken(),
848                                "android.wallpaper.tap",
849                                mTempCell[0] + (int) ev.getX(pointerIndex),
850                                mTempCell[1] + (int) ev.getY(pointerIndex), 0, null);
851                    }
852                }
853
854                // Release the drag
855                clearChildrenCache();
856                mTouchState = TOUCH_STATE_REST;
857                mActivePointerId = INVALID_POINTER;
858                mAllowLongPress = false;
859
860                if (mVelocityTracker != null) {
861                    mVelocityTracker.recycle();
862                    mVelocityTracker = null;
863                }
864
865            break;
866
867        case MotionEvent.ACTION_POINTER_UP:
868            onSecondaryPointerUp(ev);
869            break;
870        }
871
872        /*
873         * The only time we want to intercept motion events is if we are in the
874         * drag mode.
875         */
876        return mTouchState != TOUCH_STATE_REST;
877    }
878
879    private void onSecondaryPointerUp(MotionEvent ev) {
880        final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >>
881                MotionEvent.ACTION_POINTER_INDEX_SHIFT;
882        final int pointerId = ev.getPointerId(pointerIndex);
883        if (pointerId == mActivePointerId) {
884            // This was our active pointer going up. Choose a new
885            // active pointer and adjust accordingly.
886            // TODO: Make this decision more intelligent.
887            final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
888            mLastMotionX = ev.getX(newPointerIndex);
889            mLastMotionY = ev.getY(newPointerIndex);
890            mActivePointerId = ev.getPointerId(newPointerIndex);
891            if (mVelocityTracker != null) {
892                mVelocityTracker.clear();
893            }
894        }
895    }
896
897    /**
898     * If one of our descendant views decides that it could be focused now, only
899     * pass that along if it's on the current screen.
900     *
901     * This happens when live folders requery, and if they're off screen, they
902     * end up calling requestFocus, which pulls it on screen.
903     */
904    @Override
905    public void focusableViewAvailable(View focused) {
906        View current = getChildAt(mCurrentScreen);
907        View v = focused;
908        while (true) {
909            if (v == current) {
910                super.focusableViewAvailable(focused);
911                return;
912            }
913            if (v == this) {
914                return;
915            }
916            ViewParent parent = v.getParent();
917            if (parent instanceof View) {
918                v = (View) v.getParent();
919            } else {
920                return;
921            }
922        }
923    }
924
925    void enableChildrenCache(int fromScreen, int toScreen) {
926        if (fromScreen > toScreen) {
927            final int temp = fromScreen;
928            fromScreen = toScreen;
929            toScreen = temp;
930        }
931
932        final int screenCount = getChildCount();
933
934        fromScreen = Math.max(fromScreen, 0);
935        toScreen = Math.min(toScreen, screenCount - 1);
936
937        for (int i = fromScreen; i <= toScreen; i++) {
938            final CellLayout layout = (CellLayout) getChildAt(i);
939            layout.setChildrenDrawnWithCacheEnabled(true);
940            layout.setChildrenDrawingCacheEnabled(true);
941        }
942    }
943
944    void clearChildrenCache() {
945        final int screenCount = getChildCount();
946        for (int i = 0; i < screenCount; i++) {
947            final CellLayout layout = (CellLayout) getChildAt(i);
948            layout.setChildrenDrawnWithCacheEnabled(false);
949        }
950    }
951
952    @Override
953    public boolean onTouchEvent(MotionEvent ev) {
954
955        if (mLauncher.isWorkspaceLocked()) {
956            return false; // We don't want the events.  Let them fall through to the all apps view.
957        }
958        if (mLauncher.isAllAppsVisible()) {
959            // Cancel any scrolling that is in progress.
960            if (!mScroller.isFinished()) {
961                mScroller.abortAnimation();
962            }
963            snapToScreen(mCurrentScreen);
964            return false; // We don't want the events.  Let them fall through to the all apps view.
965        }
966
967        if (mVelocityTracker == null) {
968            mVelocityTracker = VelocityTracker.obtain();
969        }
970        mVelocityTracker.addMovement(ev);
971
972        final int action = ev.getAction();
973
974        switch (action & MotionEvent.ACTION_MASK) {
975        case MotionEvent.ACTION_DOWN:
976            /*
977             * If being flinged and user touches, stop the fling. isFinished
978             * will be false if being flinged.
979             */
980            if (!mScroller.isFinished()) {
981                mScroller.abortAnimation();
982            }
983
984            // Remember where the motion event started
985            mLastMotionX = ev.getX();
986            mActivePointerId = ev.getPointerId(0);
987            if (mTouchState == TOUCH_STATE_SCROLLING) {
988                enableChildrenCache(mCurrentScreen - 1, mCurrentScreen + 1);
989            }
990            break;
991        case MotionEvent.ACTION_MOVE:
992            if (mTouchState == TOUCH_STATE_SCROLLING) {
993                // Scroll to follow the motion event
994                final int pointerIndex = ev.findPointerIndex(mActivePointerId);
995                final float x = ev.getX(pointerIndex);
996                final float deltaX = mLastMotionX - x;
997                mLastMotionX = x;
998
999                if (deltaX < 0) {
1000                    if (mTouchX > 0) {
1001                        mTouchX += Math.max(-mTouchX, deltaX);
1002                        mSmoothingTime = System.nanoTime() / NANOTIME_DIV;
1003                        invalidate();
1004                    }
1005                } else if (deltaX > 0) {
1006                    final float availableToScroll = getChildAt(getChildCount() - 1).getRight() -
1007                            mTouchX - getWidth();
1008                    if (availableToScroll > 0) {
1009                        mTouchX += Math.min(availableToScroll, deltaX);
1010                        mSmoothingTime = System.nanoTime() / NANOTIME_DIV;
1011                        invalidate();
1012                    }
1013                } else {
1014                    awakenScrollBars();
1015                }
1016            }
1017            break;
1018        case MotionEvent.ACTION_UP:
1019            if (mTouchState == TOUCH_STATE_SCROLLING) {
1020                final VelocityTracker velocityTracker = mVelocityTracker;
1021                velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
1022                final int velocityX = (int) velocityTracker.getXVelocity(mActivePointerId);
1023
1024                final int screenWidth = getWidth();
1025                final int whichScreen = (mScrollX + (screenWidth / 2)) / screenWidth;
1026                final float scrolledPos = (float) mScrollX / screenWidth;
1027
1028                if (velocityX > SNAP_VELOCITY && mCurrentScreen > 0) {
1029                    // Fling hard enough to move left.
1030                    // Don't fling across more than one screen at a time.
1031                    final int bound = scrolledPos < whichScreen ?
1032                            mCurrentScreen - 1 : mCurrentScreen;
1033                    snapToScreen(Math.min(whichScreen, bound), velocityX, true);
1034                } else if (velocityX < -SNAP_VELOCITY && mCurrentScreen < getChildCount() - 1) {
1035                    // Fling hard enough to move right
1036                    // Don't fling across more than one screen at a time.
1037                    final int bound = scrolledPos > whichScreen ?
1038                            mCurrentScreen + 1 : mCurrentScreen;
1039                    snapToScreen(Math.max(whichScreen, bound), velocityX, true);
1040                } else {
1041                    snapToScreen(whichScreen, 0, true);
1042                }
1043
1044                if (mVelocityTracker != null) {
1045                    mVelocityTracker.recycle();
1046                    mVelocityTracker = null;
1047                }
1048            }
1049            mTouchState = TOUCH_STATE_REST;
1050            mActivePointerId = INVALID_POINTER;
1051            break;
1052        case MotionEvent.ACTION_CANCEL:
1053            mTouchState = TOUCH_STATE_REST;
1054            mActivePointerId = INVALID_POINTER;
1055            break;
1056        case MotionEvent.ACTION_POINTER_UP:
1057            onSecondaryPointerUp(ev);
1058            break;
1059        }
1060
1061        return true;
1062    }
1063
1064    void shrinkToTop() {
1065        shrink(true, true);
1066    }
1067
1068    void shrinkToBottom() {
1069        shrinkToBottom(true);
1070    }
1071
1072    void shrinkToBottom(boolean animated) {
1073        if (mFirstLayout) {
1074            // (mFirstLayout == "first layout has not happened yet")
1075            // if we get a call to shrink() as part of our initialization (for example, if
1076            // Launcher is started in All Apps mode) then we need to wait for a layout call
1077            // to get our width so we can layout the mini-screen views correctly
1078            mWaitingToShrinkToBottom = true;
1079        } else {
1080            shrink(false, animated);
1081        }
1082    }
1083
1084    // we use this to shrink the workspace for the all apps view and the customize view
1085    private void shrink(boolean shrinkToTop, boolean animated) {
1086        mIsSmall = true;
1087        final Resources res = getResources();
1088        final int screenWidth = getWidth();
1089        final int screenHeight = getHeight();
1090        final int scaledScreenWidth = (int) (SHRINK_FACTOR * screenWidth);
1091        final int scaledScreenHeight = (int) (SHRINK_FACTOR * screenHeight);
1092        final float scaledSpacing = res.getDimension(R.dimen.smallScreenSpacing);
1093
1094        final int screenCount = getChildCount();
1095        float totalWidth = screenCount * scaledScreenWidth + (screenCount - 1) * scaledSpacing;
1096
1097        float newY = getResources().getDimension(R.dimen.smallScreenVerticalMargin);
1098        if (!shrinkToTop) {
1099            newY = screenHeight - newY - scaledScreenHeight;
1100        }
1101
1102        // We animate all the screens to the centered position in workspace
1103        // At the same time, the screens become greyed/dimmed
1104
1105        // newX is initialized to the left-most position of the centered screens
1106        float newX = (mCurrentScreen + 1) * screenWidth - screenWidth / 2 - totalWidth / 2;
1107        Sequencer s = new Sequencer();
1108        for (int i = 0; i < screenCount; i++) {
1109            CellLayout cl = (CellLayout) getChildAt(i);
1110            cl.setPivotX(0.0f);
1111            cl.setPivotY(0.0f);
1112            if (animated) {
1113                final int duration = res.getInteger(R.integer.config_workspaceShrinkTime);
1114                s.playTogether(
1115                        new PropertyAnimator(duration, cl, "x", newX),
1116                        new PropertyAnimator(duration, cl, "y", newY),
1117                        new PropertyAnimator(duration, cl, "scaleX", SHRINK_FACTOR),
1118                        new PropertyAnimator(duration, cl, "scaleY", SHRINK_FACTOR),
1119                        new PropertyAnimator(duration, cl, "dimmedBitmapAlpha", 1.0f));
1120            } else {
1121                cl.setX((int)newX);
1122                cl.setY((int)newY);
1123                cl.setScaleX(SHRINK_FACTOR);
1124                cl.setScaleY(SHRINK_FACTOR);
1125                cl.setDimmedBitmapAlpha(1.0f);
1126            }
1127            // increment newX for the next screen
1128            newX += scaledScreenWidth + scaledSpacing;
1129            cl.setOnInterceptTouchListener(this);
1130        }
1131        setChildrenDrawnWithCacheEnabled(true);
1132        if (animated) s.start();
1133    }
1134
1135    // We call this when we trigger an unshrink by clicking on the CellLayout cl
1136    private void unshrink(CellLayout clThatWasClicked) {
1137        int newCurrentScreen = mCurrentScreen;
1138        final int screenCount = getChildCount();
1139        for (int i = 0; i < screenCount; i++) {
1140            if (getChildAt(i) == clThatWasClicked) {
1141                newCurrentScreen = i;
1142            }
1143        }
1144        unshrink(newCurrentScreen);
1145    }
1146
1147    private void unshrink(int newCurrentScreen) {
1148        if (mIsSmall) {
1149            int delta = (newCurrentScreen - mCurrentScreen)*getWidth();
1150
1151            final int screenCount = getChildCount();
1152            for (int i = 0; i < screenCount; i++) {
1153                CellLayout cl = (CellLayout) getChildAt(i);
1154                cl.setX(cl.getX() + delta);
1155            }
1156            mScrollX = newCurrentScreen * getWidth();
1157
1158            unshrink();
1159            setCurrentScreen(newCurrentScreen);
1160        }
1161    }
1162
1163    void unshrink() {
1164        unshrink(true);
1165    }
1166
1167    void unshrink(boolean animated) {
1168        if (mIsSmall) {
1169            Sequencer s = new Sequencer();
1170            final int screenCount = getChildCount();
1171
1172            final int duration = getResources().getInteger(R.integer.config_workspaceUnshrinkTime);
1173            for (int i = 0; i < screenCount; i++) {
1174                final CellLayout cl = (CellLayout)getChildAt(i);
1175                cl.setPivotX(0.0f);
1176                cl.setPivotY(0.0f);
1177                if (animated) {
1178                    s.playTogether(
1179                            new PropertyAnimator(duration, cl, "translationX", 0.0f),
1180                            new PropertyAnimator(duration, cl, "translationY", 0.0f),
1181                            new PropertyAnimator(duration, cl, "scaleX", 1.0f),
1182                            new PropertyAnimator(duration, cl, "scaleY", 1.0f),
1183                            new PropertyAnimator(duration, cl, "dimmedBitmapAlpha", 0.0f));
1184                } else {
1185                    cl.setTranslationX(0.0f);
1186                    cl.setTranslationY(0.0f);
1187                    cl.setScaleX(1.0f);
1188                    cl.setScaleY(1.0f);
1189                    cl.setDimmedBitmapAlpha(0.0f);
1190                }
1191            }
1192            s.addListener(mUnshrinkAnimationListener);
1193            s.start();
1194        }
1195    }
1196
1197    void snapToScreen(int whichScreen) {
1198        snapToScreen(whichScreen, 0, false);
1199    }
1200
1201    private void snapToScreen(int whichScreen, int velocity, boolean settle) {
1202        // if (!mScroller.isFinished()) return;
1203
1204        whichScreen = Math.max(0, Math.min(whichScreen, getChildCount() - 1));
1205
1206        enableChildrenCache(mCurrentScreen, whichScreen);
1207
1208        mNextScreen = whichScreen;
1209
1210        if (mPreviousIndicator != null) {
1211            mPreviousIndicator.setLevel(mNextScreen);
1212            mNextIndicator.setLevel(mNextScreen);
1213        }
1214
1215        View focusedChild = getFocusedChild();
1216        if (focusedChild != null && whichScreen != mCurrentScreen &&
1217                focusedChild == getChildAt(mCurrentScreen)) {
1218            focusedChild.clearFocus();
1219        }
1220
1221        final int screenDelta = Math.max(1, Math.abs(whichScreen - mCurrentScreen));
1222        final int newX = whichScreen * getWidth();
1223        final int delta = newX - mScrollX;
1224        int duration = (screenDelta + 1) * 100;
1225
1226        if (!mScroller.isFinished()) {
1227            mScroller.abortAnimation();
1228        }
1229
1230        if (settle) {
1231            mScrollInterpolator.setDistance(screenDelta);
1232        } else {
1233            mScrollInterpolator.disableSettle();
1234        }
1235
1236        velocity = Math.abs(velocity);
1237        if (velocity > 0) {
1238            duration += (duration / (velocity / BASELINE_FLING_VELOCITY))
1239                    * FLING_VELOCITY_INFLUENCE;
1240        } else {
1241            duration += 100;
1242        }
1243
1244        awakenScrollBars(duration);
1245        mScroller.startScroll(mScrollX, 0, delta, 0, duration);
1246        invalidate();
1247    }
1248
1249    void startDrag(CellLayout.CellInfo cellInfo) {
1250        View child = cellInfo.cell;
1251
1252        // Make sure the drag was started by a long press as opposed to a long click.
1253        if (!child.isInTouchMode()) {
1254            return;
1255        }
1256
1257        mDragInfo = cellInfo;
1258        mDragInfo.screen = mCurrentScreen;
1259
1260        CellLayout current = ((CellLayout) getChildAt(mCurrentScreen));
1261
1262        current.onDragChild(child);
1263        mDragController.startDrag(child, this, child.getTag(), DragController.DRAG_ACTION_MOVE);
1264        invalidate();
1265    }
1266
1267    @Override
1268    protected Parcelable onSaveInstanceState() {
1269        final SavedState state = new SavedState(super.onSaveInstanceState());
1270        state.currentScreen = mCurrentScreen;
1271        return state;
1272    }
1273
1274    @Override
1275    protected void onRestoreInstanceState(Parcelable state) {
1276        SavedState savedState = (SavedState) state;
1277        super.onRestoreInstanceState(savedState.getSuperState());
1278        if (savedState.currentScreen != -1) {
1279            setCurrentScreen(savedState.currentScreen, false);
1280            Launcher.setScreen(mCurrentScreen);
1281        }
1282    }
1283
1284    void addApplicationShortcut(ShortcutInfo info, CellLayout.CellInfo cellInfo) {
1285        addApplicationShortcut(info, cellInfo, false);
1286    }
1287
1288    void addApplicationShortcut(ShortcutInfo info, CellLayout.CellInfo cellInfo,
1289            boolean insertAtFirst) {
1290        final CellLayout layout = (CellLayout) getChildAt(cellInfo.screen);
1291        final int[] result = new int[2];
1292
1293        layout.cellToPoint(cellInfo.cellX, cellInfo.cellY, result);
1294        onDropExternal(result[0], result[1], info, layout, insertAtFirst);
1295    }
1296
1297    public void onDrop(DragSource source, int x, int y, int xOffset, int yOffset,
1298            DragView dragView, Object dragInfo) {
1299        final CellLayout cellLayout = getCurrentDropLayout();
1300        if (source != this) {
1301            onDropExternal(x - xOffset, y - yOffset, dragInfo, cellLayout);
1302        } else {
1303            // Move internally
1304            if (mDragInfo != null) {
1305                final View cell = mDragInfo.cell;
1306                int index = mScroller.isFinished() ? mCurrentScreen : mNextScreen;
1307                if (index != mDragInfo.screen) {
1308                    final CellLayout originalCellLayout = (CellLayout) getChildAt(mDragInfo.screen);
1309                    originalCellLayout.removeView(cell);
1310                    addInScreen(cell, index, mDragInfo.cellX, mDragInfo.cellY,
1311                            mDragInfo.spanX, mDragInfo.spanY);
1312                }
1313                mTargetCell = estimateDropCell(x - xOffset, y - yOffset,
1314                        mDragInfo.spanX, mDragInfo.spanY, cell, cellLayout,
1315                        mTargetCell);
1316                cellLayout.onDropChild(cell);
1317
1318                // update the item's position after drop
1319                final ItemInfo info = (ItemInfo) cell.getTag();
1320                CellLayout.LayoutParams lp = (CellLayout.LayoutParams) cell
1321                        .getLayoutParams();
1322                lp.cellX = mTargetCell[0];
1323                lp.cellY = mTargetCell[1];
1324
1325                LauncherModel.moveItemInDatabase(mLauncher, info,
1326                        LauncherSettings.Favorites.CONTAINER_DESKTOP, index,
1327                        lp.cellX, lp.cellY);
1328            }
1329        }
1330    }
1331
1332    public void onDragEnter(DragSource source, int x, int y, int xOffset,
1333            int yOffset, DragView dragView, Object dragInfo) {
1334    }
1335
1336    public DropTarget getDropTargetDelegate(DragSource source, int x, int y, int xOffset, int yOffset,
1337            DragView dragView, Object dragInfo) {
1338
1339        // We may need to delegate the drag to a child view. If a 1x1 item
1340        // would land in a cell occupied by a DragTarget (e.g. a Folder),
1341        // then drag events should be handled by that child.
1342
1343        ItemInfo item = (ItemInfo)dragInfo;
1344        CellLayout currentLayout = getCurrentDropLayout();
1345
1346        int dragPointX, dragPointY;
1347        if (item.spanX == 1 && item.spanY == 1) {
1348            // For a 1x1, calculate the drop cell exactly as in onDragOver
1349            dragPointX = x - xOffset;
1350            dragPointY = y - yOffset;
1351        } else {
1352            // Otherwise, use the exact drag coordinates
1353            dragPointX = x;
1354            dragPointY = y;
1355        }
1356
1357        // If we are dragging over a cell that contains a DropTarget that will
1358        // accept the drop, delegate to that DropTarget.
1359        final int[] cellXY = mTempCell;
1360        currentLayout.estimateDropCell(dragPointX, dragPointY, item.spanX, item.spanY, cellXY);
1361        View child = currentLayout.getChildAt(cellXY[0], cellXY[1]);
1362        if (child instanceof DropTarget) {
1363            DropTarget target = (DropTarget)child;
1364            if (target.acceptDrop(source, x, y, xOffset, yOffset, dragView, dragInfo)) {
1365                return target;
1366            }
1367        }
1368        return null;
1369    }
1370
1371    public void onDragOver(DragSource source, int x, int y, int xOffset, int yOffset,
1372            DragView dragView, Object dragInfo) {
1373
1374        final ItemInfo item = (ItemInfo)dragInfo;
1375        final CellLayout currentLayout = getCurrentDropLayout();
1376
1377        if (dragInfo instanceof LauncherAppWidgetInfo) {
1378            LauncherAppWidgetInfo widgetInfo = (LauncherAppWidgetInfo)dragInfo;
1379
1380            if (widgetInfo.spanX == -1) {
1381                // Calculate the grid spans needed to fit this widget
1382                int[] spans = currentLayout.rectToCell(widgetInfo.minWidth, widgetInfo.minHeight, null);
1383                item.spanX = spans[0];
1384                item.spanY = spans[1];
1385            }
1386        }
1387        if (currentLayout != mDragTargetLayout) {
1388            if (mDragTargetLayout != null) {
1389                mDragTargetLayout.onDragComplete();
1390            }
1391            mDragTargetLayout = currentLayout;
1392        }
1393
1394        // Find the top left corner of the item
1395        int originX = x - xOffset;
1396        int originY = y - yOffset;
1397
1398        // If not dragging from the Workspace, the size of dragView might not match the cell size
1399        if (!source.equals(this)) {
1400            // Break the drag view up into evenly sized chunks based on its spans
1401            int chunkWidth = dragView.getWidth() / item.spanX;
1402            int chunkHeight = dragView.getHeight() / item.spanY;
1403
1404            // Adjust the origin for a cell centered at the top left chunk
1405            originX += (chunkWidth - currentLayout.getCellWidth()) / 2;
1406            originY += (chunkHeight - currentLayout.getCellHeight()) / 2;
1407        }
1408
1409        final View child = (mDragInfo == null) ? null : mDragInfo.cell;
1410        currentLayout.visualizeDropLocation(child, originX, originY, item.spanX, item.spanY);
1411    }
1412
1413    public void onDragExit(DragSource source, int x, int y, int xOffset,
1414            int yOffset, DragView dragView, Object dragInfo) {
1415        if (mDragTargetLayout != null) {
1416            mDragTargetLayout.onDragComplete();
1417            mDragTargetLayout = null;
1418        }
1419    }
1420
1421    private void onDropExternal(int x, int y, Object dragInfo,
1422            CellLayout cellLayout) {
1423        onDropExternal(x, y, dragInfo, cellLayout, false);
1424    }
1425
1426    private void onDropExternal(int x, int y, Object dragInfo,
1427            CellLayout cellLayout, boolean insertAtFirst) {
1428        // Drag from somewhere else
1429        ItemInfo info = (ItemInfo) dragInfo;
1430
1431        View view = null;
1432
1433        switch (info.itemType) {
1434        case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
1435        case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
1436            if (info.container == NO_ID && info instanceof ApplicationInfo) {
1437                // Came from all apps -- make a copy
1438                info = new ShortcutInfo((ApplicationInfo) info);
1439            }
1440            view = mLauncher.createShortcut(R.layout.application, cellLayout,
1441                    (ShortcutInfo) info);
1442            break;
1443        case LauncherSettings.Favorites.ITEM_TYPE_USER_FOLDER:
1444            view = FolderIcon.fromXml(R.layout.folder_icon, mLauncher,
1445                    (ViewGroup) getChildAt(mCurrentScreen),
1446                    ((UserFolderInfo) info));
1447            break;
1448        case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
1449            cellLayout.setTagToCellInfoForPoint(x, y);
1450            mLauncher.addAppWidgetFromDrop(((LauncherAppWidgetInfo)dragInfo).providerName, cellLayout.getTag());
1451            break;
1452        default:
1453            throw new IllegalStateException("Unknown item type: "
1454                    + info.itemType);
1455        }
1456
1457        // If the view is null, it has already been added.
1458        if (view == null) {
1459            cellLayout.onDragComplete();
1460        } else {
1461            mTargetCell = estimateDropCell(x, y, 1, 1, view, cellLayout, mTargetCell);
1462            addInScreen(view, indexOfChild(cellLayout), mTargetCell[0],
1463                    mTargetCell[1], info.spanX, info.spanY, insertAtFirst);
1464            cellLayout.onDropChild(view);
1465            CellLayout.LayoutParams lp = (CellLayout.LayoutParams) view.getLayoutParams();
1466
1467            LauncherModel.addOrMoveItemInDatabase(mLauncher, info,
1468                    LauncherSettings.Favorites.CONTAINER_DESKTOP, mCurrentScreen,
1469                    lp.cellX, lp.cellY);
1470        }
1471    }
1472
1473    /**
1474     * Return the current {@link CellLayout}, correctly picking the destination
1475     * screen while a scroll is in progress.
1476     */
1477    private CellLayout getCurrentDropLayout() {
1478        int index = mScroller.isFinished() ? mCurrentScreen : mNextScreen;
1479        return (CellLayout) getChildAt(index);
1480    }
1481
1482    /**
1483     * {@inheritDoc}
1484     */
1485    public boolean acceptDrop(DragSource source, int x, int y,
1486            int xOffset, int yOffset, DragView dragView, Object dragInfo) {
1487        final CellLayout layout = getCurrentDropLayout();
1488        final CellLayout.CellInfo dragCellInfo = mDragInfo;
1489        final int spanX = dragCellInfo == null ? 1 : dragCellInfo.spanX;
1490        final int spanY = dragCellInfo == null ? 1 : dragCellInfo.spanY;
1491
1492        final View ignoreView = dragCellInfo == null ? null : dragCellInfo.cell;
1493        final CellLayout.CellInfo cellInfo = layout.updateOccupiedCells(null, ignoreView);
1494
1495        if (cellInfo.findCellForSpan(mTempEstimate, spanX, spanY)) {
1496            return true;
1497        } else {
1498            Toast.makeText(getContext(), getContext().getString(R.string.out_of_space), Toast.LENGTH_SHORT).show();
1499            return false;
1500        }
1501    }
1502
1503    /**
1504     * {@inheritDoc}
1505     */
1506    public Rect estimateDropLocation(DragSource source, int x, int y,
1507            int xOffset, int yOffset, DragView dragView, Object dragInfo, Rect recycle) {
1508        final CellLayout layout = getCurrentDropLayout();
1509
1510        final CellLayout.CellInfo cellInfo = mDragInfo;
1511        final int spanX = cellInfo == null ? 1 : cellInfo.spanX;
1512        final int spanY = cellInfo == null ? 1 : cellInfo.spanY;
1513        final View ignoreView = cellInfo == null ? null : cellInfo.cell;
1514
1515        final Rect location = recycle != null ? recycle : new Rect();
1516
1517        // Find drop cell and convert into rectangle
1518        int[] dropCell = estimateDropCell(x - xOffset, y - yOffset, spanX,
1519                spanY, ignoreView, layout, mTempCell);
1520
1521        if (dropCell == null) {
1522            return null;
1523        }
1524
1525        layout.cellToPoint(dropCell[0], dropCell[1], mTempEstimate);
1526        location.left = mTempEstimate[0];
1527        location.top = mTempEstimate[1];
1528
1529        layout.cellToPoint(dropCell[0] + spanX, dropCell[1] + spanY, mTempEstimate);
1530        location.right = mTempEstimate[0];
1531        location.bottom = mTempEstimate[1];
1532
1533        return location;
1534    }
1535
1536    /**
1537     * Calculate the nearest cell where the given object would be dropped.
1538     */
1539    private int[] estimateDropCell(int pixelX, int pixelY,
1540            int spanX, int spanY, View ignoreView, CellLayout layout, int[] recycle) {
1541
1542        final int[] cellXY = mTempCell;
1543        layout.estimateDropCell(pixelX, pixelY, spanX, spanY, cellXY);
1544        layout.cellToPoint(cellXY[0], cellXY[1], mTempEstimate);
1545
1546        final CellLayout.CellInfo cellInfo = layout.updateOccupiedCells(null, ignoreView);
1547        // Find the best target drop location
1548        return layout.findNearestVacantArea(mTempEstimate[0], mTempEstimate[1], spanX, spanY, cellInfo, recycle);
1549    }
1550
1551    /**
1552     * Estimate the size that a child with the given dimensions will take in the current screen.
1553     */
1554    void estimateChildSize(int minWidth, int minHeight, int[] result) {
1555        ((CellLayout)getChildAt(mCurrentScreen)).estimateChildSize(minWidth, minHeight, result);
1556    }
1557
1558    void setLauncher(Launcher launcher) {
1559        mLauncher = launcher;
1560    }
1561
1562    public void setDragController(DragController dragController) {
1563        mDragController = dragController;
1564    }
1565
1566    public void onDropCompleted(View target, boolean success) {
1567        if (success) {
1568            if (target != this && mDragInfo != null) {
1569                final CellLayout cellLayout = (CellLayout) getChildAt(mDragInfo.screen);
1570                cellLayout.removeView(mDragInfo.cell);
1571                if (mDragInfo.cell instanceof DropTarget) {
1572                    mDragController.removeDropTarget((DropTarget)mDragInfo.cell);
1573                }
1574                // final Object tag = mDragInfo.cell.getTag();
1575            }
1576        } else {
1577            if (mDragInfo != null) {
1578                final CellLayout cellLayout = (CellLayout) getChildAt(mDragInfo.screen);
1579                cellLayout.onDropAborted(mDragInfo.cell);
1580            }
1581        }
1582
1583        mDragInfo = null;
1584    }
1585
1586    public void scrollLeft() {
1587        if (mScroller.isFinished()) {
1588            if (mCurrentScreen > 0)
1589                snapToScreen(mCurrentScreen - 1);
1590        } else {
1591            if (mNextScreen > 0)
1592                snapToScreen(mNextScreen - 1);
1593        }
1594    }
1595
1596    public void scrollRight() {
1597        if (mScroller.isFinished()) {
1598            if (mCurrentScreen < getChildCount() - 1)
1599                snapToScreen(mCurrentScreen + 1);
1600        } else {
1601            if (mNextScreen < getChildCount() - 1)
1602                snapToScreen(mNextScreen + 1);
1603        }
1604    }
1605
1606    public int getScreenForView(View v) {
1607        int result = -1;
1608        if (v != null) {
1609            ViewParent vp = v.getParent();
1610            final int screenCount = getChildCount();
1611            for (int i = 0; i < screenCount; i++) {
1612                if (vp == getChildAt(i)) {
1613                    return i;
1614                }
1615            }
1616        }
1617        return result;
1618    }
1619
1620    public Folder getFolderForTag(Object tag) {
1621        final int screenCount = getChildCount();
1622        for (int screen = 0; screen < screenCount; screen++) {
1623            CellLayout currentScreen = ((CellLayout) getChildAt(screen));
1624            int count = currentScreen.getChildCount();
1625            for (int i = 0; i < count; i++) {
1626                View child = currentScreen.getChildAt(i);
1627                CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
1628                if (lp.cellHSpan == 4 && lp.cellVSpan == 4 && child instanceof Folder) {
1629                    Folder f = (Folder) child;
1630                    if (f.getInfo() == tag && f.getInfo().opened) {
1631                        return f;
1632                    }
1633                }
1634            }
1635        }
1636        return null;
1637    }
1638
1639    public View getViewForTag(Object tag) {
1640        int screenCount = getChildCount();
1641        for (int screen = 0; screen < screenCount; screen++) {
1642            CellLayout currentScreen = ((CellLayout) getChildAt(screen));
1643            int count = currentScreen.getChildCount();
1644            for (int i = 0; i < count; i++) {
1645                View child = currentScreen.getChildAt(i);
1646                if (child.getTag() == tag) {
1647                    return child;
1648                }
1649            }
1650        }
1651        return null;
1652    }
1653
1654    /**
1655     * @return True is long presses are still allowed for the current touch
1656     */
1657    public boolean allowLongPress() {
1658        return mAllowLongPress;
1659    }
1660
1661    /**
1662     * Set true to allow long-press events to be triggered, usually checked by
1663     * {@link Launcher} to accept or block dpad-initiated long-presses.
1664     */
1665    public void setAllowLongPress(boolean allowLongPress) {
1666        mAllowLongPress = allowLongPress;
1667    }
1668
1669    void removeItems(final ArrayList<ApplicationInfo> apps) {
1670        final int screenCount = getChildCount();
1671        final PackageManager manager = getContext().getPackageManager();
1672        final AppWidgetManager widgets = AppWidgetManager.getInstance(getContext());
1673
1674        final HashSet<String> packageNames = new HashSet<String>();
1675        final int appCount = apps.size();
1676        for (int i = 0; i < appCount; i++) {
1677            packageNames.add(apps.get(i).componentName.getPackageName());
1678        }
1679
1680        for (int i = 0; i < screenCount; i++) {
1681            final CellLayout layout = (CellLayout) getChildAt(i);
1682
1683            // Avoid ANRs by treating each screen separately
1684            post(new Runnable() {
1685                public void run() {
1686                    final ArrayList<View> childrenToRemove = new ArrayList<View>();
1687                    childrenToRemove.clear();
1688
1689                    int childCount = layout.getChildCount();
1690                    for (int j = 0; j < childCount; j++) {
1691                        final View view = layout.getChildAt(j);
1692                        Object tag = view.getTag();
1693
1694                        if (tag instanceof ShortcutInfo) {
1695                            final ShortcutInfo info = (ShortcutInfo) tag;
1696                            final Intent intent = info.intent;
1697                            final ComponentName name = intent.getComponent();
1698
1699                            if (Intent.ACTION_MAIN.equals(intent.getAction()) && name != null) {
1700                                for (String packageName: packageNames) {
1701                                    if (packageName.equals(name.getPackageName())) {
1702                                        // TODO: This should probably be done on a worker thread
1703                                        LauncherModel.deleteItemFromDatabase(mLauncher, info);
1704                                        childrenToRemove.add(view);
1705                                    }
1706                                }
1707                            }
1708                        } else if (tag instanceof UserFolderInfo) {
1709                            final UserFolderInfo info = (UserFolderInfo) tag;
1710                            final ArrayList<ShortcutInfo> contents = info.contents;
1711                            final ArrayList<ShortcutInfo> toRemove = new ArrayList<ShortcutInfo>(1);
1712                            final int contentsCount = contents.size();
1713                            boolean removedFromFolder = false;
1714
1715                            for (int k = 0; k < contentsCount; k++) {
1716                                final ShortcutInfo appInfo = contents.get(k);
1717                                final Intent intent = appInfo.intent;
1718                                final ComponentName name = intent.getComponent();
1719
1720                                if (Intent.ACTION_MAIN.equals(intent.getAction()) && name != null) {
1721                                    for (String packageName: packageNames) {
1722                                        if (packageName.equals(name.getPackageName())) {
1723                                            toRemove.add(appInfo);
1724                                            // TODO: This should probably be done on a worker thread
1725                                            LauncherModel.deleteItemFromDatabase(
1726                                                    mLauncher, appInfo);
1727                                            removedFromFolder = true;
1728                                        }
1729                                    }
1730                                }
1731                            }
1732
1733                            contents.removeAll(toRemove);
1734                            if (removedFromFolder) {
1735                                final Folder folder = getOpenFolder();
1736                                if (folder != null)
1737                                    folder.notifyDataSetChanged();
1738                            }
1739                        } else if (tag instanceof LiveFolderInfo) {
1740                            final LiveFolderInfo info = (LiveFolderInfo) tag;
1741                            final Uri uri = info.uri;
1742                            final ProviderInfo providerInfo = manager.resolveContentProvider(
1743                                    uri.getAuthority(), 0);
1744
1745                            if (providerInfo != null) {
1746                                for (String packageName: packageNames) {
1747                                    if (packageName.equals(providerInfo.packageName)) {
1748                                        // TODO: This should probably be done on a worker thread
1749                                        LauncherModel.deleteItemFromDatabase(mLauncher, info);
1750                                        childrenToRemove.add(view);
1751                                    }
1752                                }
1753                            }
1754                        } else if (tag instanceof LauncherAppWidgetInfo) {
1755                            final LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) tag;
1756                            final AppWidgetProviderInfo provider =
1757                                    widgets.getAppWidgetInfo(info.appWidgetId);
1758                            if (provider != null) {
1759                                for (String packageName: packageNames) {
1760                                    if (packageName.equals(provider.provider.getPackageName())) {
1761                                        // TODO: This should probably be done on a worker thread
1762                                        LauncherModel.deleteItemFromDatabase(mLauncher, info);
1763                                        childrenToRemove.add(view);
1764                                    }
1765                                }
1766                            }
1767                        }
1768                    }
1769
1770                    childCount = childrenToRemove.size();
1771                    for (int j = 0; j < childCount; j++) {
1772                        View child = childrenToRemove.get(j);
1773                        layout.removeViewInLayout(child);
1774                        if (child instanceof DropTarget) {
1775                            mDragController.removeDropTarget((DropTarget)child);
1776                        }
1777                    }
1778
1779                    if (childCount > 0) {
1780                        layout.requestLayout();
1781                        layout.invalidate();
1782                    }
1783                }
1784            });
1785        }
1786    }
1787
1788    void updateShortcuts(ArrayList<ApplicationInfo> apps) {
1789        final PackageManager pm = mLauncher.getPackageManager();
1790
1791        final int screenCount = getChildCount();
1792        for (int i = 0; i < screenCount; i++) {
1793            final CellLayout layout = (CellLayout) getChildAt(i);
1794            int childCount = layout.getChildCount();
1795            for (int j = 0; j < childCount; j++) {
1796                final View view = layout.getChildAt(j);
1797                Object tag = view.getTag();
1798                if (tag instanceof ShortcutInfo) {
1799                    ShortcutInfo info = (ShortcutInfo)tag;
1800                    // We need to check for ACTION_MAIN otherwise getComponent() might
1801                    // return null for some shortcuts (for instance, for shortcuts to
1802                    // web pages.)
1803                    final Intent intent = info.intent;
1804                    final ComponentName name = intent.getComponent();
1805                    if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION &&
1806                            Intent.ACTION_MAIN.equals(intent.getAction()) && name != null) {
1807                        final int appCount = apps.size();
1808                        for (int k = 0; k < appCount; k++) {
1809                            ApplicationInfo app = apps.get(k);
1810                            if (app.componentName.equals(name)) {
1811                                info.setIcon(mIconCache.getIcon(info.intent));
1812                                ((TextView)view).setCompoundDrawablesWithIntrinsicBounds(null,
1813                                        new FastBitmapDrawable(info.getIcon(mIconCache)),
1814                                        null, null);
1815                                }
1816                        }
1817                    }
1818                }
1819            }
1820        }
1821    }
1822
1823    void moveToDefaultScreen(boolean animate) {
1824        if (animate) {
1825            if (mIsSmall) {
1826                unshrink(mDefaultScreen);
1827            } else {
1828                snapToScreen(mDefaultScreen);
1829            }
1830        } else {
1831            setCurrentScreen(mDefaultScreen);
1832        }
1833        getChildAt(mDefaultScreen).requestFocus();
1834    }
1835
1836    void setIndicators(Drawable previous, Drawable next) {
1837        mPreviousIndicator = previous;
1838        mNextIndicator = next;
1839        previous.setLevel(mCurrentScreen);
1840        next.setLevel(mCurrentScreen);
1841    }
1842
1843    public static class SavedState extends BaseSavedState {
1844        int currentScreen = -1;
1845
1846        SavedState(Parcelable superState) {
1847            super(superState);
1848        }
1849
1850        private SavedState(Parcel in) {
1851            super(in);
1852            currentScreen = in.readInt();
1853        }
1854
1855        @Override
1856        public void writeToParcel(Parcel out, int flags) {
1857            super.writeToParcel(out, flags);
1858            out.writeInt(currentScreen);
1859        }
1860
1861        public static final Parcelable.Creator<SavedState> CREATOR =
1862                new Parcelable.Creator<SavedState>() {
1863            public SavedState createFromParcel(Parcel in) {
1864                return new SavedState(in);
1865            }
1866
1867            public SavedState[] newArray(int size) {
1868                return new SavedState[size];
1869            }
1870        };
1871    }
1872}
1873