Merge "More diags for b/129434166" into ub-launcher3-master
diff --git a/go/quickstep/src/com/android/quickstep/TaskActionController.java b/go/quickstep/src/com/android/quickstep/TaskActionController.java
index 77b287b..71bee91 100644
--- a/go/quickstep/src/com/android/quickstep/TaskActionController.java
+++ b/go/quickstep/src/com/android/quickstep/TaskActionController.java
@@ -42,6 +42,9 @@
      * @param viewHolder the task view holder to launch
      */
     public void launchTask(TaskHolder viewHolder) {
+        if (viewHolder.getTask() == null) {
+            return;
+        }
         TaskItemView itemView = (TaskItemView) (viewHolder.itemView);
         View v = itemView.getThumbnailView();
         int left = 0;
@@ -60,6 +63,9 @@
      * @param viewHolder the task view holder to remove
      */
     public void removeTask(TaskHolder viewHolder) {
+        if (viewHolder.getTask() == null) {
+            return;
+        }
         int position = viewHolder.getAdapterPosition();
         Task task = viewHolder.getTask();
         ActivityManagerWrapper.getInstance().removeTask(task.key.id);
diff --git a/go/quickstep/src/com/android/quickstep/TaskAdapter.java b/go/quickstep/src/com/android/quickstep/TaskAdapter.java
index c98eca6..674fcae 100644
--- a/go/quickstep/src/com/android/quickstep/TaskAdapter.java
+++ b/go/quickstep/src/com/android/quickstep/TaskAdapter.java
@@ -28,6 +28,7 @@
 import com.android.systemui.shared.recents.model.Task;
 
 import java.util.List;
+import java.util.Objects;
 
 /**
  * Recycler view adapter that dynamically inflates and binds {@link TaskHolder} instances with the
@@ -40,6 +41,7 @@
     private final TaskListLoader mLoader;
     private final ArrayMap<Integer, TaskItemView> mTaskIdToViewMap = new ArrayMap<>();
     private TaskActionController mTaskActionController;
+    private boolean mIsShowingLoadingUi;
 
     public TaskAdapter(@NonNull TaskListLoader loader) {
         mLoader = loader;
@@ -50,6 +52,18 @@
     }
 
     /**
+     * Sets all positions in the task adapter to loading views, binding new views if necessary.
+     * This changes the task adapter's view of the data, so the appropriate notify events should be
+     * called in addition to this method to reflect the changes.
+     *
+     * @param isShowingLoadingUi true to bind loading task views to all positions, false to return
+     *                           to the real data
+     */
+    public void setIsShowingLoadingUi(boolean isShowingLoadingUi) {
+        mIsShowingLoadingUi = isShowingLoadingUi;
+    }
+
+    /**
      * Get task item view for a given task id if it's attached to the view.
      *
      * @param taskId task id to search for
@@ -70,6 +84,10 @@
 
     @Override
     public void onBindViewHolder(TaskHolder holder, int position) {
+        if (mIsShowingLoadingUi) {
+            holder.bindEmptyUi();
+            return;
+        }
         List<Task> tasks = mLoader.getCurrentTaskList();
         if (position >= tasks.size()) {
             // Task list has updated.
@@ -79,13 +97,13 @@
         holder.bindTask(task);
         mLoader.loadTaskIconAndLabel(task, () -> {
             // Ensure holder still has the same task.
-            if (task.equals(holder.getTask())) {
+            if (Objects.equals(task, holder.getTask())) {
                 holder.getTaskItemView().setIcon(task.icon);
                 holder.getTaskItemView().setLabel(task.titleDescription);
             }
         });
         mLoader.loadTaskThumbnail(task, () -> {
-            if (task.equals(holder.getTask())) {
+            if (Objects.equals(task, holder.getTask())) {
                 holder.getTaskItemView().setThumbnail(task.thumbnail.thumbnail);
             }
         });
@@ -93,16 +111,27 @@
 
     @Override
     public void onViewAttachedToWindow(@NonNull TaskHolder holder) {
+        if (holder.getTask() == null) {
+            return;
+        }
         mTaskIdToViewMap.put(holder.getTask().key.id, (TaskItemView) holder.itemView);
     }
 
     @Override
     public void onViewDetachedFromWindow(@NonNull TaskHolder holder) {
+        if (holder.getTask() == null) {
+            return;
+        }
         mTaskIdToViewMap.remove(holder.getTask().key.id);
     }
 
     @Override
     public int getItemCount() {
-        return Math.min(mLoader.getCurrentTaskList().size(), MAX_TASKS_TO_DISPLAY);
+        if (mIsShowingLoadingUi) {
+            // Show loading version of all items.
+            return MAX_TASKS_TO_DISPLAY;
+        } else {
+            return Math.min(mLoader.getCurrentTaskList().size(), MAX_TASKS_TO_DISPLAY);
+        }
     }
 }
diff --git a/go/quickstep/src/com/android/quickstep/TaskHolder.java b/go/quickstep/src/com/android/quickstep/TaskHolder.java
index 744afd7..98dc989 100644
--- a/go/quickstep/src/com/android/quickstep/TaskHolder.java
+++ b/go/quickstep/src/com/android/quickstep/TaskHolder.java
@@ -15,7 +15,7 @@
  */
 package com.android.quickstep;
 
-import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 import androidx.recyclerview.widget.RecyclerView.ViewHolder;
 
 import com.android.quickstep.views.TaskItemView;
@@ -50,11 +50,23 @@
     }
 
     /**
-     * Gets the task currently bound to this view
+     * Bind a generic empty UI to the holder to make it clear that the item is loading/unbound and
+     * should not be expected to react to user input.
+     */
+    public void bindEmptyUi() {
+        mTask = null;
+        // TODO: Set the task view to a loading, empty UI.
+        // Temporarily using the one below for visual confirmation but should be swapped out to new
+        // UI later.
+        mTaskItemView.resetTaskItemView();
+    }
+
+    /**
+     * Gets the task currently bound to this view. May be null if task holder is in a loading state.
      *
      * @return the current task
      */
-    public @NonNull Task getTask() {
+    public @Nullable Task getTask() {
         return mTask;
     }
 }
diff --git a/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java b/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java
index 5bb4c5a..c742be3 100644
--- a/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java
+++ b/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java
@@ -28,6 +28,10 @@
 import android.util.FloatProperty;
 import android.view.View;
 import android.view.ViewDebug;
+import android.view.animation.AlphaAnimation;
+import android.view.animation.Animation;
+import android.view.animation.AnimationSet;
+import android.view.animation.LayoutAnimationController;
 import android.widget.FrameLayout;
 
 import androidx.annotation.NonNull;
@@ -69,6 +73,8 @@
                 }
             };
     private static final long CROSSFADE_DURATION = 300;
