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