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