Merge changes from topic "per-process-app-class-2"

* changes:
  Support per-process Application class (AM and client side)
  Support per-process Application class (package parser)
  Support per-process Application class in <process> tag
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java
index 5750484..77af474 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -1352,7 +1352,9 @@
 
         Application app = null;
 
-        String appClass = mApplicationInfo.className;
+        final String myProcessName = Process.myProcessName();
+        String appClass = mApplicationInfo.getCustomApplicationClassNameForProcess(
+                myProcessName);
         if (forceDefaultAppClass || (appClass == null)) {
             appClass = "android.app.Application";
         }
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 2998f76..76b4e5c 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -33,6 +33,7 @@
 import android.os.Parcelable;
 import android.os.UserHandle;
 import android.os.storage.StorageManager;
+import android.util.ArrayMap;
 import android.util.Printer;
 import android.util.SparseArray;
 import android.util.proto.ProtoOutputStream;
@@ -1533,6 +1534,15 @@
 
     private int mHiddenApiPolicy = HIDDEN_API_ENFORCEMENT_DEFAULT;
 
+    /**
+     * A map from a process name to an (custom) application class name in this package, derived
+     * from the <processes> tag in the app's manifest. This map may not contain all the process
+     * names. Processses not in this map will use the default app class name,
+     * which is {@link #className}, or the default class {@link android.app.Application}.
+     */
+    @Nullable
+    private ArrayMap<String, String> mAppClassNamesByProcess;
+
     public void dump(Printer pw, String prefix) {
         dump(pw, prefix, DUMP_FLAG_ALL);
     }
@@ -1540,8 +1550,14 @@
     /** @hide */
     public void dump(Printer pw, String prefix, int dumpFlags) {
         super.dumpFront(pw, prefix);
-        if ((dumpFlags & DUMP_FLAG_DETAILS) != 0 && className != null) {
-            pw.println(prefix + "className=" + className);
+        if ((dumpFlags & DUMP_FLAG_DETAILS) != 0) {
+            if (className != null) {
+                pw.println(prefix + "className=" + className);
+            }
+            for (int i = 0; i < ArrayUtils.size(mAppClassNamesByProcess); i++) {
+                pw.println(prefix + "  process=" + mAppClassNamesByProcess.keyAt(i)
+                        + " className=" + mAppClassNamesByProcess.valueAt(i));
+            }
         }
         if (permission != null) {
             pw.println(prefix + "permission=" + permission);
@@ -1967,6 +1983,16 @@
         dest.writeInt(nativeHeapZeroInitialized);
         sForBoolean.parcel(requestRawExternalStorageAccess, dest, parcelableFlags);
         dest.writeLong(createTimestamp);
+        if (mAppClassNamesByProcess == null) {
+            dest.writeInt(0);
+        } else {
+            final int size = mAppClassNamesByProcess.size();
+            dest.writeInt(size);
+            for (int i = 0; i < size; i++) {
+                dest.writeString(mAppClassNamesByProcess.keyAt(i));
+                dest.writeString(mAppClassNamesByProcess.valueAt(i));
+            }
+        }
     }
 
     public static final @android.annotation.NonNull Parcelable.Creator<ApplicationInfo> CREATOR
@@ -2055,6 +2081,13 @@
         nativeHeapZeroInitialized = source.readInt();
         requestRawExternalStorageAccess = sForBoolean.unparcel(source);
         createTimestamp = source.readLong();
+        final int allClassesSize = source.readInt();
+        if (allClassesSize > 0) {
+            mAppClassNamesByProcess = new ArrayMap<>(allClassesSize);
+            for (int i = 0; i < allClassesSize; i++) {
+                mAppClassNamesByProcess.put(source.readString(), source.readString());
+            }
+        }
     }
 
     /**
@@ -2538,6 +2571,19 @@
         requestRawExternalStorageAccess = value;
     }
 
+    /**
+     * Replaces {@link #mAppClassNamesByProcess}. This takes over the ownership of the passed map.
+     * Do not modify the argument at the callsite.
+     * {@hide}
+     */
+    public void setAppClassNamesByProcess(@Nullable ArrayMap<String, String> value) {
+        if (ArrayUtils.size(value) == 0) {
+            mAppClassNamesByProcess = null;
+        } else {
+            mAppClassNamesByProcess = value;
+        }
+    }
+
     /** {@hide} */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     public String getCodePath() { return scanSourceDir; }
@@ -2568,4 +2614,21 @@
     public int getNativeHeapZeroInitialized() {
         return nativeHeapZeroInitialized;
     }
+
+    /**
+     * Return the application class name defined in the manifest. The class name set in the
+     * <processes> tag for this process, then return it. Otherwise it'll return the class
+     * name set in the <application> tag. If neither is set, it'll return null.
+     * @hide
+     */
+    @Nullable
+    public String getCustomApplicationClassNameForProcess(String processName) {
+        if (mAppClassNamesByProcess != null) {
+            String byProcess = mAppClassNamesByProcess.get(processName);
+            if (byProcess != null) {
+                return byProcess;
+            }
+        }
+        return className;
+    }
 }
