1 /* 2 * Copyright (C) 2019 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 package com.android.launcher3.views; 17 18 import static com.android.launcher3.Utilities.getBadge; 19 import static com.android.launcher3.Utilities.getFullDrawable; 20 import static com.android.launcher3.config.FeatureFlags.ADAPTIVE_ICON_WINDOW_ANIM; 21 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; 22 import static com.android.launcher3.views.IconLabelDotView.setIconAndDotVisible; 23 24 import android.animation.Animator; 25 import android.annotation.TargetApi; 26 import android.content.Context; 27 import android.graphics.Canvas; 28 import android.graphics.Rect; 29 import android.graphics.RectF; 30 import android.graphics.drawable.AdaptiveIconDrawable; 31 import android.graphics.drawable.Drawable; 32 import android.os.Build; 33 import android.os.CancellationSignal; 34 import android.util.AttributeSet; 35 import android.util.Log; 36 import android.view.View; 37 import android.view.ViewGroup; 38 import android.view.ViewTreeObserver.OnGlobalLayoutListener; 39 import android.widget.FrameLayout; 40 import android.widget.ImageView; 41 42 import androidx.annotation.Nullable; 43 import androidx.annotation.UiThread; 44 import androidx.annotation.WorkerThread; 45 46 import com.android.launcher3.BubbleTextView; 47 import com.android.launcher3.InsettableFrameLayout; 48 import com.android.launcher3.Launcher; 49 import com.android.launcher3.R; 50 import com.android.launcher3.Utilities; 51 import com.android.launcher3.dragndrop.DragLayer; 52 import com.android.launcher3.dragndrop.FolderAdaptiveIcon; 53 import com.android.launcher3.folder.FolderIcon; 54 import com.android.launcher3.graphics.PreloadIconDrawable; 55 import com.android.launcher3.icons.FastBitmapDrawable; 56 import com.android.launcher3.icons.LauncherIcons; 57 import com.android.launcher3.model.data.ItemInfo; 58 import com.android.launcher3.model.data.ItemInfoWithIcon; 59 import com.android.launcher3.popup.SystemShortcut; 60 import com.android.launcher3.shortcuts.DeepShortcutView; 61 62 /** 63 * A view that is created to look like another view with the purpose of creating fluid animations. 64 */ 65 @TargetApi(Build.VERSION_CODES.Q) 66 public class FloatingIconView extends FrameLayout implements 67 Animator.AnimatorListener, OnGlobalLayoutListener, FloatingView { 68 69 private static final String TAG = FloatingIconView.class.getSimpleName(); 70 71 // Manages loading the icon on a worker thread 72 private static @Nullable IconLoadResult sIconLoadResult; 73 private static long sFetchIconId = 0; 74 private static long sRecycledFetchIconId = sFetchIconId; 75 76 public static final float SHAPE_PROGRESS_DURATION = 0.10f; 77 private static final RectF sTmpRectF = new RectF(); 78 private static final Object[] sTmpObjArray = new Object[1]; 79 80 private Runnable mEndRunnable; 81 private CancellationSignal mLoadIconSignal; 82 83 private final Launcher mLauncher; 84 private final boolean mIsRtl; 85 86 private boolean mIsVerticalBarLayout = false; 87 private boolean mIsOpening; 88 89 private IconLoadResult mIconLoadResult; 90 91 private View mBtvDrawable; 92 93 private ClipIconView mClipIconView; 94 private @Nullable Drawable mBadge; 95 96 private View mOriginalIcon; 97 private RectF mPositionOut; 98 private Runnable mOnTargetChangeRunnable; 99 100 private final Rect mFinalDrawableBounds = new Rect(); 101 102 private ListenerView mListenerView; 103 private Runnable mFastFinishRunnable; 104 105 private float mIconOffsetY; 106 FloatingIconView(Context context)107 public FloatingIconView(Context context) { 108 this(context, null); 109 } 110 FloatingIconView(Context context, AttributeSet attrs)111 public FloatingIconView(Context context, AttributeSet attrs) { 112 this(context, attrs, 0); 113 } 114 FloatingIconView(Context context, AttributeSet attrs, int defStyleAttr)115 public FloatingIconView(Context context, AttributeSet attrs, int defStyleAttr) { 116 super(context, attrs, defStyleAttr); 117 mLauncher = Launcher.getLauncher(context); 118 mIsRtl = Utilities.isRtl(getResources()); 119 mListenerView = new ListenerView(context, attrs); 120 mClipIconView = new ClipIconView(context, attrs); 121 mBtvDrawable = new ImageView(context, attrs); 122 addView(mBtvDrawable); 123 addView(mClipIconView); 124 setWillNotDraw(false); 125 } 126 127 @Override onAttachedToWindow()128 protected void onAttachedToWindow() { 129 super.onAttachedToWindow(); 130 if (!mIsOpening) { 131 getViewTreeObserver().addOnGlobalLayoutListener(this); 132 } 133 } 134 135 @Override onDetachedFromWindow()136 protected void onDetachedFromWindow() { 137 getViewTreeObserver().removeOnGlobalLayoutListener(this); 138 super.onDetachedFromWindow(); 139 } 140 141 /** 142 * Positions this view to match the size and location of {@param rect}. 143 * @param alpha The alpha[0, 1] of the entire floating view. 144 * @param fgIconAlpha The alpha[0-255] of the foreground layer of the icon (if applicable). 145 * @param progress A value from [0, 1] that represents the animation progress. 146 * @param shapeProgressStart The progress value at which to start the shape reveal. 147 * @param cornerRadius The corner radius of {@param rect}. 148 * @param isOpening True if view is used for app open animation, false for app close animation. 149 */ update(float alpha, int fgIconAlpha, RectF rect, float progress, float shapeProgressStart, float cornerRadius, boolean isOpening)150 public void update(float alpha, int fgIconAlpha, RectF rect, float progress, 151 float shapeProgressStart, float cornerRadius, boolean isOpening) { 152 setAlpha(alpha); 153 mClipIconView.update(rect, progress, shapeProgressStart, cornerRadius, fgIconAlpha, 154 isOpening, this, mLauncher.getDeviceProfile(), mIsVerticalBarLayout); 155 } 156 157 @Override onAnimationEnd(Animator animator)158 public void onAnimationEnd(Animator animator) { 159 if (mLoadIconSignal != null) { 160 mLoadIconSignal.cancel(); 161 } 162 if (mEndRunnable != null) { 163 mEndRunnable.run(); 164 } else { 165 // End runnable also ends the reveal animator, so we manually handle it here. 166 mClipIconView.endReveal(); 167 } 168 } 169 170 /** 171 * Sets the size and position of this view to match {@param v}. 172 * 173 * @param v The view to copy 174 * @param positionOut Rect that will hold the size and position of v. 175 */ matchPositionOf(Launcher launcher, View v, boolean isOpening, RectF positionOut)176 private void matchPositionOf(Launcher launcher, View v, boolean isOpening, RectF positionOut) { 177 getLocationBoundsForView(launcher, v, isOpening, positionOut); 178 final InsettableFrameLayout.LayoutParams lp = new InsettableFrameLayout.LayoutParams( 179 Math.round(positionOut.width()), 180 Math.round(positionOut.height())); 181 updatePosition(positionOut, lp); 182 setLayoutParams(lp); 183 184 mClipIconView.setLayoutParams(new FrameLayout.LayoutParams(lp.width, lp.height)); 185 mBtvDrawable.setLayoutParams(new FrameLayout.LayoutParams(lp.width, lp.height)); 186 } 187 updatePosition(RectF pos, InsettableFrameLayout.LayoutParams lp)188 private void updatePosition(RectF pos, InsettableFrameLayout.LayoutParams lp) { 189 mPositionOut.set(pos); 190 lp.ignoreInsets = true; 191 // Position the floating view exactly on top of the original 192 lp.topMargin = Math.round(pos.top); 193 if (mIsRtl) { 194 lp.setMarginStart(Math.round(mLauncher.getDeviceProfile().widthPx - pos.right)); 195 } else { 196 lp.setMarginStart(Math.round(pos.left)); 197 } 198 // Set the properties here already to make sure they are available when running the first 199 // animation frame. 200 int left = mIsRtl 201 ? mLauncher.getDeviceProfile().widthPx - lp.getMarginStart() - lp.width 202 : lp.leftMargin; 203 layout(left, lp.topMargin, left + lp.width, lp.topMargin + lp.height); 204 } 205 getLocationBoundsForView(Launcher launcher, View v, boolean isOpening, RectF outRect)206 private static void getLocationBoundsForView(Launcher launcher, View v, boolean isOpening, 207 RectF outRect) { 208 getLocationBoundsForView(launcher, v, isOpening, outRect, new Rect()); 209 } 210 211 /** 212 * Gets the location bounds of a view and returns the overall rotation. 213 * - For DeepShortcutView, we return the bounds of the icon view. 214 * - For BubbleTextView, we return the icon bounds. 215 */ getLocationBoundsForView(Launcher launcher, View v, boolean isOpening, RectF outRect, Rect outViewBounds)216 public static void getLocationBoundsForView(Launcher launcher, View v, boolean isOpening, 217 RectF outRect, Rect outViewBounds) { 218 boolean ignoreTransform = !isOpening; 219 if (v instanceof BubbleTextHolder) { 220 v = ((BubbleTextHolder) v).getBubbleText(); 221 ignoreTransform = false; 222 } else if (v.getParent() instanceof DeepShortcutView) { 223 v = ((DeepShortcutView) v.getParent()).getIconView(); 224 ignoreTransform = false; 225 } 226 if (v == null) { 227 return; 228 } 229 230 if (v instanceof BubbleTextView) { 231 ((BubbleTextView) v).getIconBounds(outViewBounds); 232 } else if (v instanceof FolderIcon) { 233 ((FolderIcon) v).getPreviewBounds(outViewBounds); 234 } else { 235 outViewBounds.set(0, 0, v.getWidth(), v.getHeight()); 236 } 237 238 Utilities.getBoundsForViewInDragLayer(launcher.getDragLayer(), v, outViewBounds, 239 ignoreTransform, null /** recycle */, outRect); 240 } 241 242 /** 243 * Loads the icon and saves the results to {@link #sIconLoadResult}. 244 * Runs onIconLoaded callback (if any), which signifies that the FloatingIconView is 245 * ready to display the icon. Otherwise, the FloatingIconView will grab the results when its 246 * initialized. 247 * 248 * @param originalView The View that the FloatingIconView will replace. 249 * @param info ItemInfo of the originalView 250 * @param pos The position of the view. 251 */ 252 @WorkerThread 253 @SuppressWarnings("WrongThread") getIconResult(Launcher l, View originalView, ItemInfo info, RectF pos, Drawable btvIcon, IconLoadResult iconLoadResult)254 private static void getIconResult(Launcher l, View originalView, ItemInfo info, RectF pos, 255 Drawable btvIcon, IconLoadResult iconLoadResult) { 256 Drawable drawable; 257 boolean supportsAdaptiveIcons = ADAPTIVE_ICON_WINDOW_ANIM.get() 258 && !info.isDisabled(); // Use original icon for disabled icons. 259 260 Drawable badge = null; 261 if (info instanceof SystemShortcut) { 262 if (originalView instanceof ImageView) { 263 drawable = ((ImageView) originalView).getDrawable(); 264 } else if (originalView instanceof DeepShortcutView) { 265 drawable = ((DeepShortcutView) originalView).getIconView().getBackground(); 266 } else { 267 drawable = originalView.getBackground(); 268 } 269 } else if (btvIcon instanceof PreloadIconDrawable) { 270 // Force the progress bar to display. 271 drawable = btvIcon; 272 } else { 273 int width = (int) pos.width(); 274 int height = (int) pos.height(); 275 if (supportsAdaptiveIcons) { 276 drawable = getFullDrawable(l, info, width, height, sTmpObjArray); 277 if (drawable instanceof AdaptiveIconDrawable) { 278 badge = getBadge(l, info, sTmpObjArray[0]); 279 } else { 280 // The drawable we get back is not an adaptive icon, so we need to use the 281 // BubbleTextView icon that is already legacy treated. 282 drawable = btvIcon; 283 } 284 } else { 285 if (originalView instanceof BubbleTextView) { 286 // Similar to DragView, we simply use the BubbleTextView icon here. 287 drawable = btvIcon; 288 } else { 289 drawable = getFullDrawable(l, info, width, height, sTmpObjArray); 290 } 291 } 292 } 293 294 drawable = drawable == null ? null : drawable.getConstantState().newDrawable(); 295 int iconOffset = getOffsetForIconBounds(l, drawable, pos); 296 synchronized (iconLoadResult) { 297 iconLoadResult.btvDrawable = btvIcon == null || drawable == btvIcon 298 ? null : btvIcon.getConstantState().newDrawable(); 299 iconLoadResult.drawable = drawable; 300 iconLoadResult.badge = badge; 301 iconLoadResult.iconOffset = iconOffset; 302 if (iconLoadResult.onIconLoaded != null) { 303 l.getMainExecutor().execute(iconLoadResult.onIconLoaded); 304 iconLoadResult.onIconLoaded = null; 305 } 306 iconLoadResult.isIconLoaded = true; 307 } 308 } 309 310 /** 311 * Sets the drawables of the {@param originalView} onto this view. 312 * 313 * @param drawable The drawable of the original view. 314 * @param badge The badge of the original view. 315 * @param iconOffset The amount of offset needed to match this view with the original view. 316 */ 317 @UiThread setIcon(@ullable Drawable drawable, @Nullable Drawable badge, @Nullable Drawable btvIcon, int iconOffset)318 private void setIcon(@Nullable Drawable drawable, @Nullable Drawable badge, 319 @Nullable Drawable btvIcon, int iconOffset) { 320 final InsettableFrameLayout.LayoutParams lp = 321 (InsettableFrameLayout.LayoutParams) getLayoutParams(); 322 mBadge = badge; 323 mClipIconView.setIcon(drawable, iconOffset, lp, mIsOpening, mIsVerticalBarLayout, 324 mLauncher.getDeviceProfile()); 325 if (drawable instanceof AdaptiveIconDrawable) { 326 final int originalHeight = lp.height; 327 final int originalWidth = lp.width; 328 329 mFinalDrawableBounds.set(0, 0, originalWidth, originalHeight); 330 331 float aspectRatio = mLauncher.getDeviceProfile().aspectRatio; 332 if (mIsVerticalBarLayout) { 333 lp.width = (int) Math.max(lp.width, lp.height * aspectRatio); 334 } else { 335 lp.height = (int) Math.max(lp.height, lp.width * aspectRatio); 336 } 337 setLayoutParams(lp); 338 339 final LayoutParams clipViewLp = (LayoutParams) mClipIconView.getLayoutParams(); 340 final int clipViewOgHeight = clipViewLp.height; 341 final int clipViewOgWidth = clipViewLp.width; 342 clipViewLp.width = lp.width; 343 clipViewLp.height = lp.height; 344 mClipIconView.setLayoutParams(clipViewLp); 345 346 if (mBadge != null) { 347 mBadge.setBounds(0, 0, clipViewOgWidth, clipViewOgHeight); 348 } 349 } 350 351 setOriginalDrawableBackground(btvIcon); 352 invalidate(); 353 } 354 355 /** 356 * Draws the drawable of the BubbleTextView behind ClipIconView 357 * 358 * This is used to: 359 * - Have icon displayed while Adaptive Icon is loading 360 * - Displays the built in shadow to ensure a clean handoff 361 * 362 * Allows nullable as this may be cleared when drawing is deferred to ClipIconView. 363 */ setOriginalDrawableBackground(@ullable Drawable btvIcon)364 private void setOriginalDrawableBackground(@Nullable Drawable btvIcon) { 365 if (!mIsOpening) { 366 mBtvDrawable.setBackground(btvIcon); 367 } 368 } 369 370 /** 371 * Returns true if the icon is different from main app icon 372 */ isDifferentFromAppIcon()373 public boolean isDifferentFromAppIcon() { 374 return mIconLoadResult == null ? false : mIconLoadResult.isThemed; 375 } 376 377 /** 378 * Checks if the icon result is loaded. If true, we set the icon immediately. Else, we add a 379 * callback to set the icon once the icon result is loaded. 380 */ checkIconResult(View originalView)381 private void checkIconResult(View originalView) { 382 CancellationSignal cancellationSignal = new CancellationSignal(); 383 384 if (mIconLoadResult == null) { 385 Log.w(TAG, "No icon load result found in checkIconResult"); 386 return; 387 } 388 389 synchronized (mIconLoadResult) { 390 if (mIconLoadResult.isIconLoaded) { 391 setIcon(mIconLoadResult.drawable, mIconLoadResult.badge, 392 mIconLoadResult.btvDrawable, mIconLoadResult.iconOffset); 393 setVisibility(VISIBLE); 394 setIconAndDotVisible(originalView, false); 395 } else { 396 mIconLoadResult.onIconLoaded = () -> { 397 if (cancellationSignal.isCanceled()) { 398 return; 399 } 400 401 setIcon(mIconLoadResult.drawable, mIconLoadResult.badge, 402 mIconLoadResult.btvDrawable, mIconLoadResult.iconOffset); 403 404 setVisibility(VISIBLE); 405 setIconAndDotVisible(originalView, false); 406 }; 407 mLoadIconSignal = cancellationSignal; 408 } 409 } 410 } 411 412 @WorkerThread 413 @SuppressWarnings("WrongThread") getOffsetForIconBounds(Launcher l, Drawable drawable, RectF position)414 private static int getOffsetForIconBounds(Launcher l, Drawable drawable, RectF position) { 415 if (!(drawable instanceof AdaptiveIconDrawable) 416 || (drawable instanceof FolderAdaptiveIcon)) { 417 return 0; 418 } 419 int blurSizeOutline = 420 l.getResources().getDimensionPixelSize(R.dimen.blur_size_medium_outline); 421 422 Rect bounds = new Rect(0, 0, (int) position.width() + blurSizeOutline, 423 (int) position.height() + blurSizeOutline); 424 bounds.inset(blurSizeOutline / 2, blurSizeOutline / 2); 425 426 try (LauncherIcons li = LauncherIcons.obtain(l)) { 427 Utilities.scaleRectAboutCenter(bounds, li.getNormalizer().getScale(drawable, null, 428 null, null)); 429 } 430 431 bounds.inset( 432 (int) (-bounds.width() * AdaptiveIconDrawable.getExtraInsetFraction()), 433 (int) (-bounds.height() * AdaptiveIconDrawable.getExtraInsetFraction()) 434 ); 435 436 return bounds.left; 437 } 438 439 @Override dispatchDraw(Canvas canvas)440 protected void dispatchDraw(Canvas canvas) { 441 super.dispatchDraw(canvas); 442 if (mBadge != null) { 443 mBadge.draw(canvas); 444 } 445 } 446 447 /** 448 * Sets a runnable that is called after a call to {@link #fastFinish()}. 449 */ setFastFinishRunnable(Runnable runnable)450 public void setFastFinishRunnable(Runnable runnable) { 451 mFastFinishRunnable = runnable; 452 } 453 454 @Override fastFinish()455 public void fastFinish() { 456 if (mFastFinishRunnable != null) { 457 mFastFinishRunnable.run(); 458 mFastFinishRunnable = null; 459 } 460 if (mLoadIconSignal != null) { 461 mLoadIconSignal.cancel(); 462 mLoadIconSignal = null; 463 } 464 if (mEndRunnable != null) { 465 mEndRunnable.run(); 466 mEndRunnable = null; 467 } 468 } 469 470 @Override onAnimationStart(Animator animator)471 public void onAnimationStart(Animator animator) { 472 if ((mIconLoadResult != null && mIconLoadResult.isIconLoaded) 473 || (!mIsOpening && mBtvDrawable.getBackground() != null)) { 474 // No need to wait for icon load since we can display the BubbleTextView drawable. 475 setVisibility(View.VISIBLE); 476 } 477 if (!mIsOpening && mOriginalIcon != null) { 478 // When closing an app, we want the item on the workspace to be invisible immediately 479 setIconAndDotVisible(mOriginalIcon, false); 480 } 481 } 482 483 @Override onAnimationCancel(Animator animator)484 public void onAnimationCancel(Animator animator) {} 485 486 @Override onAnimationRepeat(Animator animator)487 public void onAnimationRepeat(Animator animator) {} 488 489 @Override setPositionOffsetY(float y)490 public void setPositionOffsetY(float y) { 491 mIconOffsetY = y; 492 onGlobalLayout(); 493 } 494 495 @Override onGlobalLayout()496 public void onGlobalLayout() { 497 if (mOriginalIcon != null && mOriginalIcon.isAttachedToWindow() && mPositionOut != null) { 498 getLocationBoundsForView(mLauncher, mOriginalIcon, mIsOpening, sTmpRectF); 499 sTmpRectF.offset(0, mIconOffsetY); 500 if (!sTmpRectF.equals(mPositionOut)) { 501 updatePosition(sTmpRectF, (InsettableFrameLayout.LayoutParams) getLayoutParams()); 502 if (mOnTargetChangeRunnable != null) { 503 mOnTargetChangeRunnable.run(); 504 } 505 } 506 } 507 } 508 setOnTargetChangeListener(Runnable onTargetChangeListener)509 public void setOnTargetChangeListener(Runnable onTargetChangeListener) { 510 mOnTargetChangeRunnable = onTargetChangeListener; 511 } 512 513 /** 514 * Loads the icon drawable on a worker thread to reduce latency between swapping views. 515 */ 516 @UiThread fetchIcon(Launcher l, View v, ItemInfo info, boolean isOpening)517 public static IconLoadResult fetchIcon(Launcher l, View v, ItemInfo info, boolean isOpening) { 518 RectF position = new RectF(); 519 getLocationBoundsForView(l, v, isOpening, position); 520 521 final FastBitmapDrawable btvIcon; 522 if (v instanceof BubbleTextView) { 523 BubbleTextView btv = (BubbleTextView) v; 524 if (info instanceof ItemInfoWithIcon 525 && (((ItemInfoWithIcon) info).runtimeStatusFlags 526 & ItemInfoWithIcon.FLAG_SHOW_DOWNLOAD_PROGRESS_MASK) != 0) { 527 btvIcon = btv.makePreloadIcon(); 528 } else { 529 btvIcon = btv.getIcon(); 530 } 531 } else { 532 btvIcon = null; 533 } 534 535 IconLoadResult result = new IconLoadResult(info, 536 btvIcon == null ? false : btvIcon.isThemed()); 537 result.btvDrawable = btvIcon; 538 539 final long fetchIconId = sFetchIconId++; 540 MODEL_EXECUTOR.getHandler().postAtFrontOfQueue(() -> { 541 if (fetchIconId < sRecycledFetchIconId) { 542 return; 543 } 544 getIconResult(l, v, info, position, btvIcon, result); 545 }); 546 547 sIconLoadResult = result; 548 return result; 549 } 550 551 /** 552 * Creates a floating icon view for {@param originalView}. 553 * @param originalView The view to copy 554 * @param hideOriginal If true, it will hide {@param originalView} while this view is visible. 555 * Else, we will not draw anything in this view. 556 * @param positionOut Rect that will hold the size and position of v. 557 * @param isOpening True if this view replaces the icon for app open animation. 558 */ getFloatingIconView(Launcher launcher, View originalView, boolean hideOriginal, RectF positionOut, boolean isOpening)559 public static FloatingIconView getFloatingIconView(Launcher launcher, View originalView, 560 boolean hideOriginal, RectF positionOut, boolean isOpening) { 561 final DragLayer dragLayer = launcher.getDragLayer(); 562 ViewGroup parent = (ViewGroup) dragLayer.getParent(); 563 FloatingIconView view = launcher.getViewCache().getView(R.layout.floating_icon_view, 564 launcher, parent); 565 view.recycle(); 566 567 // Get the drawable on the background thread 568 boolean shouldLoadIcon = originalView.getTag() instanceof ItemInfo && hideOriginal; 569 if (shouldLoadIcon) { 570 if (sIconLoadResult != null && sIconLoadResult.itemInfo == originalView.getTag()) { 571 view.mIconLoadResult = sIconLoadResult; 572 } else { 573 view.mIconLoadResult = fetchIcon(launcher, originalView, 574 (ItemInfo) originalView.getTag(), isOpening); 575 } 576 view.setOriginalDrawableBackground(view.mIconLoadResult.btvDrawable); 577 } 578 sIconLoadResult = null; 579 580 view.mIsVerticalBarLayout = launcher.getDeviceProfile().isVerticalBarLayout(); 581 view.mIsOpening = isOpening; 582 view.mOriginalIcon = originalView; 583 view.mPositionOut = positionOut; 584 585 // Match the position of the original view. 586 view.matchPositionOf(launcher, originalView, isOpening, positionOut); 587 588 // We need to add it to the overlay, but keep it invisible until animation starts.. 589 view.setVisibility(INVISIBLE); 590 parent.addView(view); 591 dragLayer.addView(view.mListenerView); 592 view.mListenerView.setListener(view::fastFinish); 593 594 view.mEndRunnable = () -> { 595 view.mEndRunnable = null; 596 597 if (hideOriginal) { 598 if (isOpening) { 599 setIconAndDotVisible(originalView, true); 600 view.finish(dragLayer); 601 } else { 602 originalView.setVisibility(VISIBLE); 603 if (originalView instanceof IconLabelDotView) { 604 setIconAndDotVisible(originalView, true); 605 } 606 view.finish(dragLayer); 607 } 608 } else { 609 view.finish(dragLayer); 610 } 611 }; 612 613 // Must be called after matchPositionOf so that we know what size to load. 614 // Must be called after the fastFinish listener and end runnable is created so that 615 // the icon is not left in a hidden state. 616 if (shouldLoadIcon) { 617 view.checkIconResult(originalView); 618 } 619 620 return view; 621 } 622 finish(DragLayer dragLayer)623 private void finish(DragLayer dragLayer) { 624 ((ViewGroup) dragLayer.getParent()).removeView(this); 625 dragLayer.removeView(mListenerView); 626 recycle(); 627 mLauncher.getViewCache().recycleView(R.layout.floating_icon_view, this); 628 } 629 recycle()630 private void recycle() { 631 setTranslationX(0); 632 setTranslationY(0); 633 setScaleX(1); 634 setScaleY(1); 635 setAlpha(1); 636 if (mLoadIconSignal != null) { 637 mLoadIconSignal.cancel(); 638 } 639 mLoadIconSignal = null; 640 mEndRunnable = null; 641 mFinalDrawableBounds.setEmpty(); 642 mPositionOut = null; 643 mListenerView.setListener(null); 644 mOriginalIcon = null; 645 mOnTargetChangeRunnable = null; 646 mBadge = null; 647 sTmpObjArray[0] = null; 648 sRecycledFetchIconId = sFetchIconId; 649 mIconLoadResult = null; 650 mClipIconView.recycle(); 651 mBtvDrawable.setBackground(null); 652 mFastFinishRunnable = null; 653 mIconOffsetY = 0; 654 } 655 656 private static class IconLoadResult { 657 final ItemInfo itemInfo; 658 final boolean isThemed; 659 Drawable btvDrawable; 660 Drawable drawable; 661 Drawable badge; 662 int iconOffset; 663 Runnable onIconLoaded; 664 boolean isIconLoaded; 665 IconLoadResult(ItemInfo itemInfo, boolean isThemed)666 IconLoadResult(ItemInfo itemInfo, boolean isThemed) { 667 this.itemInfo = itemInfo; 668 this.isThemed = isThemed; 669 } 670 } 671 } 672