Merge "Removing explicit target SDK for launcher" into udc-qpr-dev
diff --git a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
index c7325ba..5c77de7 100644
--- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
+++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
@@ -229,6 +229,7 @@
     private RemoteAnimationProvider mRemoteAnimationProvider;
     // Strong refs to runners which are cleared when the launcher activity is destroyed
     private RemoteAnimationFactory mWallpaperOpenRunner;
+    private RemoteAnimationFactory mAppLaunchRunner;
     private RemoteAnimationFactory mKeyguardGoingAwayRunner;
 
     private RemoteAnimationFactory mWallpaperOpenTransitionRunner;
@@ -298,17 +299,17 @@
         boolean fromRecents = isLaunchingFromRecents(v, null /* targets */);
         RunnableList onEndCallback = new RunnableList();
 
-        RemoteAnimationFactory delegateRunner = new AppLaunchAnimationRunner(v, onEndCallback);
+        mAppLaunchRunner = new AppLaunchAnimationRunner(v, onEndCallback);
         ItemInfo tag = (ItemInfo) v.getTag();
         if (tag != null && tag.shouldUseBackgroundAnimation()) {
             ContainerAnimationRunner containerAnimationRunner =
                     ContainerAnimationRunner.from(v, mStartingWindowListener, onEndCallback);
             if (containerAnimationRunner != null) {
-                delegateRunner = containerAnimationRunner;
+                mAppLaunchRunner = containerAnimationRunner;
             }
         }
         RemoteAnimationRunnerCompat runner = new LauncherAnimationRunner(
-                mHandler, delegateRunner, true /* startAtFrontOfQueue */);
+                mHandler, mAppLaunchRunner, true /* startAtFrontOfQueue */);
 
         // Note that this duration is a guess as we do not know if the animation will be a
         // recents launch or not for sure until we know the opening app targets.
@@ -1164,6 +1165,7 @@
             // Also clear strong references to the runners registered with the remote animation
             // definition so we don't have to wait for the system gc
             mWallpaperOpenRunner = null;
+            mAppLaunchRunner = null;
             mKeyguardGoingAwayRunner = null;
         }
     }
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarBackground.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarBackground.kt
index 8a8e21f..1e3f4f1 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarBackground.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarBackground.kt
@@ -48,6 +48,8 @@
     private var showingArrow: Boolean = false
     private var arrowDrawable: ShapeDrawable
 