diff --git a/core/java/android/content/pm/parsing/ParsingPackage.java b/core/java/android/content/pm/parsing/ParsingPackage.java
index 2fa5df7..fc9f1a5 100644
--- a/core/java/android/content/pm/parsing/ParsingPackage.java
+++ b/core/java/android/content/pm/parsing/ParsingPackage.java
@@ -118,6 +118,7 @@
 
     ParsingPackage addQueriesProvider(String authority);
 
+    /** Sets a process name -> {@link ParsedProcess} map coming from the <processes> tag. */
     ParsingPackage setProcesses(@NonNull Map<String, ParsedProcess> processes);
 
     ParsingPackage asSplit(
diff --git a/core/java/android/content/pm/parsing/ParsingPackageImpl.java b/core/java/android/content/pm/parsing/ParsingPackageImpl.java
index f03ab6a..424f477 100644
--- a/core/java/android/content/pm/parsing/ParsingPackageImpl.java
+++ b/core/java/android/content/pm/parsing/ParsingPackageImpl.java
@@ -62,6 +62,7 @@
 import android.os.Parcelable;
 import android.os.storage.StorageManager;
 import android.text.TextUtils;
+import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Pair;
 import android.util.SparseArray;
@@ -297,6 +298,9 @@
 //    @DataClass.ParcelWith(ParsingUtils.StringPairListParceler.class)
     private List<Pair<String, ParsedIntentInfo>> preferredActivityFilters = emptyList();
 
+    /**
+     * Map from a process name to a {@link ParsedProcess}.
+     */
     @NonNull
     private Map<String, ParsedProcess> processes = emptyMap();
 
@@ -1131,10 +1135,46 @@
         appInfo.setSplitCodePaths(splitCodePaths);
         appInfo.setSplitResourcePaths(splitCodePaths);
         appInfo.setVersionCode(mLongVersionCode);
+        appInfo.setAppClassNamesByProcess(buildAppClassNamesByProcess());
 
         return appInfo;
     }
 
+    /**
+     * Create a map from a process name to the custom application class for this process,
+     * which comes from <processes><process android:name="xxx">.
+     *
+     * The original information is stored in {@link #processes}, but it's stored in
+     * a form of: [process name] -[1:N]-> [package name] -[1:N]-> [class name].
+     * We scan it and collect the process names and their app class names, only for this package.
+     *
+     * The resulting map only contains processes with a custom application class set.
+     */
+    @Nullable
+    private ArrayMap<String, String> buildAppClassNamesByProcess() {
+        if (processes == null) {
+            return null;
+        }
+        final ArrayMap<String, String> ret = new ArrayMap<>(4);
+        for (String processName : processes.keySet()) {
+            final ParsedProcess process = processes.get(processName);
+            final ArrayMap<String, String> appClassesByPackage =
+                    process.getAppClassNamesByPackage();
+
+            for (int i = 0; i < appClassesByPackage.size(); i++) {
+                final String packageName = appClassesByPackage.keyAt(i);
+
+                if (this.packageName.equals(packageName)) {
+                    final String appClassName = appClassesByPackage.valueAt(i);
+                    if (!TextUtils.isEmpty(appClassName)) {
+                        ret.put(processName, appClassName);
+                    }
+                }
+            }
+        }
+        return ret;
+    }
+
     @Override
     public int describeContents() {
         return 0;
diff --git a/core/java/android/content/pm/parsing/component/ParsedProcess.java b/core/java/android/content/pm/parsing/component/ParsedProcess.java
index c2d5163..27a540d 100644
--- a/core/java/android/content/pm/parsing/component/ParsedProcess.java
+++ b/core/java/android/content/pm/parsing/component/ParsedProcess.java
@@ -19,6 +19,7 @@
 import android.annotation.NonNull;
 import android.content.pm.ApplicationInfo;
 import android.os.Parcelable;
+import android.util.ArrayMap;
 
 import java.util.Set;
 
@@ -37,6 +38,15 @@
     @NonNull
     String getName();
 
+    /**
+     * The app class names in this (potentially shared) process, from a package name to
+     * the application class name.
+     * It's a map, because in shared processes, different packages can have different application
+     * classes.
+     */
+    @NonNull
+    ArrayMap<String, String> getAppClassNamesByPackage();
+
     @ApplicationInfo.NativeHeapZeroInitialized
     int getNativeHeapZeroInitialized();
 }