+    private static final long LAYOUT_ITEM_ANIMATE_IN_DURATION = 150;
+    private static final long LAYOUT_ITEM_ANIMATE_IN_DELAY_BETWEEN = 40;
     private static final long ITEM_ANIMATE_OUT_DURATION = 150;
     private static final long ITEM_ANIMATE_OUT_DELAY_BETWEEN = 40;
     private static final float ITEM_ANIMATE_OUT_TRANSLATION_X_RATIO = .25f;
@@ -84,6 +90,7 @@
     private final TaskListLoader mTaskLoader;
     private final TaskAdapter mTaskAdapter;
     private final TaskActionController mTaskActionController;
+    private final LayoutAnimationController mLayoutAnimation;
 
     private RecentsToActivityHelper mActivityHelper;
     private RecyclerView mTaskRecyclerView;
@@ -99,6 +106,7 @@
         mTaskAdapter = new TaskAdapter(mTaskLoader);
         mTaskActionController = new TaskActionController(mTaskLoader, mTaskAdapter);
         mTaskAdapter.setActionController(mTaskActionController);
+        mLayoutAnimation = createLayoutAnimation();
     }
 
     @Override
@@ -112,6 +120,7 @@
             ItemTouchHelper helper = new ItemTouchHelper(
                     new TaskSwipeCallback(mTaskActionController));
             helper.attachToRecyclerView(mTaskRecyclerView);
+            mTaskRecyclerView.setLayoutAnimation(mLayoutAnimation);
 
             mEmptyView = findViewById(R.id.recent_task_empty_view);
             mContentView = findViewById(R.id.recent_task_content_view);
@@ -131,7 +140,6 @@
         }
     }
 
