• 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.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