diff --git a/core/java/android/content/pm/parsing/component/ParsedProcessImpl.java b/core/java/android/content/pm/parsing/component/ParsedProcessImpl.java
index 3fd60eb..d404ecfd 100644
--- a/core/java/android/content/pm/parsing/component/ParsedProcessImpl.java
+++ b/core/java/android/content/pm/parsing/component/ParsedProcessImpl.java
@@ -22,6 +22,7 @@
 import android.content.pm.ApplicationInfo;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.util.ArrayMap;
 import android.util.ArraySet;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -39,6 +40,11 @@
 
     @NonNull
     private String name;
+
+    /** @see ParsedProcess#getAppClassNamesByPackage() */
+    @NonNull
+    private ArrayMap<String, String> appClassNamesByPackage = ArrayMap.EMPTY;
+
     @NonNull
     @DataClass.ParcelWith(Parcelling.BuiltIn.ForInternedStringSet.class)
     private Set<String> deniedPermissions = emptySet();
@@ -55,6 +61,8 @@
 
     public ParsedProcessImpl(@NonNull ParsedProcess other) {
         name = other.getName();
+        appClassNamesByPackage = (other.getAppClassNamesByPackage().size() == 0)
+                ? ArrayMap.EMPTY : new ArrayMap<>(other.getAppClassNamesByPackage());
         deniedPermissions = new ArraySet<>(other.getDeniedPermissions());
         gwpAsanMode = other.getGwpAsanMode();
         memtagMode = other.getMemtagMode();
@@ -66,6 +74,21 @@
         gwpAsanMode = other.getGwpAsanMode();
         memtagMode = other.getMemtagMode();
         nativeHeapZeroInitialized = other.getNativeHeapZeroInitialized();
+
+        final ArrayMap<String, String> oacn = other.getAppClassNamesByPackage();
+        for (int i = 0; i < oacn.size(); i++) {
+            appClassNamesByPackage.put(oacn.keyAt(i), oacn.valueAt(i));
+        }
+    }
+
+    /**
+     * Sets a custom application name used in this process for a given package.
+     */
+    public void putAppClassNameForPackage(String packageName, String className) {
+        if (appClassNamesByPackage.size() == 0) {
+            appClassNamesByPackage = new ArrayMap<>(4);
+        }
+        appClassNamesByPackage.put(packageName, className);
     }
 
 
@@ -83,9 +106,14 @@
     //@formatter:off
 
 
+    /**
+     * Creates a new ParsedProcessImpl.
+     *
+     */
     @DataClass.Generated.Member
     public ParsedProcessImpl(
             @NonNull String name,
+            @NonNull ArrayMap<String,String> appClassNamesByPackage,
             @NonNull Set<String> deniedPermissions,
             @ApplicationInfo.GwpAsanMode int gwpAsanMode,
             @ApplicationInfo.MemtagMode int memtagMode,
@@ -93,6 +121,9 @@
         this.name = name;
         com.android.internal.util.AnnotationValidations.validate(
                 NonNull.class, null, name);
+        this.appClassNamesByPackage = appClassNamesByPackage;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, appClassNamesByPackage);
         this.deniedPermissions = deniedPermissions;
         com.android.internal.util.AnnotationValidations.validate(
                 NonNull.class, null, deniedPermissions);
