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