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.TimeInterpolator;
22import android.animation.ValueAnimator;
23import android.animation.ValueAnimator.AnimatorUpdateListener;
24import android.content.Context;
25import android.content.res.Resources;
26import android.graphics.Canvas;
27import android.graphics.Rect;
28import android.graphics.drawable.Drawable;
29import android.util.AttributeSet;
30import android.view.KeyEvent;
31import android.view.MotionEvent;
32import android.view.View;
33import android.view.ViewGroup;
34import android.view.ViewParent;
35import android.view.accessibility.AccessibilityEvent;
36import android.view.accessibility.AccessibilityManager;
37import android.view.accessibility.AccessibilityNodeInfo;
38import android.view.animation.DecelerateInterpolator;
39import android.view.animation.Interpolator;
40import android.widget.FrameLayout;
41import android.widget.TextView;
42
43import com.android.launcher.R;
44
45import java.util.ArrayList;
46
47/**
48 * A ViewGroup that coordinates dragging across its descendants
49 */
50public class DragLayer extends FrameLayout implements ViewGroup.OnHierarchyChangeListener {
51    private DragController mDragController;
52    private int[] mTmpXY = new int[2];
53
54    private int mXDown, mYDown;
55    private Launcher mLauncher;
56
57    // Variables relating to resizing widgets
58    private final ArrayList<AppWidgetResizeFrame> mResizeFrames =
59            new ArrayList<AppWidgetResizeFrame>();
60    private AppWidgetResizeFrame mCurrentResizeFrame;
61
62    // Variables relating to animation of views after drop
63    private ValueAnimator mDropAnim = null;
64    private ValueAnimator mFadeOutAnim = null;
65    private TimeInterpolator mCubicEaseOutInterpolator = new DecelerateInterpolator(1.5f);
66    private DragView mDropView = null;
67    private int mAnchorViewInitialScrollX = 0;
68    private View mAnchorView = null;
69
70    private boolean mHoverPointClosesFolder = false;
71    private Rect mHitRect = new Rect();
72    private int mWorkspaceIndex = -1;
73    private int mQsbIndex = -1;
74    public static final int ANIMATION_END_DISAPPEAR = 0;
75    public static final int ANIMATION_END_FADE_OUT = 1;
76    public static final int ANIMATION_END_REMAIN_VISIBLE = 2;
77
78    /**
79     * Used to create a new DragLayer from XML.
80     *
81     * @param context The application's context.
82     * @param attrs The attributes set containing the Workspace's customization values.
83     */
84    public DragLayer(Context context, AttributeSet attrs) {
85        super(context, attrs);
86
87        // Disable multitouch across the workspace/all apps/customize tray
88        setMotionEventSplittingEnabled(false);
89        setChildrenDrawingOrderEnabled(true);
90        setOnHierarchyChangeListener(this);
91
92        mLeftHoverDrawable = getResources().getDrawable(R.drawable.page_hover_left_holo);
93        mRightHoverDrawable = getResources().getDrawable(R.drawable.page_hover_right_holo);
94    }
95
96    public void setup(Launcher launcher, DragController controller) {
97        mLauncher = launcher;
98        mDragController = controller;
99    }
100
101    @Override
102    public boolean dispatchKeyEvent(KeyEvent event) {
103        return mDragController.dispatchKeyEvent(event) || super.dispatchKeyEvent(event);
104    }
105
106    private boolean isEventOverFolderTextRegion(Folder folder, MotionEvent ev) {
107        getDescendantRectRelativeToSelf(folder.getEditTextRegion(), mHitRect);
108        if (mHitRect.contains((int) ev.getX(), (int) ev.getY())) {
109            return true;
110        }
111        return false;
112    }
113
114    private boolean isEventOverFolder(Folder folder, MotionEvent ev) {
115        getDescendantRectRelativeToSelf(folder, mHitRect);
116        if (mHitRect.contains((int) ev.getX(), (int) ev.getY())) {
117            return true;
118        }
119        return false;
120    }
121
122    private boolean handleTouchDown(MotionEvent ev, boolean intercept) {
123        Rect hitRect = new Rect();
124        int x = (int) ev.getX();
125        int y = (int) ev.getY();
126
127        for (AppWidgetResizeFrame child: mResizeFrames) {
128            child.getHitRect(hitRect);
129            if (hitRect.contains(x, y)) {
130                if (child.beginResizeIfPointInRegion(x - child.getLeft(), y - child.getTop())) {
131                    mCurrentResizeFrame = child;
132                    mXDown = x;
133                    mYDown = y;
134                    requestDisallowInterceptTouchEvent(true);
135                    return true;
136                }
137            }
138        }
139
140        Folder currentFolder = mLauncher.getWorkspace().getOpenFolder();
141        if (currentFolder != null && !mLauncher.isFolderClingVisible() && intercept) {
142            if (currentFolder.isEditingName()) {
143                if (!isEventOverFolderTextRegion(currentFolder, ev)) {
144                    currentFolder.dismissEditingName();
145                    return true;
146                }
147            }
148
149            getDescendantRectRelativeToSelf(currentFolder, hitRect);
150            if (!isEventOverFolder(currentFolder, ev)) {
151                mLauncher.closeFolder();
152                return true;
153            }
154        }
155        return false;
156    }
157
158    @Override
159    public boolean onInterceptTouchEvent(MotionEvent ev) {
160        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
161            if (handleTouchDown(ev, true)) {
162                return true;
163            }
164        }
165        clearAllResizeFrames();
166        return mDragController.onInterceptTouchEvent(ev);
167    }
168
169    @Override
170    public boolean onInterceptHoverEvent(MotionEvent ev) {
171        if (mLauncher == null || mLauncher.getWorkspace() == null) {
172            return false;
173        }
174        Folder currentFolder = mLauncher.getWorkspace().getOpenFolder();
175        if (currentFolder == null) {
176            return false;
177        } else {
178                AccessibilityManager accessibilityManager = (AccessibilityManager)
179                        getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
180            if (accessibilityManager.isTouchExplorationEnabled()) {
181                final int action = ev.getAction();
182                boolean isOverFolder;
183                switch (action) {
184                    case MotionEvent.ACTION_HOVER_ENTER:
185                        isOverFolder = isEventOverFolder(currentFolder, ev);
186                        if (!isOverFolder) {
187                            sendTapOutsideFolderAccessibilityEvent(currentFolder.isEditingName());
188                            mHoverPointClosesFolder = true;
189                            return true;
190                        } else if (isOverFolder) {
191                            mHoverPointClosesFolder = false;
192                        } else {
193                            return true;
194                        }
195                    case MotionEvent.ACTION_HOVER_MOVE:
196                        isOverFolder = isEventOverFolder(currentFolder, ev);
197                        if (!isOverFolder && !mHoverPointClosesFolder) {
198                            sendTapOutsideFolderAccessibilityEvent(currentFolder.isEditingName());
199                            mHoverPointClosesFolder = true;
200                            return true;
201                        } else if (isOverFolder) {
202                            mHoverPointClosesFolder = false;
203                        } else {
204                            return true;
205                        }
206                }
207            }
208        }
209        return false;
210    }
211
212    private void sendTapOutsideFolderAccessibilityEvent(boolean isEditingName) {
213        AccessibilityManager accessibilityManager = (AccessibilityManager)
214                getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
215        if (accessibilityManager.isEnabled()) {
216            int stringId = isEditingName ? R.string.folder_tap_to_rename : R.string.folder_tap_to_close;
217            AccessibilityEvent event = AccessibilityEvent.obtain(
218                    AccessibilityEvent.TYPE_VIEW_FOCUSED);
219            onInitializeAccessibilityEvent(event);
220            event.getText().add(getContext().getString(stringId));
221            accessibilityManager.sendAccessibilityEvent(event);
222        }
223    }
224
225    @Override
226    public boolean onRequestSendAccessibilityEvent(View child, AccessibilityEvent event) {
227        Folder currentFolder = mLauncher.getWorkspace().getOpenFolder();
228        if (currentFolder != null) {
229            if (child == currentFolder) {
230                return super.onRequestSendAccessibilityEvent(child, event);
231            }
232            // Skip propagating onRequestSendAccessibilityEvent all for other children
233            // when a folder is open
234            return false;
235        }
236        return super.onRequestSendAccessibilityEvent(child, event);
237    }
238
239    @Override
240    public void addChildrenForAccessibility(ArrayList<View> childrenForAccessibility) {
241        Folder currentFolder = mLauncher.getWorkspace().getOpenFolder();
242        if (currentFolder != null) {
243            // Only add the folder as a child for accessibility when it is open
244            childrenForAccessibility.add(currentFolder);
245        } else {
246            super.addChildrenForAccessibility(childrenForAccessibility);
247        }
248    }
249
250    @Override
251    public boolean onHoverEvent(MotionEvent ev) {
252        // If we've received this, we've already done the necessary handling
253        // in onInterceptHoverEvent. Return true to consume the event.
254        return false;
255    }
256
257    @Override
258    public boolean onTouchEvent(MotionEvent ev) {
259        boolean handled = false;
260        int action = ev.getAction();
261
262        int x = (int) ev.getX();
263        int y = (int) ev.getY();
264
265        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
266            if (ev.getAction() == MotionEvent.ACTION_DOWN) {
267                if (handleTouchDown(ev, false)) {
268                    return true;
269                }
270            }
271        }
272
273        if (mCurrentResizeFrame != null) {
274            handled = true;
275            switch (action) {
276                case MotionEvent.ACTION_MOVE:
277                    mCurrentResizeFrame.visualizeResizeForDelta(x - mXDown, y - mYDown);
278                    break;
279                case MotionEvent.ACTION_CANCEL:
280                case MotionEvent.ACTION_UP:
281                    mCurrentResizeFrame.visualizeResizeForDelta(x - mXDown, y - mYDown);
282                    mCurrentResizeFrame.onTouchUp();
283                    mCurrentResizeFrame = null;
284            }
285        }
286        if (handled) return true;
287        return mDragController.onTouchEvent(ev);
288    }
289
290    /**
291     * Determine the rect of the descendant in this DragLayer's coordinates
292     *
293     * @param descendant The descendant whose coordinates we want to find.
294     * @param r The rect into which to place the results.
295     * @return The factor by which this descendant is scaled relative to this DragLayer.
296     */
297    public float getDescendantRectRelativeToSelf(View descendant, Rect r) {
298        mTmpXY[0] = 0;
299        mTmpXY[1] = 0;
300        float scale = getDescendantCoordRelativeToSelf(descendant, mTmpXY);
301        r.set(mTmpXY[0], mTmpXY[1],
302                mTmpXY[0] + descendant.getWidth(), mTmpXY[1] + descendant.getHeight());
303        return scale;
304    }
305
306    public float getLocationInDragLayer(View child, int[] loc) {
307        loc[0] = 0;
308        loc[1] = 0;
309        return getDescendantCoordRelativeToSelf(child, loc);
310    }
311
312    /**
313     * Given a coordinate relative to the descendant, find the coordinate in this DragLayer's
314     * coordinates.
315     *
316     * @param descendant The descendant to which the passed coordinate is relative.
317     * @param coord The coordinate that we want mapped.
318     * @return The factor by which this descendant is scaled relative to this DragLayer. Caution
319     *         this scale factor is assumed to be equal in X and Y, and so if at any point this
320     *         assumption fails, we will need to return a pair of scale factors.
321     */
322    public float getDescendantCoordRelativeToSelf(View descendant, int[] coord) {
323        float scale = 1.0f;
324        float[] pt = {coord[0], coord[1]};
325        descendant.getMatrix().mapPoints(pt);
326        scale *= descendant.getScaleX();
327        pt[0] += descendant.getLeft();
328        pt[1] += descendant.getTop();
329        ViewParent viewParent = descendant.getParent();
330        while (viewParent instanceof View && viewParent != this) {
331            final View view = (View)viewParent;
332            view.getMatrix().mapPoints(pt);
333            scale *= view.getScaleX();
334            pt[0] += view.getLeft() - view.getScrollX();
335            pt[1] += view.getTop() - view.getScrollY();
336            viewParent = view.getParent();
337        }
338        coord[0] = (int) Math.round(pt[0]);
339        coord[1] = (int) Math.round(pt[1]);
340        return scale;
341    }
342
343    public void getViewRectRelativeToSelf(View v, Rect r) {
344        int[] loc = new int[2];
345        getLocationInWindow(loc);
346        int x = loc[0];
347        int y = loc[1];
348
349        v.getLocationInWindow(loc);
350        int vX = loc[0];
351        int vY = loc[1];
352
353        int left = vX - x;
354        int top = vY - y;
355        r.set(left, top, left + v.getMeasuredWidth(), top + v.getMeasuredHeight());
356    }
357
358    @Override
359    public boolean dispatchUnhandledMove(View focused, int direction) {
360        return mDragController.dispatchUnhandledMove(focused, direction);
361    }
362
363    public static class LayoutParams extends FrameLayout.LayoutParams {
364        public int x, y;
365        public boolean customPosition = false;
366
367        /**
368         * {@inheritDoc}
369         */
370        public LayoutParams(int width, int height) {
371            super(width, height);
372        }
373
374        public void setWidth(int width) {
375            this.width = width;
376        }
377
378        public int getWidth() {
379            return width;
380        }
381
382        public void setHeight(int height) {
383            this.height = height;
384        }
385
386        public int getHeight() {
387            return height;
388        }
389
390        public void setX(int x) {
391            this.x = x;
392        }
393
394        public int getX() {
395            return x;
396        }
397
398        public void setY(int y) {
399            this.y = y;
400        }
401
402        public int getY() {
403            return y;
404        }
405    }
406
407    protected void onLayout(boolean changed, int l, int t, int r, int b) {
408        super.onLayout(changed, l, t, r, b);
409        int count = getChildCount();
410        for (int i = 0; i < count; i++) {
411            View child = getChildAt(i);
412            final FrameLayout.LayoutParams flp = (FrameLayout.LayoutParams) child.getLayoutParams();
413            if (flp instanceof LayoutParams) {
414                final LayoutParams lp = (LayoutParams) flp;
415                if (lp.customPosition) {
416                    child.layout(lp.x, lp.y, lp.x + lp.width, lp.y + lp.height);
417                }
418            }
419        }
420    }
421
422    public void clearAllResizeFrames() {
423        if (mResizeFrames.size() > 0) {
424            for (AppWidgetResizeFrame frame: mResizeFrames) {
425                frame.commitResize();
426                removeView(frame);
427            }
428            mResizeFrames.clear();
429        }
430    }
431
432    public boolean hasResizeFrames() {
433        return mResizeFrames.size() > 0;
434    }
435
436    public boolean isWidgetBeingResized() {
437        return mCurrentResizeFrame != null;
438    }
439
440    public void addResizeFrame(ItemInfo itemInfo, LauncherAppWidgetHostView widget,
441            CellLayout cellLayout) {
442        AppWidgetResizeFrame resizeFrame = new AppWidgetResizeFrame(getContext(),
443                widget, cellLayout, this);
444
445        LayoutParams lp = new LayoutParams(-1, -1);
446        lp.customPosition = true;
447
448        addView(resizeFrame, lp);
449        mResizeFrames.add(resizeFrame);
450
451        resizeFrame.snapToWidget(false);
452    }
453
454    public void animateViewIntoPosition(DragView dragView, final View child) {
455        animateViewIntoPosition(dragView, child, null);
456    }
457
458    public void animateViewIntoPosition(DragView dragView, final int[] pos, float alpha,
459            float scaleX, float scaleY, int animationEndStyle, Runnable onFinishRunnable,
460            int duration) {
461        Rect r = new Rect();
462        getViewRectRelativeToSelf(dragView, r);
463        final int fromX = r.left;
464        final int fromY = r.top;
465
466        animateViewIntoPosition(dragView, fromX, fromY, pos[0], pos[1], alpha, 1, 1, scaleX, scaleY,
467                onFinishRunnable, animationEndStyle, duration, null);
468    }
469
470    public void animateViewIntoPosition(DragView dragView, final View child,
471            final Runnable onFinishAnimationRunnable) {
472        animateViewIntoPosition(dragView, child, -1, onFinishAnimationRunnable, null);
473    }
474
475    public void animateViewIntoPosition(DragView dragView, final View child, int duration,
476            final Runnable onFinishAnimationRunnable, View anchorView) {
477        ShortcutAndWidgetContainer parentChildren = (ShortcutAndWidgetContainer) child.getParent();
478        CellLayout.LayoutParams lp =  (CellLayout.LayoutParams) child.getLayoutParams();
479        parentChildren.measureChild(child);
480
481        Rect r = new Rect();
482        getViewRectRelativeToSelf(dragView, r);
483
484        int coord[] = new int[2];
485        float childScale = child.getScaleX();
486        coord[0] = lp.x + (int) (child.getMeasuredWidth() * (1 - childScale) / 2);
487        coord[1] = lp.y + (int) (child.getMeasuredHeight() * (1 - childScale) / 2);
488
489        // Since the child hasn't necessarily been laid out, we force the lp to be updated with
490        // the correct coordinates (above) and use these to determine the final location
491        float scale = getDescendantCoordRelativeToSelf((View) child.getParent(), coord);
492        // We need to account for the scale of the child itself, as the above only accounts for
493        // for the scale in parents.
494        scale *= childScale;
495        int toX = coord[0];
496        int toY = coord[1];
497        if (child instanceof TextView) {
498            TextView tv = (TextView) child;
499
500            // The child may be scaled (always about the center of the view) so to account for it,
501            // we have to offset the position by the scaled size.  Once we do that, we can center
502            // the drag view about the scaled child view.
503            toY += Math.round(scale * tv.getPaddingTop());
504            toY -= dragView.getMeasuredHeight() * (1 - scale) / 2;
505            toX -= (dragView.getMeasuredWidth() - Math.round(scale * child.getMeasuredWidth())) / 2;
506        } else if (child instanceof FolderIcon) {
507            // Account for holographic blur padding on the drag view
508            toY -= scale * Workspace.DRAG_BITMAP_PADDING / 2;
509            toY -= (1 - scale) * dragView.getMeasuredHeight() / 2;
510            // Center in the x coordinate about the target's drawable
511            toX -= (dragView.getMeasuredWidth() - Math.round(scale * child.getMeasuredWidth())) / 2;
512        } else {
513            toY -= (Math.round(scale * (dragView.getHeight() - child.getMeasuredHeight()))) / 2;
514            toX -= (Math.round(scale * (dragView.getMeasuredWidth()
515                    - child.getMeasuredWidth()))) / 2;
516        }
517
518        final int fromX = r.left;
519        final int fromY = r.top;
520        child.setVisibility(INVISIBLE);
521        Runnable onCompleteRunnable = new Runnable() {
522            public void run() {
523                child.setVisibility(VISIBLE);
524                if (onFinishAnimationRunnable != null) {
525                    onFinishAnimationRunnable.run();
526                }
527            }
528        };
529        animateViewIntoPosition(dragView, fromX, fromY, toX, toY, 1, 1, 1, scale, scale,
530                onCompleteRunnable, ANIMATION_END_DISAPPEAR, duration, anchorView);
531    }
532
533    public void animateViewIntoPosition(final DragView view, final int fromX, final int fromY,
534            final int toX, final int toY, float finalAlpha, float initScaleX, float initScaleY,
535            float finalScaleX, float finalScaleY, Runnable onCompleteRunnable,
536            int animationEndStyle, int duration, View anchorView) {
537        Rect from = new Rect(fromX, fromY, fromX +
538                view.getMeasuredWidth(), fromY + view.getMeasuredHeight());
539        Rect to = new Rect(toX, toY, toX + view.getMeasuredWidth(), toY + view.getMeasuredHeight());
540        animateView(view, from, to, finalAlpha, initScaleX, initScaleY, finalScaleX, finalScaleY, duration,
541                null, null, onCompleteRunnable, animationEndStyle, anchorView);
542    }
543
544    /**
545     * This method animates a view at the end of a drag and drop animation.
546     *
547     * @param view The view to be animated. This view is drawn directly into DragLayer, and so
548     *        doesn't need to be a child of DragLayer.
549     * @param from The initial location of the view. Only the left and top parameters are used.
550     * @param to The final location of the view. Only the left and top parameters are used. This
551     *        location doesn't account for scaling, and so should be centered about the desired
552     *        final location (including scaling).
553     * @param finalAlpha The final alpha of the view, in case we want it to fade as it animates.
554     * @param finalScale The final scale of the view. The view is scaled about its center.
555     * @param duration The duration of the animation.
556     * @param motionInterpolator The interpolator to use for the location of the view.
557     * @param alphaInterpolator The interpolator to use for the alpha of the view.
558     * @param onCompleteRunnable Optional runnable to run on animation completion.
559     * @param fadeOut Whether or not to fade out the view once the animation completes. If true,
560     *        the runnable will execute after the view is faded out.
561     * @param anchorView If not null, this represents the view which the animated view stays
562     *        anchored to in case scrolling is currently taking place. Note: currently this is
563     *        only used for the X dimension for the case of the workspace.
564     */
565    public void animateView(final DragView view, final Rect from, final Rect to,
566            final float finalAlpha, final float initScaleX, final float initScaleY,
567            final float finalScaleX, final float finalScaleY, int duration,
568            final Interpolator motionInterpolator, final Interpolator alphaInterpolator,
569            final Runnable onCompleteRunnable, final int animationEndStyle, View anchorView) {
570
571        // Calculate the duration of the animation based on the object's distance
572        final float dist = (float) Math.sqrt(Math.pow(to.left - from.left, 2) +
573                Math.pow(to.top - from.top, 2));
574        final Resources res = getResources();
575        final float maxDist = (float) res.getInteger(R.integer.config_dropAnimMaxDist);
576
577        // If duration < 0, this is a cue to compute the duration based on the distance
578        if (duration < 0) {
579            duration = res.getInteger(R.integer.config_dropAnimMaxDuration);
580            if (dist < maxDist) {
581                duration *= mCubicEaseOutInterpolator.getInterpolation(dist / maxDist);
582            }
583            duration = Math.max(duration, res.getInteger(R.integer.config_dropAnimMinDuration));
584        }
585
586        // Fall back to cubic ease out interpolator for the animation if none is specified
587        TimeInterpolator interpolator = null;
588        if (alphaInterpolator == null || motionInterpolator == null) {
589            interpolator = mCubicEaseOutInterpolator;
590        }
591
592        // Animate the view
593        final float initAlpha = view.getAlpha();
594        final float dropViewScale = view.getScaleX();
595        AnimatorUpdateListener updateCb = new AnimatorUpdateListener() {
596            @Override
597            public void onAnimationUpdate(ValueAnimator animation) {
598                final float percent = (Float) animation.getAnimatedValue();
599                final int width = view.getMeasuredWidth();
600                final int height = view.getMeasuredHeight();
601
602                float alphaPercent = alphaInterpolator == null ? percent :
603                        alphaInterpolator.getInterpolation(percent);
604                float motionPercent = motionInterpolator == null ? percent :
605                        motionInterpolator.getInterpolation(percent);
606
607                float initialScaleX = initScaleX * dropViewScale;
608                float initialScaleY = initScaleY * dropViewScale;
609                float scaleX = finalScaleX * percent + initialScaleX * (1 - percent);
610                float scaleY = finalScaleY * percent + initialScaleY * (1 - percent);
611                float alpha = finalAlpha * alphaPercent + initAlpha * (1 - alphaPercent);
612
613                float fromLeft = from.left + (initialScaleX - 1f) * width / 2;
614                float fromTop = from.top + (initialScaleY - 1f) * height / 2;
615
616                int x = (int) (fromLeft + Math.round(((to.left - fromLeft) * motionPercent)));
617                int y = (int) (fromTop + Math.round(((to.top - fromTop) * motionPercent)));
618
619                int xPos = x - mDropView.getScrollX() + (mAnchorView != null
620                        ? (mAnchorViewInitialScrollX - mAnchorView.getScrollX()) : 0);
621                int yPos = y - mDropView.getScrollY();
622
623                mDropView.setTranslationX(xPos);
624                mDropView.setTranslationY(yPos);
625                mDropView.setScaleX(scaleX);
626                mDropView.setScaleY(scaleY);
627                mDropView.setAlpha(alpha);
628            }
629        };
630        animateView(view, updateCb, duration, interpolator, onCompleteRunnable, animationEndStyle,
631                anchorView);
632    }
633
634    public void animateView(final DragView view, AnimatorUpdateListener updateCb, int duration,
635            TimeInterpolator interpolator, final Runnable onCompleteRunnable,
636            final int animationEndStyle, View anchorView) {
637        // Clean up the previous animations
638        if (mDropAnim != null) mDropAnim.cancel();
639        if (mFadeOutAnim != null) mFadeOutAnim.cancel();
640
641        // Show the drop view if it was previously hidden
642        mDropView = view;
643        mDropView.cancelAnimation();
644        mDropView.resetLayoutParams();
645
646        // Set the anchor view if the page is scrolling
647        if (anchorView != null) {
648            mAnchorViewInitialScrollX = anchorView.getScrollX();
649        }
650        mAnchorView = anchorView;
651
652        // Create and start the animation
653        mDropAnim = new ValueAnimator();
654        mDropAnim.setInterpolator(interpolator);
655        mDropAnim.setDuration(duration);
656        mDropAnim.setFloatValues(0f, 1f);
657        mDropAnim.addUpdateListener(updateCb);
658        mDropAnim.addListener(new AnimatorListenerAdapter() {
659            public void onAnimationEnd(Animator animation) {
660                if (onCompleteRunnable != null) {
661                    onCompleteRunnable.run();
662                }
663                switch (animationEndStyle) {
664                case ANIMATION_END_DISAPPEAR:
665                    clearAnimatedView();
666                    break;
667                case ANIMATION_END_FADE_OUT:
668                    fadeOutDragView();
669                    break;
670                case ANIMATION_END_REMAIN_VISIBLE:
671                    break;
672                }
673            }
674        });
675        mDropAnim.start();
676    }
677
678    public void clearAnimatedView() {
679        if (mDropAnim != null) {
680            mDropAnim.cancel();
681        }
682        if (mDropView != null) {
683            mDragController.onDeferredEndDrag(mDropView);
684        }
685        mDropView = null;
686        invalidate();
687    }
688
689    public View getAnimatedView() {
690        return mDropView;
691    }
692
693    private void fadeOutDragView() {
694        mFadeOutAnim = new ValueAnimator();
695        mFadeOutAnim.setDuration(150);
696        mFadeOutAnim.setFloatValues(0f, 1f);
697        mFadeOutAnim.removeAllUpdateListeners();
698        mFadeOutAnim.addUpdateListener(new AnimatorUpdateListener() {
699            public void onAnimationUpdate(ValueAnimator animation) {
700                final float percent = (Float) animation.getAnimatedValue();
701
702                float alpha = 1 - percent;
703                mDropView.setAlpha(alpha);
704            }
705        });
706        mFadeOutAnim.addListener(new AnimatorListenerAdapter() {
707            public void onAnimationEnd(Animator animation) {
708                if (mDropView != null) {
709                    mDragController.onDeferredEndDrag(mDropView);
710                }
711                mDropView = null;
712                invalidate();
713            }
714        });
715        mFadeOutAnim.start();
716    }
717
718    @Override
719    public void onChildViewAdded(View parent, View child) {
720        updateChildIndices();
721    }
722
723    @Override
724    public void onChildViewRemoved(View parent, View child) {
725        updateChildIndices();
726    }
727
728    private void updateChildIndices() {
729        if (mLauncher != null) {
730            mWorkspaceIndex = indexOfChild(mLauncher.getWorkspace());
731            mQsbIndex = indexOfChild(mLauncher.getSearchBar());
732        }
733    }
734
735    @Override
736    protected int getChildDrawingOrder(int childCount, int i) {
737        // TODO: We have turned off this custom drawing order because it now effects touch
738        // dispatch order. We need to sort that issue out and then decide how to go about this.
739        if (true || LauncherApplication.isScreenLandscape(getContext()) ||
740                mWorkspaceIndex == -1 || mQsbIndex == -1 ||
741                mLauncher.getWorkspace().isDrawingBackgroundGradient()) {
742            return i;
743        }
744
745        // This ensures that the workspace is drawn above the hotseat and qsb,
746        // except when the workspace is drawing a background gradient, in which
747        // case we want the workspace to stay behind these elements.
748        if (i == mQsbIndex) {
749            return mWorkspaceIndex;
750        } else if (i == mWorkspaceIndex) {
751            return mQsbIndex;
752        } else {
753            return i;
754        }
755    }
756
757    private boolean mInScrollArea;
758    private Drawable mLeftHoverDrawable;
759    private Drawable mRightHoverDrawable;
760
761    void onEnterScrollArea(int direction) {
762        mInScrollArea = true;
763        invalidate();
764    }
765
766    void onExitScrollArea() {
767        mInScrollArea = false;
768        invalidate();
769    }
770
771    /**
772     * Note: this is a reimplementation of View.isLayoutRtl() since that is currently hidden api.
773     */
774    private boolean isLayoutDirectionRtl() {
775        return (getLayoutDirection() == LAYOUT_DIRECTION_RTL);
776    }
777
778    @Override
779    protected void dispatchDraw(Canvas canvas) {
780        super.dispatchDraw(canvas);
781
782        if (mInScrollArea && !LauncherApplication.isScreenLarge()) {
783            Workspace workspace = mLauncher.getWorkspace();
784            int width = workspace.getWidth();
785            Rect childRect = new Rect();
786            getDescendantRectRelativeToSelf(workspace.getChildAt(0), childRect);
787
788            int page = workspace.getNextPage();
789            final boolean isRtl = isLayoutDirectionRtl();
790            CellLayout leftPage = (CellLayout) workspace.getChildAt(isRtl ? page + 1 : page - 1);
791            CellLayout rightPage = (CellLayout) workspace.getChildAt(isRtl ? page - 1 : page + 1);
792
793            if (leftPage != null && leftPage.getIsDragOverlapping()) {
794                mLeftHoverDrawable.setBounds(0, childRect.top,
795                        mLeftHoverDrawable.getIntrinsicWidth(), childRect.bottom);
796                mLeftHoverDrawable.draw(canvas);
797            } else if (rightPage != null && rightPage.getIsDragOverlapping()) {
798                mRightHoverDrawable.setBounds(width - mRightHoverDrawable.getIntrinsicWidth(),
799                        childRect.top, width, childRect.bottom);
800                mRightHoverDrawable.draw(canvas);
801            }
802        }
803    }
804}
805