• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.Utilities.getBadge;
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.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             boolean[] outIsIconThemed = new boolean[1];
293             if (supportsAdaptiveIcons) {
294                 boolean shouldThemeIcon = btvIcon instanceof FastBitmapDrawable
295                         && ((FastBitmapDrawable) btvIcon).isThemed();
296                 drawable = getFullDrawable(
297                         l, info, width, height, shouldThemeIcon, tmpObjArray, outIsIconThemed);
298                 if (drawable instanceof AdaptiveIconDrawable) {
299                     badge = getBadge(l, info, tmpObjArray[0], outIsIconThemed[0]);
300                 } else {
301                     // The drawable we get back is not an adaptive icon, so we need to use the
302                     // BubbleTextView icon that is already legacy treated.
303                     drawable = btvIcon;
304                 }
305             } else {
306                 if (originalView instanceof BubbleTextView) {
307                     // Similar to DragView, we simply use the BubbleTextView icon here.
308                     drawable = btvIcon;
309                 } else {
310                     drawable = getFullDrawable(l, info, width, height, true /* shouldThemeIcon */,
311                             tmpObjArray, outIsIconThemed);
312                 }
313             }
314         }
315 
316         drawable = drawable == null ? null : drawable.getConstantState().newDrawable();
317         int iconOffset = getOffsetForIconBounds(l, drawable, pos);
318         // Clone right away as we are on the background thread instead of blocking the
319         // main thread later
320         Drawable btvClone = btvIcon == null ? null : btvIcon.getConstantState().newDrawable();
321         synchronized (outIconLoadResult) {
322             outIconLoadResult.btvDrawable = () -> btvClone;
323             outIconLoadResult.drawable = drawable;
324             outIconLoadResult.badge = badge;
325             outIconLoadResult.iconOffset = iconOffset;
326             if (outIconLoadResult.onIconLoaded != null) {
327                 l.getMainExecutor().execute(outIconLoadResult.onIconLoaded);
328                 outIconLoadResult.onIconLoaded = null;
329             }
330             outIconLoadResult.isIconLoaded = true;
331         }
332     }
333 
334     /**
335      * Sets the drawables of the {@param originalView} onto this view.
336      *
337      * @param drawable The drawable of the original view.
338      * @param badge The badge of the original view.
339      * @param iconOffset The amount of offset needed to match this view with the original view.
340      */
341     @UiThread
setIcon(@ullable Drawable drawable, @Nullable Drawable badge, @Nullable Supplier<Drawable> btvIcon, int iconOffset)342     private void setIcon(@Nullable Drawable drawable, @Nullable Drawable badge,
343             @Nullable Supplier<Drawable> btvIcon, int iconOffset) {
344         final DeviceProfile dp = mLauncher.getDeviceProfile();
345         final InsettableFrameLayout.LayoutParams lp =
346                 (InsettableFrameLayout.LayoutParams) getLayoutParams();
347         mBadge = badge;
348         mClipIconView.setIcon(drawable, iconOffset, lp, mIsOpening, dp);
349         if (drawable instanceof AdaptiveIconDrawable) {
350             final int originalHeight = lp.height;
351             final int originalWidth = lp.width;
352 
353             mFinalDrawableBounds.set(0, 0, originalWidth, originalHeight);
354 
355             float aspectRatio = mLauncher.getDeviceProfile().aspectRatio;
356             if (dp.isLandscape) {
357                 lp.width = (int) Math.max(lp.width, lp.height * aspectRatio);
358             } else {
359                 lp.height = (int) Math.max(lp.height, lp.width * aspectRatio);
360             }
361             setLayoutParams(lp);
362 
363             final LayoutParams clipViewLp = (LayoutParams) mClipIconView.getLayoutParams();
364             if (mBadge != null) {
365                 Rect badgeBounds = new Rect(0, 0, clipViewLp.width, clipViewLp.height);
366                 FastBitmapDrawable.setBadgeBounds(mBadge, badgeBounds);
367             }
368             clipViewLp.width = lp.width;
369             clipViewLp.height = lp.height;
370             mClipIconView.setLayoutParams(clipViewLp);
371         }
372 
373         setOriginalDrawableBackground(btvIcon);
374         invalidate();
375     }
376 
377     /**
378      * Draws the drawable of the BubbleTextView behind ClipIconView
379      *
380      * This is used to:
381      * - Have icon displayed while Adaptive Icon is loading
382      * - Displays the built in shadow to ensure a clean handoff
383      *
384      * Allows nullable as this may be cleared when drawing is deferred to ClipIconView.
385      */
setOriginalDrawableBackground(@ullable Supplier<Drawable> btvIcon)386     private void setOriginalDrawableBackground(@Nullable Supplier<Drawable> btvIcon) {
387         if (!mIsOpening) {
388             mBtvDrawable.setBackground(btvIcon == null ? null : btvIcon.get());
389         }
390     }
391 
392     /**
393      * Returns true if the icon is different from main app icon
394      */
isDifferentFromAppIcon()395     public boolean isDifferentFromAppIcon() {
396         return mIconLoadResult == null ? false : mIconLoadResult.isThemed;
397     }
398 
399     /**
400      * Checks if the icon result is loaded. If true, we set the icon immediately. Else, we add a
401      * callback to set the icon once the icon result is loaded.
402      */
checkIconResult()403     private void checkIconResult() {
404         CancellationSignal cancellationSignal = new CancellationSignal();
405 
406         if (mIconLoadResult == null) {
407             Log.w(TAG, "No icon load result found in checkIconResult");
408             return;
409         }
410 
411         synchronized (mIconLoadResult) {
412             if (mIconLoadResult.isIconLoaded) {
413                 setIcon(mIconLoadResult.drawable, mIconLoadResult.badge,
414                         mIconLoadResult.btvDrawable, mIconLoadResult.iconOffset);
415                 setVisibility(VISIBLE);
416                 updateViewsVisibility(false  /* isVisible */);
417             } else {
418                 mIconLoadResult.onIconLoaded = () -> {
419                     if (cancellationSignal.isCanceled()) {
420                         return;
421                     }
422 
423                     setIcon(mIconLoadResult.drawable, mIconLoadResult.badge,
424                             mIconLoadResult.btvDrawable, mIconLoadResult.iconOffset);
425 
426                     setVisibility(VISIBLE);
427                     updateViewsVisibility(false  /* isVisible */);
428                 };
429                 mLoadIconSignal = cancellationSignal;
430             }
431         }
432     }
433 
434     @WorkerThread
435     @SuppressWarnings("WrongThread")
getOffsetForIconBounds(Launcher l, Drawable drawable, RectF position)436     private static int getOffsetForIconBounds(Launcher l, Drawable drawable, RectF position) {
437         if (!(drawable instanceof AdaptiveIconDrawable)) {
438             return 0;
439         }
440         int blurSizeOutline =
441                 l.getResources().getDimensionPixelSize(R.dimen.blur_size_medium_outline);
442 
443         Rect bounds = new Rect(0, 0, (int) position.width() + blurSizeOutline,
444                 (int) position.height() + blurSizeOutline);
445         bounds.inset(blurSizeOutline / 2, blurSizeOutline / 2);
446 
447         try (LauncherIcons li = LauncherIcons.obtain(l)) {
448             Utilities.scaleRectAboutCenter(bounds, li.getNormalizer().getScale(drawable, null,
449                     null, null));
450         }
451 
452         bounds.inset(
453                 (int) (-bounds.width() * AdaptiveIconDrawable.getExtraInsetFraction()),
454                 (int) (-bounds.height() * AdaptiveIconDrawable.getExtraInsetFraction())
455         );
456 
457         return bounds.left;
458     }
459 
460     @Override
dispatchDraw(Canvas canvas)461     protected void dispatchDraw(Canvas canvas) {
462         super.dispatchDraw(canvas);
463         if (mBadge != null) {
464             mBadge.draw(canvas);
465         }
466     }
467 
468     /**
469      * Sets a runnable that is called after a call to {@link #fastFinish()}.
470      */
setFastFinishRunnable(Runnable runnable)471     public void setFastFinishRunnable(Runnable runnable) {
472         mFastFinishRunnable = runnable;
473     }
474 
475     @Override
fastFinish()476     public void fastFinish() {
477         if (mFastFinishRunnable != null) {
478             mFastFinishRunnable.run();
479             mFastFinishRunnable = null;
480         }
481         if (mLoadIconSignal != null) {
482             mLoadIconSignal.cancel();
483             mLoadIconSignal = null;
484         }
485         if (mEndRunnable != null) {
486             mEndRunnable.run();
487             mEndRunnable = null;
488         }
489     }
490 
491     @Override
onAnimationStart(Animator animator)492     public void onAnimationStart(Animator animator) {
493         if ((mIconLoadResult != null && mIconLoadResult.isIconLoaded)
494                 || (!mIsOpening && mBtvDrawable.getBackground() != null)) {
495             // No need to wait for icon load since we can display the BubbleTextView drawable.
496             setVisibility(View.VISIBLE);
497         }
498         if (!mIsOpening) {
499             // When closing an app, we want the item on the workspace to be invisible immediately
500             updateViewsVisibility(false  /* isVisible */);
501         }
502     }
503 
504     @Override
onAnimationCancel(Animator animator)505     public void onAnimationCancel(Animator animator) {}
506 
507     @Override
onAnimationRepeat(Animator animator)508     public void onAnimationRepeat(Animator animator) {}
509 
510     @Override
setPositionOffsetY(float y)511     public void setPositionOffsetY(float y) {
512         mIconOffsetY = y;
513         onGlobalLayout();
514     }
515 
516     @Override
onGlobalLayout()517     public void onGlobalLayout() {
518         if (mOriginalIcon != null && mOriginalIcon.isAttachedToWindow() && mPositionOut != null) {
519             getLocationBoundsForView(mLauncher, mOriginalIcon, mIsOpening, sTmpRectF);
520             sTmpRectF.offset(0, mIconOffsetY);
521             if (!sTmpRectF.equals(mPositionOut)) {
522                 updatePosition(sTmpRectF, (InsettableFrameLayout.LayoutParams) getLayoutParams());
523                 if (mOnTargetChangeRunnable != null) {
524                     mOnTargetChangeRunnable.run();
525                 }
526             }
527         }
528     }
529 
setOnTargetChangeListener(Runnable onTargetChangeListener)530     public void setOnTargetChangeListener(Runnable onTargetChangeListener) {
531         mOnTargetChangeRunnable = onTargetChangeListener;
532     }
533 
534     /**
535      * Loads the icon drawable on a worker thread to reduce latency between swapping views.
536      */
537     @UiThread
fetchIcon(Launcher l, View v, ItemInfo info, boolean isOpening)538     public static IconLoadResult fetchIcon(Launcher l, View v, ItemInfo info, boolean isOpening) {
539         RectF position = new RectF();
540         getLocationBoundsForView(l, v, isOpening, position);
541 
542         final FastBitmapDrawable btvIcon;
543         final Supplier<Drawable> btvDrawableSupplier;
544         if (v instanceof BubbleTextView) {
545             BubbleTextView btv = (BubbleTextView) v;
546             if (info instanceof ItemInfoWithIcon
547                     && (((ItemInfoWithIcon) info).runtimeStatusFlags
548                     & ItemInfoWithIcon.FLAG_SHOW_DOWNLOAD_PROGRESS_MASK) != 0) {
549                 btvIcon = btv.makePreloadIcon();
550                 btvDrawableSupplier = () -> btvIcon;
551             } else {
552                 btvIcon = btv.getIcon();
553                 // Clone when needed
554                 btvDrawableSupplier = () -> btvIcon.getConstantState().newDrawable();
555             }
556         } else {
557             btvIcon = null;
558             btvDrawableSupplier = null;
559         }
560 
561         IconLoadResult result = new IconLoadResult(info, btvIcon != null && btvIcon.isThemed());
562         result.btvDrawable = btvDrawableSupplier;
563 
564         final long fetchIconId = sFetchIconId++;
565         MODEL_EXECUTOR.getHandler().postAtFrontOfQueue(() -> {
566             if (fetchIconId < sRecycledFetchIconId) {
567                 return;
568             }
569             getIconResult(l, v, info, position, btvIcon, result);
570         });
571 
572         sIconLoadResult = result;
573         return result;
574     }
575 
576     /**
577      * Resets the static icon load result used for preloading the icon for a launching app.
578      */
resetIconLoadResult()579     public static void resetIconLoadResult() {
580         sIconLoadResult = null;
581     }
582 
583     /**
584      * Creates a floating icon view for {@param originalView}.
585      * @param originalView The view to copy
586      * @param visibilitySyncView A view whose visibility should update in sync with originalView.
587      * @param fadeOutView A view that will fade out as the animation progresses.
588      * @param hideOriginal If true, it will hide {@param originalView} while this view is visible.
589      *                     Else, we will not draw anything in this view.
590      * @param positionOut Rect that will hold the size and position of v.
591      * @param isOpening True if this view replaces the icon for app open animation.
592      */
getFloatingIconView(Launcher launcher, View originalView, @Nullable View visibilitySyncView, @Nullable View fadeOutView, boolean hideOriginal, RectF positionOut, boolean isOpening)593     public static FloatingIconView getFloatingIconView(Launcher launcher, View originalView,
594             @Nullable View visibilitySyncView, @Nullable View fadeOutView, boolean hideOriginal,
595             RectF positionOut, boolean isOpening) {
596         final DragLayer dragLayer = launcher.getDragLayer();
597         ViewGroup parent = (ViewGroup) dragLayer.getParent();
598         FloatingIconView view = launcher.getViewCache().getView(R.layout.floating_icon_view,
599                 launcher, parent);
600         view.recycle();
601 
602         // Init properties before getting the drawable.
603         view.mIsOpening = isOpening;
604         view.mOriginalIcon = originalView;
605         view.mMatchVisibilityView = visibilitySyncView;
606         view.mFadeOutView = fadeOutView;
607         view.mPositionOut = positionOut;
608 
609         // Get the drawable on the background thread
610         boolean shouldLoadIcon = originalView.getTag() instanceof ItemInfo && hideOriginal;
611         if (shouldLoadIcon) {
612             if (sIconLoadResult != null && sIconLoadResult.itemInfo == originalView.getTag()) {
613                 view.mIconLoadResult = sIconLoadResult;
614             } else {
615                 view.mIconLoadResult = fetchIcon(launcher, originalView,
616                         (ItemInfo) originalView.getTag(), isOpening);
617             }
618             view.setOriginalDrawableBackground(view.mIconLoadResult.btvDrawable);
619         }
620         resetIconLoadResult();
621 
622         // Match the position of the original view.
623         view.matchPositionOf(launcher, originalView, isOpening, positionOut);
624 
625         // We need to add it to the overlay, but keep it invisible until animation starts..
626         view.setVisibility(View.INVISIBLE);
627 
628         parent.addView(view);
629         dragLayer.addView(view.mListenerView);
630         view.mListenerView.setListener(view::fastFinish);
631 
632         view.mEndRunnable = () -> {
633             view.mEndRunnable = null;
634 
635             if (view.mFadeOutView != null) {
636                 view.mFadeOutView.setAlpha(1f);
637             }
638 
639             if (hideOriginal) {
640                 view.updateViewsVisibility(true /* isVisible */);
641                 view.finish(dragLayer);
642             } else {
643                 view.finish(dragLayer);
644             }
645         };
646 
647         // Must be called after matchPositionOf so that we know what size to load.
648         // Must be called after the fastFinish listener and end runnable is created so that
649         // the icon is not left in a hidden state.
650         if (shouldLoadIcon) {
651             view.checkIconResult();
652         }
653 
654         return view;
655     }
656 
updateViewsVisibility(boolean isVisible)657     private void updateViewsVisibility(boolean isVisible) {
658         if (mOriginalIcon != null) {
659             setIconAndDotVisible(mOriginalIcon, isVisible);
660         }
661         if (mMatchVisibilityView != null) {
662             setIconAndDotVisible(mMatchVisibilityView, isVisible);
663         }
664     }
665 
finish(DragLayer dragLayer)666     private void finish(DragLayer dragLayer) {
667         ((ViewGroup) dragLayer.getParent()).removeView(this);
668         dragLayer.removeView(mListenerView);
669         recycle();
670         mLauncher.getViewCache().recycleView(R.layout.floating_icon_view, this);
671     }
672 
recycle()673     private void recycle() {
674         setTranslationX(0);
675         setTranslationY(0);
676         setScaleX(1);
677         setScaleY(1);
678         setAlpha(1);
679         if (mLoadIconSignal != null) {
680             mLoadIconSignal.cancel();
681         }
682         mLoadIconSignal = null;
683         mEndRunnable = null;
684         mFinalDrawableBounds.setEmpty();
685         mIsOpening = false;
686         mPositionOut = null;
687         mListenerView.setListener(null);
688         mOriginalIcon = null;
689         mOnTargetChangeRunnable = null;
690         mBadge = null;
691         sRecycledFetchIconId = sFetchIconId;
692         mIconLoadResult = null;
693         mClipIconView.recycle();
694         mBtvDrawable.setBackground(null);
695         mFastFinishRunnable = null;
696         mIconOffsetY = 0;
697         mMatchVisibilityView = null;
698         mFadeOutView = null;
699     }
700 
701     private static class IconLoadResult {
702         final ItemInfo itemInfo;
703         final boolean isThemed;
704         Supplier<Drawable> btvDrawable;
705         Drawable drawable;
706         Drawable badge;
707         int iconOffset;
708         Runnable onIconLoaded;
709         boolean isIconLoaded;
710 
IconLoadResult(ItemInfo itemInfo, boolean isThemed)711         IconLoadResult(ItemInfo itemInfo, boolean isThemed) {
712             this.itemInfo = itemInfo;
713             this.isThemed = isThemed;
714         }
715     }
716 }
717