@@ -114,6 +145,14 @@
         return name;
     }
 
+    /**
+     * @see ParsedProcess#getAppClassNamesByPackage()
+     */
+    @DataClass.Generated.Member
+    public @NonNull ArrayMap<String,String> getAppClassNamesByPackage() {
+        return appClassNamesByPackage;
+    }
+
     @DataClass.Generated.Member
     public @NonNull Set<String> getDeniedPermissions() {
         return deniedPermissions;
@@ -142,6 +181,17 @@
         return this;
     }
 
+    /**
+     * @see ParsedProcess#getAppClassNamesByPackage()
+     */
+    @DataClass.Generated.Member
+    public @NonNull ParsedProcessImpl setAppClassNamesByPackage(@NonNull ArrayMap<String,String> value) {
+        appClassNamesByPackage = value;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, appClassNamesByPackage);
+        return this;
+    }
+
     @DataClass.Generated.Member
     public @NonNull ParsedProcessImpl setDeniedPermissions(@NonNull Set<String> value) {
         deniedPermissions = value;
@@ -192,6 +242,7 @@
         // void parcelFieldName(Parcel dest, int flags) { ... }
 
         dest.writeString(name);
+        dest.writeMap(appClassNamesByPackage);
         sParcellingForDeniedPermissions.parcel(deniedPermissions, dest, flags);
         dest.writeInt(gwpAsanMode);
         dest.writeInt(memtagMode);
@@ -210,6 +261,8 @@
         // static FieldType unparcelFieldName(Parcel in) { ... }
 
         String _name = in.readString();
+        ArrayMap<String,String> _appClassNamesByPackage = new ArrayMap();
+        in.readMap(_appClassNamesByPackage, String.class.getClassLoader());
         Set<String> _deniedPermissions = sParcellingForDeniedPermissions.unparcel(in);
         int _gwpAsanMode = in.readInt();
         int _memtagMode = in.readInt();
@@ -218,6 +271,9 @@
         this.name = _name;
         com.android.internal.util.AnnotationValidations.validate(
                 NonNull.class, null, name);
+        this.appClassNamesByPackage = _appClassNamesByPackage;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, appClassNamesByPackage);
         this.deniedPermissions = _deniedPermissions;
         com.android.internal.util.AnnotationValidations.validate(
                 NonNull.class, null, deniedPermissions);
@@ -249,10 +305,10 @@
     };
 
     @DataClass.Generated(
-            time = 1627605368434L,
+            time = 1639076603310L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/android/content/pm/parsing/component/ParsedProcessImpl.java",
-            inputSignatures = "private @android.annotation.NonNull java.lang.String name\nprivate @android.annotation.NonNull @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedStringSet.class) java.util.Set<java.lang.String> deniedPermissions\nprivate @android.content.pm.ApplicationInfo.GwpAsanMode int gwpAsanMode\nprivate @android.content.pm.ApplicationInfo.MemtagMode int memtagMode\nprivate @android.content.pm.ApplicationInfo.NativeHeapZeroInitialized int nativeHeapZeroInitialized\npublic  void addStateFrom(android.content.pm.parsing.component.ParsedProcess)\nclass ParsedProcessImpl extends java.lang.Object implements [android.content.pm.parsing.component.ParsedProcess]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genParcelable=true, genAidl=false, genBuilder=false)")
+            inputSignatures = "private @android.annotation.NonNull java.lang.String name\nprivate @android.annotation.NonNull android.util.ArrayMap<java.lang.String,java.lang.String> appClassNamesByPackage\nprivate @android.annotation.NonNull @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedStringSet.class) java.util.Set<java.lang.String> deniedPermissions\nprivate @android.content.pm.ApplicationInfo.GwpAsanMode int gwpAsanMode\nprivate @android.content.pm.ApplicationInfo.MemtagMode int memtagMode\nprivate @android.content.pm.ApplicationInfo.NativeHeapZeroInitialized int nativeHeapZeroInitialized\npublic  void addStateFrom(android.content.pm.parsing.component.ParsedProcess)\npublic  void putAppClassNameForPackage(java.lang.String,java.lang.String)\nclass ParsedProcessImpl extends java.lang.Object implements [android.content.pm.parsing.component.ParsedProcess]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genParcelable=true, genAidl=false, genBuilder=false)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/core/java/android/content/pm/parsing/component/ParsedProcessUtils.java b/core/java/android/content/pm/parsing/component/ParsedProcessUtils.java
index cf83586..5e4cf66 100644
--- a/core/java/android/content/pm/parsing/component/ParsedProcessUtils.java
+++ b/core/java/android/content/pm/parsing/component/ParsedProcessUtils.java
@@ -97,7 +97,12 @@
                 return input.error(processNameResult);
             }
 
