Start process of next activity with top priority in advance

In common cases, to resume the next activity we need to wait for the
current one to be paused. Since starting a process for activity is
asynchronous, if we already know the process of next activity has not
started yet, we can start the process earlier so the time waiting for
the pause to complete can be saved.

Also if the launching activity is going to be the top app, we can set
the top schedule group right after its process is started so the start
time before actually launching the activity can be improved.

Although before the current activity is paused, the next top activity
may still change and results some empty processes. That should not be
a common case and the process is still useful when going back the stack,
and the empty background processes are easier to be reclaimed.

Bug: 123043091
Test: AppLaunchTest
Test: Launch calculator from launcher, the event log am_proc_start will
      show "pre-top-activity".
Test: Cold launch a top activity, the system log should not show
      "not expected top priority".
Test: Use startActivities to start serveral activities in a sequence.
      Check "adb shell cat /proc/$pid/task/$pid/cgroup" for each process.
      Only the last one has top-app, others are background.

Change-Id: I9601b66e7cc0855fd7c2b573ded31fcf8d0711ae
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index 0b25b2e..1f3c963 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -318,7 +318,7 @@
 
     /** Starts a given process. */
     public abstract void startProcess(String processName, ApplicationInfo info,
-            boolean knownToBeDead, String hostingType, ComponentName hostingName);
+            boolean knownToBeDead, boolean isTop, String hostingType, ComponentName hostingName);
 
     /** Starts up the starting activity process for debugging if needed.
      * This function needs to be called synchronously from WindowManager context so the caller
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index 74c89d6..b535e8d 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -511,6 +511,7 @@
      * @param appDataDir null-ok the data directory of the app.
      * @param invokeWith null-ok the command to invoke with.
      * @param packageName null-ok the name of the package this process belongs to.
+     * @param isTopApp whether the process starts for high priority application.
      *
      * @param zygoteArgs Additional arguments to supply to the zygote process.
      * @return An object that describes the result of the attempt to start the process.
@@ -530,11 +531,12 @@
                                            @Nullable String appDataDir,
                                            @Nullable String invokeWith,
                                            @Nullable String packageName,
+                                           boolean isTopApp,
                                            @Nullable String[] zygoteArgs) {
         return ZYGOTE_PROCESS.start(processClass, niceName, uid, gid, gids,
                     runtimeFlags, mountExternal, targetSdkVersion, seInfo,
                     abi, instructionSet, appDataDir, invokeWith, packageName,
-                    /*useUsapPool=*/ true, zygoteArgs);
+                    /*useUsapPool=*/ true, isTopApp, zygoteArgs);
     }
 
     /** @hide */
@@ -554,7 +556,7 @@
         return WebViewZygote.getProcess().start(processClass, niceName, uid, gid, gids,
                     runtimeFlags, mountExternal, targetSdkVersion, seInfo,
                     abi, instructionSet, appDataDir, invokeWith, packageName,
-                    /*useUsapPool=*/ false, zygoteArgs);
+                    /*useUsapPool=*/ false, /*isTopApp=*/ false, zygoteArgs);
     }
 
     /**
diff --git a/core/java/android/os/ZygoteProcess.java b/core/java/android/os/ZygoteProcess.java
index 32976a7..16b2d74 100644
--- a/core/java/android/os/ZygoteProcess.java
+++ b/core/java/android/os/ZygoteProcess.java
@@ -307,6 +307,7 @@
      * @param invokeWith null-ok the command to invoke with.
      * @param packageName null-ok the name of the package this process belongs to.
      * @param zygoteArgs Additional arguments to supply to the zygote process.
+     * @param isTopApp Whether the process starts for high priority application.
      *
      * @return An object that describes the result of the attempt to start the process.
      * @throws RuntimeException on fatal start failure
@@ -323,6 +324,7 @@
                                                   @Nullable String invokeWith,
                                                   @Nullable String packageName,
                                                   boolean useUsapPool,
+                                                  boolean isTopApp,
                                                   @Nullable String[] zygoteArgs) {
         // TODO (chriswailes): Is there a better place to check this value?
         if (fetchUsapPoolEnabledPropWithMinInterval()) {
@@ -333,7 +335,7 @@
             return startViaZygote(processClass, niceName, uid, gid, gids,
                     runtimeFlags, mountExternal, targetSdkVersion, seInfo,
                     abi, instructionSet, appDataDir, invokeWith, /*startChildZygote=*/ false,