+    var width: Float = 0f
+
     init {
         paint.color = context.getColor(R.color.taskbar_background)
         paint.flags = Paint.ANTI_ALIAS_FLAG
@@ -59,8 +61,11 @@
         pointerSize = res.getDimension(R.dimen.bubblebar_pointer_size)
 
         shadowAlpha =
-            if (Utilities.isDarkTheme(context)) DARK_THEME_SHADOW_ALPHA
-            else LIGHT_THEME_SHADOW_ALPHA
+            if (Utilities.isDarkTheme(context)) {
+                DARK_THEME_SHADOW_ALPHA
+            } else {
+                LIGHT_THEME_SHADOW_ALPHA
+            }
 
         arrowDrawable =
             ShapeDrawable(TriangleShape.create(pointerSize, pointerSize, /* pointUp= */ true))
@@ -102,7 +107,7 @@
         // Draw background.
         val radius = backgroundHeight / 2f
         canvas.drawRoundRect(
-            0f,
+            canvas.width.toFloat() - width,
             0f,
             canvas.width.toFloat(),
             canvas.height.toFloat(),
@@ -132,4 +137,8 @@
     override fun setColorFilter(colorFilter: ColorFilter?) {
         paint.colorFilter = colorFilter
     }
+
+    fun setArrowAlpha(alpha: Int) {
+        arrowDrawable.paint.alpha = alpha
+    }
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
index 8d20705..58c67e3 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
@@ -15,6 +15,7 @@
  */
 package com.android.launcher3.taskbar.bubbles;
 
+import android.animation.Animator;
 import android.animation.ValueAnimator;
 import android.annotation.Nullable;
 import android.content.Context;
@@ -66,8 +67,8 @@
     //  if it's smaller than 5.
     private static final int MAX_BUBBLES = 5;
     private static final int ARROW_POSITION_ANIMATION_DURATION_MS = 200;
+    private static final int WIDTH_ANIMATION_DURATION_MS = 200;
 
-    private final TaskbarActivityContext mActivityContext;
     private final BubbleBarBackground mBubbleBarBackground;
 
     // The current bounds of all the bubble bar.
@@ -90,6 +91,10 @@
 
     private final Rect mTempRect = new Rect();
 
+    // An animator that represents the expansion state of the bubble bar, where 0 corresponds to the
+    // collapsed state and 1 to the fully expanded state.
+    private final ValueAnimator mWidthAnimator = ValueAnimator.ofFloat(0, 1);
+
     // We don't reorder the bubbles when they are expanded as it could be jarring for the user
     // this runnable will be populated with any reordering of the bubbles that should be applied
     // once they are collapsed.
@@ -110,7 +115,7 @@
 
     public BubbleBarView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
-        mActivityContext = ActivityContext.lookupContext(context);
+        TaskbarActivityContext activityContext = ActivityContext.lookupContext(context);
 
         mIconOverlapAmount = getResources().getDimensionPixelSize(R.dimen.bubblebar_icon_overlap);
         mIconSpacing = getResources().getDimensionPixelSize(R.dimen.bubblebar_icon_spacing);
@@ -118,9 +123,39 @@
         mBubbleElevation = getResources().getDimensionPixelSize(R.dimen.bubblebar_icon_elevation);
         setClipToPadding(false);
 
-        mBubbleBarBackground = new BubbleBarBackground(mActivityContext,
+        mBubbleBarBackground = new BubbleBarBackground(activityContext,
                 getResources().getDimensionPixelSize(R.dimen.bubblebar_size));
         setBackgroundDrawable(mBubbleBarBackground);
+
+        mWidthAnimator.setDuration(WIDTH_ANIMATION_DURATION_MS);
+        mWidthAnimator.addUpdateListener(animation -> {
+            updateChildrenRenderNodeProperties();
+            invalidate();
+        });
+        mWidthAnimator.addListener(new Animator.AnimatorListener() {
+            @Override
+            public void onAnimationCancel(Animator animation) {
+            }
+
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                mBubbleBarBackground.showArrow(mIsBarExpanded);
+                if (!mIsBarExpanded && mReorderRunnable != null) {
+                    mReorderRunnable.run();
+                    mReorderRunnable = null;
+                }
+                updateWidth();
+            }
+
+            @Override
+            public void onAnimationRepeat(Animator animation) {
+            }
+
+            @Override
+            public void onAnimationStart(Animator animation) {
+                mBubbleBarBackground.showArrow(true);
+            }
+        });
     }
 
     @Override
@@ -146,7 +181,7 @@
         return mBubbleBarBounds;
     }
 
-    // TODO: (b/273592694) animate it
+    // TODO: (b/280605790) animate it
     @Override
     public void addView(View child, int index, ViewGroup.LayoutParams params) {
         if (getChildCount() + 1 > MAX_BUBBLES) {
@@ -155,27 +190,55 @@
             removeViewInLayout(getChildAt(getChildCount() - 2));
         }
         super.addView(child, index, params);
+        updateWidth();
+    }
+
+    // TODO: (b/283309949) animate it
+    @Override
+    public void removeView(View view) {
+        super.removeView(view);
+        updateWidth();
+    }
+
+    private void updateWidth() {
+        LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams();
+        lp.width = (int) (mIsBarExpanded ? expandedWidth() : collapsedWidth());
+        setLayoutParams(lp);
     }
 
     /**
      * Updates the z order, positions, and badge visibility of the bubble views in the bar based
      * on the expanded state.
      */
