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