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