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 android.animation.Animator;
20import android.animation.AnimatorListenerAdapter;
21import android.animation.ObjectAnimator;
22import android.animation.TimeInterpolator;
23import android.animation.ValueAnimator;
24import android.animation.ValueAnimator.AnimatorUpdateListener;
25import android.content.Context;
26import android.content.res.Resources;
27import android.graphics.Canvas;
28import android.graphics.Rect;
29import android.graphics.drawable.Drawable;
30import android.util.AttributeSet;
31import android.view.KeyEvent;
32import android.view.MotionEvent;
33import android.view.View;
34import android.view.ViewParent;
35import android.view.accessibility.AccessibilityEvent;
36import android.view.accessibility.AccessibilityManager;
37import android.view.animation.DecelerateInterpolator;
38import android.view.animation.Interpolator;
39import android.widget.FrameLayout;
40import android.widget.TextView;
41
42import com.android.launcher.R;
43
44import java.util.ArrayList;
45
46/**
47 * A ViewGroup that coordinates dragging across its descendants
48 */
49public class DragLayer extends FrameLayout {
50    private DragController mDragController;
51    private int[] mTmpXY = new int[2];
52
53    private int mXDown, mYDown;
54    private Launcher mLauncher;
55
56    // Variables relating to resizing widgets
57    private final ArrayList<AppWidgetResizeFrame> mResizeFrames =
58            new ArrayList<AppWidgetResizeFrame>();
59    private AppWidgetResizeFrame mCurrentResizeFrame;
60
61    // Variables relating to animation of views after drop
62    private ValueAnimator mDropAnim = null;
63    private ValueAnimator mFadeOutAnim = null;
64    private TimeInterpolator mCubicEaseOutInterpolator = new DecelerateInterpolator(1.5f);
65    private View mDropView = null;
66
67    private int[] mDropViewPos = new int[2];
68    private float mDropViewScale;
69    private float mDropViewAlpha;
70    private boolean mHoverPointClosesFolder = false;
71    private Rect mHitRect = new Rect();
72    private int mWorkspaceIndex = -1;
73    private int mQsbIndex = -1;
74
75    /**
76     * Used to create a new DragLayer from XML.
77     *
78     * @param context The application's context.
79     * @param attrs The attributes set containing the Workspace's customization values.
80     */
81    public DragLayer(Context context, AttributeSet attrs) {
82        super(context, attrs);
83
84        // Disable multitouch across the workspace/all apps/customize tray
85        setMotionEventSplittingEnabled(false);
86        setChildrenDrawingOrderEnabled(true);
87    }
88
89    public void setup(Launcher launcher, DragController controller) {
90        mLauncher = launcher;
91        mDragController = controller;
92    }
93
94    @Override
95    public boolean dispatchKeyEvent(KeyEvent event) {
96        return mDragController.dispatchKeyEvent(event) || super.dispatchKeyEvent(event);
97    }
98
99    private boolean isEventOverFolderTextRegion(Folder folder, MotionEvent ev) {
100        getDescendantRectRelativeToSelf(folder.getEditTextRegion(), mHitRect);
101        if (mHitRect.contains((int) ev.getX(), (int) ev.getY())) {
102            return true;
103        }
104        return false;
105    }
106
107    private boolean isEventOverFolder(Folder folder, MotionEvent ev) {
108        getDescendantRectRelativeToSelf(folder, mHitRect);
109        if (mHitRect.contains((int) ev.getX(), (int) ev.getY())) {
110            return true;
111        }
112        return false;
113    }
114
115    private boolean handleTouchDown(MotionEvent ev, boolean intercept) {
116        Rect hitRect = new Rect();
117        int x = (int) ev.getX();
118        int y = (int) ev.getY();
119
120        for (AppWidgetResizeFrame child: mResizeFrames) {
121            child.getHitRect(hitRect);
122            if (hitRect.contains(x, y)) {
123                if (child.beginResizeIfPointInRegion(x - child.getLeft(), y - child.getTop())) {
124                    mCurrentResizeFrame = child;
125                    mXDown = x;
126                    mYDown = y;
127                    requestDisallowInterceptTouchEvent(true);
128                    return true;
129                }
130            }
131        }
132
133        Folder currentFolder = mLauncher.getWorkspace().getOpenFolder();
134        if (currentFolder != null && !mLauncher.isFolderClingVisible() && intercept) {
135            if (currentFolder.isEditingName()) {
136                if (!isEventOverFolderTextRegion(currentFolder, ev)) {
137                    currentFolder.dismissEditingName();
138                    return true;
139                }
140            }
141
142            getDescendantRectRelativeToSelf(currentFolder, hitRect);
143            if (!isEventOverFolder(currentFolder, ev)) {
144                mLauncher.closeFolder();
145                return true;
146            }
147        }
148        return false;
149    }
150
151    @Override
152    public boolean onInterceptTouchEvent(MotionEvent ev) {
153        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
154            if (handleTouchDown(ev, true)) {
155                return true;
156            }
157        }
158        clearAllResizeFrames();
159        return mDragController.onInterceptTouchEvent(ev);
160    }
161
162    @Override
163    public boolean onInterceptHoverEvent(MotionEvent ev) {
164        Folder currentFolder = mLauncher.getWorkspace().getOpenFolder();
165        if (currentFolder == null) {
166            return false;
167        } else {
168            if (AccessibilityManager.getInstance(mContext).isTouchExplorationEnabled()) {
169                final int action = ev.getAction();
170                boolean isOverFolder;
171                switch (action) {
172                    case MotionEvent.ACTION_HOVER_ENTER:
173                        isOverFolder = isEventOverFolder(currentFolder, ev);
174                        if (!isOverFolder) {
175                            sendTapOutsideFolderAccessibilityEvent(currentFolder.isEditingName());
176                            mHoverPointClosesFolder = true;
177                            return true;
178                        } else if (isOverFolder) {
179                            mHoverPointClosesFolder = false;
180                        } else {
181                            return true;
182                        }
183                    case MotionEvent.ACTION_HOVER_MOVE:
184                        isOverFolder = isEventOverFolder(currentFolder, ev);
185                        if (!isOverFolder && !mHoverPointClosesFolder) {
186                            sendTapOutsideFolderAccessibilityEvent(currentFolder.isEditingName());
187                            mHoverPointClosesFolder = true;
188                            return true;
189                        } else if (isOverFolder) {
190                            mHoverPointClosesFolder = false;
191                        } else {
192                            return true;
193                        }
194                }
195            }
196        }
197        return false;
198    }
199
200    private void sendTapOutsideFolderAccessibilityEvent(boolean isEditingName) {
201        if (AccessibilityManager.getInstance(mContext).isEnabled()) {
202            int stringId = isEditingName ? R.string.folder_tap_to_rename : R.string.folder_tap_to_close;
203            AccessibilityEvent event = AccessibilityEvent.obtain(
204                    AccessibilityEvent.TYPE_VIEW_FOCUSED);
205            onInitializeAccessibilityEvent(event);
206            event.getText().add(mContext.getString(stringId));
207            AccessibilityManager.getInstance(mContext).sendAccessibilityEvent(event);
208        }
209    }
210
211    @Override
212    public boolean onHoverEvent(MotionEvent ev) {
213        // If we've received this, we've already done the necessary handling
214        // in onInterceptHoverEvent. Return true to consume the event.
215        return false;
216    }
217
218    @Override
219    public boolean onTouchEvent(MotionEvent ev) {
220        boolean handled = false;
221        int action = ev.getAction();
222
223        int x = (int) ev.getX();
224        int y = (int) ev.getY();
225
226        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
227            if (ev.getAction() == MotionEvent.ACTION_DOWN) {
228                if (handleTouchDown(ev, false)) {
229                    return true;
230                }
231            }
232        }
233
234        if (mCurrentResizeFrame != null) {
235            handled = true;
236            switch (action) {
237                case MotionEvent.ACTION_MOVE:
238                    mCurrentResizeFrame.visualizeResizeForDelta(x - mXDown, y - mYDown);
239                    break;
240                case MotionEvent.ACTION_CANCEL:
241                case MotionEvent.ACTION_UP:
242                    mCurrentResizeFrame.commitResizeForDelta(x - mXDown, y - mYDown);
243                    mCurrentResizeFrame = null;
244            }
245        }
246        if (handled) return true;
247        return mDragController.onTouchEvent(ev);
248    }
249
250    /**
251     * Determine the rect of the descendant in this DragLayer's coordinates
252     *
253     * @param descendant The descendant whose coordinates we want to find.
254     * @param r The rect into which to place the results.
255     * @return The factor by which this descendant is scaled relative to this DragLayer.
256     */
257    public float getDescendantRectRelativeToSelf(View descendant, Rect r) {
258        mTmpXY[0] = 0;
259        mTmpXY[1] = 0;
260        float scale = getDescendantCoordRelativeToSelf(descendant, mTmpXY);
261        r.set(mTmpXY[0], mTmpXY[1],
262                mTmpXY[0] + descendant.getWidth(), mTmpXY[1] + descendant.getHeight());
263        return scale;
264    }
265
266    public void getLocationInDragLayer(View child, int[] loc) {
267        loc[0] = 0;
268        loc[1] = 0;
269        getDescendantCoordRelativeToSelf(child, loc);
270    }
271
272    /**
273     * Given a coordinate relative to the descendant, find the coordinate in this DragLayer's
274     * coordinates.
275     *
276     * @param descendant The descendant to which the passed coordinate is relative.
277     * @param coord The coordinate that we want mapped.
278     * @return The factor by which this descendant is scaled relative to this DragLayer.
279     */
280    public float getDescendantCoordRelativeToSelf(View descendant, int[] coord) {
281        float scale = 1.0f;
282        float[] pt = {coord[0], coord[1]};
283        descendant.getMatrix().mapPoints(pt);
284        scale *= descendant.getScaleX();
285        pt[0] += descendant.getLeft();
286        pt[1] += descendant.getTop();
287        ViewParent viewParent = descendant.getParent();
288        while (viewParent instanceof View && viewParent != this) {
289            final View view = (View)viewParent;
290            view.getMatrix().mapPoints(pt);
291            scale *= view.getScaleX();
292            pt[0] += view.getLeft() - view.getScrollX();
293            pt[1] += view.getTop() - view.getScrollY();
294            viewParent = view.getParent();
295        }
296        coord[0] = (int) Math.round(pt[0]);
297        coord[1] = (int) Math.round(pt[1]);
298        return scale;
299    }
300
301    public void getViewRectRelativeToSelf(View v, Rect r) {
302        int[] loc = new int[2];
303        getLocationInWindow(loc);
304        int x = loc[0];
305        int y = loc[1];
306
307        v.getLocationInWindow(loc);
308        int vX = loc[0];
309        int vY = loc[1];
310
311        int left = vX - x;
312        int top = vY - y;
313        r.set(left, top, left + v.getMeasuredWidth(), top + v.getMeasuredHeight());
314    }
315
316    @Override
317    public boolean dispatchUnhandledMove(View focused, int direction) {
318        return mDragController.dispatchUnhandledMove(focused, direction);
319    }
320
321    public static class LayoutParams extends FrameLayout.LayoutParams {
322        public int x, y;
323        public boolean customPosition = false;
324
325        /**
326         * {@inheritDoc}
327         */
328        public LayoutParams(int width, int height) {
329            super(width, height);
330        }
331
332        public void setWidth(int width) {
333            this.width = width;
334        }
335
336        public int getWidth() {
337            return width;
338        }
339
340        public void setHeight(int height) {
341            this.height = height;
342        }
343
344        public int getHeight() {
345            return height;
346        }
347
348        public void setX(int x) {
349            this.x = x;
350        }
351
352        public int getX() {
353            return x;
354        }
355
356        public void setY(int y) {
357            this.y = y;
358        }
359
360        public int getY() {
361            return y;
362        }
363    }
364
365    protected void onLayout(boolean changed, int l, int t, int r, int b) {
366        super.onLayout(changed, l, t, r, b);
367        int count = getChildCount();
368        for (int i = 0; i < count; i++) {
369            View child = getChildAt(i);
370            final FrameLayout.LayoutParams flp = (FrameLayout.LayoutParams) child.getLayoutParams();
371            if (flp instanceof LayoutParams) {
372                final LayoutParams lp = (LayoutParams) flp;
373                if (lp.customPosition) {
374                    child.layout(lp.x, lp.y, lp.x + lp.width, lp.y + lp.height);
375                }
376            }
377        }
378    }
379
380    public void clearAllResizeFrames() {
381        if (mResizeFrames.size() > 0) {
382            for (AppWidgetResizeFrame frame: mResizeFrames) {
383                removeView(frame);
384            }
385            mResizeFrames.clear();
386        }
387    }
388
389    public boolean hasResizeFrames() {
390        return mResizeFrames.size() > 0;
391    }
392
393    public boolean isWidgetBeingResized() {
394        return mCurrentResizeFrame != null;
395    }
396
397    public void addResizeFrame(ItemInfo itemInfo, LauncherAppWidgetHostView widget,
398            CellLayout cellLayout) {
399        AppWidgetResizeFrame resizeFrame = new AppWidgetResizeFrame(getContext(),
400                itemInfo, widget, cellLayout, this);
401
402        LayoutParams lp = new LayoutParams(-1, -1);
403        lp.customPosition = true;
404
405        addView(resizeFrame, lp);
406        mResizeFrames.add(resizeFrame);
407
408        resizeFrame.snapToWidget(false);
409    }
410
411    public void animateViewIntoPosition(DragView dragView, final View child) {
412        animateViewIntoPosition(dragView, child, null);
413    }
414
415    public void animateViewIntoPosition(DragView dragView, final int[] pos, float scale,
416            Runnable onFinishRunnable) {
417        Rect r = new Rect();
418        getViewRectRelativeToSelf(dragView, r);
419        final int fromX = r.left;
420        final int fromY = r.top;
421
422        animateViewIntoPosition(dragView, fromX, fromY, pos[0], pos[1], scale,
423                onFinishRunnable, true, -1);
424    }
425
426    public void animateViewIntoPosition(DragView dragView, final View child,
427            final Runnable onFinishAnimationRunnable) {
428        animateViewIntoPosition(dragView, child, -1, onFinishAnimationRunnable);
429    }
430
431    public void animateViewIntoPosition(DragView dragView, final View child, int duration,
432            final Runnable onFinishAnimationRunnable) {
433        ((CellLayoutChildren) child.getParent()).measureChild(child);
434        CellLayout.LayoutParams lp =  (CellLayout.LayoutParams) child.getLayoutParams();
435
436        Rect r = new Rect();
437        getViewRectRelativeToSelf(dragView, r);
438
439        int coord[] = new int[2];
440        coord[0] = lp.x;
441        coord[1] = lp.y;
442        // Since the child hasn't necessarily been laid out, we force the lp to be updated with
443        // the correct coordinates (above) and use these to determine the final location
444        float scale = getDescendantCoordRelativeToSelf((View) child.getParent(), coord);
445        int toX = coord[0];
446        int toY = coord[1];
447        if (child instanceof TextView) {
448            TextView tv = (TextView) child;
449            Drawable d = tv.getCompoundDrawables()[1];
450
451            // Center in the y coordinate about the target's drawable
452            toY += Math.round(scale * tv.getPaddingTop());
453            toY -= (dragView.getHeight() - (int) Math.round(scale * d.getIntrinsicHeight())) / 2;
454            // Center in the x coordinate about the target's drawable
455            toX -= (dragView.getMeasuredWidth() - Math.round(scale * child.getMeasuredWidth())) / 2;
456        } else if (child instanceof FolderIcon) {
457            // Account for holographic blur padding on the drag view
458            toY -= HolographicOutlineHelper.MAX_OUTER_BLUR_RADIUS / 2;
459            // Center in the x coordinate about the target's drawable
460            toX -= (dragView.getMeasuredWidth() - Math.round(scale * child.getMeasuredWidth())) / 2;
461        } else {
462            toY -= (Math.round(scale * (dragView.getHeight() - child.getMeasuredHeight()))) / 2;
463            toX -= (Math.round(scale * (dragView.getMeasuredWidth()
464                    - child.getMeasuredWidth()))) / 2;
465        }
466
467        final int fromX = r.left;
468        final int fromY = r.top;
469        child.setVisibility(INVISIBLE);
470        child.setAlpha(0);
471        Runnable onCompleteRunnable = new Runnable() {
472            public void run() {
473                child.setVisibility(VISIBLE);
474                ObjectAnimator oa = ObjectAnimator.ofFloat(child, "alpha", 0f, 1f);
475                oa.setDuration(60);
476                oa.addListener(new AnimatorListenerAdapter() {
477                    @Override
478                    public void onAnimationEnd(android.animation.Animator animation) {
479                        if (onFinishAnimationRunnable != null) {
480                            onFinishAnimationRunnable.run();
481                        }
482                    }
483                });
484                oa.start();
485            }
486        };
487        animateViewIntoPosition(dragView, fromX, fromY, toX, toY, scale,
488                onCompleteRunnable, true, duration);
489    }
490
491    private void animateViewIntoPosition(final View view, final int fromX, final int fromY,
492            final int toX, final int toY, float finalScale, Runnable onCompleteRunnable,
493            boolean fadeOut, int duration) {
494        Rect from = new Rect(fromX, fromY, fromX +
495                view.getMeasuredWidth(), fromY + view.getMeasuredHeight());
496        Rect to = new Rect(toX, toY, toX + view.getMeasuredWidth(), toY + view.getMeasuredHeight());
497        animateView(view, from, to, 1f, finalScale, duration, null, null, onCompleteRunnable, true);
498    }
499
500    /**
501     * This method animates a view at the end of a drag and drop animation.
502     *
503     * @param view The view to be animated. This view is drawn directly into DragLayer, and so
504     *        doesn't need to be a child of DragLayer.
505     * @param from The initial location of the view. Only the left and top parameters are used.
506     * @param to The final location of the view. Only the left and top parameters are used. This
507     *        location doesn't account for scaling, and so should be centered about the desired
508     *        final location (including scaling).
509     * @param finalAlpha The final alpha of the view, in case we want it to fade as it animates.
510     * @param finalScale The final scale of the view. The view is scaled about its center.
511     * @param duration The duration of the animation.
512     * @param motionInterpolator The interpolator to use for the location of the view.
513     * @param alphaInterpolator The interpolator to use for the alpha of the view.
514     * @param onCompleteRunnable Optional runnable to run on animation completion.
515     * @param fadeOut Whether or not to fade out the view once the animation completes. If true,
516     *        the runnable will execute after the view is faded out.
517     */
518    public void animateView(final View view, final Rect from, final Rect to, final float finalAlpha,
519            final float finalScale, int duration, final Interpolator motionInterpolator,
520            final Interpolator alphaInterpolator, final Runnable onCompleteRunnable,
521            final boolean fadeOut) {
522        // Calculate the duration of the animation based on the object's distance
523        final float dist = (float) Math.sqrt(Math.pow(to.left - from.left, 2) +
524                Math.pow(to.top - from.top, 2));
525        final Resources res = getResources();
526        final float maxDist = (float) res.getInteger(R.integer.config_dropAnimMaxDist);
527
528        // If duration < 0, this is a cue to compute the duration based on the distance
529        if (duration < 0) {
530            duration = res.getInteger(R.integer.config_dropAnimMaxDuration);
531            if (dist < maxDist) {
532                duration *= mCubicEaseOutInterpolator.getInterpolation(dist / maxDist);
533            }
534        }
535
536        if (mDropAnim != null) {
537            mDropAnim.cancel();
538        }
539
540        if (mFadeOutAnim != null) {
541            mFadeOutAnim.cancel();
542        }
543
544        mDropView = view;
545        final float initialAlpha = view.getAlpha();
546        mDropAnim = new ValueAnimator();
547        if (alphaInterpolator == null || motionInterpolator == null) {
548            mDropAnim.setInterpolator(mCubicEaseOutInterpolator);
549        }
550
551        mDropAnim.setDuration(duration);
552        mDropAnim.setFloatValues(0.0f, 1.0f);
553        mDropAnim.removeAllUpdateListeners();
554        mDropAnim.addUpdateListener(new AnimatorUpdateListener() {
555            public void onAnimationUpdate(ValueAnimator animation) {
556                final float percent = (Float) animation.getAnimatedValue();
557                // Invalidate the old position
558                int width = view.getMeasuredWidth();
559                int height = view.getMeasuredHeight();
560                invalidate(mDropViewPos[0], mDropViewPos[1],
561                        mDropViewPos[0] + width, mDropViewPos[1] + height);
562
563                float alphaPercent = alphaInterpolator == null ? percent :
564                        alphaInterpolator.getInterpolation(percent);
565                float motionPercent = motionInterpolator == null ? percent :
566                        motionInterpolator.getInterpolation(percent);
567
568                mDropViewPos[0] = from.left + (int) Math.round(((to.left - from.left) * motionPercent));
569                mDropViewPos[1] = from.top + (int) Math.round(((to.top - from.top) * motionPercent));
570                mDropViewScale = percent * finalScale + (1 - percent);
571                mDropViewAlpha = alphaPercent * finalAlpha + (1 - alphaPercent) * initialAlpha;
572                invalidate(mDropViewPos[0], mDropViewPos[1],
573                        mDropViewPos[0] + width, mDropViewPos[1] + height);
574            }
575        });
576        mDropAnim.addListener(new AnimatorListenerAdapter() {
577            public void onAnimationEnd(Animator animation) {
578                if (onCompleteRunnable != null) {
579                    onCompleteRunnable.run();
580                }
581                if (fadeOut) {
582                    fadeOutDragView();
583                } else {
584                    mDropView = null;
585                }
586            }
587        });
588        mDropAnim.start();
589    }
590
591    private void fadeOutDragView() {
592        mFadeOutAnim = new ValueAnimator();
593        mFadeOutAnim.setDuration(150);
594        mFadeOutAnim.setFloatValues(0f, 1f);
595        mFadeOutAnim.removeAllUpdateListeners();
596        mFadeOutAnim.addUpdateListener(new AnimatorUpdateListener() {
597            public void onAnimationUpdate(ValueAnimator animation) {
598                final float percent = (Float) animation.getAnimatedValue();
599                mDropViewAlpha = 1 - percent;
600                int width = mDropView.getMeasuredWidth();
601                int height = mDropView.getMeasuredHeight();
602                invalidate(mDropViewPos[0], mDropViewPos[1],
603                        mDropViewPos[0] + width, mDropViewPos[1] + height);
604            }
605        });
606        mFadeOutAnim.addListener(new AnimatorListenerAdapter() {
607            public void onAnimationEnd(Animator animation) {
608                mDropView = null;
609            }
610        });
611        mFadeOutAnim.start();
612    }
613
614    @Override
615    protected void onViewAdded(View child) {
616        super.onViewAdded(child);
617        updateChildIndices();
618    }
619
620    @Override
621    protected void onViewRemoved(View child) {
622        super.onViewRemoved(child);
623        updateChildIndices();
624    }
625
626    private void updateChildIndices() {
627        if (mLauncher != null) {
628            mWorkspaceIndex = indexOfChild(mLauncher.getWorkspace());
629            mQsbIndex = indexOfChild(mLauncher.getSearchBar());
630        }
631    }
632
633    @Override
634    protected int getChildDrawingOrder(int childCount, int i) {
635        // We don't want to prioritize the workspace drawing on top of the other children in
636        // landscape for the overscroll event.
637        if (LauncherApplication.isScreenLandscape(getContext())) {
638            return super.getChildDrawingOrder(childCount, i);
639        }
640
641        if (mWorkspaceIndex == -1 || mQsbIndex == -1 ||
642                mLauncher.getWorkspace().isDrawingBackgroundGradient()) {
643            return i;
644        }
645
646        // This ensures that the workspace is drawn above the hotseat and qsb,
647        // except when the workspace is drawing a background gradient, in which
648        // case we want the workspace to stay behind these elements.
649        if (i == mQsbIndex) {
650            return mWorkspaceIndex;
651        } else if (i == mWorkspaceIndex) {
652            return mQsbIndex;
653        } else {
654            return i;
655        }
656    }
657
658    @Override
659    protected void dispatchDraw(Canvas canvas) {
660        super.dispatchDraw(canvas);
661        if (mDropView != null) {
662            // We are animating an item that was just dropped on the home screen.
663            // Render its View in the current animation position.
664            canvas.save(Canvas.MATRIX_SAVE_FLAG);
665            final int xPos = mDropViewPos[0] - mDropView.getScrollX();
666            final int yPos = mDropViewPos[1] - mDropView.getScrollY();
667            int width = mDropView.getMeasuredWidth();
668            int height = mDropView.getMeasuredHeight();
669            canvas.translate(xPos, yPos);
670            canvas.translate((1 - mDropViewScale) * width / 2, (1 - mDropViewScale) * height / 2);
671            canvas.scale(mDropViewScale, mDropViewScale);
672            mDropView.setAlpha(mDropViewAlpha);
673            mDropView.draw(canvas);
674            canvas.restore();
675        }
676    }
677}
678