+            String packageName = pkg.getPackageName();
+            String className = ParsingUtils.buildClassName(packageName,
+                    sa.getNonConfigurationString(R.styleable.AndroidManifestProcess_name, 0));
+
             proc.setName(processNameResult.getResult());
+            proc.putAppClassNameForPackage(packageName, className);
             proc.setGwpAsanMode(sa.getInt(R.styleable.AndroidManifestProcess_gwpAsanMode, -1));
             proc.setMemtagMode(sa.getInt(R.styleable.AndroidManifestProcess_memtagMode, -1));
             if (sa.hasValue(R.styleable.AndroidManifestProcess_nativeHeapZeroInitialized)) {
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 0a6ef7c..06f347f 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -2584,6 +2584,9 @@
     <declare-styleable name="AndroidManifestProcess" parent="AndroidManifestProcesses">
         <!-- Required name of the process that is allowed -->
         <attr name="process" />
+        <!-- custom Application class name. We use call it "name", not "className", to be
+             consistent with the Application tag. -->
+        <attr name="name" />
         <attr name="gwpAsanMode" />
         <attr name="memtagMode" />
         <attr name="nativeHeapZeroInitialized" />
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProcessTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProcessTest.kt
index e633850..005d3e8 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProcessTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProcessTest.kt
@@ -18,6 +18,7 @@
 
 import android.content.pm.parsing.component.ParsedProcess
 import android.content.pm.parsing.component.ParsedProcessImpl
+import android.util.ArrayMap
 import kotlin.contracts.ExperimentalContracts
 
 @ExperimentalContracts
@@ -29,6 +30,8 @@
     override val excludedMethods = listOf(
         // Copying method
         "addStateFrom",
+        // Utility method
+        "putAppClassNameForPackage",
     )
 
     override val baseParams = listOf(
@@ -39,6 +42,9 @@
     )
 
     override fun extraParams() = listOf(
-        getter(ParsedProcess::getDeniedPermissions, setOf("testDeniedPermission"))
+        getter(ParsedProcess::getDeniedPermissions, setOf("testDeniedPermission")),
+        getter(ParsedProcess::getAppClassNamesByPackage, ArrayMap<String, String>().apply {
+            put("package1", "classname1");
+        }),
     )
 }
diff --git a/tools/aapt2/link/ManifestFixer.cpp b/tools/aapt2/link/ManifestFixer.cpp
index d204190..d432341 100644
--- a/tools/aapt2/link/ManifestFixer.cpp
+++ b/tools/aapt2/link/ManifestFixer.cpp
@@ -588,10 +588,21 @@
             child_el->name == "provider" || child_el->name == "receiver" ||
             child_el->name == "service") {
           FullyQualifyClassName(original_package, xml::kSchemaAndroid, "name", child_el);
+          continue;
         }
 
         if (child_el->name == "activity-alias") {
           FullyQualifyClassName(original_package, xml::kSchemaAndroid, "targetActivity", child_el);
+          continue;
+        }
+
+        if (child_el->name == "processes") {
+          for (xml::Element* grand_child_el : child_el->GetChildElements()) {
+            if (grand_child_el->name == "process") {
+              FullyQualifyClassName(original_package, xml::kSchemaAndroid, "name", grand_child_el);
+            }
+          }
+          continue;
         }
       }
     }