-    // TODO: (b/273592694) animate it
     private void updateChildrenRenderNodeProperties() {
+        final float widthState = (float) mWidthAnimator.getAnimatedValue();
+        final float currentWidth = getWidth();
+        final float expandedWidth = expandedWidth();
+        final float collapsedWidth = collapsedWidth();
         int bubbleCount = getChildCount();
         final float ty = (mBubbleBarBounds.height() - mIconSize) / 2f;
         for (int i = 0; i < bubbleCount; i++) {
             BubbleView bv = (BubbleView) getChildAt(i);
             bv.setTranslationY(ty);
+
+            // the position of the bubble when the bar is fully expanded
+            final float expandedX = i * (mIconSize + mIconSpacing);
+            // the position of the bubble when the bar is fully collapsed
+            final float collapsedX = i * mIconOverlapAmount;
+
             if (mIsBarExpanded) {
-                final float tx = i * (mIconSize + mIconSpacing);
-                bv.setTranslationX(tx);
-                bv.setZ(0);
+                // where the bubble will end up when the animation ends
+                final float targetX = currentWidth - expandedWidth + expandedX;
+                bv.setTranslationX(widthState * (targetX - collapsedX) + collapsedX);
+                // if we're fully expanded, set the z level to 0
+                if (widthState == 1f) {
+                    bv.setZ(0);
+                }
                 bv.showBadge();
             } else {
+                final float targetX = currentWidth - collapsedWidth + collapsedX;
+                bv.setTranslationX(widthState * (expandedX - targetX) + targetX);
                 bv.setZ((MAX_BUBBLES * mBubbleElevation) - i);
-                bv.setTranslationX(i * mIconOverlapAmount);
                 if (i > 0) {
                     bv.hideBadge();
                 } else {
@@ -183,13 +246,33 @@
                 }
             }
         }
+
+        // update the arrow position
+        final float collapsedArrowPosition = arrowPositionForSelectedWhenCollapsed();
+        final float expandedArrowPosition = arrowPositionForSelectedWhenExpanded();
+        final float interpolatedWidth =
+                widthState * (expandedWidth - collapsedWidth) + collapsedWidth;
+        if (mIsBarExpanded) {
+            // when the bar is expanding, the selected bubble is always the first, so the arrow
+            // always shifts with the interpolated width.
+            final float arrowPosition = currentWidth - interpolatedWidth + collapsedArrowPosition;
+            mBubbleBarBackground.setArrowPosition(arrowPosition);
+        } else {
+            final float targetPosition = currentWidth - collapsedWidth + collapsedArrowPosition;
+            final float arrowPosition =
+                    targetPosition + widthState * (expandedArrowPosition - targetPosition);
+            mBubbleBarBackground.setArrowPosition(arrowPosition);
+        }
+
+        mBubbleBarBackground.setArrowAlpha((int) (255 * widthState));
+        mBubbleBarBackground.setWidth(interpolatedWidth);
     }
 
     /**
      * Reorders the views to match the provided list.
      */
     public void reorder(List<BubbleView> viewOrder) {
-        if (isExpanded()) {
+        if (isExpanded() || mWidthAnimator.isRunning()) {
             mReorderRunnable = () -> doReorder(viewOrder);
         } else {
             doReorder(viewOrder);
@@ -249,6 +332,16 @@
         }
     }
 
+    private float arrowPositionForSelectedWhenExpanded() {
+        final int index = indexOfChild(mSelectedBubbleView);
+        return getPaddingStart() + index * (mIconSize + mIconSpacing) + mIconSize / 2f;
+    }
+
+    private float arrowPositionForSelectedWhenCollapsed() {
+        final int index = indexOfChild(mSelectedBubbleView);
+        return getPaddingStart() + index * (mIconOverlapAmount) + mIconSize / 2f;
+    }
+
     @Override
     public void setOnClickListener(View.OnClickListener listener) {
         mOnClickListener = listener;
@@ -266,18 +359,16 @@
     /**
      * Sets whether the bubble bar is expanded or collapsed.
      */
-    // TODO: (b/273592694) animate it
     public void setExpanded(boolean isBarExpanded) {
         if (mIsBarExpanded != isBarExpanded) {
             mIsBarExpanded = isBarExpanded;
             updateArrowForSelected(/* shouldAnimate= */ false);
             setOrUnsetClickListener();
-            if (!isBarExpanded && mReorderRunnable != null) {
-                mReorderRunnable.run();
-                mReorderRunnable = null;
+            if (isBarExpanded) {
+                mWidthAnimator.start();
+            } else {
+                mWidthAnimator.reverse();
             }
-            mBubbleBarBackground.showArrow(mIsBarExpanded);
-            requestLayout(); // trigger layout to reposition views & update size for expansion
         }
     }
 
@@ -288,19 +379,16 @@
         return mIsBarExpanded;
     }
 
-    @Override
-    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+    private float expandedWidth() {
         final int childCount = getChildCount();
-        final float iconWidth = mIsBarExpanded
-                ? (childCount * (mIconSize + mIconSpacing))
-                : mIconSize + ((childCount - 1) * mIconOverlapAmount);
-        final int totalWidth = (int) iconWidth + getPaddingStart() + getPaddingEnd();
-        setMeasuredDimension(totalWidth, MeasureSpec.getSize(heightMeasureSpec));
+        final int horizontalPadding = getPaddingStart() + getPaddingEnd();
+        return childCount * (mIconSize + mIconSpacing) + horizontalPadding;
+    }
 
-        for (int i = 0; i < childCount; i++) {
-            View child = getChildAt(i);
-            measureChild(child, (int) mIconSize, (int) mIconSize);
-        }
+    private float collapsedWidth() {
+        final int childCount = getChildCount();
+        final int horizontalPadding = getPaddingStart() + getPaddingEnd();
+        return mIconSize + ((childCount - 1) * mIconOverlapAmount) + horizontalPadding;
     }
 
     /**
diff --git a/quickstep/src/com/android/quickstep/TaskIconCache.java b/quickstep/src/com/android/quickstep/TaskIconCache.java
index 7c05a10..164a366 100644
--- a/quickstep/src/com/android/quickstep/TaskIconCache.java
+++ b/quickstep/src/com/android/quickstep/TaskIconCache.java
@@ -15,7 +15,6 @@
  */
 package com.android.quickstep;
 
-import static com.android.launcher3.uioverrides.QuickstepLauncher.GO_LOW_RAM_RECENTS_ENABLED;
 import static com.android.launcher3.util.DisplayController.CHANGE_DENSITY;
 
 import android.annotation.Nullable;
@@ -182,17 +181,14 @@
             }
         }
 
