Workspace.java revision 0207c522472d7252f146f4d39efa456ca5248c6b
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 java.util.ArrayList;
20import java.util.HashSet;
21import java.util.List;
22
23import android.animation.Animator;
24import android.animation.Animator.AnimatorListener;
25import android.animation.AnimatorListenerAdapter;
26import android.animation.AnimatorSet;
27import android.animation.ObjectAnimator;
28import android.animation.PropertyValuesHolder;
29import android.animation.TimeInterpolator;
30import android.animation.ValueAnimator;
31import android.animation.ValueAnimator.AnimatorUpdateListener;
32import android.app.AlertDialog;
33import android.app.WallpaperManager;
34import android.appwidget.AppWidgetManager;
35import android.appwidget.AppWidgetProviderInfo;
36import android.content.ClipData;
37import android.content.ClipDescription;
38import android.content.ComponentName;
39import android.content.Context;
40import android.content.Intent;
41import android.content.pm.PackageManager;
42import android.content.pm.ProviderInfo;
43import android.content.res.Resources;
44import android.content.res.TypedArray;
45import android.graphics.Bitmap;
46import android.graphics.Canvas;
47import android.graphics.Matrix;
48import android.graphics.Paint;
49import android.graphics.Rect;
50import android.graphics.RectF;
51import android.graphics.Region.Op;
52import android.graphics.drawable.Drawable;
53import android.net.Uri;
54import android.os.IBinder;
55import android.os.Parcelable;
56import android.util.AttributeSet;
57import android.util.Log;
58import android.view.DragEvent;
59import android.view.MotionEvent;
60import android.view.View;
61import android.view.animation.DecelerateInterpolator;
62import android.widget.TextView;
63import android.widget.Toast;
64
65import com.android.launcher.R;
66import com.android.launcher2.InstallWidgetReceiver.WidgetMimeTypeHandlerData;
67
68/**
69 * The workspace is a wide area with a wallpaper and a finite number of pages.
70 * Each page contains a number of icons, folders or widgets the user can
71 * interact with. A workspace is meant to be used with a fixed width only.
72 */
73public class Workspace extends SmoothPagedView
74        implements DropTarget, DragSource, DragScroller, View.OnTouchListener {
75    @SuppressWarnings({"UnusedDeclaration"})
76    private static final String TAG = "Launcher.Workspace";
77
78    // This is how much the workspace shrinks when we enter all apps or
79    // customization mode
80    private static final float SHRINK_FACTOR = 0.16f;
81
82    // Y rotation to apply to the workspace screens
83    private static final float WORKSPACE_ROTATION = 12.5f;
84
85    // These are extra scale factors to apply to the mini home screens
86    // so as to achieve the desired transform
87    private static final float EXTRA_SCALE_FACTOR_0 = 0.972f;
88    private static final float EXTRA_SCALE_FACTOR_1 = 1.0f;
89    private static final float EXTRA_SCALE_FACTOR_2 = 1.10f;
90
91    private static final int BACKGROUND_FADE_OUT_DELAY = 300;
92    private static final int BACKGROUND_FADE_OUT_DURATION = 300;
93    private static final int BACKGROUND_FADE_IN_DURATION = 100;
94
95    // These animators are used to fade the background
96    private ObjectAnimator mBackgroundFadeInAnimation;
97    private ObjectAnimator mBackgroundFadeOutAnimation;
98    private float mBackgroundAlpha = 0;
99
100    private final WallpaperManager mWallpaperManager;
101
102    private int mDefaultPage;
103
104    private boolean mPageMoving = false;
105
106    /**
107     * CellInfo for the cell that is currently being dragged
108     */
109    private CellLayout.CellInfo mDragInfo;
110
111    /**
112     * Target drop area calculated during last acceptDrop call.
113     */
114    private int[] mTargetCell = null;
115
116    /**
117     * The CellLayout that is currently being dragged over
118     */
119    private CellLayout mDragTargetLayout = null;
120
121    private Launcher mLauncher;
122    private IconCache mIconCache;
123    private DragController mDragController;
124
125    // These are temporary variables to prevent having to allocate a new object just to
126    // return an (x, y) value from helper functions. Do NOT use them to maintain other state.
127    private int[] mTempCell = new int[2];
128    private int[] mTempEstimate = new int[2];
129    private float[] mTempOriginXY = new float[2];
130    private float[] mTempDragCoordinates = new float[2];
131    private float[] mTempTouchCoordinates = new float[2];
132    private float[] mTempCellLayoutCenterCoordinates = new float[2];
133    private float[] mTempDragBottomRightCoordinates = new float[2];
134    private Matrix mTempInverseMatrix = new Matrix();
135
136    private static final int DEFAULT_CELL_COUNT_X = 4;
137    private static final int DEFAULT_CELL_COUNT_Y = 4;
138
139    private Drawable mPreviousIndicator;
140    private Drawable mNextIndicator;
141
142    // State variable that indicates whether the pages are small (ie when you're
143    // in all apps or customize mode)
144    private boolean mIsSmall = false;
145    private boolean mIsInUnshrinkAnimation = false;
146    private AnimatorListener mUnshrinkAnimationListener;
147    private enum ShrinkPosition {
148        SHRINK_TO_TOP, SHRINK_TO_MIDDLE, SHRINK_TO_BOTTOM_HIDDEN, SHRINK_TO_BOTTOM_VISIBLE };
149    private ShrinkPosition mShrunkenState;
150    private boolean mWaitingToShrink = false;
151    private ShrinkPosition mWaitingToShrinkPosition;
152    private AnimatorSet mAnimator;
153
154    private boolean mInScrollArea = false;
155    private boolean mInDragMode = false;
156
157    private final HolographicOutlineHelper mOutlineHelper = new HolographicOutlineHelper();
158    private Bitmap mDragOutline = null;
159    private final Rect mTempRect = new Rect();
160    private final int[] mTempXY = new int[2];
161
162    private ValueAnimator mDropAnim = null;
163    private TimeInterpolator mQuintEaseOutInterpolator = new DecelerateInterpolator(2.5f);
164    private View mDropView = null;
165    private int[] mDropViewPos = new int[] { -1, -1 };
166
167    // Paint used to draw external drop outline
168    private final Paint mExternalDragOutlinePaint = new Paint();
169
170    /** Used to trigger an animation as soon as the workspace stops scrolling. */
171    private Animator mAnimOnPageEndMoving = null;
172
173    /**
174     * Used to inflate the Workspace from XML.
175     *
176     * @param context The application's context.
177     * @param attrs The attributes set containing the Workspace's customization values.
178     */
179    public Workspace(Context context, AttributeSet attrs) {
180        this(context, attrs, 0);
181    }
182
183    /**
184     * Used to inflate the Workspace from XML.
185     *
186     * @param context The application's context.
187     * @param attrs The attributes set containing the Workspace's customization values.
188     * @param defStyle Unused.
189     */
190    public Workspace(Context context, AttributeSet attrs, int defStyle) {
191        super(context, attrs, defStyle);
192        mContentIsRefreshable = false;
193
194        if (!LauncherApplication.isScreenXLarge()) {
195            mFadeInAdjacentScreens = false;
196        }
197
198        mWallpaperManager = WallpaperManager.getInstance(context);
199
200        TypedArray a = context.obtainStyledAttributes(attrs,
201                R.styleable.Workspace, defStyle, 0);
202        int cellCountX = a.getInt(R.styleable.Workspace_cellCountX, DEFAULT_CELL_COUNT_X);
203        int cellCountY = a.getInt(R.styleable.Workspace_cellCountY, DEFAULT_CELL_COUNT_Y);
204        mDefaultPage = a.getInt(R.styleable.Workspace_defaultScreen, 1);
205        a.recycle();
206
207        LauncherModel.updateWorkspaceLayoutCells(cellCountX, cellCountY);
208        setHapticFeedbackEnabled(false);
209
210        initWorkspace();
211    }
212
213    /**
214     * Initializes various states for this workspace.
215     */
216    protected void initWorkspace() {
217        Context context = getContext();
218        mCurrentPage = mDefaultPage;
219        Launcher.setScreen(mCurrentPage);
220        LauncherApplication app = (LauncherApplication)context.getApplicationContext();
221        mIconCache = app.getIconCache();
222        mExternalDragOutlinePaint.setAntiAlias(true);
223        setWillNotDraw(false);
224
225        mUnshrinkAnimationListener = new LauncherAnimatorListenerAdapter() {
226            @Override
227            public void onAnimationStart(Animator animation) {
228                mIsInUnshrinkAnimation = true;
229            }
230            @Override
231            public void onAnimationEndOrCancel(Animator animation) {
232                mIsInUnshrinkAnimation = false;
233            }
234        };
235        mSnapVelocity = 600;
236    }
237
238    @Override
239    protected int getScrollMode() {
240        if (LauncherApplication.isScreenXLarge()) {
241            return SmoothPagedView.QUINTIC_MODE;
242        } else {
243            return SmoothPagedView.OVERSHOOT_MODE;
244        }
245    }
246
247    @Override
248    public void addView(View child, int index, 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, index, params);
254    }
255
256    @Override
257    public void addView(View child) {
258        if (!(child instanceof CellLayout)) {
259            throw new IllegalArgumentException("A Workspace can only have CellLayout children.");
260        }
261        ((CellLayout) child).setOnInterceptTouchListener(this);
262        super.addView(child);
263    }
264
265    @Override
266    public void addView(View child, int index) {
267        if (!(child instanceof CellLayout)) {
268            throw new IllegalArgumentException("A Workspace can only have CellLayout children.");
269        }
270        ((CellLayout) child).setOnInterceptTouchListener(this);
271        super.addView(child, index);
272    }
273
274    @Override
275    public void addView(View child, int width, int height) {
276        if (!(child instanceof CellLayout)) {
277            throw new IllegalArgumentException("A Workspace can only have CellLayout children.");
278        }
279        ((CellLayout) child).setOnInterceptTouchListener(this);
280        super.addView(child, width, height);
281    }
282
283    @Override
284    public void addView(View child, LayoutParams params) {
285        if (!(child instanceof CellLayout)) {
286            throw new IllegalArgumentException("A Workspace can only have CellLayout children.");
287        }
288        ((CellLayout) child).setOnInterceptTouchListener(this);
289        super.addView(child, params);
290    }
291
292    /**
293     * @return The open folder on the current screen, or null if there is none
294     */
295    Folder getOpenFolder() {
296        CellLayout currentPage = (CellLayout) getChildAt(mCurrentPage);
297        int count = currentPage.getChildCount();
298        for (int i = 0; i < count; i++) {
299            View child = currentPage.getChildAt(i);
300            if (child instanceof Folder) {
301                Folder folder = (Folder) child;
302                if (folder.getInfo().opened)
303                    return folder;
304            }
305        }
306        return null;
307    }
308
309    ArrayList<Folder> getOpenFolders() {
310        final int screenCount = getChildCount();
311        ArrayList<Folder> folders = new ArrayList<Folder>(screenCount);
312
313        for (int screen = 0; screen < screenCount; screen++) {
314            CellLayout currentPage = (CellLayout) getChildAt(screen);
315            int count = currentPage.getChildCount();
316            for (int i = 0; i < count; i++) {
317                View child = currentPage.getChildAt(i);
318                if (child instanceof Folder) {
319                    Folder folder = (Folder) child;
320                    if (folder.getInfo().opened)
321                        folders.add(folder);
322                    break;
323                }
324            }
325        }
326        return folders;
327    }
328
329    boolean isDefaultPageShowing() {
330        return mCurrentPage == mDefaultPage;
331    }
332
333    /**
334     * Sets the current screen.
335     *
336     * @param currentPage
337     */
338    @Override
339    void setCurrentPage(int currentPage) {
340        super.setCurrentPage(currentPage);
341        updateWallpaperOffset(mScrollX);
342    }
343
344    /**
345     * Adds the specified child in the specified screen. The position and dimension of
346     * the child are defined by x, y, spanX and spanY.
347     *
348     * @param child The child to add in one of the workspace's screens.
349     * @param screen The screen in which to add the child.
350     * @param x The X position of the child in the screen's grid.
351     * @param y The Y position of the child in the screen's grid.
352     * @param spanX The number of cells spanned horizontally by the child.
353     * @param spanY The number of cells spanned vertically by the child.
354     */
355    void addInScreen(View child, int screen, int x, int y, int spanX, int spanY) {
356        addInScreen(child, screen, x, y, spanX, spanY, false);
357    }
358
359    void addInFullScreen(View child, int screen) {
360        addInScreen(child, screen, 0, 0, -1, -1);
361    }
362
363    /**
364     * Adds the specified child in the specified screen. The position and dimension of
365     * the child are defined by x, y, spanX and spanY.
366     *
367     * @param child The child to add in one of the workspace's screens.
368     * @param screen The screen in which to add the child.
369     * @param x The X position of the child in the screen's grid.
370     * @param y The Y position of the child in the screen's grid.
371     * @param spanX The number of cells spanned horizontally by the child.
372     * @param spanY The number of cells spanned vertically by the child.
373     * @param insert When true, the child is inserted at the beginning of the children list.
374     */
375    void addInScreen(View child, int screen, int x, int y, int spanX, int spanY, boolean insert) {
376        if (screen < 0 || screen >= getChildCount()) {
377            Log.e(TAG, "The screen must be >= 0 and < " + getChildCount()
378                + " (was " + screen + "); skipping child");
379            return;
380        }
381
382        final CellLayout group = (CellLayout) getChildAt(screen);
383        CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
384        if (lp == null) {
385            lp = new CellLayout.LayoutParams(x, y, spanX, spanY);
386        } else {
387            lp.cellX = x;
388            lp.cellY = y;
389            lp.cellHSpan = spanX;
390            lp.cellVSpan = spanY;
391        }
392
393        // Get the canonical child id to uniquely represent this view in this screen
394        int childId = LauncherModel.getCellLayoutChildId(-1, screen, x, y, spanX, spanY);
395        boolean markCellsAsOccupied = !(child instanceof Folder);
396        if (!group.addViewToCellLayout(child, insert ? 0 : -1, childId, lp, markCellsAsOccupied)) {
397            // TODO: This branch occurs when the workspace is adding views
398            // outside of the defined grid
399            // maybe we should be deleting these items from the LauncherModel?
400            Log.w(TAG, "Failed to add to item at (" + lp.cellX + "," + lp.cellY + ") to CellLayout");
401        }
402
403        if (!(child instanceof Folder)) {
404            child.setHapticFeedbackEnabled(false);
405            child.setOnLongClickListener(mLongClickListener);
406        }
407        if (child instanceof DropTarget) {
408            mDragController.addDropTarget((DropTarget) child);
409        }
410    }
411
412    public boolean onTouch(View v, MotionEvent event) {
413        // this is an intercepted event being forwarded from a cell layout
414        if (mIsSmall || mIsInUnshrinkAnimation) {
415            mLauncher.onWorkspaceClick((CellLayout) v);
416            return true;
417        } else if (!mPageMoving) {
418            if (v == getChildAt(mCurrentPage - 1)) {
419                snapToPage(mCurrentPage - 1);
420                return true;
421            } else if (v == getChildAt(mCurrentPage + 1)) {
422                snapToPage(mCurrentPage + 1);
423                return true;
424            }
425        }
426        return false;
427    }
428
429    @Override
430    public boolean dispatchUnhandledMove(View focused, int direction) {
431        if (mIsSmall || mIsInUnshrinkAnimation) {
432            // when the home screens are shrunken, shouldn't allow side-scrolling
433            return false;
434        }
435        return super.dispatchUnhandledMove(focused, direction);
436    }
437
438    @Override
439    public boolean onInterceptTouchEvent(MotionEvent ev) {
440        if (mIsSmall || mIsInUnshrinkAnimation) {
441            // when the home screens are shrunken, shouldn't allow side-scrolling
442            return false;
443        }
444        return super.onInterceptTouchEvent(ev);
445    }
446
447    @Override
448    protected void determineScrollingStart(MotionEvent ev) {
449        if (!mIsSmall && !mIsInUnshrinkAnimation) super.determineScrollingStart(ev);
450    }
451
452    protected void onPageBeginMoving() {
453        if (mNextPage != INVALID_PAGE) {
454            // we're snapping to a particular screen
455            enableChildrenCache(mCurrentPage, mNextPage);
456        } else {
457            // this is when user is actively dragging a particular screen, they might
458            // swipe it either left or right (but we won't advance by more than one screen)
459            enableChildrenCache(mCurrentPage - 1, mCurrentPage + 1);
460        }
461        showOutlines();
462        mPageMoving = true;
463    }
464
465    protected void onPageEndMoving() {
466        clearChildrenCache();
467        // Hide the outlines, as long as we're not dragging
468        if (!mDragController.dragging()) {
469            hideOutlines();
470        }
471        // Check for an animation that's waiting to be started
472        if (mAnimOnPageEndMoving != null) {
473            mAnimOnPageEndMoving.start();
474            mAnimOnPageEndMoving = null;
475        }
476
477        mPageMoving = false;
478    }
479
480    @Override
481    protected void notifyPageSwitchListener() {
482        super.notifyPageSwitchListener();
483
484        if (mPreviousIndicator != null) {
485            // if we know the next page, we show the indication for it right away; it looks
486            // weird if the indicators are lagging
487            int page = mNextPage;
488            if (page == INVALID_PAGE) {
489                page = mCurrentPage;
490            }
491            mPreviousIndicator.setLevel(page);
492            mNextIndicator.setLevel(page);
493        }
494        Launcher.setScreen(mCurrentPage);
495    };
496
497    private void updateWallpaperOffset() {
498        updateWallpaperOffset(getChildAt(getChildCount() - 1).getRight() - (mRight - mLeft));
499    }
500
501    private void updateWallpaperOffset(int scrollRange) {
502        final boolean isStaticWallpaper = (mWallpaperManager != null) &&
503                (mWallpaperManager.getWallpaperInfo() == null);
504        if (LauncherApplication.isScreenXLarge() && !isStaticWallpaper) {
505            IBinder token = getWindowToken();
506            if (token != null) {
507                mWallpaperManager.setWallpaperOffsetSteps(1.0f / (getChildCount() - 1), 0 );
508                mWallpaperManager.setWallpaperOffsets(getWindowToken(),
509                        Math.max(0.f, Math.min(mScrollX/(float)scrollRange, 1.f)), 0);
510            }
511        }
512    }
513
514    public void showOutlines() {
515        if (!mIsSmall && !mIsInUnshrinkAnimation) {
516            if (mBackgroundFadeOutAnimation != null) mBackgroundFadeOutAnimation.cancel();
517            if (mBackgroundFadeInAnimation != null) mBackgroundFadeInAnimation.cancel();
518            mBackgroundFadeInAnimation = ObjectAnimator.ofFloat(this, "backgroundAlpha", 1.0f);
519            mBackgroundFadeInAnimation.setDuration(BACKGROUND_FADE_IN_DURATION);
520            mBackgroundFadeInAnimation.start();
521        }
522    }
523
524    public void hideOutlines() {
525        if (!mIsSmall && !mIsInUnshrinkAnimation) {
526            if (mBackgroundFadeInAnimation != null) mBackgroundFadeInAnimation.cancel();
527            if (mBackgroundFadeOutAnimation != null) mBackgroundFadeOutAnimation.cancel();
528            mBackgroundFadeOutAnimation = ObjectAnimator.ofFloat(this, "backgroundAlpha", 0.0f);
529            mBackgroundFadeOutAnimation.setDuration(BACKGROUND_FADE_OUT_DURATION);
530            mBackgroundFadeOutAnimation.setStartDelay(BACKGROUND_FADE_OUT_DELAY);
531            mBackgroundFadeOutAnimation.start();
532        }
533    }
534
535    public void setBackgroundAlpha(float alpha) {
536        mBackgroundAlpha = alpha;
537        for (int i = 0; i < getChildCount(); i++) {
538            CellLayout cl = (CellLayout) getChildAt(i);
539            cl.setBackgroundAlpha(alpha);
540        }
541    }
542
543    public float getBackgroundAlpha() {
544        return mBackgroundAlpha;
545    }
546
547    @Override
548    protected void screenScrolled(int screenCenter) {
549        final int halfScreenSize = getMeasuredWidth() / 2;
550        for (int i = 0; i < getChildCount(); i++) {
551            CellLayout cl = (CellLayout) getChildAt(i);
552            if (cl != null) {
553                int totalDistance = cl.getMeasuredWidth() + mPageSpacing;
554                int delta = screenCenter - (getChildOffset(i) -
555                        getRelativeChildOffset(i) + halfScreenSize);
556
557                float scrollProgress = delta/(totalDistance*1.0f);
558                scrollProgress = Math.min(scrollProgress, 1.0f);
559                scrollProgress = Math.max(scrollProgress, -1.0f);
560
561                float mult =  mInDragMode ? 1.0f : Math.abs(scrollProgress);
562                cl.setBackgroundAlphaMultiplier(mult);
563
564                float rotation = WORKSPACE_ROTATION * scrollProgress;
565                cl.setRotationY(rotation);
566            }
567        }
568    }
569
570    protected void onAttachedToWindow() {
571        super.onAttachedToWindow();
572        computeScroll();
573        mDragController.setWindowToken(getWindowToken());
574    }
575
576    @Override
577    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
578        super.onLayout(changed, left, top, right, bottom);
579
580        // if shrinkToBottom() is called on initialization, it has to be deferred
581        // until after the first call to onLayout so that it has the correct width
582        if (mWaitingToShrink) {
583            shrink(mWaitingToShrinkPosition, false);
584            mWaitingToShrink = false;
585        }
586
587        if (LauncherApplication.isInPlaceRotationEnabled()) {
588            // When the device is rotated, the scroll position of the current screen
589            // needs to be refreshed
590            setCurrentPage(getCurrentPage());
591        }
592    }
593
594    @Override
595    protected void dispatchDraw(Canvas canvas) {
596        if (mIsSmall || mIsInUnshrinkAnimation) {
597            // Draw all the workspaces if we're small
598            final int pageCount = getChildCount();
599            final long drawingTime = getDrawingTime();
600            for (int i = 0; i < pageCount; i++) {
601                final View page = (View) getChildAt(i);
602
603                drawChild(canvas, page, drawingTime);
604            }
605        } else {
606            super.dispatchDraw(canvas);
607
608            final int width = getWidth();
609            final int height = getHeight();
610
611            // In portrait orientation, draw the glowing edge when dragging to adjacent screens
612            if (mInScrollArea && (height > width)) {
613                final int pageHeight = getChildAt(0).getHeight();
614
615                // This determines the height of the glowing edge: 90% of the page height
616                final int padding = (int) ((height - pageHeight) * 0.5f + pageHeight * 0.1f);
617
618                final CellLayout leftPage = (CellLayout) getChildAt(mCurrentPage - 1);
619                final CellLayout rightPage = (CellLayout) getChildAt(mCurrentPage + 1);
620
621                if (leftPage != null && leftPage.getHover()) {
622                    final Drawable d = getResources().getDrawable(R.drawable.page_hover_left);
623                    d.setBounds(mScrollX, padding, mScrollX + d.getIntrinsicWidth(), height - padding);
624                    d.draw(canvas);
625                } else if (rightPage != null && rightPage.getHover()) {
626                    final Drawable d = getResources().getDrawable(R.drawable.page_hover_right);
627                    d.setBounds(mScrollX + width - d.getIntrinsicWidth(), padding, mScrollX + width, height - padding);
628                    d.draw(canvas);
629                }
630            }
631
632            if (mDropView != null) {
633                // We are animating an item that was just dropped on the home screen.
634                // Render its View in the current animation position.
635                canvas.save(Canvas.MATRIX_SAVE_FLAG);
636                final int xPos = mDropViewPos[0] - mDropView.getScrollX();
637                final int yPos = mDropViewPos[1] - mDropView.getScrollY();
638                canvas.translate(xPos, yPos);
639                mDropView.draw(canvas);
640                canvas.restore();
641            }
642        }
643    }
644
645    @Override
646    protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
647        if (!mLauncher.isAllAppsVisible()) {
648            final Folder openFolder = getOpenFolder();
649            if (openFolder != null) {
650                return openFolder.requestFocus(direction, previouslyFocusedRect);
651            } else {
652                return super.onRequestFocusInDescendants(direction, previouslyFocusedRect);
653            }
654        }
655        return false;
656    }
657
658    @Override
659    public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
660        if (!mLauncher.isAllAppsVisible()) {
661            final Folder openFolder = getOpenFolder();
662            if (openFolder != null) {
663                openFolder.addFocusables(views, direction);
664            } else {
665                super.addFocusables(views, direction, focusableMode);
666            }
667        }
668    }
669
670    @Override
671    public boolean dispatchTouchEvent(MotionEvent ev) {
672        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
673            // (In XLarge mode, the workspace is shrunken below all apps, and responds to taps
674            // ie when you click on a mini-screen, it zooms back to that screen)
675            if (!LauncherApplication.isScreenXLarge() && mLauncher.isAllAppsVisible()) {
676                return false;
677            }
678        }
679        return super.dispatchTouchEvent(ev);
680    }
681
682    void enableChildrenCache(int fromPage, int toPage) {
683        if (fromPage > toPage) {
684            final int temp = fromPage;
685            fromPage = toPage;
686            toPage = temp;
687        }
688
689        final int screenCount = getChildCount();
690
691        fromPage = Math.max(fromPage, 0);
692        toPage = Math.min(toPage, screenCount - 1);
693
694        for (int i = fromPage; i <= toPage; i++) {
695            final CellLayout layout = (CellLayout) getChildAt(i);
696            layout.setChildrenDrawnWithCacheEnabled(true);
697            layout.setChildrenDrawingCacheEnabled(true);
698        }
699    }
700
701    void clearChildrenCache() {
702        final int screenCount = getChildCount();
703        for (int i = 0; i < screenCount; i++) {
704            final CellLayout layout = (CellLayout) getChildAt(i);
705            layout.setChildrenDrawnWithCacheEnabled(false);
706        }
707    }
708
709    @Override
710    public boolean onTouchEvent(MotionEvent ev) {
711        if (mLauncher.isAllAppsVisible()) {
712            // Cancel any scrolling that is in progress.
713            if (!mScroller.isFinished()) {
714                mScroller.abortAnimation();
715            }
716            setCurrentPage(mCurrentPage);
717            return false; // We don't want the events.  Let them fall through to the all apps view.
718        }
719
720        return super.onTouchEvent(ev);
721    }
722
723    public boolean isSmall() {
724        return mIsSmall;
725    }
726
727    void shrinkToTop(boolean animated) {
728        shrink(ShrinkPosition.SHRINK_TO_TOP, animated);
729    }
730
731    void shrinkToMiddle() {
732        shrink(ShrinkPosition.SHRINK_TO_MIDDLE, true);
733    }
734
735    void shrinkToBottom() {
736        shrinkToBottom(true);
737    }
738
739    void shrinkToBottom(boolean animated) {
740        shrink(ShrinkPosition.SHRINK_TO_BOTTOM_HIDDEN, animated);
741    }
742
743    private float getYScaleForScreen(int screen) {
744        int x = Math.abs(screen - 2);
745
746        // TODO: This should be generalized for use with arbitrary rotation angles.
747        switch(x) {
748            case 0: return EXTRA_SCALE_FACTOR_0;
749            case 1: return EXTRA_SCALE_FACTOR_1;
750            case 2: return EXTRA_SCALE_FACTOR_2;
751        }
752        return 1.0f;
753    }
754
755    // we use this to shrink the workspace for the all apps view and the customize view
756    private void shrink(ShrinkPosition shrinkPosition, boolean animated) {
757        if (mFirstLayout) {
758            // (mFirstLayout == "first layout has not happened yet")
759            // if we get a call to shrink() as part of our initialization (for example, if
760            // Launcher is started in All Apps mode) then we need to wait for a layout call
761            // to get our width so we can layout the mini-screen views correctly
762            mWaitingToShrink = true;
763            mWaitingToShrinkPosition = shrinkPosition;
764            return;
765        }
766        mIsSmall = true;
767        mShrunkenState = shrinkPosition;
768
769        // Stop any scrolling, move to the current page right away
770        setCurrentPage((mNextPage != INVALID_PAGE) ? mNextPage : mCurrentPage);
771        updateWhichPagesAcceptDrops(mShrunkenState);
772
773        // we intercept and reject all touch events when we're small, so be sure to reset the state
774        mTouchState = TOUCH_STATE_REST;
775        mActivePointerId = INVALID_POINTER;
776
777        CellLayout currentPage = (CellLayout) getChildAt(mCurrentPage);
778        currentPage.setBackgroundAlphaMultiplier(1.0f);
779
780        final Resources res = getResources();
781        final int screenWidth = getWidth();
782        final int screenHeight = getHeight();
783
784        // Making the assumption that all pages have the same width as the 0th
785        final int pageWidth = getChildAt(0).getMeasuredWidth();
786        final int pageHeight = getChildAt(0).getMeasuredHeight();
787
788        final int scaledPageWidth = (int) (SHRINK_FACTOR * pageWidth);
789        final int scaledPageHeight = (int) (SHRINK_FACTOR * pageHeight);
790        final float extraScaledSpacing = res.getDimension(R.dimen.smallScreenExtraSpacing);
791
792        final int screenCount = getChildCount();
793        float totalWidth = screenCount * scaledPageWidth + (screenCount - 1) * extraScaledSpacing;
794
795        boolean isPortrait = getMeasuredHeight() > getMeasuredWidth();
796        float newY = (isPortrait ?
797                getResources().getDimension(R.dimen.allAppsSmallScreenVerticalMarginPortrait) :
798                getResources().getDimension(R.dimen.allAppsSmallScreenVerticalMarginLandscape));
799        float finalAlpha = 1.0f;
800        float extraShrinkFactor = 1.0f;
801        if (shrinkPosition == ShrinkPosition.SHRINK_TO_BOTTOM_VISIBLE) {
802             newY = screenHeight - newY - scaledPageHeight;
803        } else if (shrinkPosition == ShrinkPosition.SHRINK_TO_BOTTOM_HIDDEN) {
804
805            // We shrink and disappear to nothing in the case of all apps
806            // (which is when we shrink to the bottom)
807            newY = screenHeight - newY - scaledPageHeight;
808            finalAlpha = 0.25f;
809        } else if (shrinkPosition == ShrinkPosition.SHRINK_TO_MIDDLE) {
810            newY = screenHeight / 2 - scaledPageHeight / 2;
811            finalAlpha = 1.0f;
812        } else if (shrinkPosition == ShrinkPosition.SHRINK_TO_TOP) {
813            newY = (isPortrait ?
814                getResources().getDimension(R.dimen.customizeSmallScreenVerticalMarginPortrait) :
815                getResources().getDimension(R.dimen.customizeSmallScreenVerticalMarginLandscape));
816        }
817
818        // We animate all the screens to the centered position in workspace
819        // At the same time, the screens become greyed/dimmed
820
821        // newX is initialized to the left-most position of the centered screens
822        float newX = mScroller.getFinalX() + screenWidth / 2 - totalWidth / 2;
823
824        // We are going to scale about the center of the view, so we need to adjust the positions
825        // of the views accordingly
826        newX -= (pageWidth - scaledPageWidth) / 2.0f;
827        newY -= (pageHeight - scaledPageHeight) / 2.0f;
828
829        if (mAnimator != null) {
830            mAnimator.cancel();
831        }
832        mAnimator = new AnimatorSet();
833        for (int i = 0; i < screenCount; i++) {
834            CellLayout cl = (CellLayout) getChildAt(i);
835
836            float rotation = (-i + 2) * WORKSPACE_ROTATION;
837            float rotationScaleX = (float) (1.0f / Math.cos(Math.PI * rotation / 180.0f));
838            float rotationScaleY = getYScaleForScreen(i);
839
840            if (animated) {
841                final int duration = res.getInteger(R.integer.config_workspaceShrinkTime);
842                ObjectAnimator anim = ObjectAnimator.ofPropertyValuesHolder(cl,
843                        PropertyValuesHolder.ofFloat("x", newX),
844                        PropertyValuesHolder.ofFloat("y", newY),
845                        PropertyValuesHolder.ofFloat("scaleX",
846                                SHRINK_FACTOR * rotationScaleX * extraShrinkFactor),
847                        PropertyValuesHolder.ofFloat("scaleY",
848                                SHRINK_FACTOR * rotationScaleY * extraShrinkFactor),
849                        PropertyValuesHolder.ofFloat("backgroundAlpha", finalAlpha),
850                        PropertyValuesHolder.ofFloat("alpha", finalAlpha),
851                        PropertyValuesHolder.ofFloat("rotationY", rotation));
852                anim.setDuration(duration);
853                mAnimator.playTogether(anim);
854            } else {
855                cl.setX((int)newX);
856                cl.setY((int)newY);
857                cl.setScaleX(SHRINK_FACTOR * rotationScaleX * extraShrinkFactor);
858                cl.setScaleY(SHRINK_FACTOR * rotationScaleY * extraShrinkFactor);
859                cl.setBackgroundAlpha(finalAlpha);
860                cl.setAlpha(finalAlpha);
861                cl.setRotationY(rotation);
862            }
863            // increment newX for the next screen
864            newX += scaledPageWidth + extraScaledSpacing;
865        }
866        if (animated) {
867            mAnimator.start();
868        }
869        setChildrenDrawnWithCacheEnabled(true);
870    }
871
872
873    private void updateWhichPagesAcceptDrops(ShrinkPosition state) {
874        updateWhichPagesAcceptDropsHelper(state, false, 1, 1);
875    }
876
877
878    private void updateWhichPagesAcceptDropsDuringDrag(ShrinkPosition state, int spanX, int spanY) {
879        updateWhichPagesAcceptDropsHelper(state, true, spanX, spanY);
880    }
881
882    private void updateWhichPagesAcceptDropsHelper(
883            ShrinkPosition state, boolean isDragHappening, int spanX, int spanY) {
884        final int screenCount = getChildCount();
885        for (int i = 0; i < screenCount; i++) {
886            CellLayout cl = (CellLayout) getChildAt(i);
887
888            switch (state) {
889                case SHRINK_TO_TOP:
890                    if (!isDragHappening) {
891                        boolean showDropHighlight = i == mCurrentPage;
892                        cl.setAcceptsDrops(showDropHighlight);
893                        break;
894                    }
895                    // otherwise, fall through below and mark non-full screens as accepting drops
896                case SHRINK_TO_BOTTOM_HIDDEN:
897                case SHRINK_TO_BOTTOM_VISIBLE:
898                    if (!isDragHappening) {
899                        // even if a drag isn't happening, we don't want to show a screen as
900                        // accepting drops if it doesn't have at least one free cell
901                        spanX = 1;
902                        spanY = 1;
903                    }
904                    // the page accepts drops if we can find at least one empty spot
905                    cl.setAcceptsDrops(cl.findCellForSpan(null, spanX, spanY));
906                    break;
907                default:
908                     throw new RuntimeException(
909                             "updateWhichPagesAcceptDropsHelper passed an unhandled ShrinkPosition");
910            }
911        }
912    }
913
914    /*
915     *
916     * We call these methods (onDragStartedWithItemSpans/onDragStartedWithItemMinSize) whenever we
917     * start a drag in Launcher, regardless of whether the drag has ever entered the Workspace
918     *
919     * These methods mark the appropriate pages as accepting drops (which alters their visual
920     * appearance) and, if the pages are hidden, makes them visible.
921     *
922     */
923    public void onDragStartedWithItemSpans(int spanX, int spanY) {
924        updateWhichPagesAcceptDropsDuringDrag(mShrunkenState, spanX, spanY);
925        if (mShrunkenState == ShrinkPosition.SHRINK_TO_BOTTOM_HIDDEN) {
926            shrink(ShrinkPosition.SHRINK_TO_BOTTOM_VISIBLE, true);
927        }
928    }
929
930    public void onDragStartedWithItemMinSize(int minWidth, int minHeight) {
931        int[] spanXY = CellLayout.rectToCell(getResources(), minWidth, minHeight, null);
932        onDragStartedWithItemSpans(spanXY[0], spanXY[1]);
933    }
934
935    // we call this method whenever a drag and drop in Launcher finishes, even if Workspace was
936    // never dragged over
937    public void onDragStopped() {
938        updateWhichPagesAcceptDrops(mShrunkenState);
939        if (mShrunkenState == ShrinkPosition.SHRINK_TO_BOTTOM_VISIBLE) {
940            shrink(ShrinkPosition.SHRINK_TO_BOTTOM_HIDDEN, true);
941        }
942    }
943
944    // We call this when we trigger an unshrink by clicking on the CellLayout cl
945    public void unshrink(CellLayout clThatWasClicked) {
946        int newCurrentPage = mCurrentPage;
947        final int screenCount = getChildCount();
948        for (int i = 0; i < screenCount; i++) {
949            if (getChildAt(i) == clThatWasClicked) {
950                newCurrentPage = i;
951            }
952        }
953        unshrink(newCurrentPage);
954    }
955
956    @Override
957    protected boolean handlePagingClicks() {
958        return true;
959    }
960
961    private void unshrink(int newCurrentPage) {
962        if (mIsSmall) {
963            int newX = getChildOffset(newCurrentPage) - getRelativeChildOffset(newCurrentPage);
964            int delta = newX - mScrollX;
965
966            final int screenCount = getChildCount();
967            for (int i = 0; i < screenCount; i++) {
968                CellLayout cl = (CellLayout) getChildAt(i);
969                cl.setX(cl.getX() + delta);
970            }
971            setCurrentPage(newCurrentPage);
972            unshrink();
973        }
974    }
975
976    void unshrink() {
977        unshrink(true);
978    }
979
980    void unshrink(boolean animated) {
981        if (mIsSmall) {
982            mIsSmall = false;
983            if (mAnimator != null) {
984                mAnimator.cancel();
985            }
986            mAnimator = new AnimatorSet();
987            final int screenCount = getChildCount();
988
989            final int duration = getResources().getInteger(R.integer.config_workspaceUnshrinkTime);
990            for (int i = 0; i < screenCount; i++) {
991                final CellLayout cl = (CellLayout)getChildAt(i);
992                float finalAlphaValue = (i == mCurrentPage) ? 1.0f : 0.0f;
993                float rotation = 0.0f;
994
995                if (i < mCurrentPage) {
996                    rotation = WORKSPACE_ROTATION;
997                } else if (i > mCurrentPage) {
998                    rotation = -WORKSPACE_ROTATION;
999                }
1000
1001                if (animated) {
1002                    mAnimator.playTogether(
1003                            ObjectAnimator.ofFloat(cl, "translationX", 0.0f).setDuration(duration),
1004                            ObjectAnimator.ofFloat(cl, "translationY", 0.0f).setDuration(duration),
1005                            ObjectAnimator.ofFloat(cl, "scaleX", 1.0f).setDuration(duration),
1006                            ObjectAnimator.ofFloat(cl, "scaleY", 1.0f).setDuration(duration),
1007                            ObjectAnimator.ofFloat(cl, "backgroundAlpha", 0.0f).setDuration(duration),
1008                            ObjectAnimator.ofFloat(cl, "alpha", finalAlphaValue).setDuration(duration),
1009                            ObjectAnimator.ofFloat(cl, "rotationY", rotation).setDuration(duration));
1010                } else {
1011                    cl.setTranslationX(0.0f);
1012                    cl.setTranslationY(0.0f);
1013                    cl.setScaleX(1.0f);
1014                    cl.setScaleY(1.0f);
1015                    cl.setBackgroundAlpha(0.0f);
1016                    cl.setAlpha(finalAlphaValue);
1017                    cl.setRotationY(rotation);
1018                }
1019            }
1020            if (animated) {
1021                // If we call this when we're not animated, onAnimationEnd is never called on
1022                // the listener; make sure we only use the listener when we're actually animating
1023                mAnimator.addListener(mUnshrinkAnimationListener);
1024                mAnimator.start();
1025            }
1026        }
1027    }
1028
1029    /**
1030     * Draw the View v into the given Canvas.
1031     *
1032     * @param v the view to draw
1033     * @param destCanvas the canvas to draw on
1034     * @param padding the horizontal and vertical padding to use when drawing
1035     */
1036    private void drawDragView(View v, Canvas destCanvas, int padding) {
1037        final Rect clipRect = mTempRect;
1038        v.getDrawingRect(clipRect);
1039
1040        // For a TextView, adjust the clip rect so that we don't include the text label
1041        if (v instanceof TextView) {
1042            final int iconHeight = ((TextView)v).getCompoundPaddingTop() - v.getPaddingTop();
1043            clipRect.bottom = clipRect.top + iconHeight;
1044        }
1045
1046        // Draw the View into the bitmap.
1047        // The translate of scrollX and scrollY is necessary when drawing TextViews, because
1048        // they set scrollX and scrollY to large values to achieve centered text
1049
1050        destCanvas.save();
1051        destCanvas.translate(-v.getScrollX() + padding / 2, -v.getScrollY() + padding / 2);
1052        destCanvas.clipRect(clipRect, Op.REPLACE);
1053        v.draw(destCanvas);
1054        destCanvas.restore();
1055    }
1056
1057    /**
1058     * Returns a new bitmap to be used as the object outline, e.g. to visualize the drop location.
1059     * Responsibility for the bitmap is transferred to the caller.
1060     */
1061    private Bitmap createDragOutline(View v, Canvas canvas, int padding) {
1062        final int outlineColor = getResources().getColor(R.color.drag_outline_color);
1063        final Bitmap b = Bitmap.createBitmap(
1064                v.getWidth() + padding, v.getHeight() + padding, Bitmap.Config.ARGB_8888);
1065
1066        canvas.setBitmap(b);
1067        drawDragView(v, canvas, padding);
1068        mOutlineHelper.applyExpensiveOuterOutline(b, canvas, outlineColor, true);
1069
1070        return b;
1071    }
1072
1073    /**
1074     * Creates a drag outline to represent a drop (that we don't have the actual information for
1075     * yet).  May be changed in the future to alter the drop outline slightly depending on the
1076     * clip description mime data.
1077     */
1078    private Bitmap createExternalDragOutline(Canvas canvas, int padding) {
1079        Resources r = getResources();
1080        final int outlineColor = r.getColor(R.color.drag_outline_color);
1081        final int iconWidth = r.getDimensionPixelSize(R.dimen.workspace_cell_width);
1082        final int iconHeight = r.getDimensionPixelSize(R.dimen.workspace_cell_height);
1083        final int rectRadius = r.getDimensionPixelSize(R.dimen.external_drop_icon_rect_radius);
1084        final int inset = (int) (Math.min(iconWidth, iconHeight) * 0.2f);
1085        final Bitmap b = Bitmap.createBitmap(
1086                iconWidth + padding, iconHeight + padding, Bitmap.Config.ARGB_8888);
1087
1088        canvas.setBitmap(b);
1089        canvas.drawRoundRect(new RectF(inset, inset, iconWidth - inset, iconHeight - inset),
1090                rectRadius, rectRadius, mExternalDragOutlinePaint);
1091        mOutlineHelper.applyExpensiveOuterOutline(b, canvas, outlineColor, true);
1092
1093        return b;
1094    }
1095
1096    /**
1097     * Returns a new bitmap to show when the given View is being dragged around.
1098     * Responsibility for the bitmap is transferred to the caller.
1099     */
1100    private Bitmap createDragBitmap(View v, Canvas canvas, int padding) {
1101        final int outlineColor = getResources().getColor(R.color.drag_outline_color);
1102        final Bitmap b = Bitmap.createBitmap(
1103                mDragOutline.getWidth(), mDragOutline.getHeight(), Bitmap.Config.ARGB_8888);
1104
1105        canvas.setBitmap(b);
1106        canvas.drawBitmap(mDragOutline, 0, 0, null);
1107        drawDragView(v, canvas, padding);
1108        mOutlineHelper.applyOuterBlur(b, canvas, outlineColor);
1109
1110        return b;
1111    }
1112
1113    void startDrag(CellLayout.CellInfo cellInfo) {
1114        View child = cellInfo.cell;
1115
1116        // Make sure the drag was started by a long press as opposed to a long click.
1117        if (!child.isInTouchMode()) {
1118            return;
1119        }
1120
1121        mDragInfo = cellInfo;
1122        mDragInfo.screen = mCurrentPage;
1123
1124        CellLayout current = getCurrentDropLayout();
1125
1126        current.onDragChild(child);
1127        child.setVisibility(View.GONE);
1128
1129        child.clearFocus();
1130        child.setPressed(false);
1131
1132        final Canvas canvas = new Canvas();
1133
1134        // We need to add extra padding to the bitmap to make room for the glow effect
1135        final int bitmapPadding = HolographicOutlineHelper.OUTER_BLUR_RADIUS;
1136
1137        // The outline is used to visualize where the item will land if dropped
1138        mDragOutline = createDragOutline(child, canvas, bitmapPadding);
1139
1140        // The drag bitmap follows the touch point around on the screen
1141        final Bitmap b = createDragBitmap(child, canvas, bitmapPadding);
1142
1143        final int bmpWidth = b.getWidth();
1144        final int bmpHeight = b.getHeight();
1145        child.getLocationOnScreen(mTempXY);
1146        final int screenX = (int) mTempXY[0] + (child.getWidth() - bmpWidth) / 2;
1147        final int screenY = (int) mTempXY[1] + (child.getHeight() - bmpHeight) / 2;
1148        mDragController.startDrag(b, screenX, screenY, 0, 0, bmpWidth, bmpHeight, this,
1149                child.getTag(), DragController.DRAG_ACTION_MOVE, null);
1150        b.recycle();
1151    }
1152
1153    void addApplicationShortcut(ShortcutInfo info, int screen, int cellX, int cellY,
1154            boolean insertAtFirst, int intersectX, int intersectY) {
1155        final CellLayout cellLayout = (CellLayout) getChildAt(screen);
1156        View view = mLauncher.createShortcut(R.layout.application, cellLayout, (ShortcutInfo) info);
1157
1158        final int[] cellXY = new int[2];
1159        cellLayout.findCellForSpanThatIntersects(cellXY, 1, 1, intersectX, intersectY);
1160        addInScreen(view, screen, cellXY[0], cellXY[1], 1, 1, insertAtFirst);
1161        LauncherModel.addOrMoveItemInDatabase(mLauncher, info,
1162                LauncherSettings.Favorites.CONTAINER_DESKTOP, screen,
1163                cellXY[0], cellXY[1]);
1164    }
1165
1166    private void setPositionForDropAnimation(
1167            View dragView, int dragViewX, int dragViewY, View parent, View child) {
1168        final CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
1169
1170        // Based on the position of the drag view, find the top left of the original view
1171        int viewX = dragViewX + (dragView.getWidth() - child.getWidth()) / 2;
1172        int viewY = dragViewY + (dragView.getHeight() - child.getHeight()) / 2;
1173        viewX -= getResources().getInteger(R.integer.config_dragViewOffsetX);
1174        viewY -= getResources().getInteger(R.integer.config_dragViewOffsetY);
1175
1176        // Set its old pos (in the new parent's coordinates); it will be animated
1177        // in animateViewIntoPosition after the next layout pass
1178        lp.oldX = viewX - (parent.getLeft() - mScrollX);
1179        lp.oldY = viewY - (parent.getTop() - mScrollY);
1180    }
1181
1182    public void animateViewIntoPosition(final View view) {
1183        final CellLayout parent = (CellLayout) view.getParent();
1184        final CellLayout.LayoutParams lp = (CellLayout.LayoutParams) view.getLayoutParams();
1185
1186        // Convert the animation params to be relative to the Workspace, not the CellLayout
1187        final int fromX = lp.oldX + parent.getLeft();
1188        final int fromY = lp.oldY + parent.getTop();
1189
1190        final int dx = lp.x - lp.oldX;
1191        final int dy = lp.y - lp.oldY;
1192
1193        // Calculate the duration of the animation based on the object's distance
1194        final float dist = (float) Math.sqrt(dx*dx + dy*dy);
1195        final Resources res = getResources();
1196        final float maxDist = (float) res.getInteger(R.integer.config_dropAnimMaxDist);
1197        int duration = res.getInteger(R.integer.config_dropAnimMaxDuration);
1198        if (dist < maxDist) {
1199            duration *= mQuintEaseOutInterpolator.getInterpolation(dist / maxDist);
1200        }
1201
1202        if (mDropAnim != null) {
1203            // This should really be end(), but that will not be called synchronously,
1204            // so instead we use LauncherAnimatorListenerAdapter.onAnimationEndOrCancel()
1205            // and call cancel() here.
1206            mDropAnim.cancel();
1207        }
1208        mDropAnim = new ValueAnimator();
1209        mDropAnim.setInterpolator(mQuintEaseOutInterpolator);
1210
1211        // The view is invisible during the animation; we render it manually.
1212        mDropAnim.addListener(new LauncherAnimatorListenerAdapter() {
1213            public void onAnimationStart(Animator animation) {
1214                // Set this here so that we don't render it until the animation begins
1215                mDropView = view;
1216            }
1217
1218            public void onAnimationEndOrCancel(Animator animation) {
1219                if (mDropView != null) {
1220                    mDropView.setVisibility(View.VISIBLE);
1221                    mDropView = null;
1222                }
1223            }
1224        });
1225
1226        mDropAnim.setDuration(duration);
1227        mDropAnim.setFloatValues(0.0f, 1.0f);
1228        mDropAnim.removeAllUpdateListeners();
1229        mDropAnim.addUpdateListener(new AnimatorUpdateListener() {
1230            public void onAnimationUpdate(ValueAnimator animation) {
1231                final float percent = (Float) animation.getAnimatedValue();
1232                // Invalidate the old position
1233                invalidate(mDropViewPos[0], mDropViewPos[1],
1234                        mDropViewPos[0] + view.getWidth(), mDropViewPos[1] + view.getHeight());
1235
1236                mDropViewPos[0] = fromX + (int) (percent * dx + 0.5f);
1237                mDropViewPos[1] = fromY + (int) (percent * dy + 0.5f);
1238                invalidate(mDropViewPos[0], mDropViewPos[1],
1239                        mDropViewPos[0] + view.getWidth(), mDropViewPos[1] + view.getHeight());
1240            }
1241        });
1242
1243
1244        view.setVisibility(View.INVISIBLE);
1245
1246        if (!mScroller.isFinished()) {
1247            mAnimOnPageEndMoving = mDropAnim;
1248        } else {
1249            mDropAnim.start();
1250        }
1251    }
1252
1253    /**
1254     * {@inheritDoc}
1255     */
1256    public boolean acceptDrop(DragSource source, int x, int y,
1257            int xOffset, int yOffset, DragView dragView, Object dragInfo) {
1258
1259        // If it's an external drop (e.g. from All Apps), check if it should be accepted
1260        if (source != this) {
1261            // Don't accept the drop if we're not over a screen at time of drop
1262            if (mDragTargetLayout == null) {
1263                return false;
1264            }
1265
1266            final CellLayout.CellInfo dragCellInfo = mDragInfo;
1267            final int spanX = dragCellInfo == null ? 1 : dragCellInfo.spanX;
1268            final int spanY = dragCellInfo == null ? 1 : dragCellInfo.spanY;
1269
1270            final View ignoreView = dragCellInfo == null ? null : dragCellInfo.cell;
1271
1272            // Don't accept the drop if there's no room for the item
1273            if (!mDragTargetLayout.findCellForSpanIgnoring(null, spanX, spanY, ignoreView)) {
1274                mLauncher.showOutOfSpaceMessage();
1275                return false;
1276            }
1277        }
1278        return true;
1279    }
1280
1281    public void onDrop(DragSource source, int x, int y, int xOffset, int yOffset,
1282            DragView dragView, Object dragInfo) {
1283
1284        int originX = x - xOffset;
1285        int originY = y - yOffset;
1286
1287        if (mIsSmall || mIsInUnshrinkAnimation) {
1288            // get originX and originY in the local coordinate system of the screen
1289            mTempOriginXY[0] = originX;
1290            mTempOriginXY[1] = originY;
1291            mapPointFromSelfToChild(mDragTargetLayout, mTempOriginXY);
1292            originX = (int)mTempOriginXY[0];
1293            originY = (int)mTempOriginXY[1];
1294        }
1295
1296        if (source != this) {
1297            onDropExternal(originX, originY, dragInfo, mDragTargetLayout);
1298        } else if (mDragInfo != null) {
1299            final View cell = mDragInfo.cell;
1300            if (mDragTargetLayout != null) {
1301                // Move internally
1302                mTargetCell = findNearestVacantArea(originX, originY,
1303                        mDragInfo.spanX, mDragInfo.spanY, cell, mDragTargetLayout,
1304                        mTargetCell);
1305
1306                if (mTargetCell == null) {
1307                    snapToPage(mDragInfo.screen);
1308                } else {
1309                    int screen = indexOfChild(mDragTargetLayout);
1310                    if (screen != mDragInfo.screen) {
1311                        // Reparent the view
1312                        ((CellLayout) getChildAt(mDragInfo.screen)).removeView(cell);
1313                        addInScreen(cell, screen, mTargetCell[0], mTargetCell[1],
1314                                mDragInfo.spanX, mDragInfo.spanY);
1315                    }
1316
1317                    // update the item's position after drop
1318                    final ItemInfo info = (ItemInfo) cell.getTag();
1319                    CellLayout.LayoutParams lp = (CellLayout.LayoutParams) cell.getLayoutParams();
1320                    mDragTargetLayout.onMove(cell, mTargetCell[0], mTargetCell[1]);
1321                    lp.cellX = mTargetCell[0];
1322                    lp.cellY = mTargetCell[1];
1323                    cell.setId(LauncherModel.getCellLayoutChildId(-1, mDragInfo.screen,
1324                            mTargetCell[0], mTargetCell[1], mDragInfo.spanX, mDragInfo.spanY));
1325
1326                    LauncherModel.moveItemInDatabase(mLauncher, info,
1327                            LauncherSettings.Favorites.CONTAINER_DESKTOP, screen,
1328                            lp.cellX, lp.cellY);
1329                }
1330            }
1331
1332            final CellLayout parent = (CellLayout) cell.getParent();
1333
1334            // Prepare it to be animated into its new position
1335            // This must be called after the view has been re-parented
1336            setPositionForDropAnimation(dragView, originX, originY, parent, cell);
1337            parent.onDropChild(cell);
1338        }
1339    }
1340
1341    public void onDragEnter(DragSource source, int x, int y, int xOffset,
1342            int yOffset, DragView dragView, Object dragInfo) {
1343        mDragTargetLayout = null; // Reset the drag state
1344
1345        if (!mIsSmall) {
1346            mDragTargetLayout = getCurrentDropLayout();
1347            mDragTargetLayout.onDragEnter();
1348            showOutlines();
1349            mInDragMode = true;
1350            CellLayout cl = (CellLayout) getChildAt(mCurrentPage);
1351            cl.setBackgroundAlphaMultiplier(1.0f);
1352        }
1353    }
1354
1355    public DropTarget getDropTargetDelegate(DragSource source, int x, int y,
1356            int xOffset, int yOffset, DragView dragView, Object dragInfo) {
1357
1358        if (mIsSmall || mIsInUnshrinkAnimation) {
1359            // If we're shrunken, don't let anyone drag on folders/etc that are on the mini-screens
1360            return null;
1361        }
1362        // We may need to delegate the drag to a child view. If a 1x1 item
1363        // would land in a cell occupied by a DragTarget (e.g. a Folder),
1364        // then drag events should be handled by that child.
1365
1366        ItemInfo item = (ItemInfo)dragInfo;
1367        CellLayout currentLayout = getCurrentDropLayout();
1368
1369        int dragPointX, dragPointY;
1370        if (item.spanX == 1 && item.spanY == 1) {
1371            // For a 1x1, calculate the drop cell exactly as in onDragOver
1372            dragPointX = x - xOffset;
1373            dragPointY = y - yOffset;
1374        } else {
1375            // Otherwise, use the exact drag coordinates
1376            dragPointX = x;
1377            dragPointY = y;
1378        }
1379        dragPointX += mScrollX - currentLayout.getLeft();
1380        dragPointY += mScrollY - currentLayout.getTop();
1381
1382        // If we are dragging over a cell that contains a DropTarget that will
1383        // accept the drop, delegate to that DropTarget.
1384        final int[] cellXY = mTempCell;
1385        currentLayout.estimateDropCell(dragPointX, dragPointY, item.spanX, item.spanY, cellXY);
1386        View child = currentLayout.getChildAt(cellXY[0], cellXY[1]);
1387        if (child instanceof DropTarget) {
1388            DropTarget target = (DropTarget)child;
1389            if (target.acceptDrop(source, x, y, xOffset, yOffset, dragView, dragInfo)) {
1390                return target;
1391            }
1392        }
1393        return null;
1394    }
1395
1396    /**
1397     * Global drag and drop handler
1398     */
1399    @Override
1400    public boolean onDragEvent(DragEvent event) {
1401        final ClipDescription desc = event.getClipDescription();
1402        final CellLayout layout = (CellLayout) getChildAt(mCurrentPage);
1403        final int[] pos = new int[2];
1404        layout.getLocationOnScreen(pos);
1405        // We need to offset the drag coordinates to layout coordinate space
1406        final int x = (int) event.getX() - pos[0];
1407        final int y = (int) event.getY() - pos[1];
1408
1409        switch (event.getAction()) {
1410        case DragEvent.ACTION_DRAG_STARTED:
1411            // Check if we have enough space on this screen to add a new shortcut
1412            if (!layout.findCellForSpan(pos, 1, 1)) {
1413                Toast.makeText(mContext, mContext.getString(R.string.out_of_space),
1414                        Toast.LENGTH_SHORT).show();
1415                return false;
1416            }
1417
1418            // Create the drag outline
1419            // We need to add extra padding to the bitmap to make room for the glow effect
1420            final Canvas canvas = new Canvas();
1421            final int bitmapPadding = HolographicOutlineHelper.OUTER_BLUR_RADIUS;
1422            mDragOutline = createExternalDragOutline(canvas, bitmapPadding);
1423
1424            // Show the current page outlines to indicate that we can accept this drop
1425            showOutlines();
1426            layout.setHover(true);
1427            layout.onDragEnter();
1428            layout.visualizeDropLocation(null, mDragOutline, x, y, 1, 1);
1429
1430            return true;
1431        case DragEvent.ACTION_DRAG_LOCATION:
1432            // Visualize the drop location
1433            layout.visualizeDropLocation(null, mDragOutline, x, y, 1, 1);
1434            return true;
1435        case DragEvent.ACTION_DROP:
1436            // Check if we have enough space on this screen to add a new shortcut
1437            if (!layout.findCellForSpan(pos, 1, 1)) {
1438                Toast.makeText(mContext, mContext.getString(R.string.out_of_space),
1439                        Toast.LENGTH_SHORT).show();
1440                return false;
1441            }
1442
1443            // Try and add any shortcuts
1444            int newDropCount = 0;
1445            final LauncherModel model = mLauncher.getModel();
1446            final ClipData data = event.getClipData();
1447
1448            // We assume that the mime types are ordered in descending importance of
1449            // representation. So we enumerate the list of mime types and alert the
1450            // user if any widgets can handle the drop.  Only the most preferred
1451            // representation will be handled.
1452            pos[0] = x;
1453            pos[1] = y;
1454            final int mimeTypeCount = desc.getMimeTypeCount();
1455            for (int j = 0; j < mimeTypeCount; ++j) {
1456                final String mimeType = desc.getMimeType(j);
1457
1458                if (mimeType.equals(InstallShortcutReceiver.SHORTCUT_MIMETYPE)) {
1459                    final Intent intent = data.getItem(j).getIntent();
1460                    Object info = model.infoFromShortcutIntent(mContext, intent, data.getIcon());
1461                    onDropExternal(x, y, info, layout);
1462                } else {
1463                    final List<WidgetMimeTypeHandlerData> widgets =
1464                        model.resolveWidgetsForMimeType(mContext, mimeType);
1465                    final int numWidgets = widgets.size();
1466
1467                    if (numWidgets == 0) {
1468                        continue;
1469                    } else if (numWidgets == 1) {
1470                        // If there is only one item, then go ahead and add and configure
1471                        // that widget
1472                        final AppWidgetProviderInfo widgetInfo = widgets.get(0).widgetInfo;
1473                        final PendingAddWidgetInfo createInfo =
1474                                new PendingAddWidgetInfo(widgetInfo, mimeType, data);
1475                        mLauncher.addAppWidgetFromDrop(createInfo, mCurrentPage, pos);
1476                    } else if (numWidgets > 1) {
1477                        // Show the widget picker dialog if there is more than one widget
1478                        // that can handle this data type
1479                        final InstallWidgetReceiver.WidgetListAdapter adapter =
1480                            new InstallWidgetReceiver.WidgetListAdapter(mLauncher, mimeType,
1481                                    data, widgets, layout, mCurrentPage, pos);
1482                        final AlertDialog.Builder builder =
1483                            new AlertDialog.Builder(mContext);
1484                        builder.setAdapter(adapter, adapter);
1485                        builder.setCancelable(true);
1486                        builder.setTitle(mContext.getString(
1487                                R.string.external_drop_widget_pick_title));
1488                        builder.setIcon(R.drawable.ic_no_applications);
1489                        builder.show();
1490                    }
1491                }
1492                newDropCount++;
1493                break;
1494            }
1495
1496            // Show error message if we couldn't accept any of the items
1497            if (newDropCount <= 0) {
1498                Toast.makeText(mContext, mContext.getString(R.string.external_drop_widget_error),
1499                        Toast.LENGTH_SHORT).show();
1500            }
1501
1502            return true;
1503        case DragEvent.ACTION_DRAG_ENDED:
1504            // Hide the page outlines after the drop
1505            layout.setHover(false);
1506            layout.onDragExit();
1507            hideOutlines();
1508            return true;
1509        }
1510        return super.onDragEvent(event);
1511    }
1512
1513    /*
1514    *
1515    * Convert the 2D coordinate xy from the parent View's coordinate space to this CellLayout's
1516    * coordinate space. The argument xy is modified with the return result.
1517    *
1518    */
1519   void mapPointFromSelfToChild(View v, float[] xy) {
1520       mapPointFromSelfToChild(v, xy, null);
1521   }
1522
1523   /*
1524    *
1525    * Convert the 2D coordinate xy from the parent View's coordinate space to this CellLayout's
1526    * coordinate space. The argument xy is modified with the return result.
1527    *
1528    * if cachedInverseMatrix is not null, this method will just use that matrix instead of
1529    * computing it itself; we use this to avoid redundant matrix inversions in
1530    * findMatchingPageForDragOver
1531    *
1532    */
1533   void mapPointFromSelfToChild(View v, float[] xy, Matrix cachedInverseMatrix) {
1534       if (cachedInverseMatrix == null) {
1535           v.getMatrix().invert(mTempInverseMatrix);
1536           cachedInverseMatrix = mTempInverseMatrix;
1537       }
1538       xy[0] = xy[0] + mScrollX - v.getLeft();
1539       xy[1] = xy[1] + mScrollY - v.getTop();
1540       cachedInverseMatrix.mapPoints(xy);
1541   }
1542
1543   /*
1544    *
1545    * Convert the 2D coordinate xy from this CellLayout's coordinate space to
1546    * the parent View's coordinate space. The argument xy is modified with the return result.
1547    *
1548    */
1549   void mapPointFromChildToSelf(View v, float[] xy) {
1550       v.getMatrix().mapPoints(xy);
1551       xy[0] -= (mScrollX - v.getLeft());
1552       xy[1] -= (mScrollY - v.getTop());
1553   }
1554
1555    static private float squaredDistance(float[] point1, float[] point2) {
1556        float distanceX = point1[0] - point2[0];
1557        float distanceY = point2[1] - point2[1];
1558        return distanceX * distanceX + distanceY * distanceY;
1559    }
1560
1561    /*
1562     *
1563     * Returns true if the passed CellLayout cl overlaps with dragView
1564     *
1565     */
1566    boolean overlaps(CellLayout cl, DragView dragView,
1567            int dragViewX, int dragViewY, Matrix cachedInverseMatrix) {
1568        // Transform the coordinates of the item being dragged to the CellLayout's coordinates
1569        final float[] draggedItemTopLeft = mTempDragCoordinates;
1570        draggedItemTopLeft[0] = dragViewX + dragView.getScaledDragRegionXOffset();
1571        draggedItemTopLeft[1] = dragViewY + dragView.getScaledDragRegionYOffset();
1572        final float[] draggedItemBottomRight = mTempDragBottomRightCoordinates;
1573        draggedItemBottomRight[0] = draggedItemTopLeft[0] + dragView.getScaledDragRegionWidth();
1574        draggedItemBottomRight[1] = draggedItemTopLeft[1] + dragView.getScaledDragRegionHeight();
1575
1576        // Transform the dragged item's top left coordinates
1577        // to the CellLayout's local coordinates
1578        mapPointFromSelfToChild(cl, draggedItemTopLeft, cachedInverseMatrix);
1579        float overlapRegionLeft = Math.max(0f, draggedItemTopLeft[0]);
1580        float overlapRegionTop = Math.max(0f, draggedItemTopLeft[1]);
1581
1582        if (overlapRegionLeft <= cl.getWidth() && overlapRegionTop >= 0) {
1583            // Transform the dragged item's bottom right coordinates
1584            // to the CellLayout's local coordinates
1585            mapPointFromSelfToChild(cl, draggedItemBottomRight, cachedInverseMatrix);
1586            float overlapRegionRight = Math.min(cl.getWidth(), draggedItemBottomRight[0]);
1587            float overlapRegionBottom = Math.min(cl.getHeight(), draggedItemBottomRight[1]);
1588
1589            if (overlapRegionRight >= 0 && overlapRegionBottom <= cl.getHeight()) {
1590                float overlap = (overlapRegionRight - overlapRegionLeft) *
1591                         (overlapRegionBottom - overlapRegionTop);
1592                if (overlap > 0) {
1593                    return true;
1594                }
1595             }
1596        }
1597        return false;
1598    }
1599
1600    /*
1601     *
1602     * This method returns the CellLayout that is currently being dragged to. In order to drag
1603     * to a CellLayout, either the touch point must be directly over the CellLayout, or as a second
1604     * strategy, we see if the dragView is overlapping any CellLayout and choose the closest one
1605     *
1606     * Return null if no CellLayout is currently being dragged over
1607     *
1608     */
1609    private CellLayout findMatchingPageForDragOver(
1610            DragView dragView, int originX, int originY, int offsetX, int offsetY) {
1611        // We loop through all the screens (ie CellLayouts) and see which ones overlap
1612        // with the item being dragged and then choose the one that's closest to the touch point
1613        final int screenCount = getChildCount();
1614        CellLayout bestMatchingScreen = null;
1615        float smallestDistSoFar = Float.MAX_VALUE;
1616
1617        for (int i = 0; i < screenCount; i++) {
1618            CellLayout cl = (CellLayout)getChildAt(i);
1619
1620            final float[] touchXy = mTempTouchCoordinates;
1621            touchXy[0] = originX + offsetX;
1622            touchXy[1] = originY + offsetY;
1623
1624            // Transform the touch coordinates to the CellLayout's local coordinates
1625            // If the touch point is within the bounds of the cell layout, we can return immediately
1626            cl.getMatrix().invert(mTempInverseMatrix);
1627            mapPointFromSelfToChild(cl, touchXy, mTempInverseMatrix);
1628
1629            if (touchXy[0] >= 0 && touchXy[0] <= cl.getWidth() &&
1630                    touchXy[1] >= 0 && touchXy[1] <= cl.getHeight()) {
1631                return cl;
1632            }
1633
1634            if (overlaps(cl, dragView, originX, originY, mTempInverseMatrix)) {
1635                // Get the center of the cell layout in screen coordinates
1636                final float[] cellLayoutCenter = mTempCellLayoutCenterCoordinates;
1637                cellLayoutCenter[0] = cl.getWidth()/2;
1638                cellLayoutCenter[1] = cl.getHeight()/2;
1639                mapPointFromChildToSelf(cl, cellLayoutCenter);
1640
1641                touchXy[0] = originX + offsetX;
1642                touchXy[1] = originY + offsetY;
1643
1644                // Calculate the distance between the center of the CellLayout
1645                // and the touch point
1646                float dist = squaredDistance(touchXy, cellLayoutCenter);
1647
1648                if (dist < smallestDistSoFar) {
1649                    smallestDistSoFar = dist;
1650                    bestMatchingScreen = cl;
1651                }
1652            }
1653        }
1654        return bestMatchingScreen;
1655    }
1656
1657    public void onDragOver(DragSource source, int x, int y, int xOffset, int yOffset,
1658            DragView dragView, Object dragInfo) {
1659        // When touch is inside the scroll area, skip dragOver actions for the current screen
1660        if (!mInScrollArea) {
1661            CellLayout layout;
1662            int originX = x - xOffset;
1663            int originY = y - yOffset;
1664            if (mIsSmall || mIsInUnshrinkAnimation) {
1665                layout = findMatchingPageForDragOver(
1666                        dragView, originX, originY, xOffset, yOffset);
1667
1668                if (layout != mDragTargetLayout) {
1669                    if (mDragTargetLayout != null) {
1670                        mDragTargetLayout.setHover(false);
1671                    }
1672                    mDragTargetLayout = layout;
1673                    if (mDragTargetLayout != null) {
1674                        mDragTargetLayout.setHover(true);
1675                    }
1676                }
1677            } else {
1678                layout = getCurrentDropLayout();
1679
1680                final ItemInfo item = (ItemInfo)dragInfo;
1681                if (dragInfo instanceof LauncherAppWidgetInfo) {
1682                    LauncherAppWidgetInfo widgetInfo = (LauncherAppWidgetInfo)dragInfo;
1683
1684                    if (widgetInfo.spanX == -1) {
1685                        // Calculate the grid spans needed to fit this widget
1686                        int[] spans = layout.rectToCell(
1687                                widgetInfo.minWidth, widgetInfo.minHeight, null);
1688                        item.spanX = spans[0];
1689                        item.spanY = spans[1];
1690                    }
1691                }
1692
1693                if (source instanceof AllAppsPagedView) {
1694                    // This is a hack to fix the point used to determine which cell an icon from
1695                    // the all apps screen is over
1696                    if (item != null && item.spanX == 1 && layout != null) {
1697                        int dragRegionLeft = (dragView.getWidth() - layout.getCellWidth()) / 2;
1698
1699                        originX += dragRegionLeft - dragView.getDragRegionLeft();
1700                        if (dragView.getDragRegionWidth() != layout.getCellWidth()) {
1701                            dragView.setDragRegion(dragView.getDragRegionLeft(),
1702                                    dragView.getDragRegionTop(),
1703                                    layout.getCellWidth(),
1704                                    dragView.getDragRegionHeight());
1705                        }
1706                    }
1707                }
1708
1709                if (layout != mDragTargetLayout) {
1710                    if (mDragTargetLayout != null) {
1711                        mDragTargetLayout.onDragExit();
1712                    }
1713                    layout.onDragEnter();
1714                    mDragTargetLayout = layout;
1715                }
1716
1717                // only visualize the drop locations for moving icons within the home screen on
1718                // tablet on phone, we also visualize icons dragged in from All Apps
1719                if ((!LauncherApplication.isScreenXLarge() || source == this)
1720                        && mDragTargetLayout != null) {
1721                    final View child = (mDragInfo == null) ? null : mDragInfo.cell;
1722                    int localOriginX = originX - (mDragTargetLayout.getLeft() - mScrollX);
1723                    int localOriginY = originY - (mDragTargetLayout.getTop() - mScrollY);
1724                    mDragTargetLayout.visualizeDropLocation(child, mDragOutline,
1725                            localOriginX, localOriginY, item.spanX, item.spanY);
1726                }
1727            }
1728        }
1729    }
1730
1731    public void onDragExit(DragSource source, int x, int y, int xOffset,
1732            int yOffset, DragView dragView, Object dragInfo) {
1733        if (mDragTargetLayout != null) {
1734            mDragTargetLayout.onDragExit();
1735        }
1736        if (!mIsPageMoving) {
1737            hideOutlines();
1738            mInDragMode = false;
1739        }
1740        clearAllHovers();
1741    }
1742
1743    private void onDropExternal(int x, int y, Object dragInfo,
1744            CellLayout cellLayout) {
1745        onDropExternal(x, y, dragInfo, cellLayout, false);
1746    }
1747
1748    /**
1749     * Add the item specified by dragInfo to the given layout.
1750     * This is basically the equivalent of onDropExternal, except it's not initiated
1751     * by drag and drop.
1752     * @return true if successful
1753     */
1754    public boolean addExternalItemToScreen(Object dragInfo, View layout) {
1755        CellLayout cl = (CellLayout) layout;
1756        ItemInfo info = (ItemInfo) dragInfo;
1757
1758        if (cl.findCellForSpan(mTempEstimate, info.spanX, info.spanY)) {
1759            onDropExternal(-1, -1, dragInfo, cl, false);
1760            return true;
1761        }
1762        mLauncher.showOutOfSpaceMessage();
1763        return false;
1764    }
1765
1766    // Drag from somewhere else
1767    // NOTE: This can also be called when we are outside of a drag event, when we want
1768    // to add an item to one of the workspace screens.
1769    private void onDropExternal(int x, int y, Object dragInfo,
1770            CellLayout cellLayout, boolean insertAtFirst) {
1771        int screen = indexOfChild(cellLayout);
1772        if (dragInfo instanceof PendingAddItemInfo) {
1773            PendingAddItemInfo info = (PendingAddItemInfo) dragInfo;
1774            // When dragging and dropping from customization tray, we deal with creating
1775            // widgets/shortcuts/folders in a slightly different way
1776            int[] touchXY = new int[2];
1777            touchXY[0] = x;
1778            touchXY[1] = y;
1779            switch (info.itemType) {
1780                case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
1781                    mLauncher.addAppWidgetFromDrop((PendingAddWidgetInfo) info, screen, touchXY);
1782                    break;
1783                case LauncherSettings.Favorites.ITEM_TYPE_LIVE_FOLDER:
1784                    mLauncher.addLiveFolderFromDrop(info.componentName, screen, touchXY);
1785                    break;
1786                case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
1787                    mLauncher.processShortcutFromDrop(info.componentName, screen, touchXY);
1788                    break;
1789                default:
1790                    throw new IllegalStateException("Unknown item type: " + info.itemType);
1791            }
1792            cellLayout.onDragExit();
1793            cellLayout.animateDrop();
1794            return;
1795        }
1796
1797        // This is for other drag/drop cases, like dragging from All Apps
1798        ItemInfo info = (ItemInfo) dragInfo;
1799
1800        View view = null;
1801
1802        switch (info.itemType) {
1803        case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
1804        case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
1805            if (info.container == NO_ID && info instanceof ApplicationInfo) {
1806                // Came from all apps -- make a copy
1807                info = new ShortcutInfo((ApplicationInfo) info);
1808            }
1809            view = mLauncher.createShortcut(R.layout.application, cellLayout,
1810                    (ShortcutInfo) info);
1811            break;
1812        case LauncherSettings.Favorites.ITEM_TYPE_USER_FOLDER:
1813            view = FolderIcon.fromXml(R.layout.folder_icon, mLauncher,
1814                    cellLayout, (UserFolderInfo) info, mIconCache);
1815            break;
1816        default:
1817            throw new IllegalStateException("Unknown item type: " + info.itemType);
1818        }
1819
1820        // If the view is null, it has already been added.
1821        if (view == null) {
1822            cellLayout.onDragExit();
1823        } else {
1824            mTargetCell = findNearestVacantArea(x, y, 1, 1, null, cellLayout, mTargetCell);
1825            addInScreen(view, indexOfChild(cellLayout), mTargetCell[0],
1826                    mTargetCell[1], info.spanX, info.spanY, insertAtFirst);
1827            cellLayout.onDropChild(view);
1828            cellLayout.animateDrop();
1829            CellLayout.LayoutParams lp = (CellLayout.LayoutParams) view.getLayoutParams();
1830
1831            LauncherModel.addOrMoveItemInDatabase(mLauncher, info,
1832                    LauncherSettings.Favorites.CONTAINER_DESKTOP, screen,
1833                    lp.cellX, lp.cellY);
1834        }
1835    }
1836
1837    /**
1838     * Return the current {@link CellLayout}, correctly picking the destination
1839     * screen while a scroll is in progress.
1840     */
1841    private CellLayout getCurrentDropLayout() {
1842        // if we're currently small, use findMatchingPageForDragOver instead
1843        if (mIsSmall) return null;
1844        int index = mScroller.isFinished() ? mCurrentPage : mNextPage;
1845        return (CellLayout) getChildAt(index);
1846    }
1847
1848    /**
1849     * Return the current CellInfo describing our current drag; this method exists
1850     * so that Launcher can sync this object with the correct info when the activity is created/
1851     * destroyed
1852     *
1853     */
1854    public CellLayout.CellInfo getDragInfo() {
1855        return mDragInfo;
1856    }
1857
1858    /**
1859     * Calculate the nearest cell where the given object would be dropped.
1860     */
1861    private int[] findNearestVacantArea(int pixelX, int pixelY,
1862            int spanX, int spanY, View ignoreView, CellLayout layout, int[] recycle) {
1863
1864        int localPixelX = pixelX - (layout.getLeft() - mScrollX);
1865        int localPixelY = pixelY - (layout.getTop() - mScrollY);
1866
1867        // Find the best target drop location
1868        return layout.findNearestVacantArea(
1869                localPixelX, localPixelY, spanX, spanY, ignoreView, recycle);
1870    }
1871
1872    /**
1873     * Estimate the size that a child with the given dimensions will take in the current screen.
1874     */
1875    void estimateChildSize(int minWidth, int minHeight, int[] result) {
1876        ((CellLayout)getChildAt(mCurrentPage)).estimateChildSize(minWidth, minHeight, result);
1877    }
1878
1879    void setLauncher(Launcher launcher) {
1880        mLauncher = launcher;
1881    }
1882
1883    public void setDragController(DragController dragController) {
1884        mDragController = dragController;
1885    }
1886
1887    public void onDropCompleted(View target, boolean success) {
1888        if (success) {
1889            if (target != this && mDragInfo != null) {
1890                final CellLayout cellLayout = (CellLayout) getChildAt(mDragInfo.screen);
1891                cellLayout.removeView(mDragInfo.cell);
1892                if (mDragInfo.cell instanceof DropTarget) {
1893                    mDragController.removeDropTarget((DropTarget)mDragInfo.cell);
1894                }
1895                // final Object tag = mDragInfo.cell.getTag();
1896            }
1897        } else if (mDragInfo != null) {
1898            ((CellLayout) getChildAt(mDragInfo.screen)).onDropChild(mDragInfo.cell);
1899        }
1900
1901        mDragOutline = null;
1902        mDragInfo = null;
1903    }
1904
1905    public boolean isDropEnabled() {
1906        return true;
1907    }
1908
1909    @Override
1910    protected void onRestoreInstanceState(Parcelable state) {
1911        super.onRestoreInstanceState(state);
1912        Launcher.setScreen(mCurrentPage);
1913    }
1914
1915    @Override
1916    public void scrollLeft() {
1917        if (!mIsSmall && !mIsInUnshrinkAnimation) {
1918            super.scrollLeft();
1919        }
1920    }
1921
1922    @Override
1923    public void scrollRight() {
1924        if (!mIsSmall && !mIsInUnshrinkAnimation) {
1925            super.scrollRight();
1926        }
1927    }
1928
1929    @Override
1930    public void onEnterScrollArea(int direction) {
1931        if (!mIsSmall && !mIsInUnshrinkAnimation) {
1932            mInScrollArea = true;
1933            final int screen = getCurrentPage() + ((direction == DragController.SCROLL_LEFT) ? -1 : 1);
1934            if (0 <= screen && screen < getChildCount()) {
1935                ((CellLayout) getChildAt(screen)).setHover(true);
1936
1937                if (mDragTargetLayout != null) {
1938                    mDragTargetLayout.onDragExit();
1939                    mDragTargetLayout = null;
1940                }
1941            }
1942        }
1943    }
1944
1945    private void clearAllHovers() {
1946        final int childCount = getChildCount();
1947        for (int i = 0; i < childCount; i++) {
1948            ((CellLayout) getChildAt(i)).setHover(false);
1949        }
1950    }
1951
1952    @Override
1953    public void onExitScrollArea() {
1954        if (mInScrollArea) {
1955            mInScrollArea = false;
1956            clearAllHovers();
1957        }
1958    }
1959
1960    public Folder getFolderForTag(Object tag) {
1961        final int screenCount = getChildCount();
1962        for (int screen = 0; screen < screenCount; screen++) {
1963            CellLayout currentScreen = ((CellLayout) getChildAt(screen));
1964            int count = currentScreen.getChildCount();
1965            for (int i = 0; i < count; i++) {
1966                View child = currentScreen.getChildAt(i);
1967                CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
1968                if (lp.cellHSpan == 4 && lp.cellVSpan == 4 && child instanceof Folder) {
1969                    Folder f = (Folder) child;
1970                    if (f.getInfo() == tag && f.getInfo().opened) {
1971                        return f;
1972                    }
1973                }
1974            }
1975        }
1976        return null;
1977    }
1978
1979    public View getViewForTag(Object tag) {
1980        int screenCount = getChildCount();
1981        for (int screen = 0; screen < screenCount; screen++) {
1982            CellLayout currentScreen = ((CellLayout) getChildAt(screen));
1983            int count = currentScreen.getChildCount();
1984            for (int i = 0; i < count; i++) {
1985                View child = currentScreen.getChildAt(i);
1986                if (child.getTag() == tag) {
1987                    return child;
1988                }
1989            }
1990        }
1991        return null;
1992    }
1993
1994
1995    void removeItems(final ArrayList<ApplicationInfo> apps) {
1996        final int screenCount = getChildCount();
1997        final PackageManager manager = getContext().getPackageManager();
1998        final AppWidgetManager widgets = AppWidgetManager.getInstance(getContext());
1999
2000        final HashSet<String> packageNames = new HashSet<String>();
2001        final int appCount = apps.size();
2002        for (int i = 0; i < appCount; i++) {
2003            packageNames.add(apps.get(i).componentName.getPackageName());
2004        }
2005
2006        for (int i = 0; i < screenCount; i++) {
2007            final CellLayout layout = (CellLayout) getChildAt(i);
2008
2009            // Avoid ANRs by treating each screen separately
2010            post(new Runnable() {
2011                public void run() {
2012                    final ArrayList<View> childrenToRemove = new ArrayList<View>();
2013                    childrenToRemove.clear();
2014
2015                    int childCount = layout.getChildCount();
2016                    for (int j = 0; j < childCount; j++) {
2017                        final View view = layout.getChildAt(j);
2018                        Object tag = view.getTag();
2019
2020                        if (tag instanceof ShortcutInfo) {
2021                            final ShortcutInfo info = (ShortcutInfo) tag;
2022                            final Intent intent = info.intent;
2023                            final ComponentName name = intent.getComponent();
2024
2025                            if (Intent.ACTION_MAIN.equals(intent.getAction()) && name != null) {
2026                                for (String packageName: packageNames) {
2027                                    if (packageName.equals(name.getPackageName())) {
2028                                        LauncherModel.deleteItemFromDatabase(mLauncher, info);
2029                                        childrenToRemove.add(view);
2030                                    }
2031                                }
2032                            }
2033                        } else if (tag instanceof UserFolderInfo) {
2034                            final UserFolderInfo info = (UserFolderInfo) tag;
2035                            final ArrayList<ShortcutInfo> contents = info.contents;
2036                            final ArrayList<ShortcutInfo> toRemove = new ArrayList<ShortcutInfo>(1);
2037                            final int contentsCount = contents.size();
2038                            boolean removedFromFolder = false;
2039
2040                            for (int k = 0; k < contentsCount; k++) {
2041                                final ShortcutInfo appInfo = contents.get(k);
2042                                final Intent intent = appInfo.intent;
2043                                final ComponentName name = intent.getComponent();
2044
2045                                if (Intent.ACTION_MAIN.equals(intent.getAction()) && name != null) {
2046                                    for (String packageName: packageNames) {
2047                                        if (packageName.equals(name.getPackageName())) {
2048                                            toRemove.add(appInfo);
2049                                            LauncherModel.deleteItemFromDatabase(mLauncher, appInfo);
2050                                            removedFromFolder = true;
2051                                        }
2052                                    }
2053                                }
2054                            }
2055
2056                            contents.removeAll(toRemove);
2057                            if (removedFromFolder) {
2058                                final Folder folder = getOpenFolder();
2059                                if (folder != null)
2060                                    folder.notifyDataSetChanged();
2061                            }
2062                        } else if (tag instanceof LiveFolderInfo) {
2063                            final LiveFolderInfo info = (LiveFolderInfo) tag;
2064                            final Uri uri = info.uri;
2065                            final ProviderInfo providerInfo = manager.resolveContentProvider(
2066                                    uri.getAuthority(), 0);
2067
2068                            if (providerInfo != null) {
2069                                for (String packageName: packageNames) {
2070                                    if (packageName.equals(providerInfo.packageName)) {
2071                                        LauncherModel.deleteItemFromDatabase(mLauncher, info);
2072                                        childrenToRemove.add(view);
2073                                    }
2074                                }
2075                            }
2076                        } else if (tag instanceof LauncherAppWidgetInfo) {
2077                            final LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) tag;
2078                            final AppWidgetProviderInfo provider =
2079                                    widgets.getAppWidgetInfo(info.appWidgetId);
2080                            if (provider != null) {
2081                                for (String packageName: packageNames) {
2082                                    if (packageName.equals(provider.provider.getPackageName())) {
2083                                        LauncherModel.deleteItemFromDatabase(mLauncher, info);
2084                                        childrenToRemove.add(view);
2085                                    }
2086                                }
2087                            }
2088                        }
2089                    }
2090
2091                    childCount = childrenToRemove.size();
2092                    for (int j = 0; j < childCount; j++) {
2093                        View child = childrenToRemove.get(j);
2094                        layout.removeViewInLayout(child);
2095                        if (child instanceof DropTarget) {
2096                            mDragController.removeDropTarget((DropTarget)child);
2097                        }
2098                    }
2099
2100                    if (childCount > 0) {
2101                        layout.requestLayout();
2102                        layout.invalidate();
2103                    }
2104                }
2105            });
2106        }
2107    }
2108
2109    void updateShortcuts(ArrayList<ApplicationInfo> apps) {
2110        final int screenCount = getChildCount();
2111        for (int i = 0; i < screenCount; i++) {
2112            final CellLayout layout = (CellLayout) getChildAt(i);
2113            int childCount = layout.getChildCount();
2114            for (int j = 0; j < childCount; j++) {
2115                final View view = layout.getChildAt(j);
2116                Object tag = view.getTag();
2117                if (tag instanceof ShortcutInfo) {
2118                    ShortcutInfo info = (ShortcutInfo)tag;
2119                    // We need to check for ACTION_MAIN otherwise getComponent() might
2120                    // return null for some shortcuts (for instance, for shortcuts to
2121                    // web pages.)
2122                    final Intent intent = info.intent;
2123                    final ComponentName name = intent.getComponent();
2124                    if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION &&
2125                            Intent.ACTION_MAIN.equals(intent.getAction()) && name != null) {
2126                        final int appCount = apps.size();
2127                        for (int k = 0; k < appCount; k++) {
2128                            ApplicationInfo app = apps.get(k);
2129                            if (app.componentName.equals(name)) {
2130                                info.setIcon(mIconCache.getIcon(info.intent));
2131                                ((TextView)view).setCompoundDrawablesWithIntrinsicBounds(null,
2132                                        new FastBitmapDrawable(info.getIcon(mIconCache)),
2133                                        null, null);
2134                                }
2135                        }
2136                    }
2137                }
2138            }
2139        }
2140    }
2141
2142    void moveToDefaultScreen(boolean animate) {
2143        if (mIsSmall || mIsInUnshrinkAnimation) {
2144            mLauncher.showWorkspace(animate, (CellLayout)getChildAt(mDefaultPage));
2145        } else if (animate) {
2146            snapToPage(mDefaultPage);
2147        } else {
2148            setCurrentPage(mDefaultPage);
2149        }
2150        getChildAt(mDefaultPage).requestFocus();
2151    }
2152
2153    void setIndicators(Drawable previous, Drawable next) {
2154        mPreviousIndicator = previous;
2155        mNextIndicator = next;
2156        previous.setLevel(mCurrentPage);
2157        next.setLevel(mCurrentPage);
2158    }
2159
2160    @Override
2161    public void syncPages() {
2162    }
2163
2164    @Override
2165    public void syncPageItems(int page) {
2166    }
2167
2168}
2169