-                    packageName, useUsapPool, zygoteArgs);
+                    packageName, useUsapPool, isTopApp, zygoteArgs);
         } catch (ZygoteStartFailedEx ex) {
             Log.e(LOG_TAG,
                     "Starting VM process through Zygote failed");
@@ -534,6 +536,7 @@
      * @param startChildZygote Start a sub-zygote. This creates a new zygote process
      * that has its state cloned from this zygote process.
      * @param packageName null-ok the name of the package this process belongs to.
+     * @param isTopApp Whether the process starts for high priority application.
      * @param extraArgs Additional arguments to supply to the zygote process.
      * @return An object that describes the result of the attempt to start the process.
      * @throws ZygoteStartFailedEx if process start failed for any reason
@@ -552,6 +555,7 @@
                                                       boolean startChildZygote,
                                                       @Nullable String packageName,
                                                       boolean useUsapPool,
+                                                      boolean isTopApp,
                                                       @Nullable String[] extraArgs)
                                                       throws ZygoteStartFailedEx {
         ArrayList<String> argsForZygote = new ArrayList<>();
@@ -623,6 +627,10 @@
             argsForZygote.add("--package-name=" + packageName);
         }
 
+        if (isTopApp) {
+            argsForZygote.add(Zygote.START_AS_TOP_APP_ARG);
+        }
+
         argsForZygote.add(processClass);
 
         if (extraArgs != null) {
@@ -1142,7 +1150,7 @@
                     gids, runtimeFlags, 0 /* mountExternal */, 0 /* targetSdkVersion */, seInfo,
                     abi, instructionSet, null /* appDataDir */, null /* invokeWith */,
                     true /* startChildZygote */, null /* packageName */,
-                    false /* useUsapPool */, extraArgs);
+                    false /* useUsapPool */, false /* isTopApp */, extraArgs);
         } catch (ZygoteStartFailedEx ex) {
             throw new RuntimeException("Starting child-zygote through Zygote failed", ex);
         }
diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java
index 10aee96..7c51871 100644
--- a/core/java/com/android/internal/os/Zygote.java
+++ b/core/java/com/android/internal/os/Zygote.java
@@ -130,6 +130,9 @@
     /** Number of bytes sent to the Zygote over USAP pipes or the pool event FD */
     static final int USAP_MANAGEMENT_MESSAGE_BYTES = 8;
 
+    /** Make the new process have top application priority. */
+    public static final String START_AS_TOP_APP_ARG = "--is-top-app";
+
     /**
      * An extraArg passed when a zygote process is forking a child-zygote, specifying a name
      * in the abstract socket namespace. This socket name is what the new child zygote
@@ -232,6 +235,7 @@
      * new zygote process.
      * @param instructionSet null-ok the instruction set to use.
      * @param appDataDir null-ok the data directory of the app.
+     * @param isTopApp true if the process is for top (high priority) application.
      *
      * @return 0 if this is the child, pid of the child
      * if this is the parent, or -1 on error.
@@ -239,12 +243,12 @@
     static int forkAndSpecialize(int uid, int gid, int[] gids, int runtimeFlags,
             int[][] rlimits, int mountExternal, String seInfo, String niceName, int[] fdsToClose,
             int[] fdsToIgnore, boolean startChildZygote, String instructionSet, String appDataDir,
-            int targetSdkVersion) {
+            int targetSdkVersion, boolean isTopApp) {
         ZygoteHooks.preFork();
 
         int pid = nativeForkAndSpecialize(
                 uid, gid, gids, runtimeFlags, rlimits, mountExternal, seInfo, niceName, fdsToClose,
-                fdsToIgnore, startChildZygote, instructionSet, appDataDir);
+                fdsToIgnore, startChildZygote, instructionSet, appDataDir, isTopApp);
         // Enable tracing as soon as possible for the child process.
         if (pid == 0) {
             Zygote.disableExecuteOnly(targetSdkVersion);
@@ -264,7 +268,7 @@
     private static native int nativeForkAndSpecialize(int uid, int gid, int[] gids,
             int runtimeFlags, int[][] rlimits, int mountExternal, String seInfo, String niceName,
             int[] fdsToClose, int[] fdsToIgnore, boolean startChildZygote, String instructionSet,
-            String appDataDir);
+            String appDataDir, boolean isTopApp);
 
     /**
      * Specialize an unspecialized app process.  The current VM must have been started
@@ -286,12 +290,13 @@
      * new zygote process.
      * @param instructionSet null-ok  The instruction set to use.
      * @param appDataDir null-ok  The data directory of the app.
+     * @param isTopApp  True if the process is for top (high priority) application.
      */
     private static void specializeAppProcess(int uid, int gid, int[] gids, int runtimeFlags,
             int[][] rlimits, int mountExternal, String seInfo, String niceName,
-            boolean startChildZygote, String instructionSet, String appDataDir) {
+            boolean startChildZygote, String instructionSet, String appDataDir, boolean isTopApp) {
         nativeSpecializeAppProcess(uid, gid, gids, runtimeFlags, rlimits, mountExternal, seInfo,
-                                 niceName, startChildZygote, instructionSet, appDataDir);
+                niceName, startChildZygote, instructionSet, appDataDir, isTopApp);
 
         // Enable tracing as soon as possible for the child process.
         Trace.setTracingEnabled(true, runtimeFlags);