-        // Loading content descriptions if accessibility or low RAM recents is enabled.
-        if (GO_LOW_RAM_RECENTS_ENABLED || mAccessibilityManager.isEnabled()) {
-            // Skip loading the content description if the activity no longer exists
-            if (activityInfo == null) {
-                activityInfo = PackageManagerWrapper.getInstance().getActivityInfo(
-                        key.getComponent(), key.userId);
-            }
-            if (activityInfo != null) {
-                entry.contentDescription = getBadgedContentDescription(
-                        activityInfo, task.key.userId, task.taskDescription);
-            }
+        // Skip loading the content description if the activity no longer exists
+        if (activityInfo == null) {
+            activityInfo = PackageManagerWrapper.getInstance().getActivityInfo(
+                    key.getComponent(), key.userId);
+        }
+        if (activityInfo != null) {
+            entry.contentDescription = getBadgedContentDescription(
+                    activityInfo, task.key.userId, task.taskDescription);
         }
 
         mIconCache.put(task.key, entry);
diff --git a/quickstep/src/com/android/quickstep/TaskViewUtils.java b/quickstep/src/com/android/quickstep/TaskViewUtils.java
index bfe52dd..c9bad38 100644
--- a/quickstep/src/com/android/quickstep/TaskViewUtils.java
+++ b/quickstep/src/com/android/quickstep/TaskViewUtils.java
@@ -473,16 +473,14 @@
                     throw new IllegalStateException(
                             "Expected task to be showing, but it is " + mode);
                 }