-
     @Override
     public void setEnabled(boolean enabled) {
         super.setEnabled(enabled);
@@ -157,10 +165,13 @@
      * becomes visible.
      */
     public void onBeginTransitionToOverview() {
+        mTaskRecyclerView.scheduleLayoutAnimation();
+        mTaskAdapter.setIsShowingLoadingUi(true);
+        mTaskAdapter.notifyDataSetChanged();
         // Load any task changes
         mTaskLoader.loadTaskList(tasks -> {
-            // TODO: Put up some loading UI while task content is loading. May have to do something
-            // smarter when animating from app to overview.
+            mTaskAdapter.setIsShowingLoadingUi(false);
+            // TODO: Animate the loading UI out and the loaded data in.
             mTaskAdapter.notifyDataSetChanged();
         });
     }
@@ -322,4 +333,18 @@
                     }
                 });
     }
+
+    private static LayoutAnimationController createLayoutAnimation() {
+        AnimationSet anim = new AnimationSet(false /* shareInterpolator */);
+
+        Animation alphaAnim = new AlphaAnimation(0, 1);
+        alphaAnim.setDuration(LAYOUT_ITEM_ANIMATE_IN_DURATION);
+        anim.addAnimation(alphaAnim);
+
+        LayoutAnimationController layoutAnim = new LayoutAnimationController(anim);
+        layoutAnim.setDelay(
+                (float) LAYOUT_ITEM_ANIMATE_IN_DELAY_BETWEEN / LAYOUT_ITEM_ANIMATE_IN_DURATION);
+
+        return layoutAnim;
+    }
 }
diff --git a/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java b/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
index 895485d..6034791 100644
--- a/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
+++ b/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
@@ -32,7 +32,6 @@
 
 import com.android.launcher3.tapl.LauncherInstrumentation;
 import com.android.launcher3.tapl.TestHelpers;
-import com.android.systemui.shared.system.QuickStepContract;
 
 import org.junit.Assert;
 import org.junit.rules.TestRule;
@@ -78,9 +77,9 @@
                 @Override
                 public void evaluate() throws Throwable {
                     final Context context = getInstrumentation().getContext();
-                    final String prevOverlayPkg = QuickStepContract.isGesturalMode(context)
+                    final String prevOverlayPkg = LauncherInstrumentation.isGesturalMode(context)
                             ? NAV_BAR_MODE_GESTURAL_OVERLAY
-                            : QuickStepContract.isSwipeUpMode(context)
+                            : LauncherInstrumentation.isSwipeUpMode(context)
                                     ? NAV_BAR_MODE_2BUTTON_OVERLAY
                                     : NAV_BAR_MODE_3BUTTON_OVERLAY;
                     final LauncherInstrumentation.NavigationModel originalMode =
@@ -150,4 +149,4 @@
             return base;
         }
     }
-}
\ No newline at end of file
+}
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 252cae1..ec63e35 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -44,7 +44,7 @@
         <item name="widgetsTheme">@style/WidgetContainerTheme</item>
         <item name="folderDotColor">?android:attr/colorPrimary</item>
         <item name="folderIconBorderColor">?android:attr/colorPrimary</item>
-        <item name="loadingIconColor">#FFF</item>
+        <item name="loadingIconColor">#CCFFFFFF</item>
 
         <item name="android:windowTranslucentStatus">false</item>
         <item name="android:windowTranslucentNavigation">false</item>
@@ -82,7 +82,7 @@
         <item name="folderDotColor">#FF464646</item>
         <item name="folderIconBorderColor">#FF80868B</item>
         <item name="isMainColorDark">true</item>
-        <item name="loadingIconColor">#000</item>
+        <item name="loadingIconColor">#99FFFFFF</item>
     </style>
 
     <style name="LauncherTheme.Dark.DarkText" parent="@style/LauncherTheme.Dark">
diff --git a/src/com/android/launcher3/graphics/PlaceHolderIconDrawable.java b/src/com/android/launcher3/graphics/PlaceHolderIconDrawable.java
index 5f2fb59..23745cb 100644
--- a/src/com/android/launcher3/graphics/PlaceHolderIconDrawable.java
+++ b/src/com/android/launcher3/graphics/PlaceHolderIconDrawable.java
@@ -15,6 +15,8 @@
  */
 package com.android.launcher3.graphics;
 