@@ -313,7 +318,7 @@
 
     private static native void nativeSpecializeAppProcess(int uid, int gid, int[] gids,
             int runtimeFlags, int[][] rlimits, int mountExternal, String seInfo, String niceName,
-            boolean startChildZygote, String instructionSet, String appDataDir);
+            boolean startChildZygote, String instructionSet, String appDataDir, boolean isTopApp);
 
     /**
      * Called to do any initialization before starting an application.
@@ -642,7 +647,7 @@
             specializeAppProcess(args.mUid, args.mGid, args.mGids,
                                  args.mRuntimeFlags, rlimits, args.mMountExternal,
                                  args.mSeInfo, args.mNiceName, args.mStartChildZygote,
-                                 args.mInstructionSet, args.mAppDataDir);
+                                 args.mInstructionSet, args.mAppDataDir, args.mIsTopApp);
 
             disableExecuteOnly(args.mTargetSdkVersion);
 
diff --git a/core/java/com/android/internal/os/ZygoteArguments.java b/core/java/com/android/internal/os/ZygoteArguments.java
index bbb62dd..6d40266 100644
--- a/core/java/com/android/internal/os/ZygoteArguments.java
+++ b/core/java/com/android/internal/os/ZygoteArguments.java
@@ -205,6 +205,11 @@
     int mHiddenApiAccessStatslogSampleRate = -1;
 
     /**
+     * @see Zygote#START_AS_TOP_APP_ARG
+     */
+    boolean mIsTopApp;
+
+    /**
      * Constructs instance and parses args
      *
      * @param args zygote command-line args
@@ -405,6 +410,8 @@
                 mUsapPoolStatusSpecified = true;
                 mUsapPoolEnabled = Boolean.parseBoolean(getAssignmentValue(arg));
                 expectRuntimeArgs = false;
+            } else if (arg.startsWith(Zygote.START_AS_TOP_APP_ARG)) {
+                mIsTopApp = true;
             } else {
                 break;
             }
diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java
index 8b0899d..06a14f0 100644
--- a/core/java/com/android/internal/os/ZygoteConnection.java
+++ b/core/java/com/android/internal/os/ZygoteConnection.java
@@ -252,7 +252,8 @@
         pid = Zygote.forkAndSpecialize(parsedArgs.mUid, parsedArgs.mGid, parsedArgs.mGids,
                 parsedArgs.mRuntimeFlags, rlimits, parsedArgs.mMountExternal, parsedArgs.mSeInfo,
                 parsedArgs.mNiceName, fdsToClose, fdsToIgnore, parsedArgs.mStartChildZygote,
-                parsedArgs.mInstructionSet, parsedArgs.mAppDataDir, parsedArgs.mTargetSdkVersion);
+                parsedArgs.mInstructionSet, parsedArgs.mAppDataDir, parsedArgs.mTargetSdkVersion,
+                parsedArgs.mIsTopApp);
 
         try {
             if (pid == 0) {
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index 0cd3dbf..1776100 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -596,11 +596,25 @@
   }
 }
 
-static void SetSchedulerPolicy(fail_fn_t fail_fn) {
-  errno = -set_sched_policy(0, SP_DEFAULT);
-  if (errno != 0) {
-    fail_fn(CREATE_ERROR("set_sched_policy(0, SP_DEFAULT) failed: %s", strerror(errno)));
+static void SetSchedulerPolicy(fail_fn_t fail_fn, bool is_top_app) {
+  SchedPolicy policy = is_top_app ? SP_TOP_APP : SP_DEFAULT;
+
+  if (is_top_app && cpusets_enabled()) {
+    errno = -set_cpuset_policy(0, policy);
+    if (errno != 0) {
+      fail_fn(CREATE_ERROR("set_cpuset_policy(0, %d) failed: %s", policy, strerror(errno)));
+    }
   }
+
+  errno = -set_sched_policy(0, policy);
+  if (errno != 0) {
+    fail_fn(CREATE_ERROR("set_sched_policy(0, %d) failed: %s", policy, strerror(errno)));
+  }
+
+  // We are going to lose the permission to set scheduler policy during the specialization, so make
+  // sure that we don't cache the fd of cgroup path that may cause sepolicy violation by writing
+  // value to the cached fd directly when creating new thread.
+  DropTaskProfilesResourceCaching();
 }
 
 static int UnmountTree(const char* path) {
@@ -978,7 +992,7 @@
                              jint mount_external, jstring managed_se_info,
                              jstring managed_nice_name, bool is_system_server,
                              bool is_child_zygote, jstring managed_instruction_set,
-                             jstring managed_app_data_dir) {
+                             jstring managed_app_data_dir, bool is_top_app) {
   const char* process_name = is_system_server ? "system_server" : "zygote";
   auto fail_fn = std::bind(ZygoteFailure, env, process_name, managed_nice_name, _1);
   auto extract_fn = std::bind(ExtractJString, env, process_name, managed_nice_name, _1);
@@ -1046,6 +1060,9 @@
   // privileged syscalls used below still need to be accessible in app process.
   SetUpSeccompFilter(uid, is_child_zygote);
 
+  // Must be called before losing the permission to set scheduler policy.
+  SetSchedulerPolicy(fail_fn, is_top_app);
+
   if (setresuid(uid, uid, uid) == -1) {
     fail_fn(CREATE_ERROR("setresuid(%d) failed: %s", uid, strerror(errno)));
   }
@@ -1094,8 +1111,6 @@
 
   SetCapabilities(permitted_capabilities, effective_capabilities, permitted_capabilities, fail_fn);
 
-  SetSchedulerPolicy(fail_fn);
-
   __android_log_close();
   stats_log_close();
 
@@ -1372,7 +1387,7 @@
         jint runtime_flags, jobjectArray rlimits,
         jint mount_external, jstring se_info, jstring nice_name,
         jintArray managed_fds_to_close, jintArray managed_fds_to_ignore, jboolean is_child_zygote,
-        jstring instruction_set, jstring app_data_dir) {
+        jstring instruction_set, jstring app_data_dir, jboolean is_top_app) {
     jlong capabilities = CalculateCapabilities(env, uid, gid, gids, is_child_zygote);
 
     if (UNLIKELY(managed_fds_to_close == nullptr)) {
@@ -1403,7 +1418,8 @@
       SpecializeCommon(env, uid, gid, gids, runtime_flags, rlimits,
                        capabilities, capabilities,
                        mount_external, se_info, nice_name, false,
-                       is_child_zygote == JNI_TRUE, instruction_set, app_data_dir);
+                       is_child_zygote == JNI_TRUE, instruction_set, app_data_dir,
+                       is_top_app == JNI_TRUE);
     }
     return pid;
 }
@@ -1430,7 +1446,7 @@
       SpecializeCommon(env, uid, gid, gids, runtime_flags, rlimits,
                        permitted_capabilities, effective_capabilities,
                        MOUNT_EXTERNAL_DEFAULT, nullptr, nullptr, true,
-                       false, nullptr, nullptr);
+                       false, nullptr, nullptr, /* is_top_app= */ false);
   } else if (pid > 0) {
       // The zygote process checks whether the child process has died or not.
       ALOGI("System server process %d has been created", pid);
@@ -1547,18 +1563,20 @@
  * @param is_child_zygote  If the process is to become a WebViewZygote
  * @param instruction_set  The instruction set expected/requested by the new application
  * @param app_data_dir  Path to the application's data directory
+ * @param is_top_app  If the process is for top (high priority) application
  */
 static void com_android_internal_os_Zygote_nativeSpecializeAppProcess(
     JNIEnv* env, jclass, jint uid, jint gid, jintArray gids,
     jint runtime_flags, jobjectArray rlimits,
     jint mount_external, jstring se_info, jstring nice_name,
-    jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir) {
+    jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir, jboolean is_top_app) {
   jlong capabilities = CalculateCapabilities(env, uid, gid, gids, is_child_zygote);
 
   SpecializeCommon(env, uid, gid, gids, runtime_flags, rlimits,
                    capabilities, capabilities,
                    mount_external, se_info, nice_name, false,
-                   is_child_zygote == JNI_TRUE, instruction_set, app_data_dir);
+                   is_child_zygote == JNI_TRUE, instruction_set, app_data_dir,
+                   is_top_app == JNI_TRUE);
 }
 
 /**
@@ -1724,7 +1742,7 @@
 
 static const JNINativeMethod gMethods[] = {
     { "nativeForkAndSpecialize",
-      "(II[II[[IILjava/lang/String;Ljava/lang/String;[I[IZLjava/lang/String;Ljava/lang/String;)I",
+      "(II[II[[IILjava/lang/String;Ljava/lang/String;[I[IZLjava/lang/String;Ljava/lang/String;Z)I",
       (void *) com_android_internal_os_Zygote_nativeForkAndSpecialize },
     { "nativeForkSystemServer", "(II[II[[IJJ)I",
       (void *) com_android_internal_os_Zygote_nativeForkSystemServer },
@@ -1737,7 +1755,7 @@
     { "nativeForkUsap", "(II[IZ)I",
       (void *) com_android_internal_os_Zygote_nativeForkUsap },
     { "nativeSpecializeAppProcess",
-      "(II[II[[IILjava/lang/String;Ljava/lang/String;ZLjava/lang/String;Ljava/lang/String;)V",
+      "(II[II[[IILjava/lang/String;Ljava/lang/String;ZLjava/lang/String;Ljava/lang/String;Z)V",
       (void *) com_android_internal_os_Zygote_nativeSpecializeAppProcess },
     { "nativeInitNativeState", "(Z)V",
       (void *) com_android_internal_os_Zygote_nativeInitNativeState },
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 8915b9a..66ab511 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -4735,7 +4735,7 @@
         EventLog.writeEvent(EventLogTags.AM_PROC_BOUND, app.userId, app.pid, app.processName);
 
         app.curAdj = app.setAdj = app.verifiedAdj = ProcessList.INVALID_ADJ;
-        app.setCurrentSchedulingGroup(app.setSchedGroup = ProcessList.SCHED_GROUP_DEFAULT);
+        mOomAdjuster.setAttachingSchedGroupLocked(app);
         app.forcingToImportant = null;
         updateProcessForegroundLocked(app, false, 0, false);
         app.hasShownUi = false;
@@ -18294,16 +18294,19 @@
         }
 
         @Override
-        public void startProcess(String processName, ApplicationInfo info,
-                boolean knownToBeDead, String hostingType, ComponentName hostingName) {
+        public void startProcess(String processName, ApplicationInfo info, boolean knownToBeDead,
+                boolean isTop, String hostingType, ComponentName hostingName) {
             try {
                 if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
                     Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "startProcess:"
                             + processName);
                 }
                 synchronized (ActivityManagerService.this) {
+                    // If the process is known as top app, set a hint so when the process is
+                    // started, the top priority can be applied immediately to avoid cpu being
+                    // preempted by other processes before attaching the process of top app.
                     startProcessLocked(processName, info, knownToBeDead, 0 /* intentFlags */,
