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