diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java index 5750484c6169b..77af474a04ac4 100644 --- a/core/java/android/app/LoadedApk.java +++ b/core/java/android/app/LoadedApk.java @@ -1352,7 +1352,9 @@ public final class LoadedApk { 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 2998f764a2f3d..76b4e5c4dba0c 100644 --- a/core/java/android/content/pm/ApplicationInfo.java +++ b/core/java/android/content/pm/ApplicationInfo.java @@ -33,6 +33,7 @@ import android.os.Parcel; 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 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { 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 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 mAppClassNamesByProcess; + public void dump(Printer pw, String prefix) { dump(pw, prefix, DUMP_FLAG_ALL); } @@ -1540,8 +1550,14 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { /** @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 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { 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 CREATOR @@ -2055,6 +2081,13 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { 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 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { 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 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 class ApplicationInfo extends PackageItemInfo implements Parcelable { public int getNativeHeapZeroInitialized() { return nativeHeapZeroInitialized; } + + /** + * Return the application class name defined in the manifest. The class name set in the + * tag for this process, then return it. Otherwise it'll return the class + * name set in the 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 2fa5df72656bd..fc9f1a55d84c9 100644 --- a/core/java/android/content/pm/parsing/ParsingPackage.java +++ b/core/java/android/content/pm/parsing/ParsingPackage.java @@ -118,6 +118,7 @@ public interface ParsingPackage extends ParsingPackageRead { ParsingPackage addQueriesProvider(String authority); + /** Sets a process name -> {@link ParsedProcess} map coming from the tag. */ ParsingPackage setProcesses(@NonNull Map processes); ParsingPackage asSplit( diff --git a/core/java/android/content/pm/parsing/ParsingPackageImpl.java b/core/java/android/content/pm/parsing/ParsingPackageImpl.java index f03ab6ab94f29..424f477de62d2 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.Parcel; 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 @@ public class ParsingPackageImpl implements ParsingPackage, ParsingPackageHidden, // @DataClass.ParcelWith(ParsingUtils.StringPairListParceler.class) private List> preferredActivityFilters = emptyList(); + /** + * Map from a process name to a {@link ParsedProcess}. + */ @NonNull private Map processes = emptyMap(); @@ -1131,10 +1135,46 @@ public class ParsingPackageImpl implements ParsingPackage, ParsingPackageHidden, 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 . + * + * 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 buildAppClassNamesByProcess() { + if (processes == null) { + return null; + } + final ArrayMap ret = new ArrayMap<>(4); + for (String processName : processes.keySet()) { + final ParsedProcess process = processes.get(processName); + final ArrayMap 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 c2d5163cfcc17..27a540d25891f 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 @@ package android.content.pm.parsing.component; 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 @@ public interface ParsedProcess extends Parcelable { @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 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 3fd60eb495766..d404ecfd38c72 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.annotation.NonNull; 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 @@ public class ParsedProcessImpl implements ParsedProcess { @NonNull private String name; + + /** @see ParsedProcess#getAppClassNamesByPackage() */ + @NonNull + private ArrayMap appClassNamesByPackage = ArrayMap.EMPTY; + @NonNull @DataClass.ParcelWith(Parcelling.BuiltIn.ForInternedStringSet.class) private Set deniedPermissions = emptySet(); @@ -55,6 +61,8 @@ public class ParsedProcessImpl implements ParsedProcess { 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 @@ public class ParsedProcessImpl implements ParsedProcess { gwpAsanMode = other.getGwpAsanMode(); memtagMode = other.getMemtagMode(); nativeHeapZeroInitialized = other.getNativeHeapZeroInitialized(); + + final ArrayMap 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 @@ public class ParsedProcessImpl implements ParsedProcess { //@formatter:off + /** + * Creates a new ParsedProcessImpl. + * + */ @DataClass.Generated.Member public ParsedProcessImpl( @NonNull String name, + @NonNull ArrayMap appClassNamesByPackage, @NonNull Set deniedPermissions, @ApplicationInfo.GwpAsanMode int gwpAsanMode, @ApplicationInfo.MemtagMode int memtagMode, @@ -93,6 +121,9 @@ public class ParsedProcessImpl implements ParsedProcess { 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 @@ public class ParsedProcessImpl implements ParsedProcess { return name; } + /** + * @see ParsedProcess#getAppClassNamesByPackage() + */ + @DataClass.Generated.Member + public @NonNull ArrayMap getAppClassNamesByPackage() { + return appClassNamesByPackage; + } + @DataClass.Generated.Member public @NonNull Set getDeniedPermissions() { return deniedPermissions; @@ -142,6 +181,17 @@ public class ParsedProcessImpl implements ParsedProcess { return this; } + /** + * @see ParsedProcess#getAppClassNamesByPackage() + */ + @DataClass.Generated.Member + public @NonNull ParsedProcessImpl setAppClassNamesByPackage(@NonNull ArrayMap value) { + appClassNamesByPackage = value; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, appClassNamesByPackage); + return this; + } + @DataClass.Generated.Member public @NonNull ParsedProcessImpl setDeniedPermissions(@NonNull Set value) { deniedPermissions = value; @@ -192,6 +242,7 @@ public class ParsedProcessImpl implements ParsedProcess { // 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 @@ public class ParsedProcessImpl implements ParsedProcess { // static FieldType unparcelFieldName(Parcel in) { ... } String _name = in.readString(); + ArrayMap _appClassNamesByPackage = new ArrayMap(); + in.readMap(_appClassNamesByPackage, String.class.getClassLoader()); Set _deniedPermissions = sParcellingForDeniedPermissions.unparcel(in); int _gwpAsanMode = in.readInt(); int _memtagMode = in.readInt(); @@ -218,6 +271,9 @@ public class ParsedProcessImpl implements ParsedProcess { 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 @@ public class ParsedProcessImpl implements ParsedProcess { }; @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 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 appClassNamesByPackage\nprivate @android.annotation.NonNull @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedStringSet.class) java.util.Set 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 cf83586a5dd5e..5e4cf661b1944 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 @@ public class ParsedProcessUtils { 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 0a6ef7cf6f21f..06f347f4daff6 100644 --- a/core/res/res/values/attrs_manifest.xml +++ b/core/res/res/values/attrs_manifest.xml @@ -2584,6 +2584,9 @@ + + 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 e6338507e970a..005d3e81d3a3d 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 @@ package com.android.server.pm.test.parsing.parcelling 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 @@ class ParsedProcessTest : ParcelableComponentTest(ParsedProcess::class, ParsedPr override val excludedMethods = listOf( // Copying method "addStateFrom", + // Utility method + "putAppClassNameForPackage", ) override val baseParams = listOf( @@ -39,6 +42,9 @@ class ParsedProcessTest : ParcelableComponentTest(ParsedProcess::class, ParsedPr ) override fun extraParams() = listOf( - getter(ParsedProcess::getDeniedPermissions, setOf("testDeniedPermission")) + getter(ParsedProcess::getDeniedPermissions, setOf("testDeniedPermission")), + getter(ParsedProcess::getAppClassNamesByPackage, ArrayMap().apply { + put("package1", "classname1"); + }), ) } diff --git a/tools/aapt2/link/ManifestFixer.cpp b/tools/aapt2/link/ManifestFixer.cpp index d204190d59dc7..d432341a8cded 100644 --- a/tools/aapt2/link/ManifestFixer.cpp +++ b/tools/aapt2/link/ManifestFixer.cpp @@ -588,10 +588,21 @@ static bool RenameManifestPackage(const StringPiece& package_override, xml::Elem 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; } } }