-                            new HostingRecord(hostingType, hostingName),
+                            new HostingRecord(hostingType, hostingName, isTop),
                             false /* allowWhileBooting */, false /* isolated */,
                             true /* keepIfLarge */);
                 }
diff --git a/services/core/java/com/android/server/am/HostingRecord.java b/services/core/java/com/android/server/am/HostingRecord.java
index 784dde1..6bb5def 100644
--- a/services/core/java/com/android/server/am/HostingRecord.java
+++ b/services/core/java/com/android/server/am/HostingRecord.java
@@ -41,6 +41,8 @@
  * {@link android.content.Context#BIND_EXTERNAL_SERVICE} service. In that case, the packageName
  * and uid in the ApplicationInfo will be set to those of the caller, not of the defining package.
  *
+ * {@code mIsTopApp} will be passed to {@link android.os.Process#start}. So Zygote will initialize
+ * the process with high priority.
  */
 
 public final class HostingRecord {
@@ -53,15 +55,22 @@
     private final int mHostingZygote;
     private final String mDefiningPackageName;
     private final int mDefiningUid;
+    private final boolean mIsTopApp;
 
     public HostingRecord(String hostingType) {
-        this(hostingType, null, REGULAR_ZYGOTE, null, -1);
+        this(hostingType, null /* hostingName */, REGULAR_ZYGOTE, null /* definingPackageName */,
+                -1 /* mDefiningUid */, false /* isTopApp */);
     }
 
     public HostingRecord(String hostingType, ComponentName hostingName) {
         this(hostingType, hostingName, REGULAR_ZYGOTE);
     }
 
+    public HostingRecord(String hostingType, ComponentName hostingName, boolean isTopApp) {
+        this(hostingType, hostingName.toShortString(), REGULAR_ZYGOTE,
+                null /* definingPackageName */, -1 /* mDefiningUid */, isTopApp /* isTopApp */);
+    }
+
     public HostingRecord(String hostingType, String hostingName) {
         this(hostingType, hostingName, REGULAR_ZYGOTE);
     }
@@ -71,16 +80,18 @@
     }
 
     private HostingRecord(String hostingType, String hostingName, int hostingZygote) {
-        this(hostingType, hostingName, hostingZygote, null, -1);
+        this(hostingType, hostingName, hostingZygote, null /* definingPackageName */,
+                -1 /* mDefiningUid */, false /* isTopApp */);
     }
 
     private HostingRecord(String hostingType, String hostingName, int hostingZygote,
-            String definingPackageName, int definingUid) {
+            String definingPackageName, int definingUid, boolean isTopApp) {
         mHostingType = hostingType;
         mHostingName = hostingName;
         mHostingZygote = hostingZygote;
         mDefiningPackageName = definingPackageName;
         mDefiningUid = definingUid;
+        mIsTopApp = isTopApp;
     }
 
     public String getType() {
@@ -91,6 +102,10 @@
         return mHostingName;
     }
 
+    public boolean isTopApp() {
+        return mIsTopApp;
+    }
+
     /**
      * Returns the UID of the package defining the component we want to start. Only valid
      * when {@link #usesAppZygote()} returns true.
@@ -130,7 +145,7 @@
     public static HostingRecord byAppZygote(ComponentName hostingName, String definingPackageName,
             int definingUid) {
         return new HostingRecord("", hostingName.toShortString(), APP_ZYGOTE,
-                definingPackageName, definingUid);
+                definingPackageName, definingUid, false /* isTopApp */);
     }
 
     /**
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 7abfcea..b930e17 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -177,9 +177,12 @@
         adjusterThread.start();
         Process.setThreadGroupAndCpuset(adjusterThread.getThreadId(), THREAD_GROUP_TOP_APP);
         mProcessGroupHandler = new Handler(adjusterThread.getLooper(), msg -> {
-            Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "setProcessGroup");
             final int pid = msg.arg1;
             final int group = msg.arg2;
+            if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
+                Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "setProcessGroup "
+                        + msg.obj + " to " + group);
+            }
             try {
                 setProcessGroup(pid, group);
             } catch (Exception e) {
@@ -1757,7 +1760,7 @@
                         break;
                 }
                 mProcessGroupHandler.sendMessage(mProcessGroupHandler.obtainMessage(
-                        0 /* unused */, app.pid, processGroup));
+                        0 /* unused */, app.pid, processGroup, app.processName));
                 try {
                     if (curSchedGroup == ProcessList.SCHED_GROUP_TOP_APP) {
                         // do nothing if we already switched to RT
@@ -1948,6 +1951,38 @@
         return success;
     }
 
+    @GuardedBy("mService")
+    void setAttachingSchedGroupLocked(ProcessRecord app) {
+        int initialSchedGroup = ProcessList.SCHED_GROUP_DEFAULT;
+        // If the process has been marked as foreground via Zygote.START_FLAG_USE_TOP_APP_PRIORITY,
+        // then verify that the top priority is actually is applied.
+        if (app.hasForegroundActivities()) {
+            String fallbackReason = null;
+            try {
+                // The priority must be the same as how does {@link #applyOomAdjLocked} set for
+                // {@link ProcessList.SCHED_GROUP_TOP_APP}. We don't check render thread because it
+                // is not ready when attaching.
+                if (Process.getProcessGroup(app.pid) == THREAD_GROUP_TOP_APP) {
+                    app.getWindowProcessController().onTopProcChanged();
+                    setThreadPriority(app.pid, TOP_APP_PRIORITY_BOOST);
+                } else {
+                    fallbackReason = "not expected top priority";
+                }
+            } catch (Exception e) {
+                fallbackReason = e.toString();
+            }
+            if (fallbackReason == null) {
+                initialSchedGroup = ProcessList.SCHED_GROUP_TOP_APP;
+            } else {
+                // The real scheduling group will depend on if there is any component of the process
+                // did something during attaching.
+                Slog.w(TAG, "Fallback pre-set sched group to default: " + fallbackReason);
+            }
+        }
+
+        app.setCurrentSchedulingGroup(app.setSchedGroup = initialSchedGroup);
+    }
+
     // ONLY used for unit testing in OomAdjusterTests.java
     @VisibleForTesting
     void maybeUpdateUsageStats(ProcessRecord app, long nowElapsed) {
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index d64a2c2..11f0e3d 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -1803,6 +1803,14 @@
             Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "Start proc: " +
                     app.processName);
             checkSlow(startTime, "startProcess: asking zygote to start proc");
+            final boolean isTopApp = hostingRecord.isTopApp();
+            if (isTopApp) {
+                // Use has-foreground-activities as a temporary hint so the current scheduling
+                // group won't be lost when the process is attaching. The actual state will be
+                // refreshed when computing oom-adj.
+                app.setHasForegroundActivities(true);
+            }
+
             final Process.ProcessStartResult startResult;
             if (hostingRecord.usesWebviewZygote()) {
                 startResult = startWebView(entryPoint,
@@ -1817,13 +1825,13 @@
                         app.processName, uid, uid, gids, runtimeFlags, mountExternal,
                         app.info.targetSdkVersion, seInfo, requiredAbi, instructionSet,
                         app.info.dataDir, null, app.info.packageName,
-                        /*useUsapPool=*/ false,
+                        /*useUsapPool=*/ false, isTopApp,
                         new String[] {PROC_START_SEQ_IDENT + app.startSeq});
             } else {
                 startResult = Process.start(entryPoint,
                         app.processName, uid, uid, gids, runtimeFlags, mountExternal,
                         app.info.targetSdkVersion, seInfo, requiredAbi, instructionSet,
-                        app.info.dataDir, invokeWith, app.info.packageName,
+                        app.info.dataDir, invokeWith, app.info.packageName, isTopApp,
                         new String[] {PROC_START_SEQ_IDENT + app.startSeq});
             }
             checkSlow(startTime, "startProcess: returned from zygote!");
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 1344727..d306b85 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -3549,7 +3549,7 @@
         mStackSupervisor.scheduleRestartTimeout(this);
     }
 
