• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package com.android.launcher3.dragndrop;
18 
19 import android.animation.Animator;
20 import android.animation.AnimatorListenerAdapter;
21 import android.animation.TimeInterpolator;
22 import android.animation.ValueAnimator;
23 import android.animation.ValueAnimator.AnimatorUpdateListener;
24 import android.annotation.TargetApi;
25 import android.content.ClipDescription;
26 import android.content.Context;
27 import android.content.Intent;
28 import android.content.res.Resources;
29 import android.graphics.Bitmap;
30 import android.graphics.Bitmap.Config;
31 import android.graphics.Canvas;
32 import android.graphics.Color;
33 import android.graphics.Point;
34 import android.graphics.Rect;
35 import android.graphics.Region;
36 import android.graphics.drawable.Drawable;
37 import android.os.Build;
38 import android.util.AttributeSet;
39 import android.util.Log;
40 import android.view.DragEvent;
41 import android.view.KeyEvent;
42 import android.view.MotionEvent;
43 import android.view.View;
44 import android.view.ViewGroup;
45 import android.view.accessibility.AccessibilityEvent;
46 import android.view.accessibility.AccessibilityManager;
47 import android.view.animation.DecelerateInterpolator;
48 import android.view.animation.Interpolator;
49 import android.widget.FrameLayout;
50 import android.widget.TextView;
51 
52 import com.android.launcher3.AppWidgetResizeFrame;
53 import com.android.launcher3.CellLayout;
54 import com.android.launcher3.DropTargetBar;
55 import com.android.launcher3.InsettableFrameLayout;
56 import com.android.launcher3.InstallShortcutReceiver;
57 import com.android.launcher3.ItemInfo;
58 import com.android.launcher3.Launcher;
59 import com.android.launcher3.LauncherAppWidgetHostView;
60 import com.android.launcher3.PinchToOverviewListener;
61 import com.android.launcher3.R;
62 import com.android.launcher3.ShortcutAndWidgetContainer;
63 import com.android.launcher3.ShortcutInfo;
64 import com.android.launcher3.Utilities;
65 import com.android.launcher3.Workspace;
66 import com.android.launcher3.allapps.AllAppsTransitionController;
67 import com.android.launcher3.config.FeatureFlags;
68 import com.android.launcher3.folder.Folder;
69 import com.android.launcher3.folder.FolderIcon;
70 import com.android.launcher3.keyboard.ViewGroupFocusHelper;
71 import com.android.launcher3.shortcuts.DeepShortcutsContainer;
72 import com.android.launcher3.util.Thunk;
73 import com.android.launcher3.util.TouchController;
74 
75 import java.net.URISyntaxException;
76 import java.util.ArrayList;
77 
78 /**
79  * A ViewGroup that coordinates dragging across its descendants
80  */
81 public 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      */
DragLayer(Context context, AttributeSet attrs)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 
setup(Launcher launcher, DragController dragController, AllAppsTransitionController allAppsTransitionController)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 
getFocusIndicatorHelper()171     public ViewGroupFocusHelper getFocusIndicatorHelper() {
172         return mFocusIndicatorHelper;
173     }
174 
175     @Override
dispatchKeyEvent(KeyEvent event)176     public boolean dispatchKeyEvent(KeyEvent event) {
177         return mDragController.dispatchKeyEvent(event) || super.dispatchKeyEvent(event);
178     }
179 
onAccessibilityStateChanged(boolean isAccessibilityEnabled)180     public void onAccessibilityStateChanged(boolean isAccessibilityEnabled) {
181         mPinchListener = FeatureFlags.LAUNCHER3_DISABLE_PINCH_TO_OVERVIEW || isAccessibilityEnabled
182                 ? null : new PinchToOverviewListener(mLauncher);
183     }
184 
isEventOverPageIndicator(MotionEvent ev)185     public boolean isEventOverPageIndicator(MotionEvent ev) {
186         getDescendantRectRelativeToSelf(mLauncher.getWorkspace().getPageIndicator(), mHitRect);
187         return mHitRect.contains((int) ev.getX(), (int) ev.getY());
188     }
189 
isEventOverHotseat(MotionEvent ev)190     public boolean isEventOverHotseat(MotionEvent ev) {
191         return isEventOverView(mLauncher.getHotseat(), ev);
192     }
193 
isEventOverFolderTextRegion(Folder folder, MotionEvent ev)194     private boolean isEventOverFolderTextRegion(Folder folder, MotionEvent ev) {
195         return isEventOverView(folder.getEditTextRegion(), ev);
196     }
197 
isEventOverFolder(Folder folder, MotionEvent ev)198     private boolean isEventOverFolder(Folder folder, MotionEvent ev) {
199         return isEventOverView(folder, ev);
200     }
201 
isEventOverDropTargetBar(MotionEvent ev)202     private boolean isEventOverDropTargetBar(MotionEvent ev) {
203         return isEventOverView(mLauncher.getDropTargetBar(), ev);
204     }
205 
isEventOverView(View view, MotionEvent ev)206     public boolean isEventOverView(View view, MotionEvent ev) {
207         getDescendantRectRelativeToSelf(view, mHitRect);
208         return mHitRect.contains((int) ev.getX(), (int) ev.getY());
209     }
210 
handleTouchDown(MotionEvent ev, boolean intercept)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
onInterceptTouchEvent(MotionEvent ev)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
onInterceptHoverEvent(MotionEvent ev)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 
sendTapOutsideFolderAccessibilityEvent(boolean isEditingName)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 
isInAccessibleDrag()362     private boolean isInAccessibleDrag() {
363         return mLauncher.getAccessibilityDelegate().isInAccessibleDrag();
364     }
365 
366     @Override
onRequestSendAccessibilityEvent(View child, AccessibilityEvent event)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
addChildrenForAccessibility(ArrayList<View> childrenForAccessibility)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
onHoverEvent(MotionEvent ev)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
onTouchEvent(MotionEvent ev)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)
handleSystemDragStart(DragEvent event)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
onDragEvent(DragEvent event)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      */
getDescendantRectRelativeToSelf(View descendant, Rect r)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 
getLocationInDragLayer(View child, int[] loc)506     public float getLocationInDragLayer(View child, int[] loc) {
507         loc[0] = 0;
508         loc[1] = 0;
509         return getDescendantCoordRelativeToSelf(child, loc);
510     }
511 
getDescendantCoordRelativeToSelf(View descendant, int[] coord)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      */
getDescendantCoordRelativeToSelf(View descendant, int[] coord, boolean includeRootScroll)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      */
mapCoordInSelfToDescendent(View descendant, int[] coord)537     public float mapCoordInSelfToDescendent(View descendant, int[] coord) {
538         return Utilities.mapCoordInSelfToDescendent(descendant, this, coord);
539     }
540 
getViewRectRelativeToSelf(View v, Rect r)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
dispatchUnhandledMove(View focused, int direction)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
generateLayoutParams(AttributeSet attrs)564     public LayoutParams generateLayoutParams(AttributeSet attrs) {
565         return new LayoutParams(getContext(), attrs);
566     }
567 
568     @Override
generateDefaultLayoutParams()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
checkLayoutParams(ViewGroup.LayoutParams p)575     protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
576         return p instanceof LayoutParams;
577     }
578 
579     @Override
generateLayoutParams(ViewGroup.LayoutParams p)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 
LayoutParams(Context c, AttributeSet attrs)588         public LayoutParams(Context c, AttributeSet attrs) {
589             super(c, attrs);
590         }
591 
LayoutParams(int width, int height)592         public LayoutParams(int width, int height) {
593             super(width, height);
594         }
595 
LayoutParams(ViewGroup.LayoutParams lp)596         public LayoutParams(ViewGroup.LayoutParams lp) {
597             super(lp);
598         }
599 
setWidth(int width)600         public void setWidth(int width) {
601             this.width = width;
602         }
603 
getWidth()604         public int getWidth() {
605             return width;
606         }
607 
setHeight(int height)608         public void setHeight(int height) {
609             this.height = height;
610         }
611 
getHeight()612         public int getHeight() {
613             return height;
614         }
615 
setX(int x)616         public void setX(int x) {
617             this.x = x;
618         }
619 
getX()620         public int getX() {
621             return x;
622         }
623 
setY(int y)624         public void setY(int y) {
625             this.y = y;
626         }
627 
getY()628         public int getY() {
629             return y;
630         }
631     }
632 
onLayout(boolean changed, int l, int t, int r, int b)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 
clearAllResizeFrames()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 
hasResizeFrames()658     public boolean hasResizeFrames() {
659         return mResizeFrames.size() > 0;
660     }
661 
isWidgetBeingResized()662     public boolean isWidgetBeingResized() {
663         return mCurrentResizeFrame != null;
664     }
665 
addResizeFrame(ItemInfo itemInfo, LauncherAppWidgetHostView widget, CellLayout cellLayout)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 
animateViewIntoPosition(DragView dragView, final int[] pos, float alpha, float scaleX, float scaleY, int animationEndStyle, Runnable onFinishRunnable, int duration)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 
animateViewIntoPosition(DragView dragView, final View child, final Runnable onFinishAnimationRunnable, View anchorView)692     public void animateViewIntoPosition(DragView dragView, final View child,
693             final Runnable onFinishAnimationRunnable, View anchorView) {
694         animateViewIntoPosition(dragView, child, -1, onFinishAnimationRunnable, anchorView);
695     }
696 
animateViewIntoPosition(DragView dragView, final View child, int duration, final Runnable onFinishAnimationRunnable, View anchorView)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 
animateViewIntoPosition(final DragView view, final int fromX, final int fromY, final int toX, final int toY, float finalAlpha, float initScaleX, float initScaleY, float finalScaleX, float finalScaleY, Runnable onCompleteRunnable, int animationEndStyle, int duration, View anchorView)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      */
animateView(final DragView view, final Rect from, final Rect to, final float finalAlpha, final float initScaleX, final float initScaleY, final float finalScaleX, final float finalScaleY, int duration, final Interpolator motionInterpolator, final Interpolator alphaInterpolator, final Runnable onCompleteRunnable, final int animationEndStyle, View anchorView)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 
animateView(final DragView view, AnimatorUpdateListener updateCb, int duration, TimeInterpolator interpolator, final Runnable onCompleteRunnable, final int animationEndStyle, View anchorView)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 
clearAnimatedView()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 
getAnimatedView()922     public View getAnimatedView() {
923         return mDropView;
924     }
925 
926     @Override
onChildViewAdded(View parent, View child)927     public void onChildViewAdded(View parent, View child) {
928         super.onChildViewAdded(parent, child);
929         updateChildIndices();
930     }
931 
932     @Override
onChildViewRemoved(View parent, View child)933     public void onChildViewRemoved(View parent, View child) {
934         updateChildIndices();
935     }
936 
937     @Override
bringChildToFront(View child)938     public void bringChildToFront(View child) {
939         super.bringChildToFront(child);
940         updateChildIndices();
941     }
942 
updateChildIndices()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
getChildDrawingOrder(int childCount, int i)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 
onEnterScrollArea()980     void onEnterScrollArea() {
981         mInScrollArea = true;
982         invalidate();
983     }
984 
onExitScrollArea()985     void onExitScrollArea() {
986         mInScrollArea = false;
987         invalidate();
988     }
989 
showPageHints()990     public void showPageHints() {
991         mShowPageHints = true;
992         Workspace workspace = mLauncher.getWorkspace();
993         getDescendantRectRelativeToSelf(workspace.getChildAt(workspace.numCustomPages()),
994                 mScrollChildPosition);
995         invalidate();
996     }
997 
hidePageHints()998     public void hidePageHints() {
999         mShowPageHints = false;
1000         invalidate();
1001     }
1002 
invalidateScrim()1003     public void invalidateScrim() {
1004         if (mBackgroundAlpha > 0.0f) {
1005             invalidate();
1006         }
1007     }
1008 
1009     @Override
dispatchDraw(Canvas canvas)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 
drawPageHints(Canvas canvas)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 
drawChild(Canvas canvas, View child, long drawingTime)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 
setBackgroundAlpha(float alpha)1067     public void setBackgroundAlpha(float alpha) {
1068         if (alpha != mBackgroundAlpha) {
1069             mBackgroundAlpha = alpha;
1070             invalidate();
1071         }
1072     }
1073 
getBackgroundAlpha()1074     public float getBackgroundAlpha() {
1075         return mBackgroundAlpha;
1076     }
1077 
1078     @Override
onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect)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
addFocusables(ArrayList<View> views, int direction, int focusableMode)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 
setTouchCompleteListener(TouchCompleteListener listener)1098     public void setTouchCompleteListener(TouchCompleteListener listener) {
1099         mTouchCompleteListener = listener;
1100     }
1101 
1102     public interface TouchCompleteListener {
onTouchComplete()1103         public void onTouchComplete();
1104     }
1105 }
1106