Preload overview activity in background

To reduce the jank when swiping-up the overview which is not
ready yet, we restart the overview activity if the process
was died and update its configuration if there is change.

Bug: 127350205
Test: manual - Enable 2-button or gesture navigation.
      Swipe-up overview after:
      Case 1: Other app is in foreground and kill the process
              of launcher.
      Case 2: Change configuration, e.g. font size, language.

Change-Id: Ia6e365cc0faf3765781484d040bdddd4e10a2650
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
index 9353c04..bf9d531 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
@@ -43,6 +43,8 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.pm.ActivityInfo;
+import android.content.res.Configuration;
 import android.graphics.Point;
 import android.graphics.RectF;
 import android.graphics.Region;
@@ -74,6 +76,7 @@
 import com.android.launcher3.compat.UserManagerCompat;
 import com.android.launcher3.logging.EventLogArray;
 import com.android.launcher3.logging.UserEventDispatcher;
+import com.android.launcher3.model.AppLaunchTracker;
 import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.util.LooperExecutor;
 import com.android.launcher3.util.UiThreadHelper;
@@ -92,6 +95,7 @@
 import com.android.systemui.shared.recents.IOverviewProxy;
 import com.android.systemui.shared.recents.ISystemUiProxy;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.shared.system.BackgroundExecutor;
 import com.android.systemui.shared.system.InputChannelCompat.InputEventReceiver;
 import com.android.systemui.shared.system.InputConsumerController;
 import com.android.systemui.shared.system.InputMonitorCompat;
@@ -153,6 +157,7 @@
                     .asInterface(bundle.getBinder(KEY_EXTRA_SYSUI_PROXY));
             MAIN_THREAD_EXECUTOR.execute(TouchInteractionService.this::initInputMonitor);
             MAIN_THREAD_EXECUTOR.execute(TouchInteractionService.this::onSystemUiProxySet);
+            MAIN_THREAD_EXECUTOR.execute(() -> preloadOverview(true /* fromInit */));
         }
 
         @Override
@@ -688,6 +693,54 @@
         }
     }
 
+    private void preloadOverview(boolean fromInit) {
+        if (!mIsUserUnlocked) {
+            return;
+        }
+
+        final ActivityControlHelper<BaseDraggingActivity> activityControl =
+                mOverviewComponentObserver.getActivityControlHelper();
+        if (activityControl.getCreatedActivity() == null) {
+            // Make sure that UI states will be initialized.
+            activityControl.createActivityInitListener((activity, wasVisible) -> {
+                AppLaunchTracker.INSTANCE.get(activity);
+                return false;
+            }).register();
+        } else if (fromInit) {
+            // The activity has been created before the initialization of overview service. It is
+            // usually happens when booting or launcher is the top activity, so we should already
+            // have the latest state.
+            return;
+        }
+
+        // Pass null animation handler to indicate this start is preload.
+        BackgroundExecutor.get().submit(
+                () -> ActivityManagerWrapper.getInstance().startRecentsActivity(
+                        mOverviewComponentObserver.getOverviewIntentIgnoreSysUiState(),
+                        null /* assistDataReceiver */, null /* animationHandler */,
+                        null /* resultCallback */, null /* resultCallbackHandler */));
+    }
+
+    @Override
+    public void onConfigurationChanged(Configuration newConfig) {
+        if (!mIsUserUnlocked) {
+            return;
+        }
+        final ActivityControlHelper activityControl =
+                mOverviewComponentObserver.getActivityControlHelper();
+        final BaseDraggingActivity activity = activityControl.getCreatedActivity();
+        if (activity == null || activity.isStarted()) {
+            // We only care about the existing background activity.
+            return;
+        }
+        if (mOverviewComponentObserver.canHandleConfigChanges(activity.getComponentName(),
+                activity.getResources().getConfiguration().diff(newConfig))) {
+            return;
+        }
+
+        preloadOverview(false /* fromInit */);
+    }
+
     @Override
     protected void dump(FileDescriptor fd, PrintWriter pw, String[] rawArgs) {
         if (rawArgs.length > 0 && Utilities.IS_DEBUG_DEVICE) {
diff --git a/quickstep/src/com/android/quickstep/OverviewComponentObserver.java b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
index 0738aff..4a2ed3a 100644
--- a/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
+++ b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
@@ -29,11 +29,15 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
+import android.util.SparseIntArray;
 
 import com.android.systemui.shared.system.PackageManagerWrapper;
 
 import java.util.ArrayList;
+import java.util.Objects;
 
 /**
  * Class to keep track of the current overview component based off user preferences and app updates
@@ -53,22 +57,41 @@
         }
     };
     private final Context mContext;
-    private final ComponentName mMyHomeComponent;
+    private final Intent mCurrentHomeIntent;
+    private final Intent mMyHomeIntent;
+    private final Intent mFallbackIntent;
+    private final SparseIntArray mConfigChangesMap = new SparseIntArray();
     private String mUpdateRegisteredPackage;
     private ActivityControlHelper mActivityControlHelper;
     private Intent mOverviewIntent;
-    private Intent mHomeIntent;
     private int mSystemUiStateFlags;
     private boolean mIsHomeAndOverviewSame;
+    private boolean mIsDefaultHome;
 
     public OverviewComponentObserver(Context context) {
         mContext = context;
 
-        Intent myHomeIntent = new Intent(Intent.ACTION_MAIN)
+        mCurrentHomeIntent = new Intent(Intent.ACTION_MAIN)
                 .addCategory(Intent.CATEGORY_HOME)
-                .setPackage(mContext.getPackageName());
-        ResolveInfo info = context.getPackageManager().resolveActivity(myHomeIntent, 0);
-        mMyHomeComponent = new ComponentName(context.getPackageName(), info.activityInfo.name);
+                .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        mMyHomeIntent = new Intent(mCurrentHomeIntent).setPackage(mContext.getPackageName());
+        ResolveInfo info = context.getPackageManager().resolveActivity(mMyHomeIntent, 0);
+        ComponentName myHomeComponent =
+                new ComponentName(context.getPackageName(), info.activityInfo.name);
+        mMyHomeIntent.setComponent(myHomeComponent);
+        mConfigChangesMap.append(myHomeComponent.hashCode(), info.activityInfo.configChanges);
+
+        ComponentName fallbackComponent = new ComponentName(mContext, RecentsActivity.class);
+        mFallbackIntent = new Intent(Intent.ACTION_MAIN)
+                .addCategory(Intent.CATEGORY_DEFAULT)
+                .setComponent(fallbackComponent)
+                .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+        try {
+            ActivityInfo fallbackInfo = context.getPackageManager().getActivityInfo(
+                    mFallbackIntent.getComponent(), 0 /* flags */);
+            mConfigChangesMap.append(fallbackComponent.hashCode(), fallbackInfo.configChanges);
+        } catch (PackageManager.NameNotFoundException ignored) { /* Impossible */ }
 
         mContext.registerReceiver(mUserPreferenceChangeReceiver,
                 new IntentFilter(ACTION_PREFERRED_ACTIVITY_CHANGED));