-    private boolean isProcessRunning() {
+    boolean isProcessRunning() {
         WindowProcessController proc = app;
         if (proc == null) {
             proc = mAtmService.mProcessNames.get(processName, info.applicationInfo.uid);
diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java
index 74c3069..156fb98 100644
--- a/services/core/java/com/android/server/wm/ActivityStack.java
+++ b/services/core/java/com/android/server/wm/ActivityStack.java
@@ -2743,7 +2743,7 @@
         final boolean resumeWhilePausing = (next.info.flags & FLAG_RESUME_WHILE_PAUSING) != 0
                 && !lastResumedCanPip;
 
-        boolean pausing = getDisplay().pauseBackStacks(userLeaving, next, false);
+        boolean pausing = display.pauseBackStacks(userLeaving, next, false);
         if (mResumedActivity != null) {
             if (DEBUG_STATES) Slog.d(TAG_STATES,
                     "resumeTopActivityLocked: Pausing " + mResumedActivity);
@@ -2759,6 +2759,13 @@
             if (next.attachedToProcess()) {
                 next.app.updateProcessInfo(false /* updateServiceConnectionActivities */,
                         true /* activityChange */, false /* updateOomAdj */);
+            } else if (!next.isProcessRunning()) {
+                // Since the start-process is asynchronous, if we already know the process of next
+                // activity isn't running, we can start the process earlier to save the time to wait
+                // for the current activity to be paused.
+                final boolean isTop = this == display.getFocusedStack();
+                mService.startProcessAsync(next, false /* knownToBeDead */, isTop,
+                        isTop ? "pre-top-activity" : "pre-activity");
             }
             if (lastResumed != null) {
                 lastResumed.setWillCloseOrEnterPip(true);
@@ -2827,7 +2834,7 @@
         // that the previous one will be hidden soon.  This way it can know
         // to ignore it when computing the desired screen orientation.
         boolean anim = true;
-        final DisplayContent dc = getDisplay().mDisplayContent;
+        final DisplayContent dc = display.mDisplayContent;
         if (prev != null) {
             if (prev.finishing) {
                 if (DEBUG_TRANSITION) Slog.v(TAG_TRANSITION,
@@ -2983,7 +2990,7 @@
                 next.clearOptionsLocked();
                 transaction.setLifecycleStateRequest(
                         ResumeActivityItem.obtain(next.app.getReportedProcState(),
-                                getDisplay().mDisplayContent.isNextTransitionForward()));
+                                dc.isNextTransitionForward()));
                 mService.getLifecycleManager().scheduleTransaction(transaction);
 
                 if (DEBUG_STATES) Slog.d(TAG_STATES, "resumeTopActivityLocked: Resumed "
diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
index c992a69..94800fc 100644
--- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
@@ -980,20 +980,8 @@
             r.notifyUnknownVisibilityLaunched();
         }
 
-        try {
-            if (Trace.isTagEnabled(TRACE_TAG_ACTIVITY_MANAGER)) {
-                Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "dispatchingStartProcess:"
-                        + r.processName);
-            }
-            // Post message to start process to avoid possible deadlock of calling into AMS with the
-            // ATMS lock held.
-            final Message msg = PooledLambda.obtainMessage(
-                    ActivityManagerInternal::startProcess, mService.mAmInternal, r.processName,
-                    r.info.applicationInfo, knownToBeDead, "activity", r.intent.getComponent());
-            mService.mH.sendMessage(msg);
-        } finally {
-            Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
-        }
+        final boolean isTop = andResume && r.isTopRunningActivity();
+        mService.startProcessAsync(r, knownToBeDead, isTop, isTop ? "top-activity" : "activity");
     }
 
     boolean checkStartAnyActivityPermission(Intent intent, ActivityInfo aInfo, String resultWho,
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index f76d0eb..d4b49aa 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -5631,6 +5631,24 @@
         mH.sendMessage(m);
     }
 
+    void startProcessAsync(ActivityRecord activity, boolean knownToBeDead, boolean isTop,
+            String hostingType) {
+        try {
+            if (Trace.isTagEnabled(TRACE_TAG_ACTIVITY_MANAGER)) {
+                Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "dispatchingStartProcess:"
+                        + activity.processName);
+            }
+            // Post message to start process to avoid possible deadlock of calling into AMS with the
+            // ATMS lock held.
+            final Message m = PooledLambda.obtainMessage(ActivityManagerInternal::startProcess,
+                    mAmInternal, activity.processName, activity.info.applicationInfo, knownToBeDead,
+                    isTop, hostingType, activity.intent.getComponent());
+            mH.sendMessage(m);
+        } finally {
+            Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
+        }
+    }
+
     void setBooting(boolean booting) {
         mAmInternal.setBooting(booting);
     }