+import static androidx.core.graphics.ColorUtils.compositeColors;
+
 import android.content.Context;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
@@ -47,7 +49,8 @@
         super(b, iconColor);
 
         mProgressPath = progressPath;
-        mPaint.setColor(Themes.getAttrColor(context, R.attr.loadingIconColor));
+        mPaint.setColor(compositeColors(
+                Themes.getAttrColor(context, R.attr.loadingIconColor), iconColor));
     }
 
     @Override
diff --git a/tests/AndroidManifest-common.xml b/tests/AndroidManifest-common.xml
index 46b463b..089d672 100644
--- a/tests/AndroidManifest-common.xml
+++ b/tests/AndroidManifest-common.xml
@@ -97,5 +97,13 @@
                 <category android:name="android.intent.category.LAUNCHER_APP" />
             </intent-filter>
         </activity>
+        <activity
+            android:name="com.android.launcher3.testcomponent.BaseTestingActivity"
+            android:label="LauncherTestApp">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
     </application>
 </manifest>
diff --git a/tests/src/com/android/launcher3/testcomponent/BaseTestingActivity.java b/tests/src/com/android/launcher3/testcomponent/BaseTestingActivity.java
index 904590c..9c6d102 100644
--- a/tests/src/com/android/launcher3/testcomponent/BaseTestingActivity.java
+++ b/tests/src/com/android/launcher3/testcomponent/BaseTestingActivity.java
@@ -20,6 +20,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.graphics.Color;
 import android.os.Bundle;
 import android.util.TypedValue;
 import android.view.View;
@@ -63,6 +64,7 @@
         mView = new LinearLayout(this);
         mView.setPadding(mMargin, mMargin, mMargin, mMargin);
         mView.setOrientation(LinearLayout.VERTICAL);
+        mView.setBackgroundColor(Color.BLUE);
         setContentView(mView);
 
         registerReceiver(mCommandReceiver, new IntentFilter(mAction + SUFFIX_COMMAND));
diff --git a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
index c2e6749..e11e62e 100644
--- a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
+++ b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
@@ -330,9 +330,7 @@
     public void testDragAppIcon() throws Throwable {
         try {
             TestProtocol.sDebugTracing = true;
-            LauncherActivityInfo settingsApp = getSettingsApp();
-
-            final String appName = settingsApp.getLabel().toString();
+            final String appName = "LauncherTestApp";
             // 1. Open all apps and wait for load complete.
             // 2. Drag icon to homescreen.
             // 3. Verify that the icon works on homescreen.
@@ -341,7 +339,7 @@
                     getAppIcon(appName).
                     dragToWorkspace().
                     getWorkspaceAppIcon(appName).
-                    launch(settingsApp.getComponentName().getPackageName());
+                    launch(getInstrumentation().getContext().getPackageName());
         } finally {
             TestProtocol.sDebugTracing = false;
         }
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index c72f7d0..3a45e93 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -16,12 +16,16 @@
 
 package com.android.launcher3.tapl;
 
+import static com.android.launcher3.TestProtocol.BACKGROUND_APP_STATE_ORDINAL;
+import static com.android.launcher3.TestProtocol.NORMAL_STATE_ORDINAL;
+
 import android.app.ActivityManager;
 import android.app.Instrumentation;
 import android.app.UiAutomation;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.pm.PackageManager;
+import android.content.res.Resources;
 import android.graphics.Point;
 import android.net.Uri;
 import android.os.Build;
@@ -172,11 +176,11 @@
             // Workaround, use constructed context because both the instrumentation context and the
             // app context are not constructed with resources that take overlays into account
             final Context ctx = baseContext.createPackageContext("android", 0);
-            if (QuickStepContract.isGesturalMode(ctx)) {
+            if (isGesturalMode(ctx)) {
                 return NavigationModel.ZERO_BUTTON;
-            } else if (QuickStepContract.isSwipeUpMode(ctx)) {
+            } else if (isSwipeUpMode(ctx)) {
                 return NavigationModel.TWO_BUTTON;
-            } else if (QuickStepContract.isLegacyMode(ctx)) {
+            } else if (isLegacyMode(ctx)) {
                 return NavigationModel.THREE_BUTTON;
             } else {
                 fail("Can't detect navigation mode");
@@ -343,18 +347,33 @@
                 log(action = "0-button: already in workspace");
             } else if (hasLauncherObject(OVERVIEW_RES_ID)) {
                 log(action = "0-button: from overview");
-                mDevice.pressHome();
+                final UiObject2 navBar = waitForSystemUiObject("navigation_bar_frame");
+
+                swipe(
+                        navBar.getVisibleBounds().centerX(), navBar.getVisibleBounds().centerY(),
+                        navBar.getVisibleBounds().centerX(), 0,
+                        NORMAL_STATE_ORDINAL, ZERO_BUTTON_STEPS_FROM_BACKGROUND_TO_HOME);
             } else if (hasLauncherObject(WIDGETS_RES_ID)) {
                 log(action = "0-button: from widgets");
                 mDevice.pressHome();
             } else if (hasLauncherObject(APPS_RES_ID)) {
                 log(action = "0-button: from all apps");
-                mDevice.pressHome();
+                final UiObject2 navBar = waitForSystemUiObject("navigation_bar_frame");
+
+                swipe(
+                        navBar.getVisibleBounds().centerX(), navBar.getVisibleBounds().centerY(),
+                        navBar.getVisibleBounds().centerX(), 0,
+                        NORMAL_STATE_ORDINAL, ZERO_BUTTON_STEPS_FROM_BACKGROUND_TO_HOME);
             } else {
                 log(action = "0-button: from another app");
                 assertTrue("Launcher is visible, don't know how to go home",
                         !mDevice.hasObject(By.pkg(getLauncherPackageName())));
-                mDevice.pressHome();
+                final UiObject2 navBar = waitForSystemUiObject("navigation_bar_frame");
+
+                swipe(
+                        navBar.getVisibleBounds().centerX(), navBar.getVisibleBounds().centerY(),
+                        navBar.getVisibleBounds().centerX(), 0,
+                        BACKGROUND_APP_STATE_ORDINAL, ZERO_BUTTON_STEPS_FROM_BACKGROUND_TO_HOME);
             }
         } else {
             log(action = "clicking home button");
@@ -607,6 +626,46 @@
         }
     }
 
