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.launcher3.dragndrop;
18
19import android.animation.Animator;
20import android.animation.AnimatorListenerAdapter;
21import android.animation.TimeInterpolator;
22import android.animation.ValueAnimator;
23import android.animation.ValueAnimator.AnimatorUpdateListener;
24import android.annotation.TargetApi;
25import android.content.ClipDescription;
26import android.content.Context;
27import android.content.Intent;
28import android.content.res.Resources;
29import android.graphics.Bitmap;
30import android.graphics.Bitmap.Config;
31import android.graphics.Canvas;
32import android.graphics.Color;
33import android.graphics.Point;
34import android.graphics.Rect;
35import android.graphics.Region;
36import android.graphics.drawable.Drawable;
37import android.os.Build;
38import android.util.AttributeSet;
39import android.util.Log;
40import android.view.DragEvent;
41import android.view.KeyEvent;
42import android.view.MotionEvent;
43import android.view.View;
44import android.view.ViewGroup;
45import android.view.accessibility.AccessibilityEvent;
46import android.view.accessibility.AccessibilityManager;
47import android.view.animation.DecelerateInterpolator;
48import android.view.animation.Interpolator;
49import android.widget.FrameLayout;
50import android.widget.TextView;
51
52import com.android.launcher3.AppWidgetResizeFrame;
53import com.android.launcher3.CellLayout;
54import com.android.launcher3.DropTargetBar;
55import com.android.launcher3.InsettableFrameLayout;
56import com.android.launcher3.InstallShortcutReceiver;
57import com.android.launcher3.ItemInfo;
58import com.android.launcher3.Launcher;
59import com.android.launcher3.LauncherAppWidgetHostView;
60import com.android.launcher3.PinchToOverviewListener;
61import com.android.launcher3.R;
62import com.android.launcher3.ShortcutAndWidgetContainer;
63import com.android.launcher3.ShortcutInfo;
64import com.android.launcher3.Utilities;
65import com.android.launcher3.Workspace;
66import com.android.launcher3.allapps.AllAppsTransitionController;
67import com.android.launcher3.config.FeatureFlags;
68import com.android.launcher3.folder.Folder;
69import com.android.launcher3.folder.FolderIcon;
70import com.android.launcher3.keyboard.ViewGroupFocusHelper;
71import com.android.launcher3.shortcuts.DeepShortcutsContainer;
72import com.android.launcher3.util.Thunk;
73import com.android.launcher3.util.TouchController;
74
75import java.net.URISyntaxException;
76import java.util.ArrayList;
77
78/**
79 * A ViewGroup that coordinates dragging across its descendants
80 */
81public class DragLayer extends InsettableFrameLayout {
82
83    public static final int ANIMATION_END_DISAPPEAR = 0;
84    public static final int ANIMATION_END_REMAIN_VISIBLE = 2;
85
86    // Scrim color without any alpha component.
87    private static final int SCRIM_COLOR = Color.BLACK & 0x00FFFFFF;
88
89    private final int[] mTmpXY = new int[2];
90
91    @Thunk DragController mDragController;
92
93    private int mXDown, mYDown;
94    private Launcher mLauncher;
95
96    // Variables relating to resizing widgets
97    private final ArrayList<AppWidgetResizeFrame> mResizeFrames = new ArrayList<>();
98    private final boolean mIsRtl;
99    private AppWidgetResizeFrame mCurrentResizeFrame;
100
101    // Variables relating to animation of views after drop
102    private ValueAnimator mDropAnim = null;
103    private final TimeInterpolator mCubicEaseOutInterpolator = new DecelerateInterpolator(1.5f);
104    @Thunk DragView mDropView = null;
105    @Thunk int mAnchorViewInitialScrollX = 0;
106    @Thunk View mAnchorView = null;
107
108    private boolean mHoverPointClosesFolder = false;
109    private final Rect mHitRect = new Rect();
110    private final Rect mHighlightRect = new Rect();
111
112    private TouchCompleteListener mTouchCompleteListener;
113
114    private int mTopViewIndex;
115    private int mChildCountOnLastUpdate = -1;
116
117    // Darkening scrim
118    private float mBackgroundAlpha = 0;
119
120    // Related to adjacent page hints
121    private final Rect mScrollChildPosition = new Rect();
122    private final ViewGroupFocusHelper mFocusIndicatorHelper;
123
124    private boolean mInScrollArea;
125    private boolean mShowPageHints;
126    private Drawable mLeftHoverDrawable;
127    private Drawable mRightHoverDrawable;
128    private Drawable mLeftHoverDrawableActive;
129    private Drawable mRightHoverDrawableActive;
130
131    // Related to pinch-to-go-to-overview gesture.
132    private PinchToOverviewListener mPinchListener = null;
133
134    // Handles all apps pull up interaction
135    private AllAppsTransitionController mAllAppsController;
136
137    private TouchController mActiveController;
138    /**
139     * Used to create a new DragLayer from XML.
140     *
141     * @param context The application's context.
142     * @param attrs The attributes set containing the Workspace's customization values.
143     */
144    public DragLayer(Context context, AttributeSet attrs) {
145        super(context, attrs);
146
147        // Disable multitouch across the workspace/all apps/customize tray
148        setMotionEventSplittingEnabled(false);
149        setChildrenDrawingOrderEnabled(true);
150
151        final Resources res = getResources();
152        mLeftHoverDrawable = res.getDrawable(R.drawable.page_hover_left);
153        mRightHoverDrawable = res.getDrawable(R.drawable.page_hover_right);
154        mLeftHoverDrawableActive = res.getDrawable(R.drawable.page_hover_left_active);
155        mRightHoverDrawableActive = res.getDrawable(R.drawable.page_hover_right_active);
156        mIsRtl = Utilities.isRtl(res);
157        mFocusIndicatorHelper = new ViewGroupFocusHelper(this);
158    }
159
160    public void setup(Launcher launcher, DragController dragController,
161            AllAppsTransitionController allAppsTransitionController) {
162        mLauncher = launcher;
163        mDragController = dragController;
164        mAllAppsController = allAppsTransitionController;
165
166        boolean isAccessibilityEnabled = ((AccessibilityManager) mLauncher.getSystemService(
167                Context.ACCESSIBILITY_SERVICE)).isEnabled();
168        onAccessibilityStateChanged(isAccessibilityEnabled);
169    }
170
171    public ViewGroupFocusHelper getFocusIndicatorHelper() {
172        return mFocusIndicatorHelper;
173    }
174
175    @Override
176    public boolean dispatchKeyEvent(KeyEvent event) {
177        return mDragController.dispatchKeyEvent(event) || super.dispatchKeyEvent(event);
178    }
179
180    public void onAccessibilityStateChanged(boolean isAccessibilityEnabled) {
181        mPinchListener = FeatureFlags.LAUNCHER3_DISABLE_PINCH_TO_OVERVIEW || isAccessibilityEnabled
182                ? null : new PinchToOverviewListener(mLauncher);
183    }
184
185    public boolean isEventOverPageIndicator(MotionEvent ev) {
186        getDescendantRectRelativeToSelf(mLauncher.getWorkspace().getPageIndicator(), mHitRect);
187        return mHitRect.contains((int) ev.getX(), (int) ev.getY());
188    }
189
190    public boolean isEventOverHotseat(MotionEvent ev) {
191        return isEventOverView(mLauncher.getHotseat(), ev);
192    }
193
194    private boolean isEventOverFolderTextRegion(Folder folder, MotionEvent ev) {
195        return isEventOverView(folder.getEditTextRegion(), ev);
196    }
197
198    private boolean isEventOverFolder(Folder folder, MotionEvent ev) {
199        return isEventOverView(folder, ev);
200    }
201
202    private boolean isEventOverDropTargetBar(MotionEvent ev) {
203        return isEventOverView(mLauncher.getDropTargetBar(), ev);
204    }
205
206    public boolean isEventOverView(View view, MotionEvent ev) {
207        getDescendantRectRelativeToSelf(view, mHitRect);
208        return mHitRect.contains((int) ev.getX(), (int) ev.getY());
209    }
210
211    private boolean handleTouchDown(MotionEvent ev, boolean intercept) {
212        Rect hitRect = new Rect();
213        int x = (int) ev.getX();
214        int y = (int) ev.getY();
215
216        for (AppWidgetResizeFrame child: mResizeFrames) {
217            child.getHitRect(hitRect);
218            if (hitRect.contains(x, y)) {
219                if (child.beginResizeIfPointInRegion(x - child.getLeft(), y - child.getTop())) {
220                    mCurrentResizeFrame = child;
221                    mXDown = x;
222                    mYDown = y;
223                    requestDisallowInterceptTouchEvent(true);
224                    return true;
225                }
226            }
227        }
228
229        // Remove the shortcuts container when touching outside of it.
230        DeepShortcutsContainer deepShortcutsContainer = mLauncher.getOpenShortcutsContainer();
231        if (deepShortcutsContainer != null) {
232            if (isEventOverView(deepShortcutsContainer, ev)) {
233                // Let the container handle the event.
234                return false;
235            } else {
236                if (isInAccessibleDrag()) {
237                    // Do not close the container if in drag and drop.
238                    if (!isEventOverDropTargetBar(ev)) {
239                        return true;
240                    }
241                } else {
242                    mLauncher.closeShortcutsContainer();
243                    // We let touches on the original icon go through so that users can launch
244                    // the app with one tap if they don't find a shortcut they want.
245                    return !isEventOverView(deepShortcutsContainer.getDeferredDragIcon(), ev);
246                }
247            }
248        }
249
250        Folder currentFolder = mLauncher.getWorkspace().getOpenFolder();
251        if (currentFolder != null && intercept) {
252            if (currentFolder.isEditingName()) {
253                if (!isEventOverFolderTextRegion(currentFolder, ev)) {
254                    currentFolder.dismissEditingName();
255                    return true;
256                }
257            }
258
259            if (!isEventOverFolder(currentFolder, ev)) {
260                if (isInAccessibleDrag()) {
261                    // Do not close the folder if in drag and drop.
262                    if (!isEventOverDropTargetBar(ev)) {
263                        return true;
264                    }
265                } else {
266                    mLauncher.closeFolder();
267                    return true;
268                }
269            }
270        }
271        return false;
272    }
273
274    @Override
275    public boolean onInterceptTouchEvent(MotionEvent ev) {
276        int action = ev.getAction();
277
278        if (action == MotionEvent.ACTION_DOWN) {
279            // Cancel discovery bounce animation when a user start interacting on anywhere on
280            // dray layer even if mAllAppsController is NOT the active controller.
281            // TODO: handle other input other than touch
282            mAllAppsController.cancelDiscoveryAnimation();
283            if (handleTouchDown(ev, true)) {
284                return true;
285            }
286        } else if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
287            if (mTouchCompleteListener != null) {
288                mTouchCompleteListener.onTouchComplete();
289            }
290            mTouchCompleteListener = null;
291        }
292        clearAllResizeFrames();
293
294        mActiveController = null;
295
296        if (mDragController.onInterceptTouchEvent(ev)) {
297            mActiveController = mDragController;
298            return true;
299        }
300
301        if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP && mAllAppsController.onInterceptTouchEvent(ev)) {
302            mActiveController = mAllAppsController;
303            return true;
304        }
305
306        if (mPinchListener != null && mPinchListener.onInterceptTouchEvent(ev)) {
307            // Stop listening for scrolling etc. (onTouchEvent() handles the rest of the pinch.)
308            mActiveController = mPinchListener;
309            return true;
310        }
311        return false;
312    }
313
314    @Override
315    public boolean onInterceptHoverEvent(MotionEvent ev) {
316        if (mLauncher == null || mLauncher.getWorkspace() == null) {
317            return false;
318        }
319        Folder currentFolder = mLauncher.getWorkspace().getOpenFolder();
320        if (currentFolder == null) {
321            return false;
322        } else {
323                AccessibilityManager accessibilityManager = (AccessibilityManager)
324                        getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
325            if (accessibilityManager.isTouchExplorationEnabled()) {
326                final int action = ev.getAction();
327                boolean isOverFolderOrSearchBar;
328                switch (action) {
329                    case MotionEvent.ACTION_HOVER_ENTER:
330                        isOverFolderOrSearchBar = isEventOverFolder(currentFolder, ev) ||
331                            (isInAccessibleDrag() && isEventOverDropTargetBar(ev));
332                        if (!isOverFolderOrSearchBar) {
333                            sendTapOutsideFolderAccessibilityEvent(currentFolder.isEditingName());
334                            mHoverPointClosesFolder = true;
335                            return true;
336                        }
337                        mHoverPointClosesFolder = false;
338                        break;
339                    case MotionEvent.ACTION_HOVER_MOVE:
340                        isOverFolderOrSearchBar = isEventOverFolder(currentFolder, ev) ||
341                            (isInAccessibleDrag() && isEventOverDropTargetBar(ev));
342                        if (!isOverFolderOrSearchBar && !mHoverPointClosesFolder) {
343                            sendTapOutsideFolderAccessibilityEvent(currentFolder.isEditingName());
344                            mHoverPointClosesFolder = true;
345                            return true;
346                        } else if (!isOverFolderOrSearchBar) {
347                            return true;
348                        }
349                        mHoverPointClosesFolder = false;
350                }
351            }
352        }
353        return false;
354    }
355
356    private void sendTapOutsideFolderAccessibilityEvent(boolean isEditingName) {
357        int stringId = isEditingName ? R.string.folder_tap_to_rename : R.string.folder_tap_to_close;
358        Utilities.sendCustomAccessibilityEvent(
359                this, AccessibilityEvent.TYPE_VIEW_FOCUSED, getContext().getString(stringId));
360    }
361
362    private boolean isInAccessibleDrag() {
363        return mLauncher.getAccessibilityDelegate().isInAccessibleDrag();
364    }
365
366    @Override
367    public boolean onRequestSendAccessibilityEvent(View child, AccessibilityEvent event) {
368        // Shortcuts can appear above folder
369        View topView = mLauncher.getTopFloatingView();
370        if (topView != null) {
371            if (child == topView) {
372                return super.onRequestSendAccessibilityEvent(child, event);
373            }
374            if (isInAccessibleDrag() && child instanceof DropTargetBar) {
375                return super.onRequestSendAccessibilityEvent(child, event);
376            }
377            // Skip propagating onRequestSendAccessibilityEvent for all other children
378            // which are not topView
379            return false;
380        }
381        return super.onRequestSendAccessibilityEvent(child, event);
382    }
383
384    @Override
385    public void addChildrenForAccessibility(ArrayList<View> childrenForAccessibility) {
386        View topView = mLauncher.getTopFloatingView();
387        if (topView != null) {
388            // Only add the top view as a child for accessibility when it is open
389            childrenForAccessibility.add(topView);
390
391            if (isInAccessibleDrag()) {
392                childrenForAccessibility.add(mLauncher.getDropTargetBar());
393            }
394        } else {
395            super.addChildrenForAccessibility(childrenForAccessibility);
396        }
397    }
398
399    @Override
400    public boolean onHoverEvent(MotionEvent ev) {
401        // If we've received this, we've already done the necessary handling
402        // in onInterceptHoverEvent. Return true to consume the event.
403        return false;
404    }
405
406    @Override
407    public boolean onTouchEvent(MotionEvent ev) {
408        boolean handled = false;
409        int action = ev.getAction();
410
411        int x = (int) ev.getX();
412        int y = (int) ev.getY();
413
414        if (action == MotionEvent.ACTION_DOWN) {
415            if (handleTouchDown(ev, false)) {
416                return true;
417            }
418        } else if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
419            if (mTouchCompleteListener != null) {
420                mTouchCompleteListener.onTouchComplete();
421            }
422            mTouchCompleteListener = null;
423        }
424
425        if (mCurrentResizeFrame != null) {
426            handled = true;
427            switch (action) {
428                case MotionEvent.ACTION_MOVE:
429                    mCurrentResizeFrame.visualizeResizeForDelta(x - mXDown, y - mYDown);
430                    break;
431                case MotionEvent.ACTION_CANCEL:
432                case MotionEvent.ACTION_UP:
433                    mCurrentResizeFrame.visualizeResizeForDelta(x - mXDown, y - mYDown);
434                    mCurrentResizeFrame.onTouchUp();
435                    mCurrentResizeFrame = null;
436            }
437        }
438        if (handled) return true;
439        if (mActiveController != null) {
440            return mActiveController.onTouchEvent(ev);
441        }
442        return false;
443    }
444
445    @TargetApi(Build.VERSION_CODES.N)
446    private void handleSystemDragStart(DragEvent event) {
447        if (!FeatureFlags.LAUNCHER3_USE_SYSTEM_DRAG_DRIVER || !Utilities.isNycOrAbove()) {
448            return;
449        }
450        if (mLauncher.isWorkspaceLocked()) {
451            return;
452        }
453
454        ClipDescription description = event.getClipDescription();
455        if (!description.hasMimeType(ClipDescription.MIMETYPE_TEXT_INTENT)) {
456            return;
457        }
458        ShortcutInfo info = new ShortcutInfo();
459        // Set a dummy intent until we get the final value
460        info.intent = new Intent();
461
462        // Since we are not going through the workspace for starting the drag, set drag related
463        // information on the workspace before starting the drag.
464        ExternalDragPreviewProvider previewProvider =
465                new ExternalDragPreviewProvider(mLauncher, info);
466        mLauncher.getWorkspace().prepareDragWithProvider(previewProvider);
467
468        DragOptions options = new DragOptions();
469        options.systemDndStartPoint = new Point((int) event.getX(), (int) event.getY());
470
471        int halfPadding = previewProvider.previewPadding / 2;
472        mDragController.startDrag(
473                Bitmap.createBitmap(1, 1, Config.ARGB_8888),
474                0, 0,
475                new AnotherWindowDragSource(mLauncher), info,
476                new Point(- halfPadding, halfPadding),
477                previewProvider.getPreviewBounds(), 1f, options);
478    }
479
480    @Override
481    public boolean onDragEvent (DragEvent event) {
482        if (event.getAction() == DragEvent.ACTION_DRAG_STARTED) {
483            handleSystemDragStart(event);
484        }
485        return mDragController.onDragEvent(event);
486    }
487
488    /**
489     * Determine the rect of the descendant in this DragLayer's coordinates
490     *
491     * @param descendant The descendant whose coordinates we want to find.
492     * @param r The rect into which to place the results.
493     * @return The factor by which this descendant is scaled relative to this DragLayer.
494     */
495    public float getDescendantRectRelativeToSelf(View descendant, Rect r) {
496        mTmpXY[0] = 0;
497        mTmpXY[1] = 0;
498        float scale = getDescendantCoordRelativeToSelf(descendant, mTmpXY);
499
500        r.set(mTmpXY[0], mTmpXY[1],
501                (int) (mTmpXY[0] + scale * descendant.getMeasuredWidth()),
502                (int) (mTmpXY[1] + scale * descendant.getMeasuredHeight()));
503        return scale;
504    }
505
506    public float getLocationInDragLayer(View child, int[] loc) {
507        loc[0] = 0;
508        loc[1] = 0;
509        return getDescendantCoordRelativeToSelf(child, loc);
510    }
511
512    public float getDescendantCoordRelativeToSelf(View descendant, int[] coord) {
513        return getDescendantCoordRelativeToSelf(descendant, coord, false);
514    }
515
516    /**
517     * Given a coordinate relative to the descendant, find the coordinate in this DragLayer's
518     * coordinates.
519     *
520     * @param descendant The descendant to which the passed coordinate is relative.
521     * @param coord The coordinate that we want mapped.
522     * @param includeRootScroll Whether or not to account for the scroll of the root descendant:
523     *          sometimes this is relevant as in a child's coordinates within the root descendant.
524     * @return The factor by which this descendant is scaled relative to this DragLayer. Caution
525     *         this scale factor is assumed to be equal in X and Y, and so if at any point this
526     *         assumption fails, we will need to return a pair of scale factors.
527     */
528    public float getDescendantCoordRelativeToSelf(View descendant, int[] coord,
529            boolean includeRootScroll) {
530        return Utilities.getDescendantCoordRelativeToAncestor(descendant, this,
531                coord, includeRootScroll);
532    }
533
534    /**
535     * Inverse of {@link #getDescendantCoordRelativeToSelf(View, int[])}.
536     */
537    public float mapCoordInSelfToDescendent(View descendant, int[] coord) {
538        return Utilities.mapCoordInSelfToDescendent(descendant, this, coord);
539    }
540
541    public void getViewRectRelativeToSelf(View v, Rect r) {
542        int[] loc = new int[2];
543        getLocationInWindow(loc);
544        int x = loc[0];
545        int y = loc[1];
546
547        v.getLocationInWindow(loc);
548        int vX = loc[0];
549        int vY = loc[1];
550
551        int left = vX - x;
552        int top = vY - y;
553        r.set(left, top, left + v.getMeasuredWidth(), top + v.getMeasuredHeight());
554    }
555
556    @Override
557    public boolean dispatchUnhandledMove(View focused, int direction) {
558        // Consume the unhandled move if a container is open, to avoid switching pages underneath.
559        boolean isContainerOpen = mLauncher.getTopFloatingView() != null;
560        return isContainerOpen || mDragController.dispatchUnhandledMove(focused, direction);
561    }
562
563    @Override
564    public LayoutParams generateLayoutParams(AttributeSet attrs) {
565        return new LayoutParams(getContext(), attrs);
566    }
567
568    @Override
569    protected LayoutParams generateDefaultLayoutParams() {
570        return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
571    }
572
573    // Override to allow type-checking of LayoutParams.
574    @Override
575    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
576        return p instanceof LayoutParams;
577    }
578
579    @Override
580    protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
581        return new LayoutParams(p);
582    }
583
584    public static class LayoutParams extends InsettableFrameLayout.LayoutParams {
585        public int x, y;
586        public boolean customPosition = false;
587
588        public LayoutParams(Context c, AttributeSet attrs) {
589            super(c, attrs);
590        }
591
592        public LayoutParams(int width, int height) {
593            super(width, height);
594        }
595
596        public LayoutParams(ViewGroup.LayoutParams lp) {
597            super(lp);
598        }
599
600        public void setWidth(int width) {
601            this.width = width;
602        }
603
604        public int getWidth() {
605            return width;
606        }
607
608        public void setHeight(int height) {
609            this.height = height;
610        }
611
612        public int getHeight() {
613            return height;
614        }
615
616        public void setX(int x) {
617            this.x = x;
618        }
619
620        public int getX() {
621            return x;
622        }
623
624        public void setY(int y) {
625            this.y = y;
626        }
627
628        public int getY() {
629            return y;
630        }
631    }
632
633    protected void onLayout(boolean changed, int l, int t, int r, int b) {
634        super.onLayout(changed, l, t, r, b);
635        int count = getChildCount();
636        for (int i = 0; i < count; i++) {
637            View child = getChildAt(i);
638            final FrameLayout.LayoutParams flp = (FrameLayout.LayoutParams) child.getLayoutParams();
639            if (flp instanceof LayoutParams) {
640                final LayoutParams lp = (LayoutParams) flp;
641                if (lp.customPosition) {
642                    child.layout(lp.x, lp.y, lp.x + lp.width, lp.y + lp.height);
643                }
644            }
645        }
646    }
647
648    public void clearAllResizeFrames() {
649        if (mResizeFrames.size() > 0) {
650            for (AppWidgetResizeFrame frame: mResizeFrames) {
651                frame.commitResize();
652                removeView(frame);
653            }
654            mResizeFrames.clear();
655        }
656    }
657
658    public boolean hasResizeFrames() {
659        return mResizeFrames.size() > 0;
660    }
661
662    public boolean isWidgetBeingResized() {
663        return mCurrentResizeFrame != null;
664    }
665
666    public void addResizeFrame(ItemInfo itemInfo, LauncherAppWidgetHostView widget,
667            CellLayout cellLayout) {
668        AppWidgetResizeFrame resizeFrame = new AppWidgetResizeFrame(getContext(),
669                widget, cellLayout, this);
670
671        LayoutParams lp = new LayoutParams(-1, -1);
672        lp.customPosition = true;
673
674        addView(resizeFrame, lp);
675        mResizeFrames.add(resizeFrame);
676
677        resizeFrame.snapToWidget(false);
678    }
679
680    public void animateViewIntoPosition(DragView dragView, final int[] pos, float alpha,
681            float scaleX, float scaleY, int animationEndStyle, Runnable onFinishRunnable,
682            int duration) {
683        Rect r = new Rect();
684        getViewRectRelativeToSelf(dragView, r);
685        final int fromX = r.left;
686        final int fromY = r.top;
687
688        animateViewIntoPosition(dragView, fromX, fromY, pos[0], pos[1], alpha, 1, 1, scaleX, scaleY,
689                onFinishRunnable, animationEndStyle, duration, null);
690    }
691
692    public void animateViewIntoPosition(DragView dragView, final View child,
693            final Runnable onFinishAnimationRunnable, View anchorView) {
694        animateViewIntoPosition(dragView, child, -1, onFinishAnimationRunnable, anchorView);
695    }
696
697    public void animateViewIntoPosition(DragView dragView, final View child, int duration,
698            final Runnable onFinishAnimationRunnable, View anchorView) {
699        ShortcutAndWidgetContainer parentChildren = (ShortcutAndWidgetContainer) child.getParent();
700        CellLayout.LayoutParams lp =  (CellLayout.LayoutParams) child.getLayoutParams();
701        parentChildren.measureChild(child);
702
703        Rect r = new Rect();
704        getViewRectRelativeToSelf(dragView, r);
705
706        int coord[] = new int[2];
707        float childScale = child.getScaleX();
708        coord[0] = lp.x + (int) (child.getMeasuredWidth() * (1 - childScale) / 2);
709        coord[1] = lp.y + (int) (child.getMeasuredHeight() * (1 - childScale) / 2);
710
711        // Since the child hasn't necessarily been laid out, we force the lp to be updated with
712        // the correct coordinates (above) and use these to determine the final location
713        float scale = getDescendantCoordRelativeToSelf((View) child.getParent(), coord);
714        // We need to account for the scale of the child itself, as the above only accounts for
715        // for the scale in parents.
716        scale *= childScale;
717        int toX = coord[0];
718        int toY = coord[1];
719        float toScale = scale;
720        if (child instanceof TextView) {
721            TextView tv = (TextView) child;
722            // Account for the source scale of the icon (ie. from AllApps to Workspace, in which
723            // the workspace may have smaller icon bounds).
724            toScale = scale / dragView.getIntrinsicIconScaleFactor();
725
726            // The child may be scaled (always about the center of the view) so to account for it,
727            // we have to offset the position by the scaled size.  Once we do that, we can center
728            // the drag view about the scaled child view.
729            toY += Math.round(toScale * tv.getPaddingTop());
730            toY -= dragView.getMeasuredHeight() * (1 - toScale) / 2;
731            if (dragView.getDragVisualizeOffset() != null) {
732                toY -=  Math.round(toScale * dragView.getDragVisualizeOffset().y);
733            }
734
735            toX -= (dragView.getMeasuredWidth() - Math.round(scale * child.getMeasuredWidth())) / 2;
736        } else if (child instanceof FolderIcon) {
737            // Account for holographic blur padding on the drag view
738            toY += Math.round(scale * (child.getPaddingTop() - dragView.getDragRegionTop()));
739            toY -= scale * Workspace.DRAG_BITMAP_PADDING / 2;
740            toY -= (1 - scale) * dragView.getMeasuredHeight() / 2;
741            // Center in the x coordinate about the target's drawable
742            toX -= (dragView.getMeasuredWidth() - Math.round(scale * child.getMeasuredWidth())) / 2;
743        } else {
744            toY -= (Math.round(scale * (dragView.getHeight() - child.getMeasuredHeight()))) / 2;
745            toX -= (Math.round(scale * (dragView.getMeasuredWidth()
746                    - child.getMeasuredWidth()))) / 2;
747        }
748
749        final int fromX = r.left;
750        final int fromY = r.top;
751        child.setVisibility(INVISIBLE);
752        Runnable onCompleteRunnable = new Runnable() {
753            public void run() {
754                child.setVisibility(VISIBLE);
755                if (onFinishAnimationRunnable != null) {
756                    onFinishAnimationRunnable.run();
757                }
758            }
759        };
760        animateViewIntoPosition(dragView, fromX, fromY, toX, toY, 1, 1, 1, toScale, toScale,
761                onCompleteRunnable, ANIMATION_END_DISAPPEAR, duration, anchorView);
762    }
763
764    public void animateViewIntoPosition(final DragView view, final int fromX, final int fromY,
765            final int toX, final int toY, float finalAlpha, float initScaleX, float initScaleY,
766            float finalScaleX, float finalScaleY, Runnable onCompleteRunnable,
767            int animationEndStyle, int duration, View anchorView) {
768        Rect from = new Rect(fromX, fromY, fromX +
769                view.getMeasuredWidth(), fromY + view.getMeasuredHeight());
770        Rect to = new Rect(toX, toY, toX + view.getMeasuredWidth(), toY + view.getMeasuredHeight());
771        animateView(view, from, to, finalAlpha, initScaleX, initScaleY, finalScaleX, finalScaleY, duration,
772                null, null, onCompleteRunnable, animationEndStyle, anchorView);
773    }
774
775    /**
776     * This method animates a view at the end of a drag and drop animation.
777     *
778     * @param view The view to be animated. This view is drawn directly into DragLayer, and so
779     *        doesn't need to be a child of DragLayer.
780     * @param from The initial location of the view. Only the left and top parameters are used.
781     * @param to The final location of the view. Only the left and top parameters are used. This
782     *        location doesn't account for scaling, and so should be centered about the desired
783     *        final location (including scaling).
784     * @param finalAlpha The final alpha of the view, in case we want it to fade as it animates.
785     * @param finalScaleX The final scale of the view. The view is scaled about its center.
786     * @param finalScaleY The final scale of the view. The view is scaled about its center.
787     * @param duration The duration of the animation.
788     * @param motionInterpolator The interpolator to use for the location of the view.
789     * @param alphaInterpolator The interpolator to use for the alpha of the view.
790     * @param onCompleteRunnable Optional runnable to run on animation completion.
791     * @param animationEndStyle Whether or not to fade out the view once the animation completes.
792     *        {@link #ANIMATION_END_DISAPPEAR} or {@link #ANIMATION_END_REMAIN_VISIBLE}.
793     * @param anchorView If not null, this represents the view which the animated view stays
794     *        anchored to in case scrolling is currently taking place. Note: currently this is
795     *        only used for the X dimension for the case of the workspace.
796     */
797    public void animateView(final DragView view, final Rect from, final Rect to,
798            final float finalAlpha, final float initScaleX, final float initScaleY,
799            final float finalScaleX, final float finalScaleY, int duration,
800            final Interpolator motionInterpolator, final Interpolator alphaInterpolator,
801            final Runnable onCompleteRunnable, final int animationEndStyle, View anchorView) {
802
803        // Calculate the duration of the animation based on the object's distance
804        final float dist = (float) Math.hypot(to.left - from.left, to.top - from.top);
805        final Resources res = getResources();
806        final float maxDist = (float) res.getInteger(R.integer.config_dropAnimMaxDist);
807
808        // If duration < 0, this is a cue to compute the duration based on the distance
809        if (duration < 0) {
810            if (mDragController != null && mDragController.isExternalDrag()) {
811                duration = 1;
812            } else {
813                duration = res.getInteger(R.integer.config_dropAnimMaxDuration);
814                if (dist < maxDist) {
815                    duration *= mCubicEaseOutInterpolator.getInterpolation(dist / maxDist);
816                }
817                duration = Math.max(duration, res.getInteger(R.integer.config_dropAnimMinDuration));
818            }
819        }
820
821        // Fall back to cubic ease out interpolator for the animation if none is specified
822        TimeInterpolator interpolator = null;
823        if (alphaInterpolator == null || motionInterpolator == null) {
824            interpolator = mCubicEaseOutInterpolator;
825        }
826
827        // Animate the view
828        final float initAlpha = view.getAlpha();
829        final float dropViewScale = view.getScaleX();
830        AnimatorUpdateListener updateCb = new AnimatorUpdateListener() {
831            @Override
832            public void onAnimationUpdate(ValueAnimator animation) {
833                final float percent = (Float) animation.getAnimatedValue();
834                final int width = view.getMeasuredWidth();
835                final int height = view.getMeasuredHeight();
836
837                float alphaPercent = alphaInterpolator == null ? percent :
838                        alphaInterpolator.getInterpolation(percent);
839                float motionPercent = motionInterpolator == null ? percent :
840                        motionInterpolator.getInterpolation(percent);
841
842                float initialScaleX = initScaleX * dropViewScale;
843                float initialScaleY = initScaleY * dropViewScale;
844                float scaleX = finalScaleX * percent + initialScaleX * (1 - percent);
845                float scaleY = finalScaleY * percent + initialScaleY * (1 - percent);
846                float alpha = finalAlpha * alphaPercent + initAlpha * (1 - alphaPercent);
847
848                float fromLeft = from.left + (initialScaleX - 1f) * width / 2;
849                float fromTop = from.top + (initialScaleY - 1f) * height / 2;
850
851                int x = (int) (fromLeft + Math.round(((to.left - fromLeft) * motionPercent)));
852                int y = (int) (fromTop + Math.round(((to.top - fromTop) * motionPercent)));
853
854                int anchorAdjust = mAnchorView == null ? 0 : (int) (mAnchorView.getScaleX() *
855                    (mAnchorViewInitialScrollX - mAnchorView.getScrollX()));
856
857                int xPos = x - mDropView.getScrollX() + anchorAdjust;
858                int yPos = y - mDropView.getScrollY();
859
860                mDropView.setTranslationX(xPos);
861                mDropView.setTranslationY(yPos);
862                mDropView.setScaleX(scaleX);
863                mDropView.setScaleY(scaleY);
864                mDropView.setAlpha(alpha);
865            }
866        };
867        animateView(view, updateCb, duration, interpolator, onCompleteRunnable, animationEndStyle,
868                anchorView);
869    }
870
871    public void animateView(final DragView view, AnimatorUpdateListener updateCb, int duration,
872            TimeInterpolator interpolator, final Runnable onCompleteRunnable,
873            final int animationEndStyle, View anchorView) {
874        // Clean up the previous animations
875        if (mDropAnim != null) mDropAnim.cancel();
876
877        // Show the drop view if it was previously hidden
878        mDropView = view;
879        mDropView.cancelAnimation();
880        mDropView.requestLayout();
881
882        // Set the anchor view if the page is scrolling
883        if (anchorView != null) {
884            mAnchorViewInitialScrollX = anchorView.getScrollX();
885        }
886        mAnchorView = anchorView;
887
888        // Create and start the animation
889        mDropAnim = new ValueAnimator();
890        mDropAnim.setInterpolator(interpolator);
891        mDropAnim.setDuration(duration);
892        mDropAnim.setFloatValues(0f, 1f);
893        mDropAnim.addUpdateListener(updateCb);
894        mDropAnim.addListener(new AnimatorListenerAdapter() {
895            public void onAnimationEnd(Animator animation) {
896                if (onCompleteRunnable != null) {
897                    onCompleteRunnable.run();
898                }
899                switch (animationEndStyle) {
900                case ANIMATION_END_DISAPPEAR:
901                    clearAnimatedView();
902                    break;
903                case ANIMATION_END_REMAIN_VISIBLE:
904                    break;
905                }
906            }
907        });
908        mDropAnim.start();
909    }
910
911    public void clearAnimatedView() {
912        if (mDropAnim != null) {
913            mDropAnim.cancel();
914        }
915        if (mDropView != null) {
916            mDragController.onDeferredEndDrag(mDropView);
917        }
918        mDropView = null;
919        invalidate();
920    }
921
922    public View getAnimatedView() {
923        return mDropView;
924    }
925
926    @Override
927    public void onChildViewAdded(View parent, View child) {
928        super.onChildViewAdded(parent, child);
929        updateChildIndices();
930    }
931
932    @Override
933    public void onChildViewRemoved(View parent, View child) {
934        updateChildIndices();
935    }
936
937    @Override
938    public void bringChildToFront(View child) {
939        super.bringChildToFront(child);
940        updateChildIndices();
941    }
942
943    private void updateChildIndices() {
944        mTopViewIndex = -1;
945        int childCount = getChildCount();
946        for (int i = 0; i < childCount; i++) {
947            if (getChildAt(i) instanceof DragView) {
948                mTopViewIndex = i;
949            }
950        }
951        mChildCountOnLastUpdate = childCount;
952    }
953
954    @Override
955    protected int getChildDrawingOrder(int childCount, int i) {
956        if (mChildCountOnLastUpdate != childCount) {
957            // between platform versions 17 and 18, behavior for onChildViewRemoved / Added changed.
958            // Pre-18, the child was not added / removed by the time of those callbacks. We need to
959            // force update our representation of things here to avoid crashing on pre-18 devices
960            // in certain instances.
961            updateChildIndices();
962        }
963
964        // i represents the current draw iteration
965        if (mTopViewIndex == -1) {
966            // in general we do nothing
967            return i;
968        } else if (i == childCount - 1) {
969            // if we have a top index, we return it when drawing last item (highest z-order)
970            return mTopViewIndex;
971        } else if (i < mTopViewIndex) {
972            return i;
973        } else {
974            // for indexes greater than the top index, we fetch one item above to shift for the
975            // displacement of the top index
976            return i + 1;
977        }
978    }
979
980    void onEnterScrollArea() {
981        mInScrollArea = true;
982        invalidate();
983    }
984
985    void onExitScrollArea() {
986        mInScrollArea = false;
987        invalidate();
988    }
989
990    public void showPageHints() {
991        mShowPageHints = true;
992        Workspace workspace = mLauncher.getWorkspace();
993        getDescendantRectRelativeToSelf(workspace.getChildAt(workspace.numCustomPages()),
994                mScrollChildPosition);
995        invalidate();
996    }
997
998    public void hidePageHints() {
999        mShowPageHints = false;
1000        invalidate();
1001    }
1002
1003    public void invalidateScrim() {
1004        if (mBackgroundAlpha > 0.0f) {
1005            invalidate();
1006        }
1007    }
1008
1009    @Override
1010    protected void dispatchDraw(Canvas canvas) {
1011        // Draw the background below children.
1012        if (mBackgroundAlpha > 0.0f) {
1013            // Update the scroll position first to ensure scrim cutout is in the right place.
1014            mLauncher.getWorkspace().computeScrollWithoutInvalidation();
1015
1016            int alpha = (int) (mBackgroundAlpha * 255);
1017            CellLayout currCellLayout = mLauncher.getWorkspace().getCurrentDragOverlappingLayout();
1018            canvas.save();
1019            if (currCellLayout != null && currCellLayout != mLauncher.getHotseat().getLayout()) {
1020                // Cut a hole in the darkening scrim on the page that should be highlighted, if any.
1021                getDescendantRectRelativeToSelf(currCellLayout, mHighlightRect);
1022                canvas.clipRect(mHighlightRect, Region.Op.DIFFERENCE);
1023            }
1024            canvas.drawColor((alpha << 24) | SCRIM_COLOR);
1025            canvas.restore();
1026        }
1027
1028        mFocusIndicatorHelper.draw(canvas);
1029        super.dispatchDraw(canvas);
1030    }
1031
1032    private void drawPageHints(Canvas canvas) {
1033        if (mShowPageHints) {
1034            Workspace workspace = mLauncher.getWorkspace();
1035            int width = getMeasuredWidth();
1036            int page = workspace.getNextPage();
1037            CellLayout leftPage = (CellLayout) workspace.getChildAt(mIsRtl ? page + 1 : page - 1);
1038            CellLayout rightPage = (CellLayout) workspace.getChildAt(mIsRtl ? page - 1 : page + 1);
1039
1040            if (leftPage != null && leftPage.isDragTarget()) {
1041                Drawable left = mInScrollArea && leftPage.getIsDragOverlapping() ?
1042                        mLeftHoverDrawableActive : mLeftHoverDrawable;
1043                left.setBounds(0, mScrollChildPosition.top,
1044                        left.getIntrinsicWidth(), mScrollChildPosition.bottom);
1045                left.draw(canvas);
1046            }
1047            if (rightPage != null && rightPage.isDragTarget()) {
1048                Drawable right = mInScrollArea && rightPage.getIsDragOverlapping() ?
1049                        mRightHoverDrawableActive : mRightHoverDrawable;
1050                right.setBounds(width - right.getIntrinsicWidth(),
1051                        mScrollChildPosition.top, width, mScrollChildPosition.bottom);
1052                right.draw(canvas);
1053            }
1054        }
1055    }
1056
1057    protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
1058        boolean ret = super.drawChild(canvas, child, drawingTime);
1059
1060        // We want to draw the page hints above the workspace, but below the drag view.
1061        if (child instanceof Workspace) {
1062            drawPageHints(canvas);
1063        }
1064        return ret;
1065    }
1066
1067    public void setBackgroundAlpha(float alpha) {
1068        if (alpha != mBackgroundAlpha) {
1069            mBackgroundAlpha = alpha;
1070            invalidate();
1071        }
1072    }
1073
1074    public float getBackgroundAlpha() {
1075        return mBackgroundAlpha;
1076    }
1077
1078    @Override
1079    protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
1080        View topView = mLauncher.getTopFloatingView();
1081        if (topView != null) {
1082            return topView.requestFocus(direction, previouslyFocusedRect);
1083        } else {
1084            return super.onRequestFocusInDescendants(direction, previouslyFocusedRect);
1085        }
1086    }
1087
1088    @Override
1089    public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
1090        View topView = mLauncher.getTopFloatingView();
1091        if (topView != null) {
1092            topView.addFocusables(views, direction);
1093        } else {
1094            super.addFocusables(views, direction, focusableMode);
1095        }
1096    }
1097
1098    public void setTouchCompleteListener(TouchCompleteListener listener) {
1099        mTouchCompleteListener = listener;
1100    }
1101
1102    public interface TouchCompleteListener {
1103        public void onTouchComplete();
1104    }
1105}
1106