Workspace.java revision 9c4949e12c909d5e01d24386147b1c528015b31b
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.animation.Animator;
22import android.animation.Animator.AnimatorListener;
23import android.animation.AnimatorSet;
24import android.animation.ObjectAnimator;
25import android.animation.PropertyValuesHolder;
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.Camera;
37import android.graphics.Canvas;
38import android.graphics.Color;
39import android.graphics.Matrix;
40import android.graphics.Rect;
41import android.graphics.drawable.Drawable;
42import android.net.Uri;
43import android.os.IBinder;
44import android.os.Parcelable;
45import android.util.AttributeSet;
46import android.util.Log;
47import android.view.MotionEvent;
48import android.view.View;
49import android.widget.TextView;
50
51import java.util.ArrayList;
52import java.util.HashSet;
53
54/**
55 * The workspace is a wide area with a wallpaper and a finite number of pages.
56 * Each page contains a number of icons, folders or widgets the user can
57 * interact with. A workspace is meant to be used with a fixed width only.
58 */
59public class Workspace extends SmoothPagedView
60        implements DropTarget, DragSource, DragScroller, View.OnTouchListener {
61    @SuppressWarnings({"UnusedDeclaration"})
62    private static final String TAG = "Launcher.Workspace";
63
64    // This is how much the workspace shrinks when we enter all apps or
65    // customization mode
66    private static final float SHRINK_FACTOR = 0.16f;
67
68    // Y rotation to apply to the workspace screens
69    private static final float WORKSPACE_ROTATION = 12.5f;
70
71    // These are extra scale factors to apply to the mini home screens
72    // so as to achieve the desired transform
73    private static final float EXTRA_SCALE_FACTOR_0 = 0.97f;
74    private static final float EXTRA_SCALE_FACTOR_1 = 1.0f;
75    private static final float EXTRA_SCALE_FACTOR_2 = 1.08f;
76
77    private static final int BACKGROUND_FADE_OUT_DELAY = 300;
78    private static final int BACKGROUND_FADE_OUT_DURATION = 300;
79    private static final int BACKGROUND_FADE_IN_DURATION = 100;
80
81    static final int SCROLL_RIGHT = 0;
82    static final int SCROLL_LEFT = 1;
83
84    // These animators are used to fade the
85    private ObjectAnimator<Float> mBackgroundFadeIn;
86    private ObjectAnimator<Float> mBackgroundFadeOut;
87    private float mBackgroundAlpha = 0;
88
89    private enum ShrinkPosition { SHRINK_TO_TOP, SHRINK_TO_MIDDLE, SHRINK_TO_BOTTOM };
90
91    private final WallpaperManager mWallpaperManager;
92
93    private int mDefaultPage;
94
95    private boolean mWaitingToShrinkToBottom = false;
96
97    /**
98     * CellInfo for the cell that is currently being dragged
99     */
100    private CellLayout.CellInfo mDragInfo;
101
102    /**
103     * Target drop area calculated during last acceptDrop call.
104     */
105    private int[] mTargetCell = null;
106
107    /**
108     * The CellLayout that is currently being dragged over
109     */
110    private CellLayout mDragTargetLayout = null;
111
112    private Launcher mLauncher;
113    private IconCache mIconCache;
114    private DragController mDragController;
115
116    private int[] mTempCell = new int[2];
117    private int[] mTempEstimate = new int[2];
118    private float[] mTempOriginXY = new float[2];
119    private float[] mTempDragCoordinates = new float[2];
120    private float[] mTempDragBottomRightCoordinates = new float[2];
121    private Matrix mTempInverseMatrix = new Matrix();
122
123    private static final int DEFAULT_CELL_COUNT_X = 4;
124    private static final int DEFAULT_CELL_COUNT_Y = 4;
125
126    private Drawable mPreviousIndicator;
127    private Drawable mNextIndicator;
128
129    // State variable that indicated whether the pages are small (ie when you're
130    // in all apps or customize mode)
131    private boolean mIsSmall;
132    private AnimatorListener mUnshrinkAnimationListener;
133
134    /**
135     * Used to inflate the Workspace from XML.
136     *
137     * @param context The application's context.
138     * @param attrs The attributes set containing the Workspace's customization values.
139     */
140    public Workspace(Context context, AttributeSet attrs) {
141        this(context, attrs, 0);
142    }
143
144    /**
145     * Used to inflate the Workspace from XML.
146     *
147     * @param context The application's context.
148     * @param attrs The attributes set containing the Workspace's customization values.
149     * @param defStyle Unused.
150     */
151    public Workspace(Context context, AttributeSet attrs, int defStyle) {
152        super(context, attrs, defStyle);
153        mContentIsRefreshable = false;
154
155        if (!LauncherApplication.isScreenXLarge()) {
156            mFadeInAdjacentScreens = false;
157        }
158
159        mWallpaperManager = WallpaperManager.getInstance(context);
160
161        TypedArray a = context.obtainStyledAttributes(attrs,
162                R.styleable.Workspace, defStyle, 0);
163        int cellCountX = a.getInt(R.styleable.Workspace_cellCountX, DEFAULT_CELL_COUNT_X);
164        int cellCountY = a.getInt(R.styleable.Workspace_cellCountY, DEFAULT_CELL_COUNT_Y);
165        mDefaultPage = a.getInt(R.styleable.Workspace_defaultScreen, 1);
166        a.recycle();
167
168        LauncherModel.updateWorkspaceLayoutCells(cellCountX, cellCountY);
169        setHapticFeedbackEnabled(false);
170
171        initWorkspace();
172    }
173
174    /**
175     * Initializes various states for this workspace.
176     */
177    protected void initWorkspace() {
178        Context context = getContext();
179        mCurrentPage = mDefaultPage;
180        Launcher.setScreen(mCurrentPage);
181        LauncherApplication app = (LauncherApplication)context.getApplicationContext();
182        mIconCache = app.getIconCache();
183
184        mUnshrinkAnimationListener = new AnimatorListener() {
185            public void onAnimationStart(Animator animation) {}
186            public void onAnimationEnd(Animator animation) {
187                mIsSmall = false;
188            }
189            public void onAnimationCancel(Animator animation) {}
190            public void onAnimationRepeat(Animator animation) {}
191        };
192
193        mSnapVelocity = 600;
194    }
195
196    @Override
197    protected int getScrollMode() {
198        if (LauncherApplication.isScreenXLarge()) {
199            return SmoothPagedView.QUINTIC_MODE;
200        } else {
201            return SmoothPagedView.OVERSHOOT_MODE;
202        }
203    }
204
205    @Override
206    public void addView(View child, int index, LayoutParams params) {
207        if (!(child instanceof CellLayout)) {
208            throw new IllegalArgumentException("A Workspace can only have CellLayout children.");
209        }
210        super.addView(child, index, params);
211    }
212
213    @Override
214    public void addView(View child) {
215        if (!(child instanceof CellLayout)) {
216            throw new IllegalArgumentException("A Workspace can only have CellLayout children.");
217        }
218        super.addView(child);
219    }
220
221    @Override
222    public void addView(View child, int index) {
223        if (!(child instanceof CellLayout)) {
224            throw new IllegalArgumentException("A Workspace can only have CellLayout children.");
225        }
226        super.addView(child, index);
227    }
228
229    @Override
230    public void addView(View child, int width, int height) {
231        if (!(child instanceof CellLayout)) {
232            throw new IllegalArgumentException("A Workspace can only have CellLayout children.");
233        }
234        super.addView(child, width, height);
235    }
236
237    @Override
238    public void addView(View child, LayoutParams params) {
239        if (!(child instanceof CellLayout)) {
240            throw new IllegalArgumentException("A Workspace can only have CellLayout children.");
241        }
242        super.addView(child, params);
243    }
244
245    /**
246     * @return The open folder on the current screen, or null if there is none
247     */
248    Folder getOpenFolder() {
249        CellLayout currentPage = (CellLayout) getChildAt(mCurrentPage);
250        int count = currentPage.getChildCount();
251        for (int i = 0; i < count; i++) {
252            View child = currentPage.getChildAt(i);
253            if (child instanceof Folder) {
254                Folder folder = (Folder) child;
255                if (folder.getInfo().opened)
256                    return folder;
257            }
258        }
259        return null;
260    }
261
262    ArrayList<Folder> getOpenFolders() {
263        final int screenCount = getChildCount();
264        ArrayList<Folder> folders = new ArrayList<Folder>(screenCount);
265
266        for (int screen = 0; screen < screenCount; screen++) {
267            CellLayout currentPage = (CellLayout) getChildAt(screen);
268            int count = currentPage.getChildCount();
269            for (int i = 0; i < count; i++) {
270                View child = currentPage.getChildAt(i);
271                if (child instanceof Folder) {
272                    Folder folder = (Folder) child;
273                    if (folder.getInfo().opened)
274                        folders.add(folder);
275                    break;
276                }
277            }
278        }
279
280        return folders;
281    }
282
283    boolean isDefaultPageShowing() {
284        return mCurrentPage == mDefaultPage;
285    }
286
287    /**
288     * Sets the current screen.
289     *
290     * @param currentPage
291     */
292    @Override
293    void setCurrentPage(int currentPage) {
294        super.setCurrentPage(currentPage);
295        updateWallpaperOffset(mScrollX);
296    }
297
298    /**
299     * Adds the specified child in the specified screen. The position and dimension of
300     * the child are defined by x, y, spanX and spanY.
301     *
302     * @param child The child to add in one of the workspace's screens.
303     * @param screen The screen in which to add the child.
304     * @param x The X position of the child in the screen's grid.
305     * @param y The Y position of the child in the screen's grid.
306     * @param spanX The number of cells spanned horizontally by the child.
307     * @param spanY The number of cells spanned vertically by the child.
308     */
309    void addInScreen(View child, int screen, int x, int y, int spanX, int spanY) {
310        addInScreen(child, screen, x, y, spanX, spanY, false);
311    }
312
313    void addInFullScreen(View child, int screen) {
314        addInScreen(child, screen, 0, 0, -1, -1);
315    }
316
317    /**
318     * Adds the specified child in the specified screen. The position and dimension of
319     * the child are defined by x, y, spanX and spanY.
320     *
321     * @param child The child to add in one of the workspace's screens.
322     * @param screen The screen in which to add the child.
323     * @param x The X position of the child in the screen's grid.
324     * @param y The Y position of the child in the screen's grid.
325     * @param spanX The number of cells spanned horizontally by the child.
326     * @param spanY The number of cells spanned vertically by the child.
327     * @param insert When true, the child is inserted at the beginning of the children list.
328     */
329    void addInScreen(View child, int screen, int x, int y, int spanX, int spanY, boolean insert) {
330        if (screen < 0 || screen >= getChildCount()) {
331            Log.e(TAG, "The screen must be >= 0 and < " + getChildCount()
332                + " (was " + screen + "); skipping child");
333            return;
334        }
335
336        final CellLayout group = (CellLayout) getChildAt(screen);
337        CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
338        if (lp == null) {
339            lp = new CellLayout.LayoutParams(x, y, spanX, spanY);
340        } else {
341            lp.cellX = x;
342            lp.cellY = y;
343            lp.cellHSpan = spanX;
344            lp.cellVSpan = spanY;
345        }
346
347        // Get the canonical child id to uniquely represent this view in this screen
348        int childId = LauncherModel.getCellLayoutChildId(-1, screen, x, y, spanX, spanY);
349        if (!group.addViewToCellLayout(child, insert ? 0 : -1, childId, lp)) {
350            // TODO: This branch occurs when the workspace is adding views
351            // outside of the defined grid
352            // maybe we should be deleting these items from the LauncherModel?
353            Log.w(TAG, "Failed to add to item at (" + lp.cellX + "," + lp.cellY + ") to CellLayout");
354        }
355
356        if (!(child instanceof Folder)) {
357            child.setHapticFeedbackEnabled(false);
358            child.setOnLongClickListener(mLongClickListener);
359        }
360        if (child instanceof DropTarget) {
361            mDragController.addDropTarget((DropTarget) child);
362        }
363    }
364
365    public boolean onTouch(View v, MotionEvent event) {
366        // this is an intercepted event being forwarded from a cell layout
367        if (mIsSmall) {
368            mLauncher.onWorkspaceClick((CellLayout) v);
369            return true;
370        }
371        return false;
372    }
373
374    @Override
375    public boolean dispatchUnhandledMove(View focused, int direction) {
376        if (mIsSmall) {
377            // when the home screens are shrunken, shouldn't allow side-scrolling
378            return false;
379        }
380        return super.dispatchUnhandledMove(focused, direction);
381    }
382
383    @Override
384    public boolean onInterceptTouchEvent(MotionEvent ev) {
385        if (mIsSmall) {
386            // when the home screens are shrunken, shouldn't allow side-scrolling
387            return false;
388        }
389        return super.onInterceptTouchEvent(ev);
390    }
391
392    protected void pageBeginMoving() {
393        if (mNextPage != INVALID_PAGE) {
394            // we're snapping to a particular screen
395            enableChildrenCache(mCurrentPage, mNextPage);
396        } else {
397            // this is when user is actively dragging a particular screen, they might
398            // swipe it either left or right (but we won't advance by more than one screen)
399            enableChildrenCache(mCurrentPage - 1, mCurrentPage + 1);
400        }
401        showOutlines();
402    }
403
404    protected void pageEndMoving() {
405        clearChildrenCache();
406        hideOutlines();
407    }
408
409    @Override
410    protected void notifyPageSwitchListener() {
411        super.notifyPageSwitchListener();
412
413        if (mPreviousIndicator != null) {
414            // if we know the next page, we show the indication for it right away; it looks
415            // weird if the indicators are lagging
416            int page = mNextPage;
417            if (page == INVALID_PAGE) {
418                page = mCurrentPage;
419            }
420            mPreviousIndicator.setLevel(page);
421            mNextIndicator.setLevel(page);
422        }
423        Launcher.setScreen(mCurrentPage);
424    };
425
426    private void updateWallpaperOffset() {
427        updateWallpaperOffset(getChildAt(getChildCount() - 1).getRight() - (mRight - mLeft));
428    }
429
430    private void updateWallpaperOffset(int scrollRange) {
431        final boolean isStaticWallpaper = (mWallpaperManager != null) &&
432                (mWallpaperManager.getWallpaperInfo() == null);
433        if (LauncherApplication.isScreenXLarge() && !isStaticWallpaper) {
434            IBinder token = getWindowToken();
435            if (token != null) {
436                mWallpaperManager.setWallpaperOffsetSteps(1.0f / (getChildCount() - 1), 0 );
437                mWallpaperManager.setWallpaperOffsets(getWindowToken(),
438                        Math.max(0.f, Math.min(mScrollX/(float)scrollRange, 1.f)), 0);
439            }
440        }
441    }
442
443    private float getScaleXForRotation(float degrees) {
444        return (float) (1.0f / Math.cos(Math.PI * degrees / 180.0f));
445    }
446
447    public void showOutlines() {
448        if (mBackgroundFadeOut != null) mBackgroundFadeOut.cancel();
449        if (mBackgroundFadeIn != null) mBackgroundFadeIn.cancel();
450        mBackgroundFadeIn = new ObjectAnimator<Float>(BACKGROUND_FADE_IN_DURATION, this,
451                        new PropertyValuesHolder<Float>("backgroundAlpha", 1.0f));
452        mBackgroundFadeIn.start();
453    }
454
455    public void hideOutlines() {
456        if (mBackgroundFadeIn != null) mBackgroundFadeIn.cancel();
457        if (mBackgroundFadeOut != null) mBackgroundFadeOut.cancel();
458        mBackgroundFadeOut = new ObjectAnimator<Float>(BACKGROUND_FADE_OUT_DURATION, this,
459                        new PropertyValuesHolder<Float>("backgroundAlpha", 0.0f));
460        mBackgroundFadeOut.setStartDelay(BACKGROUND_FADE_OUT_DELAY);
461        mBackgroundFadeOut.start();
462    }
463
464    public void setBackgroundAlpha(float alpha) {
465        mBackgroundAlpha = alpha;
466        for (int i = 0; i < getChildCount(); i++) {
467            CellLayout cl = (CellLayout) getChildAt(i);
468            cl.setBackgroundAlpha(alpha);
469        }
470    }
471
472    public float getBackgroundAlpha() {
473        return mBackgroundAlpha;
474    }
475
476    @Override
477    protected void screenScrolled(int screenCenter) {
478        CellLayout cur = (CellLayout) getChildAt(mCurrentPage);
479        CellLayout toRight = (CellLayout) getChildAt(mCurrentPage + 1);
480        CellLayout toLeft = (CellLayout) getChildAt(mCurrentPage - 1);
481
482        for (int i = 0; i < mCurrentPage - 1; i++) {
483            View v = getChildAt(i);
484            if (v != null) {
485                v.setRotationY(WORKSPACE_ROTATION);
486                v.setScaleX(getScaleXForRotation(WORKSPACE_ROTATION));
487            }
488        }
489        for (int i = mCurrentPage + 1; i < getChildCount(); i++) {
490            View v = getChildAt(i);
491            if (v != null) {
492                v.setRotationY(-WORKSPACE_ROTATION);
493                v.setScaleX(getScaleXForRotation(-WORKSPACE_ROTATION));
494            }
495        }
496
497        int halfScreenSize = getMeasuredWidth() / 2;
498        int pageWidth = cur.getMeasuredWidth();
499        int delta = screenCenter - (getChildOffset(mCurrentPage) -
500                getRelativeChildOffset(mCurrentPage) + halfScreenSize);
501
502        float scrollProgress = Math.abs(delta/(pageWidth*1.0f + mPageSpacing));
503        int scrollDirection = delta > 0 ? SCROLL_LEFT : SCROLL_RIGHT;
504
505        float rotation;
506
507        if (scrollDirection == SCROLL_RIGHT) {
508            rotation = -scrollProgress * WORKSPACE_ROTATION;
509            cur.setRotationY(rotation);
510            cur.setScaleX(getScaleXForRotation(rotation));
511
512            if (toLeft != null) {
513                rotation = WORKSPACE_ROTATION * (1 - scrollProgress);
514                toLeft.setRotationY(rotation);
515                toLeft.setScaleX(getScaleXForRotation(rotation));
516            }
517            if (toRight != null) {
518                toRight.setRotationY(-WORKSPACE_ROTATION);
519                toRight.setScaleX(getScaleXForRotation(WORKSPACE_ROTATION));
520            }
521        } else {
522            rotation = scrollProgress * WORKSPACE_ROTATION;
523            cur.setRotationY(rotation);
524            cur.setScaleX(getScaleXForRotation(rotation));
525
526            if (toRight != null) {
527                rotation = -WORKSPACE_ROTATION * (1 - scrollProgress);
528                toRight.setRotationY(rotation);
529                toRight.setScaleX(getScaleXForRotation(rotation));
530            }
531            if (toLeft != null) {
532                toLeft.setRotationY(WORKSPACE_ROTATION);
533                toLeft.setScaleX(getScaleXForRotation(WORKSPACE_ROTATION));
534            }
535        }
536    }
537
538    protected void onAttachedToWindow() {
539        super.onAttachedToWindow();
540        computeScroll();
541        mDragController.setWindowToken(getWindowToken());
542    }
543
544    @Override
545    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
546        super.onLayout(changed, left, top, right, bottom);
547
548        // if shrinkToBottom() is called on initialization, it has to be deferred
549        // until after the first call to onLayout so that it has the correct width
550        if (mWaitingToShrinkToBottom) {
551            shrinkToBottom(false);
552            mWaitingToShrinkToBottom = false;
553        }
554
555        if (LauncherApplication.isInPlaceRotationEnabled()) {
556            // When the device is rotated, the scroll position of the current screen
557            // needs to be refreshed
558            setCurrentPage(getCurrentPage());
559        }
560    }
561
562    @Override
563    protected void dispatchDraw(Canvas canvas) {
564        if (mIsSmall) {
565            // Draw all the workspaces if we're small
566            final int pageCount = getChildCount();
567            final long drawingTime = getDrawingTime();
568            for (int i = 0; i < pageCount; i++) {
569                final View page = (View) getChildAt(i);
570
571                drawChild(canvas, page, drawingTime);
572            }
573        } else {
574            super.dispatchDraw(canvas);
575        }
576    }
577
578    @Override
579    protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
580        if (!mLauncher.isAllAppsVisible()) {
581            final Folder openFolder = getOpenFolder();
582            if (openFolder != null) {
583                return openFolder.requestFocus(direction, previouslyFocusedRect);
584            } else {
585                return super.onRequestFocusInDescendants(direction, previouslyFocusedRect);
586            }
587        }
588        return false;
589    }
590
591    @Override
592    public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
593        if (!mLauncher.isAllAppsVisible()) {
594            final Folder openFolder = getOpenFolder();
595            if (openFolder != null) {
596                openFolder.addFocusables(views, direction);
597            } else {
598                super.addFocusables(views, direction, focusableMode);
599            }
600        }
601    }
602
603    @Override
604    public boolean dispatchTouchEvent(MotionEvent ev) {
605        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
606            // (In XLarge mode, the workspace is shrunken below all apps, and responds to taps
607            // ie when you click on a mini-screen, it zooms back to that screen)
608            if (!LauncherApplication.isScreenXLarge() && mLauncher.isAllAppsVisible()) {
609                return false;
610            }
611        }
612        return super.dispatchTouchEvent(ev);
613    }
614
615    void enableChildrenCache(int fromPage, int toPage) {
616        if (fromPage > toPage) {
617            final int temp = fromPage;
618            fromPage = toPage;
619            toPage = temp;
620        }
621
622        final int screenCount = getChildCount();
623
624        fromPage = Math.max(fromPage, 0);
625        toPage = Math.min(toPage, screenCount - 1);
626
627        for (int i = fromPage; i <= toPage; i++) {
628            final CellLayout layout = (CellLayout) getChildAt(i);
629            layout.setChildrenDrawnWithCacheEnabled(true);
630            layout.setChildrenDrawingCacheEnabled(true);
631        }
632    }
633
634    void clearChildrenCache() {
635        final int screenCount = getChildCount();
636        for (int i = 0; i < screenCount; i++) {
637            final CellLayout layout = (CellLayout) getChildAt(i);
638            layout.setChildrenDrawnWithCacheEnabled(false);
639        }
640    }
641
642    @Override
643    public boolean onTouchEvent(MotionEvent ev) {
644        if (mLauncher.isAllAppsVisible()) {
645            // Cancel any scrolling that is in progress.
646            if (!mScroller.isFinished()) {
647                mScroller.abortAnimation();
648            }
649            snapToPage(mCurrentPage);
650            return false; // We don't want the events.  Let them fall through to the all apps view.
651        }
652
653        return super.onTouchEvent(ev);
654    }
655
656    public boolean isSmall() {
657        return mIsSmall;
658    }
659
660    void shrinkToTop(boolean animated) {
661        shrink(ShrinkPosition.SHRINK_TO_TOP, animated);
662    }
663
664    void shrinkToMiddle() {
665        shrink(ShrinkPosition.SHRINK_TO_MIDDLE, true);
666    }
667
668    void shrinkToBottom() {
669        shrinkToBottom(true);
670    }
671
672    void shrinkToBottom(boolean animated) {
673        if (mFirstLayout) {
674            // (mFirstLayout == "first layout has not happened yet")
675            // if we get a call to shrink() as part of our initialization (for example, if
676            // Launcher is started in All Apps mode) then we need to wait for a layout call
677            // to get our width so we can layout the mini-screen views correctly
678            mWaitingToShrinkToBottom = true;
679        } else {
680            shrink(ShrinkPosition.SHRINK_TO_BOTTOM, animated);
681        }
682    }
683
684    private float getYScaleForScreen(int screen) {
685        int x = Math.abs(screen - 2);
686
687        // TODO: This should be generalized for use with arbitrary rotation angles.
688        switch(x) {
689            case 0: return EXTRA_SCALE_FACTOR_0;
690            case 1: return EXTRA_SCALE_FACTOR_1;
691            case 2: return EXTRA_SCALE_FACTOR_2;
692        }
693        return 1.0f;
694    }
695
696    // we use this to shrink the workspace for the all apps view and the customize view
697    private void shrink(ShrinkPosition shrinkPosition, boolean animated) {
698        mIsSmall = true;
699        // we intercept and reject all touch events when we're small, so be sure to reset the state
700        mTouchState = TOUCH_STATE_REST;
701        mActivePointerId = INVALID_POINTER;
702
703        final Resources res = getResources();
704        final int screenWidth = getWidth();
705        final int screenHeight = getHeight();
706
707        // Making the assumption that all pages have the same width as the 0th
708        final int pageWidth = getChildAt(0).getMeasuredWidth();
709        final int pageHeight = getChildAt(0).getMeasuredHeight();
710
711        final int scaledPageWidth = (int) (SHRINK_FACTOR * pageWidth);
712        final int scaledPageHeight = (int) (SHRINK_FACTOR * pageHeight);
713        final float extraScaledSpacing = res.getDimension(R.dimen.smallScreenExtraSpacing);
714
715        final int screenCount = getChildCount();
716        float totalWidth = screenCount * scaledPageWidth + (screenCount - 1) * extraScaledSpacing;
717
718        float newY = getResources().getDimension(R.dimen.smallScreenVerticalMargin);
719        if (shrinkPosition == ShrinkPosition.SHRINK_TO_BOTTOM) {
720            newY = screenHeight - newY - scaledPageHeight;
721        } else if (shrinkPosition == ShrinkPosition.SHRINK_TO_MIDDLE) {
722            newY = screenHeight / 2 - scaledPageHeight / 2;
723        }
724
725        // We animate all the screens to the centered position in workspace
726        // At the same time, the screens become greyed/dimmed
727
728        // newX is initialized to the left-most position of the centered screens
729        float newX = mScroller.getFinalX() + screenWidth / 2 - totalWidth / 2;
730
731        // We are going to scale about the center of the view, so we need to adjust the positions
732        // of the views accordingly
733        newX -= (pageWidth - scaledPageWidth) / 2.0f;
734        newY -= (pageHeight - scaledPageHeight) / 2.0f;
735        for (int i = 0; i < screenCount; i++) {
736            CellLayout cl = (CellLayout) getChildAt(i);
737
738            float rotation = (-i + 2) * WORKSPACE_ROTATION;
739            float rotationScaleX = (float) (1.0f / Math.cos(Math.PI * rotation / 180.0f));
740            float rotationScaleY = getYScaleForScreen(i);
741
742            if (animated) {
743                final int duration = res.getInteger(R.integer.config_workspaceShrinkTime);
744                new ObjectAnimator<Float>(duration, cl,
745                        new PropertyValuesHolder<Float>("x", newX),
746                        new PropertyValuesHolder<Float>("y", newY),
747                        new PropertyValuesHolder<Float>("scaleX", SHRINK_FACTOR * rotationScaleX),
748                        new PropertyValuesHolder<Float>("scaleY", SHRINK_FACTOR * rotationScaleY),
749                        new PropertyValuesHolder<Float>("backgroundAlpha", 1.0f),
750                        new PropertyValuesHolder<Float>("alpha", 1.0f),
751                        new PropertyValuesHolder<Float>("rotationY", rotation)).start();
752            } else {
753                cl.setX((int)newX);
754                cl.setY((int)newY);
755                cl.setScaleX(SHRINK_FACTOR * rotationScaleX);
756                cl.setScaleY(SHRINK_FACTOR * rotationScaleY);
757                cl.setBackgroundAlpha(1.0f);
758                cl.setAlpha(0.0f);
759                cl.setRotationY(rotation);
760            }
761            // increment newX for the next screen
762            newX += scaledPageWidth + extraScaledSpacing;
763            cl.setOnInterceptTouchListener(this);
764        }
765        setChildrenDrawnWithCacheEnabled(true);
766    }
767
768    // We call this when we trigger an unshrink by clicking on the CellLayout cl
769    public void unshrink(CellLayout clThatWasClicked) {
770        int newCurrentPage = mCurrentPage;
771        final int screenCount = getChildCount();
772        for (int i = 0; i < screenCount; i++) {
773            if (getChildAt(i) == clThatWasClicked) {
774                newCurrentPage = i;
775            }
776        }
777        unshrink(newCurrentPage);
778    }
779
780    private void unshrink(int newCurrentPage) {
781        if (mIsSmall) {
782            int newX = getChildOffset(newCurrentPage) - getRelativeChildOffset(newCurrentPage);
783            int delta = newX - mScrollX;
784
785            final int screenCount = getChildCount();
786            for (int i = 0; i < screenCount; i++) {
787                CellLayout cl = (CellLayout) getChildAt(i);
788                cl.setX(cl.getX() + delta);
789            }
790            snapToPage(newCurrentPage);
791            unshrink();
792
793            setCurrentPage(newCurrentPage);
794        }
795    }
796
797    void unshrink() {
798        unshrink(true);
799    }
800
801    void unshrink(boolean animated) {
802        if (mIsSmall) {
803            AnimatorSet s = new AnimatorSet();
804            final int screenCount = getChildCount();
805
806            final int duration = getResources().getInteger(R.integer.config_workspaceUnshrinkTime);
807            for (int i = 0; i < screenCount; i++) {
808                final CellLayout cl = (CellLayout)getChildAt(i);
809                float finalAlphaValue = (i == mCurrentPage) ? 1.0f : 0.0f;
810                float rotation = 0.0f;
811
812                if (i < mCurrentPage) {
813                    rotation = WORKSPACE_ROTATION;
814                } else if (i > mCurrentPage) {
815                    rotation = -WORKSPACE_ROTATION;
816                }
817
818                if (animated) {
819                    s.playTogether(
820                            new ObjectAnimator<Float>(duration, cl, "translationX", 0.0f),
821                            new ObjectAnimator<Float>(duration, cl, "translationY", 0.0f),
822                            new ObjectAnimator<Float>(duration, cl, "scaleX", 1.0f),
823                            new ObjectAnimator<Float>(duration, cl, "scaleY", 1.0f),
824                            new ObjectAnimator<Float>(duration, cl, "backgroundAlpha", 0.0f),
825                            new ObjectAnimator<Float>(duration, cl, "alpha", finalAlphaValue),
826                            new ObjectAnimator<Float>(duration, cl, "rotationY", rotation));
827                } else {
828                    cl.setTranslationX(0.0f);
829                    cl.setTranslationY(0.0f);
830                    cl.setScaleX(1.0f);
831                    cl.setScaleY(1.0f);
832                    cl.setBackgroundAlpha(0.0f);
833                    cl.setAlpha(finalAlphaValue);
834                    cl.setRotationY(rotation);
835                }
836            }
837            s.addListener(mUnshrinkAnimationListener);
838            s.start();
839        }
840    }
841
842    void startDrag(CellLayout.CellInfo cellInfo) {
843        View child = cellInfo.cell;
844
845        // Make sure the drag was started by a long press as opposed to a long click.
846        if (!child.isInTouchMode()) {
847            return;
848        }
849
850        mDragInfo = cellInfo;
851        mDragInfo.screen = mCurrentPage;
852
853        CellLayout current = ((CellLayout) getChildAt(mCurrentPage));
854
855        current.onDragChild(child);
856        mDragController.startDrag(child, this, child.getTag(), DragController.DRAG_ACTION_MOVE);
857        current.onDragEnter(child);
858        invalidate();
859    }
860
861    void addApplicationShortcut(ShortcutInfo info, int screen, int cellX, int cellY,
862            boolean insertAtFirst, int intersectX, int intersectY) {
863        final CellLayout cellLayout = (CellLayout) getChildAt(screen);
864        View view = mLauncher.createShortcut(R.layout.application, cellLayout, (ShortcutInfo) info);
865
866        final int[] cellXY = new int[2];
867        cellLayout.findCellForSpanThatIntersects(cellXY, 1, 1, intersectX, intersectY);
868        addInScreen(view, screen, cellXY[0], cellXY[1], 1, 1, insertAtFirst);
869        LauncherModel.addOrMoveItemInDatabase(mLauncher, info,
870                LauncherSettings.Favorites.CONTAINER_DESKTOP, screen,
871                cellXY[0], cellXY[1]);
872    }
873
874
875    public void onDrop(DragSource source, int x, int y, int xOffset, int yOffset,
876            DragView dragView, Object dragInfo) {
877        CellLayout cellLayout;
878        int originX = x - xOffset;
879        int originY = y - yOffset;
880        if (mIsSmall) {
881            cellLayout = findMatchingPageForDragOver(dragView, originX, originY);
882            if (cellLayout == null) {
883                // cancel the drag if we're not over a mini-screen at time of drop
884                // TODO: maybe add a nice fade here?
885                return;
886            }
887            // get originX and originY in the local coordinate system of the screen
888            mTempOriginXY[0] = originX;
889            mTempOriginXY[1] = originY;
890            mapPointGlobalToLocal(cellLayout, mTempOriginXY);
891            originX = (int)mTempOriginXY[0];
892            originY = (int)mTempOriginXY[1];
893        } else {
894            cellLayout = getCurrentDropLayout();
895        }
896
897        if (source != this) {
898            onDropExternal(originX, originY, dragInfo, cellLayout);
899        } else {
900            // Move internally
901            if (mDragInfo != null) {
902                final View cell = mDragInfo.cell;
903
904                mTargetCell = findNearestVacantArea(originX, originY,
905                        mDragInfo.spanX, mDragInfo.spanY, cell, cellLayout,
906                        mTargetCell);
907
908                int screen = indexOfChild(cellLayout);
909                if (screen != mDragInfo.screen) {
910                    final CellLayout originalCellLayout = (CellLayout) getChildAt(mDragInfo.screen);
911                    originalCellLayout.removeView(cell);
912                    addInScreen(cell, screen, mTargetCell[0], mTargetCell[1],
913                            mDragInfo.spanX, mDragInfo.spanY);
914                }
915                cellLayout.onDropChild(cell);
916
917                // update the item's position after drop
918                final ItemInfo info = (ItemInfo) cell.getTag();
919                CellLayout.LayoutParams lp = (CellLayout.LayoutParams) cell.getLayoutParams();
920                cellLayout.onMove(cell, mTargetCell[0], mTargetCell[1]);
921                lp.cellX = mTargetCell[0];
922                lp.cellY = mTargetCell[1];
923                cell.setId(LauncherModel.getCellLayoutChildId(-1, mDragInfo.screen,
924                        mTargetCell[0], mTargetCell[1], mDragInfo.spanX, mDragInfo.spanY));
925
926                LauncherModel.moveItemInDatabase(mLauncher, info,
927                        LauncherSettings.Favorites.CONTAINER_DESKTOP, screen,
928                        lp.cellX, lp.cellY);
929            }
930        }
931    }
932
933    public void onDragEnter(DragSource source, int x, int y, int xOffset,
934            int yOffset, DragView dragView, Object dragInfo) {
935        getCurrentDropLayout().onDragEnter(dragView);
936    }
937
938    public DropTarget getDropTargetDelegate(DragSource source, int x, int y, int xOffset, int yOffset,
939            DragView dragView, Object dragInfo) {
940
941        if (mIsSmall) {
942            // If we're shrunken, don't let anyone drag on folders/etc  that are on the mini-screens
943            return null;
944        }
945        // We may need to delegate the drag to a child view. If a 1x1 item
946        // would land in a cell occupied by a DragTarget (e.g. a Folder),
947        // then drag events should be handled by that child.
948
949        ItemInfo item = (ItemInfo)dragInfo;
950        CellLayout currentLayout = getCurrentDropLayout();
951
952        int dragPointX, dragPointY;
953        if (item.spanX == 1 && item.spanY == 1) {
954            // For a 1x1, calculate the drop cell exactly as in onDragOver
955            dragPointX = x - xOffset;
956            dragPointY = y - yOffset;
957        } else {
958            // Otherwise, use the exact drag coordinates
959            dragPointX = x;
960            dragPointY = y;
961        }
962        dragPointX += mScrollX - currentLayout.getLeft();
963        dragPointY += mScrollY - currentLayout.getTop();
964
965        // If we are dragging over a cell that contains a DropTarget that will
966        // accept the drop, delegate to that DropTarget.
967        final int[] cellXY = mTempCell;
968        currentLayout.estimateDropCell(dragPointX, dragPointY, item.spanX, item.spanY, cellXY);
969        View child = currentLayout.getChildAt(cellXY[0], cellXY[1]);
970        if (child instanceof DropTarget) {
971            DropTarget target = (DropTarget)child;
972            if (target.acceptDrop(source, x, y, xOffset, yOffset, dragView, dragInfo)) {
973                return target;
974            }
975        }
976        return null;
977    }
978
979    private void mapPointGlobalToLocal(View v, float[] xy) {
980        xy[0] = xy[0] + mScrollX - v.getLeft();
981        xy[1] = xy[1] + mScrollY - v.getTop();
982        v.getMatrix().invert(mTempInverseMatrix);
983        mTempInverseMatrix.mapPoints(xy);
984    }
985
986    // xy = upper left corner of item being dragged
987    // bottomRightXy = lower right corner of item being dragged
988    // This method will see which mini-screen is most overlapped by the item being dragged, and
989    // return it. It will also transform the parameters xy and bottomRightXy into the local
990    // coordinate space of the returned screen
991    private CellLayout findMatchingPageForDragOver(DragView dragView, int originX, int originY) {
992        float x = originX + dragView.getScaledDragRegionXOffset();
993        float y = originY + dragView.getScaledDragRegionYOffset();
994        float right = x + dragView.getScaledDragRegionWidth();
995        float bottom = y + dragView.getScaledDragRegionHeight();
996
997        // We loop through all the screens (ie CellLayouts) and see which one overlaps the most
998        // with the item being dragged.
999        final int screenCount = getChildCount();
1000        CellLayout bestMatchingScreen = null;
1001        float smallestDistSoFar = Float.MAX_VALUE;
1002        final float[] xy = mTempDragCoordinates;
1003        final float[] bottomRightXy = mTempDragBottomRightCoordinates;
1004        for (int i = 0; i < screenCount; i++) {
1005            CellLayout cl = (CellLayout)getChildAt(i);
1006            // Transform the coordinates of the item being dragged to the CellLayout's coordinates
1007            float left = cl.getLeft();
1008            float top = cl.getTop();
1009            xy[0] = x + mScrollX - left;
1010            xy[1] = y + mScrollY - top;
1011
1012            bottomRightXy[0] = right + mScrollX - left;
1013            bottomRightXy[1] = bottom + mScrollY - top;
1014
1015            cl.getMatrix().invert(mTempInverseMatrix);
1016            mTempInverseMatrix.mapPoints(xy);
1017            mTempInverseMatrix.mapPoints(bottomRightXy);
1018
1019            float dragRegionX = xy[0];
1020            float dragRegionY = xy[1];
1021            float dragRegionRight = bottomRightXy[0];
1022            float dragRegionBottom = bottomRightXy[1];
1023            float dragRegionCenterX = (dragRegionX + dragRegionRight) / 2.0f;
1024            float dragRegionCenterY = (dragRegionY + dragRegionBottom) / 2.0f;
1025
1026            // Find the overlapping region
1027            float overlapLeft = Math.max(0f, dragRegionX);
1028            float overlapTop = Math.max(0f, dragRegionY);
1029            float overlapBottom = Math.min(cl.getHeight(), dragRegionBottom);
1030            float overlapRight = Math.min(cl.getWidth(), dragRegionRight);
1031            if (overlapRight >= 0 && overlapLeft <= cl.getWidth() &&
1032                    (overlapTop >= 0 && overlapBottom <= cl.getHeight())) {
1033                // Calculate the distance between the two centers
1034                float distX = dragRegionCenterX - cl.getWidth()/2;
1035                float distY = dragRegionCenterY - cl.getHeight()/2;
1036                float dist = distX * distX + distY * distY;
1037
1038                float overlap = (overlapRight - overlapLeft) * (overlapBottom - overlapTop);
1039
1040                // Calculate the closest overlapping region
1041                if (overlap > 0 && dist < smallestDistSoFar) {
1042                    smallestDistSoFar = dist;
1043                    bestMatchingScreen = cl;
1044                }
1045             }
1046        }
1047
1048        if (bestMatchingScreen != mDragTargetLayout) {
1049            if (mDragTargetLayout != null) {
1050                mDragTargetLayout.onDragExit();
1051            }
1052            mDragTargetLayout = bestMatchingScreen;
1053        }
1054        return bestMatchingScreen;
1055    }
1056
1057    public void onDragOver(DragSource source, int x, int y, int xOffset, int yOffset,
1058            DragView dragView, Object dragInfo) {
1059        CellLayout currentLayout;
1060        int originX = x - xOffset;
1061        int originY = y - yOffset;
1062        if (mIsSmall) {
1063            currentLayout = findMatchingPageForDragOver(dragView, originX, originY);
1064
1065            if (currentLayout == null) {
1066                return;
1067            }
1068
1069            currentLayout.setHover(true);
1070            // get originX and originY in the local coordinate system of the screen
1071            mTempOriginXY[0] = originX;
1072            mTempOriginXY[1] = originY;
1073            mapPointGlobalToLocal(currentLayout, mTempOriginXY);
1074            originX = (int)mTempOriginXY[0];
1075            originY = (int)mTempOriginXY[1];
1076        } else {
1077            currentLayout = getCurrentDropLayout();
1078        }
1079
1080        final ItemInfo item = (ItemInfo)dragInfo;
1081
1082        if (dragInfo instanceof LauncherAppWidgetInfo) {
1083            LauncherAppWidgetInfo widgetInfo = (LauncherAppWidgetInfo)dragInfo;
1084
1085            if (widgetInfo.spanX == -1) {
1086                // Calculate the grid spans needed to fit this widget
1087                int[] spans = currentLayout.rectToCell(widgetInfo.minWidth, widgetInfo.minHeight, null);
1088                item.spanX = spans[0];
1089                item.spanY = spans[1];
1090            }
1091        }
1092
1093        if (source != this) {
1094            // This is a hack to fix the point used to determine which cell an icon from the all
1095            // apps screen is over
1096            if (item != null && item.spanX == 1 && currentLayout != null) {
1097                int dragRegionLeft = (dragView.getWidth() - currentLayout.getCellWidth()) / 2;
1098
1099                originX += dragRegionLeft - dragView.getDragRegionLeft();
1100                if (dragView.getDragRegionWidth() != currentLayout.getCellWidth()) {
1101                    dragView.setDragRegion(dragView.getDragRegionLeft(), dragView.getDragRegionTop(),
1102                            currentLayout.getCellWidth(), dragView.getDragRegionHeight());
1103                }
1104            }
1105        }
1106        if (currentLayout != mDragTargetLayout) {
1107            if (mDragTargetLayout != null) {
1108                mDragTargetLayout.onDragExit();
1109                currentLayout.onDragEnter(dragView);
1110            }
1111            mDragTargetLayout = currentLayout;
1112        }
1113
1114        // only visualize the drop locations for moving icons within the home screen on tablet
1115        // on phone, we also visualize icons dragged in from All Apps
1116        if ((!LauncherApplication.isScreenXLarge() || source == this)
1117                && mDragTargetLayout != null) {
1118            final View child = (mDragInfo == null) ? null : mDragInfo.cell;
1119            int localOriginX = originX - (mDragTargetLayout.getLeft() - mScrollX);
1120            int localOriginY = originY - (mDragTargetLayout.getTop() - mScrollY);
1121            mDragTargetLayout.visualizeDropLocation(
1122                    child, localOriginX, localOriginY, item.spanX, item.spanY);
1123        }
1124    }
1125
1126    public void onDragExit(DragSource source, int x, int y, int xOffset,
1127            int yOffset, DragView dragView, Object dragInfo) {
1128        if (mDragTargetLayout != null) {
1129            mDragTargetLayout.onDragExit();
1130            mDragTargetLayout = null;
1131        }
1132    }
1133
1134    private void onDropExternal(int x, int y, Object dragInfo,
1135            CellLayout cellLayout) {
1136        onDropExternal(x, y, dragInfo, cellLayout, false);
1137    }
1138
1139    /**
1140     * Add the item specified by dragInfo to the given layout.
1141     * This is basically the equivalent of onDropExternal, except it's not initiated
1142     * by drag and drop.
1143     * @return true if successful
1144     */
1145    public boolean addExternalItemToScreen(Object dragInfo, View layout) {
1146        CellLayout cl = (CellLayout) layout;
1147        ItemInfo info = (ItemInfo) dragInfo;
1148
1149        if (cl.findCellForSpan(mTempEstimate, info.spanX, info.spanY)) {
1150            onDropExternal(0, 0, dragInfo, cl, false);
1151            return true;
1152        }
1153        mLauncher.showOutOfSpaceMessage();
1154        return false;
1155    }
1156
1157    // Drag from somewhere else
1158    private void onDropExternal(int x, int y, Object dragInfo,
1159            CellLayout cellLayout, boolean insertAtFirst) {
1160        int screen = indexOfChild(cellLayout);
1161        if (dragInfo instanceof PendingAddItemInfo) {
1162            PendingAddItemInfo info = (PendingAddItemInfo) dragInfo;
1163            // When dragging and dropping from customization tray, we deal with creating
1164            // widgets/shortcuts/folders in a slightly different way
1165            int[] touchXY = new int[2];
1166            touchXY[0] = x;
1167            touchXY[1] = y;
1168            switch (info.itemType) {
1169                case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
1170                    mLauncher.addAppWidgetFromDrop(info.componentName, screen, touchXY);
1171                    break;
1172                case LauncherSettings.Favorites.ITEM_TYPE_LIVE_FOLDER:
1173                    mLauncher.addLiveFolderFromDrop(info.componentName, screen, touchXY);
1174                    break;
1175                case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
1176                    mLauncher.processShortcutFromDrop(info.componentName, screen, touchXY);
1177                    break;
1178                default:
1179                    throw new IllegalStateException("Unknown item type: " + info.itemType);
1180            }
1181            cellLayout.onDragExit();
1182            return;
1183        }
1184
1185        // This is for other drag/drop cases, like dragging from All Apps
1186        ItemInfo info = (ItemInfo) dragInfo;
1187
1188        View view = null;
1189
1190        switch (info.itemType) {
1191        case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
1192        case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
1193            if (info.container == NO_ID && info instanceof ApplicationInfo) {
1194                // Came from all apps -- make a copy
1195                info = new ShortcutInfo((ApplicationInfo) info);
1196            }
1197            view = mLauncher.createShortcut(R.layout.application, cellLayout,
1198                    (ShortcutInfo) info);
1199            break;
1200        case LauncherSettings.Favorites.ITEM_TYPE_USER_FOLDER:
1201            view = FolderIcon.fromXml(R.layout.folder_icon, mLauncher,
1202                    cellLayout, ((UserFolderInfo) info));
1203            break;
1204        default:
1205            throw new IllegalStateException("Unknown item type: " + info.itemType);
1206        }
1207
1208        // If the view is null, it has already been added.
1209        if (view == null) {
1210            cellLayout.onDragExit();
1211        } else {
1212            mTargetCell = findNearestVacantArea(x, y, 1, 1, null, cellLayout, mTargetCell);
1213            addInScreen(view, indexOfChild(cellLayout), mTargetCell[0],
1214                    mTargetCell[1], info.spanX, info.spanY, insertAtFirst);
1215            cellLayout.onDropChild(view);
1216            CellLayout.LayoutParams lp = (CellLayout.LayoutParams) view.getLayoutParams();
1217
1218            LauncherModel.addOrMoveItemInDatabase(mLauncher, info,
1219                    LauncherSettings.Favorites.CONTAINER_DESKTOP, screen,
1220                    lp.cellX, lp.cellY);
1221        }
1222    }
1223
1224    /**
1225     * Return the current {@link CellLayout}, correctly picking the destination
1226     * screen while a scroll is in progress.
1227     */
1228    private CellLayout getCurrentDropLayout() {
1229        int index = mScroller.isFinished() ? mCurrentPage : mNextPage;
1230        return (CellLayout) getChildAt(index);
1231    }
1232
1233    /**
1234     * Return the current CellInfo describing our current drag; this method exists
1235     * so that Launcher can sync this object with the correct info when the activity is created/
1236     * destroyed
1237     *
1238     */
1239    public CellLayout.CellInfo getDragInfo() {
1240        return mDragInfo;
1241    }
1242
1243    /**
1244     * {@inheritDoc}
1245     */
1246    public boolean acceptDrop(DragSource source, int x, int y,
1247            int xOffset, int yOffset, DragView dragView, Object dragInfo) {
1248        CellLayout layout;
1249        if (mIsSmall) {
1250            layout = findMatchingPageForDragOver(dragView, x - xOffset, y - yOffset);
1251            if (layout == null) {
1252                // cancel the drag if we're not over a mini-screen at time of drop
1253                return false;
1254            }
1255        } else {
1256            layout = getCurrentDropLayout();
1257        }
1258        final CellLayout.CellInfo dragCellInfo = mDragInfo;
1259        final int spanX = dragCellInfo == null ? 1 : dragCellInfo.spanX;
1260        final int spanY = dragCellInfo == null ? 1 : dragCellInfo.spanY;
1261
1262        final View ignoreView = dragCellInfo == null ? null : dragCellInfo.cell;
1263
1264        if (layout.findCellForSpanIgnoring(null, spanX, spanY, ignoreView)) {
1265            return true;
1266        } else {
1267            mLauncher.showOutOfSpaceMessage();
1268            return false;
1269        }
1270    }
1271
1272    /**
1273     * Calculate the nearest cell where the given object would be dropped.
1274     */
1275    private int[] findNearestVacantArea(int pixelX, int pixelY,
1276            int spanX, int spanY, View ignoreView, CellLayout layout, int[] recycle) {
1277
1278        int localPixelX = pixelX - (layout.getLeft() - mScrollX);
1279        int localPixelY = pixelY - (layout.getTop() - mScrollY);
1280
1281        // Find the best target drop location
1282        return layout.findNearestVacantArea(
1283                localPixelX, localPixelY, spanX, spanY, ignoreView, recycle);
1284    }
1285
1286    /**
1287     * Estimate the size that a child with the given dimensions will take in the current screen.
1288     */
1289    void estimateChildSize(int minWidth, int minHeight, int[] result) {
1290        ((CellLayout)getChildAt(mCurrentPage)).estimateChildSize(minWidth, minHeight, result);
1291    }
1292
1293    void setLauncher(Launcher launcher) {
1294        mLauncher = launcher;
1295    }
1296
1297    public void setDragController(DragController dragController) {
1298        mDragController = dragController;
1299    }
1300
1301    public void onDropCompleted(View target, boolean success) {
1302        if (success) {
1303            if (target != this && mDragInfo != null) {
1304                final CellLayout cellLayout = (CellLayout) getChildAt(mDragInfo.screen);
1305                cellLayout.removeView(mDragInfo.cell);
1306                if (mDragInfo.cell instanceof DropTarget) {
1307                    mDragController.removeDropTarget((DropTarget)mDragInfo.cell);
1308                }
1309                // final Object tag = mDragInfo.cell.getTag();
1310            }
1311        } else {
1312            if (mDragInfo != null) {
1313                final CellLayout cellLayout = (CellLayout) getChildAt(mDragInfo.screen);
1314                cellLayout.onDropAborted(mDragInfo.cell);
1315            }
1316        }
1317
1318        mDragInfo = null;
1319    }
1320
1321    public boolean isDropEnabled() {
1322        return true;
1323    }
1324
1325    @Override
1326    protected void onRestoreInstanceState(Parcelable state) {
1327        super.onRestoreInstanceState(state);
1328        Launcher.setScreen(mCurrentPage);
1329    }
1330
1331    @Override
1332    public void scrollLeft() {
1333        if (!mIsSmall) {
1334            super.scrollLeft();
1335        }
1336    }
1337
1338    @Override
1339    public void scrollRight() {
1340        if (!mIsSmall) {
1341            super.scrollRight();
1342        }
1343    }
1344
1345    public Folder getFolderForTag(Object tag) {
1346        final int screenCount = getChildCount();
1347        for (int screen = 0; screen < screenCount; screen++) {
1348            CellLayout currentScreen = ((CellLayout) getChildAt(screen));
1349            int count = currentScreen.getChildCount();
1350            for (int i = 0; i < count; i++) {
1351                View child = currentScreen.getChildAt(i);
1352                CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
1353                if (lp.cellHSpan == 4 && lp.cellVSpan == 4 && child instanceof Folder) {
1354                    Folder f = (Folder) child;
1355                    if (f.getInfo() == tag && f.getInfo().opened) {
1356                        return f;
1357                    }
1358                }
1359            }
1360        }
1361        return null;
1362    }
1363
1364    public View getViewForTag(Object tag) {
1365        int screenCount = getChildCount();
1366        for (int screen = 0; screen < screenCount; screen++) {
1367            CellLayout currentScreen = ((CellLayout) getChildAt(screen));
1368            int count = currentScreen.getChildCount();
1369            for (int i = 0; i < count; i++) {
1370                View child = currentScreen.getChildAt(i);
1371                if (child.getTag() == tag) {
1372                    return child;
1373                }
1374            }
1375        }
1376        return null;
1377    }
1378
1379
1380    void removeItems(final ArrayList<ApplicationInfo> apps) {
1381        final int screenCount = getChildCount();
1382        final PackageManager manager = getContext().getPackageManager();
1383        final AppWidgetManager widgets = AppWidgetManager.getInstance(getContext());
1384
1385        final HashSet<String> packageNames = new HashSet<String>();
1386        final int appCount = apps.size();
1387        for (int i = 0; i < appCount; i++) {
1388            packageNames.add(apps.get(i).componentName.getPackageName());
1389        }
1390
1391        for (int i = 0; i < screenCount; i++) {
1392            final CellLayout layout = (CellLayout) getChildAt(i);
1393
1394            // Avoid ANRs by treating each screen separately
1395            post(new Runnable() {
1396                public void run() {
1397                    final ArrayList<View> childrenToRemove = new ArrayList<View>();
1398                    childrenToRemove.clear();
1399
1400                    int childCount = layout.getChildCount();
1401                    for (int j = 0; j < childCount; j++) {
1402                        final View view = layout.getChildAt(j);
1403                        Object tag = view.getTag();
1404
1405                        if (tag instanceof ShortcutInfo) {
1406                            final ShortcutInfo info = (ShortcutInfo) tag;
1407                            final Intent intent = info.intent;
1408                            final ComponentName name = intent.getComponent();
1409
1410                            if (Intent.ACTION_MAIN.equals(intent.getAction()) && name != null) {
1411                                for (String packageName: packageNames) {
1412                                    if (packageName.equals(name.getPackageName())) {
1413                                        LauncherModel.deleteItemFromDatabase(mLauncher, info);
1414                                        childrenToRemove.add(view);
1415                                    }
1416                                }
1417                            }
1418                        } else if (tag instanceof UserFolderInfo) {
1419                            final UserFolderInfo info = (UserFolderInfo) tag;
1420                            final ArrayList<ShortcutInfo> contents = info.contents;
1421                            final ArrayList<ShortcutInfo> toRemove = new ArrayList<ShortcutInfo>(1);
1422                            final int contentsCount = contents.size();
1423                            boolean removedFromFolder = false;
1424
1425                            for (int k = 0; k < contentsCount; k++) {
1426                                final ShortcutInfo appInfo = contents.get(k);
1427                                final Intent intent = appInfo.intent;
1428                                final ComponentName name = intent.getComponent();
1429
1430                                if (Intent.ACTION_MAIN.equals(intent.getAction()) && name != null) {
1431                                    for (String packageName: packageNames) {
1432                                        if (packageName.equals(name.getPackageName())) {
1433                                            toRemove.add(appInfo);
1434                                            LauncherModel.deleteItemFromDatabase(mLauncher, appInfo);
1435                                            removedFromFolder = true;
1436                                        }
1437                                    }
1438                                }
1439                            }
1440
1441                            contents.removeAll(toRemove);
1442                            if (removedFromFolder) {
1443                                final Folder folder = getOpenFolder();
1444                                if (folder != null)
1445                                    folder.notifyDataSetChanged();
1446                            }
1447                        } else if (tag instanceof LiveFolderInfo) {
1448                            final LiveFolderInfo info = (LiveFolderInfo) tag;
1449                            final Uri uri = info.uri;
1450                            final ProviderInfo providerInfo = manager.resolveContentProvider(
1451                                    uri.getAuthority(), 0);
1452
1453                            if (providerInfo != null) {
1454                                for (String packageName: packageNames) {
1455                                    if (packageName.equals(providerInfo.packageName)) {
1456                                        LauncherModel.deleteItemFromDatabase(mLauncher, info);
1457                                        childrenToRemove.add(view);
1458                                    }
1459                                }
1460                            }
1461                        } else if (tag instanceof LauncherAppWidgetInfo) {
1462                            final LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) tag;
1463                            final AppWidgetProviderInfo provider =
1464                                    widgets.getAppWidgetInfo(info.appWidgetId);
1465                            if (provider != null) {
1466                                for (String packageName: packageNames) {
1467                                    if (packageName.equals(provider.provider.getPackageName())) {
1468                                        LauncherModel.deleteItemFromDatabase(mLauncher, info);
1469                                        childrenToRemove.add(view);
1470                                    }
1471                                }
1472                            }
1473                        }
1474                    }
1475
1476                    childCount = childrenToRemove.size();
1477                    for (int j = 0; j < childCount; j++) {
1478                        View child = childrenToRemove.get(j);
1479                        layout.removeViewInLayout(child);
1480                        if (child instanceof DropTarget) {
1481                            mDragController.removeDropTarget((DropTarget)child);
1482                        }
1483                    }
1484
1485                    if (childCount > 0) {
1486                        layout.requestLayout();
1487                        layout.invalidate();
1488                    }
1489                }
1490            });
1491        }
1492    }
1493
1494    void updateShortcuts(ArrayList<ApplicationInfo> apps) {
1495        final int screenCount = getChildCount();
1496        for (int i = 0; i < screenCount; i++) {
1497            final CellLayout layout = (CellLayout) getChildAt(i);
1498            int childCount = layout.getChildCount();
1499            for (int j = 0; j < childCount; j++) {
1500                final View view = layout.getChildAt(j);
1501                Object tag = view.getTag();
1502                if (tag instanceof ShortcutInfo) {
1503                    ShortcutInfo info = (ShortcutInfo)tag;
1504                    // We need to check for ACTION_MAIN otherwise getComponent() might
1505                    // return null for some shortcuts (for instance, for shortcuts to
1506                    // web pages.)
1507                    final Intent intent = info.intent;
1508                    final ComponentName name = intent.getComponent();
1509                    if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION &&
1510                            Intent.ACTION_MAIN.equals(intent.getAction()) && name != null) {
1511                        final int appCount = apps.size();
1512                        for (int k = 0; k < appCount; k++) {
1513                            ApplicationInfo app = apps.get(k);
1514                            if (app.componentName.equals(name)) {
1515                                info.setIcon(mIconCache.getIcon(info.intent));
1516                                ((TextView)view).setCompoundDrawablesWithIntrinsicBounds(null,
1517                                        new FastBitmapDrawable(info.getIcon(mIconCache)),
1518                                        null, null);
1519                                }
1520                        }
1521                    }
1522                }
1523            }
1524        }
1525    }
1526
1527    void moveToDefaultScreen(boolean animate) {
1528        if (animate) {
1529            if (mIsSmall) {
1530                unshrink(mDefaultPage);
1531            } else {
1532                snapToPage(mDefaultPage);
1533            }
1534        } else {
1535            setCurrentPage(mDefaultPage);
1536        }
1537        getChildAt(mDefaultPage).requestFocus();
1538    }
1539
1540    void setIndicators(Drawable previous, Drawable next) {
1541        mPreviousIndicator = previous;
1542        mNextIndicator = next;
1543        previous.setLevel(mCurrentPage);
1544        next.setLevel(mCurrentPage);
1545    }
1546
1547    @Override
1548    public void syncPages() {
1549    }
1550
1551    @Override
1552    public void syncPageItems(int page) {
1553    }
1554
1555}
1556