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