+    public static boolean isGesturalMode(Context context) {
+        return QuickStepContract.isGesturalMode(getCurrentInteractionMode(context));
+    }
+
+    public static boolean isSwipeUpMode(Context context) {
+        return QuickStepContract.isSwipeUpMode(getCurrentInteractionMode(context));
+    }
+
+    public static boolean isLegacyMode(Context context) {
+        return QuickStepContract.isLegacyMode(getCurrentInteractionMode(context));
+    }
+
+    private static int getCurrentInteractionMode(Context context) {
+        return getSystemIntegerRes(context, "config_navBarInteractionMode");
+    }
+
+    private static int getSystemIntegerRes(Context context, String resName) {
+        Resources res = context.getResources();
+        int resId = res.getIdentifier(resName, "integer", "android");
+
+        if (resId != 0) {
+            return res.getInteger(resId);
+        } else {
+            Log.e(TAG, "Failed to get system resource ID. Incompatible framework version?");
+            return -1;
+        }
+    }
+
+    private static int getSystemDimensionResId(Context context, String resName) {
+        Resources res = context.getResources();
+        int resId = res.getIdentifier(resName, "dimen", "android");
+
+        if (resId != 0) {
+            return resId;
+        } else {
+            Log.e(TAG, "Failed to get system resource ID. Incompatible framework version?");
+            return -1;
+        }
+    }
+
     static void sleep(int duration) {
         try {
             Thread.sleep(duration);
@@ -616,8 +675,10 @@
 
     int getEdgeSensitivityWidth() {
         try {
-            return QuickStepContract.getEdgeSensitivityWidth(
-                    mInstrumentation.getTargetContext().createPackageContext("android", 0)) + 1;
+            final Context context = mInstrumentation.getTargetContext().createPackageContext(
+                    "android", 0);
+            return context.getResources().getDimensionPixelSize(
+                    getSystemDimensionResId(context, "config_backGestureInset")) + 1;
         } catch (PackageManager.NameNotFoundException e) {
             fail("Can't get edge sensitivity: " + e);
             return 0;