-                if (change.getParent() == null) {
-                    throw new IllegalStateException("Initiating multi-split launch but the split"
-                            + "root of " + taskId + " is already visible or has broken hierarchy.");
-                }
             }
             if (taskId == initialTaskId) {
-                splitRoot1 = transitionInfo.getChange(change.getParent());
+                splitRoot1 = change.getParent() == null ? change :
+                        transitionInfo.getChange(change.getParent());
             }
             if (taskId == secondTaskId) {
-                splitRoot2 = transitionInfo.getChange(change.getParent());
+                splitRoot2 = change.getParent() == null ? change :
+                        transitionInfo.getChange(change.getParent());
             }
         }
 
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index 40e3dca..5301c7c 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -842,17 +842,20 @@
                 // the actual overview state
                 failureListener.register(mActivity, mTask.key.id, () -> {
                     notifyTaskLaunchFailed(TAG);
-                    // Disable animations for now, as it is an edge case and the app usually covers
-                    // launcher and also any state transition animation also gets clobbered by
-                    // QuickstepTransitionManager.createWallpaperOpenAnimations when launcher
-                    // shows again
-                    getRecentsView().startHome(false /* animated */);
                     RecentsView rv = getRecentsView();
-                    if (rv != null && rv.mSizeStrategy.getTaskbarController() != null) {
-                        // LauncherTaskbarUIController depends on the launcher state when checking
-                        // whether to handle resume, but that can come in before startHome() changes
-                        // the state, so force-refresh here to ensure the taskbar is updated
-                        rv.mSizeStrategy.getTaskbarController().refreshResumedState();
+                    if (rv != null) {
+                        // Disable animations for now, as it is an edge case and the app usually
+                        // covers launcher and also any state transition animation also gets
+                        // clobbered by QuickstepTransitionManager.createWallpaperOpenAnimations
+                        // when launcher shows again
+                        rv.startHome(false /* animated */);
+                        if (rv.mSizeStrategy.getTaskbarController() != null) {
+                            // LauncherTaskbarUIController depends on the launcher state when
+                            // checking whether to handle resume, but that can come in before
+                            // startHome() changes the state, so force-refresh here to ensure the
+                            // taskbar is updated
+                            rv.mSizeStrategy.getTaskbarController().refreshResumedState();
+                        }
                     }
                 });
             }
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index 46fe73e..4f5de05 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -581,12 +581,13 @@
      */
     @TargetApi(Build.VERSION_CODES.TIRAMISU)
     public static Drawable getFullDrawable(Context context, ItemInfo info, int width, int height,
-            boolean shouldThemeIcon, Object[] outObj) {
+            boolean shouldThemeIcon, Object[] outObj, boolean[] outIsIconThemed) {
         Drawable icon = loadFullDrawableWithoutTheme(context, info, width, height, outObj);
         if (ATLEAST_T && icon instanceof AdaptiveIconDrawable && shouldThemeIcon) {
             AdaptiveIconDrawable aid = (AdaptiveIconDrawable) icon.mutate();
             Drawable mono = aid.getMonochrome();
             if (mono != null && Themes.isThemedIconEnabled(context)) {
+                outIsIconThemed[0] = true;
                 int[] colors = ThemedIconDrawable.getColors(context);
                 mono = mono.mutate();
                 mono.setTint(colors[1]);
@@ -647,7 +648,8 @@
      * badge. When dragged from workspace or folder, it may contain app AND/OR work profile badge
      **/
     @TargetApi(Build.VERSION_CODES.O)
-    public static Drawable getBadge(Context context, ItemInfo info, Object obj) {
+    public static Drawable getBadge(Context context, ItemInfo info, Object obj,
+            boolean isIconThemed) {
         LauncherAppState appState = LauncherAppState.getInstance(context);
         if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
             boolean iconBadged = (info instanceof ItemInfoWithIcon)
@@ -665,7 +667,8 @@
         } else {
             return Process.myUserHandle().equals(info.user)
                     ? new ColorDrawable(Color.TRANSPARENT)
-                    : context.getDrawable(R.drawable.ic_work_app_badge);
+                    : context.getDrawable(isIconThemed
+                            ? R.drawable.ic_work_app_badge_themed : R.drawable.ic_work_app_badge);
         }
     }
 
diff --git a/src/com/android/launcher3/dragndrop/DragView.java b/src/com/android/launcher3/dragndrop/DragView.java
index c26d673..b9bb52c 100644
--- a/src/com/android/launcher3/dragndrop/DragView.java
+++ b/src/com/android/launcher3/dragndrop/DragView.java
@@ -224,11 +224,11 @@
         // Load the adaptive icon on a background thread and add the view in ui thread.
         MODEL_EXECUTOR.getHandler().postAtFrontOfQueue(() -> {
             Object[] outObj = new Object[1];
+            boolean[] outIsIconThemed = new boolean[1];
             int w = mWidth;
             int h = mHeight;
             Drawable dr = Utilities.getFullDrawable(mActivity, info, w, h,
-                    true /* shouldThemeIcon */, outObj);
-
+                    true /* shouldThemeIcon */, outObj, outIsIconThemed);
             if (dr instanceof AdaptiveIconDrawable) {
                 int blurMargin = (int) mActivity.getResources()
                         .getDimension(R.dimen.blur_size_medium_outline) / 2;
@@ -237,7 +237,7 @@
                 bounds.inset(blurMargin, blurMargin);
                 // Badge is applied after icon normalization so the bounds for badge should not
                 // be scaled down due to icon normalization.
-                mBadge = getBadge(mActivity, info, outObj[0]);
+                mBadge = getBadge(mActivity, info, outObj[0], outIsIconThemed[0]);
                 FastBitmapDrawable.setBadgeBounds(mBadge, bounds);
 
                 // Do not draw the background in case of folder as its translucent
diff --git a/src/com/android/launcher3/views/FloatingIconView.java b/src/com/android/launcher3/views/FloatingIconView.java
index f425821..41b98c7 100644
--- a/src/com/android/launcher3/views/FloatingIconView.java
+++ b/src/com/android/launcher3/views/FloatingIconView.java
@@ -289,12 +289,14 @@
             int width = (int) pos.width();
             int height = (int) pos.height();
             Object[] tmpObjArray = new Object[1];
+            boolean[] outIsIconThemed = new boolean[1];
             if (supportsAdaptiveIcons) {
                 boolean shouldThemeIcon = btvIcon instanceof FastBitmapDrawable
                         && ((FastBitmapDrawable) btvIcon).isThemed();
-                drawable = getFullDrawable(l, info, width, height, shouldThemeIcon, tmpObjArray);
+                drawable = getFullDrawable(
+                        l, info, width, height, shouldThemeIcon, tmpObjArray, outIsIconThemed);
                 if (drawable instanceof AdaptiveIconDrawable) {
-                    badge = getBadge(l, info, tmpObjArray[0]);
+                    badge = getBadge(l, info, tmpObjArray[0], outIsIconThemed[0]);
                 } else {
                     // The drawable we get back is not an adaptive icon, so we need to use the
                     // BubbleTextView icon that is already legacy treated.
@@ -306,7 +308,7 @@
                     drawable = btvIcon;
                 } else {
                     drawable = getFullDrawable(l, info, width, height, true /* shouldThemeIcon */,
-                            tmpObjArray);
+                            tmpObjArray, outIsIconThemed);
                 }
             }
         }
diff --git a/tests/shared/com/android/launcher3/testing/shared/TestProtocol.java b/tests/shared/com/android/launcher3/testing/shared/TestProtocol.java
index 8cc01ff..36e4e76 100644
--- a/tests/shared/com/android/launcher3/testing/shared/TestProtocol.java
+++ b/tests/shared/com/android/launcher3/testing/shared/TestProtocol.java
@@ -156,6 +156,7 @@
     public static final String WORK_TAB_MISSING = "b/243688989";
     public static final String TWO_TASKBAR_LONG_CLICKS = "b/262282528";
     public static final String FLAKY_ACTIVITY_COUNT = "b/260260325";
+    public static final String ICON_MISSING = "b/282963545";
 
     public static final String REQUEST_EMULATE_DISPLAY = "emulate-display";
     public static final String REQUEST_STOP_EMULATE_DISPLAY = "stop-emulate-display";
diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
index 1262a26..7f796e7 100644
--- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
+++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
@@ -17,6 +17,7 @@
 
 import static androidx.test.InstrumentationRegistry.getInstrumentation;
 
+import static com.android.launcher3.testing.shared.TestProtocol.ICON_MISSING;
 import static com.android.launcher3.ui.TaplTestsLauncher3.getAppPackageName;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 
@@ -603,6 +604,8 @@
 
     protected HomeAppIcon createShortcutIfNotExist(String name, int cellX, int cellY) {
         HomeAppIcon homeAppIcon = mLauncher.getWorkspace().tryGetWorkspaceAppIcon(name);
+        Log.d(ICON_MISSING, "homeAppIcon: " + homeAppIcon + " name: " + name +
+                " cell: " + cellX + ", " + cellY);
         if (homeAppIcon == null) {
             HomeAllApps allApps = mLauncher.getWorkspace().switchToAllApps();
             allApps.freeze();
diff --git a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
index 095b135..2c8acc4 100644
--- a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
+++ b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
@@ -18,6 +18,8 @@
 
 import static androidx.test.InstrumentationRegistry.getInstrumentation;
 
+import static com.android.launcher3.testing.shared.TestProtocol.ICON_MISSING;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.assertEquals;
@@ -579,6 +581,11 @@
     @PlatinumTest(focusArea = "launcher")
     public void getIconsPosition_afterIconRemoved_notContained() throws IOException {
         Point[] gridPositions = getCornersAndCenterPositions();
+        StringBuilder sb = new StringBuilder();
+        for (Point p : gridPositions) {
+            sb.append(p).append(", ");
+        }
+        Log.d(ICON_MISSING, "allGridPositions: " + sb);
         createShortcutIfNotExist(STORE_APP_NAME, gridPositions[0]);
         createShortcutIfNotExist(MAPS_APP_NAME, gridPositions[1]);
         installDummyAppAndWaitForUIUpdate();