@@ -92,17 +115,14 @@
         ComponentName defaultHome = PackageManagerWrapper.getInstance()
                 .getHomeActivities(new ArrayList<>());
 
-        final String overviewIntentCategory;
-        ComponentName overviewComponent;
-        mHomeIntent = null;
-
-        if ((mSystemUiStateFlags & SYSUI_STATE_HOME_DISABLED) == 0 &&
-                (defaultHome == null || mMyHomeComponent.equals(defaultHome))) {
+        mIsDefaultHome = Objects.equals(mMyHomeIntent.getComponent(), defaultHome);
+        if ((mSystemUiStateFlags & SYSUI_STATE_HOME_DISABLED) == 0
+                && (defaultHome == null || mIsDefaultHome)) {
             // User default home is same as out home app. Use Overview integrated in Launcher.
-            overviewComponent = mMyHomeComponent;
             mActivityControlHelper = new LauncherActivityControllerHelper();
             mIsHomeAndOverviewSame = true;
-            overviewIntentCategory = Intent.CATEGORY_HOME;
+            mOverviewIntent = mMyHomeIntent;
+            mCurrentHomeIntent.setComponent(mMyHomeIntent.getComponent());
 
             if (mUpdateRegisteredPackage != null) {
                 // Remove any update listener as we don't care about other packages.
@@ -111,14 +131,11 @@
             }
         } else {
             // The default home app is a different launcher. Use the fallback Overview instead.
-            overviewComponent = new ComponentName(mContext, RecentsActivity.class);
             mActivityControlHelper = new FallbackActivityControllerHelper();
             mIsHomeAndOverviewSame = false;
-            overviewIntentCategory = Intent.CATEGORY_DEFAULT;
+            mOverviewIntent = mFallbackIntent;
+            mCurrentHomeIntent.setComponent(defaultHome);
 
-            mHomeIntent = new Intent(Intent.ACTION_MAIN)
-                    .addCategory(Intent.CATEGORY_HOME)
-                    .setComponent(defaultHome);
             // User's default home app can change as a result of package updates of this app (such
             // as uninstalling the app or removing the "Launcher" feature in an update).
             // Listen for package updates of this app (and remove any previously attached
@@ -138,14 +155,6 @@
                         ACTION_PACKAGE_REMOVED));
             }
         }
-
-        mOverviewIntent = new Intent(Intent.ACTION_MAIN)
-                .addCategory(overviewIntentCategory)
-                .setComponent(overviewComponent)
-                .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-        if (mHomeIntent == null) {
-            mHomeIntent = mOverviewIntent;
-        }
     }
 
     /**
@@ -161,6 +170,32 @@
     }
 
     /**
+     * @return {@code true} if the overview component is able to handle the configuration changes.
+     */
+    boolean canHandleConfigChanges(ComponentName component, int changes) {
+        final int orientationChange =
+                ActivityInfo.CONFIG_ORIENTATION | ActivityInfo.CONFIG_SCREEN_SIZE;
+        if ((changes & orientationChange) == orientationChange) {
+            // This is just an approximate guess for simple orientation change because the changes
+            // may contain non-public bits (e.g. window configuration).
+            return true;
+        }
+
+        int configMask = mConfigChangesMap.get(component.hashCode());
+        return configMask != 0 && (~configMask & changes) == 0;
+    }
+
+    /**
+     * Get the intent for overview activity. It is used when lockscreen is shown and home was died
+     * in background, we still want to restart the one that will be used after unlock.
+     *
+     * @return the overview intent
+     */
+    Intent getOverviewIntentIgnoreSysUiState() {
+        return mIsDefaultHome ? mMyHomeIntent : mOverviewIntent;
+    }
+
+    /**
      * Get the current intent for going to the overview activity.
      *
      * @return the overview intent
@@ -173,7 +208,7 @@
      * Get the current intent for going to the home activity.
      */
     public Intent getHomeIntent() {
-        return mHomeIntent;
+        return mCurrentHomeIntent;
     }
 
     /**