Merge "Optimize (Parsing)PackageImpl implementation"

This commit is contained in:
Winson Chiu
2020-09-15 16:22:37 +00:00
committed by Android (Google) Code Review
17 changed files with 793 additions and 413 deletions

View File

@@ -63,6 +63,9 @@ import java.util.Set;
/** @hide **/
public class PackageInfoWithoutStateUtils {
public static final String SYSTEM_DATA_PATH =
Environment.getDataDirectoryPath() + File.separator + "system";
@Nullable
public static PackageInfo generate(ParsingPackageRead pkg, int[] gids,
@PackageManager.PackageInfoFlags int flags, long firstInstallTime, long lastUpdateTime,
@@ -168,7 +171,8 @@ public class PackageInfoWithoutStateUtils {
info.instrumentation = new InstrumentationInfo[N];
for (int i = 0; i < N; i++) {
info.instrumentation[i] = generateInstrumentationInfo(
pkg.getInstrumentations().get(i), pkg, flags, userId);
pkg.getInstrumentations().get(i), pkg, flags, userId,
true /* assignUserFields */);
}
}
}
@@ -332,7 +336,8 @@ public class PackageInfoWithoutStateUtils {
return null;
}
return generateApplicationInfoUnchecked(pkg, flags, state, userId);
return generateApplicationInfoUnchecked(pkg, flags, state, userId,
true /* assignUserFields */);
}
/**
@@ -340,15 +345,23 @@ public class PackageInfoWithoutStateUtils {
* system server.
*
* Prefer {@link #generateApplicationInfo(ParsingPackageRead, int, PackageUserState, int)}.
*
* @param assignUserFields whether to fill the returned {@link ApplicationInfo} with user
* specific fields. This can be skipped when building from a system
* server package, as there are cached strings which can be used rather
* than querying and concatenating the comparatively expensive
* {@link Environment#getDataDirectory(String)}}.
*/
@NonNull
public static ApplicationInfo generateApplicationInfoUnchecked(@NonNull ParsingPackageRead pkg,
@PackageManager.ApplicationInfoFlags int flags, PackageUserState state, int userId) {
@PackageManager.ApplicationInfoFlags int flags, PackageUserState state, int userId,
boolean assignUserFields) {
// Make shallow copy so we can store the metadata/libraries safely
ApplicationInfo ai = pkg.toAppInfoWithoutState();
// Init handles data directories
// TODO(b/135203078): Consolidate the data directory logic, remove initForUser
ai.initForUser(userId);
if (assignUserFields) {
assignUserFields(pkg, ai, userId);
}
if ((flags & PackageManager.GET_META_DATA) == 0) {
ai.metaData = null;
@@ -567,9 +580,14 @@ public class PackageInfoWithoutStateUtils {
return generateProviderInfo(pkg, p, flags, state, null, userId);
}
/**
* @param assignUserFields see {@link #generateApplicationInfoUnchecked(ParsingPackageRead, int,
* PackageUserState, int, boolean)}
*/
@Nullable
public static InstrumentationInfo generateInstrumentationInfo(ParsedInstrumentation i,
ParsingPackageRead pkg, @PackageManager.ComponentInfoFlags int flags, int userId) {
ParsingPackageRead pkg, @PackageManager.ComponentInfoFlags int flags, int userId,
boolean assignUserFields) {
if (i == null) return null;
InstrumentationInfo ii = new InstrumentationInfo();
@@ -585,10 +603,10 @@ public class PackageInfoWithoutStateUtils {
ii.splitSourceDirs = pkg.getSplitCodePaths();
ii.splitPublicSourceDirs = pkg.getSplitCodePaths();
ii.splitDependencies = pkg.getSplitDependencies();
ii.dataDir = getDataDir(pkg, userId).getAbsolutePath();
ii.deviceProtectedDataDir = getDeviceProtectedDataDir(pkg, userId).getAbsolutePath();
ii.credentialProtectedDataDir = getCredentialProtectedDataDir(pkg,
userId).getAbsolutePath();
if (assignUserFields) {
assignUserFields(pkg, ii, userId);
}
if ((flags & PackageManager.GET_META_DATA) == 0) {
return ii;
@@ -770,4 +788,55 @@ public class PackageInfoWithoutStateUtils {
return Environment.getDataUserCePackageDirectory(pkg.getVolumeUuid(), userId,
pkg.getPackageName());
}
private static void assignUserFields(ParsingPackageRead pkg, ApplicationInfo info, int userId) {
// This behavior is undefined for no-state ApplicationInfos when called by a public API,
// since the uid is never assigned by the system. It will always effectively be appId 0.
info.uid = UserHandle.getUid(userId, UserHandle.getAppId(info.uid));
String pkgName = pkg.getPackageName();
if ("android".equals(pkgName)) {
info.dataDir = SYSTEM_DATA_PATH;
return;
}
// For performance reasons, all these paths are built as strings
String baseDataDirPrefix =
Environment.getDataDirectoryPath(pkg.getVolumeUuid()) + File.separator;
String userIdPkgSuffix = File.separator + userId + File.separator + pkgName;
info.credentialProtectedDataDir = baseDataDirPrefix + Environment.DIR_USER_CE
+ userIdPkgSuffix;
info.deviceProtectedDataDir = baseDataDirPrefix + Environment.DIR_USER_DE + userIdPkgSuffix;
if (pkg.isDefaultToDeviceProtectedStorage()
&& PackageManager.APPLY_DEFAULT_TO_DEVICE_PROTECTED_STORAGE) {
info.dataDir = info.deviceProtectedDataDir;
} else {
info.dataDir = info.credentialProtectedDataDir;
}
}
private static void assignUserFields(ParsingPackageRead pkg, InstrumentationInfo info,
int userId) {
String pkgName = pkg.getPackageName();
if ("android".equals(pkgName)) {
info.dataDir = SYSTEM_DATA_PATH;
return;
}
// For performance reasons, all these paths are built as strings
String baseDataDirPrefix =
Environment.getDataDirectoryPath(pkg.getVolumeUuid()) + File.separator;
String userIdPkgSuffix = File.separator + userId + File.separator + pkgName;
info.credentialProtectedDataDir = baseDataDirPrefix + Environment.DIR_USER_CE
+ userIdPkgSuffix;
info.deviceProtectedDataDir = baseDataDirPrefix + Environment.DIR_USER_DE + userIdPkgSuffix;
if (pkg.isDefaultToDeviceProtectedStorage()
&& PackageManager.APPLY_DEFAULT_TO_DEVICE_PROTECTED_STORAGE) {
info.dataDir = info.deviceProtectedDataDir;
} else {
info.dataDir = info.credentialProtectedDataDir;
}
}
}

View File

@@ -16,6 +16,7 @@
package android.content.pm.parsing;
import android.annotation.CallSuper;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Intent;
@@ -343,6 +344,6 @@ public interface ParsingPackage extends ParsingPackageRead {
// TODO(b/135203078): This class no longer has access to ParsedPackage, find a replacement
// for moving to the next step
@Deprecated
@CallSuper
Object hideAsParsed();
}

File diff suppressed because it is too large Load Diff

View File

@@ -867,7 +867,7 @@ public interface ParsingPackageRead extends Parcelable {
* @see ApplicationInfo#gwpAsanMode
* @see R.styleable#AndroidManifest_gwpAsanMode
*/
public int getGwpAsanMode();
int getGwpAsanMode();
// TODO(b/135203078): Hide and enforce going through PackageInfoUtils
ApplicationInfo toAppInfoWithoutState();

View File

@@ -185,6 +185,9 @@ public class ParsingPackageUtils {
ParsingPackageUtils.getSigningDetails(pkg, false /* skipVerify */));
}
// Need to call this to finish the parsing stage
pkg.hideAsParsed();
return input.success(pkg);
} catch (PackageParser.PackageParserException e) {
return input.error(PackageManager.INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,

View File

@@ -29,6 +29,7 @@ import android.compat.annotation.Disabled;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.os.storage.StorageManager;
import android.os.storage.StorageVolume;
@@ -73,12 +74,29 @@ public class Environment {
private static final String DIR_FILES = "files";
private static final String DIR_CACHE = "cache";
/**
* The folder name prefix for the user credential protected data directory. This is exposed for
* use in string path caching for {@link ApplicationInfo} objects, and should not be accessed
* directly otherwise. Prefer {@link #getDataUserCeDirectory(String, int)}.
* {@hide}
*/
public static final String DIR_USER_CE = "user";
/**
* The folder name prefix for the user device protected data directory. This is exposed for use
* in string path caching for {@link ApplicationInfo} objects, and should not be accessed
* directly otherwise. Prefer {@link #getDataUserDeDirectory(String, int)}.
* {@hide}
*/
public static final String DIR_USER_DE = "user_de";
/** {@hide} */
@Deprecated
public static final String DIRECTORY_ANDROID = DIR_ANDROID;
private static final File DIR_ANDROID_ROOT = getDirectory(ENV_ANDROID_ROOT, "/system");
private static final File DIR_ANDROID_DATA = getDirectory(ENV_ANDROID_DATA, "/data");
private static final String DIR_ANDROID_DATA_PATH = getDirectoryPath(ENV_ANDROID_DATA, "/data");
private static final File DIR_ANDROID_DATA = new File(DIR_ANDROID_DATA_PATH);
private static final File DIR_ANDROID_EXPAND = getDirectory(ENV_ANDROID_EXPAND, "/mnt/expand");
private static final File DIR_ANDROID_STORAGE = getDirectory(ENV_ANDROID_STORAGE, "/storage");
private static final File DIR_DOWNLOAD_CACHE = getDirectory(ENV_DOWNLOAD_CACHE, "/cache");
@@ -357,6 +375,14 @@ public class Environment {
return DIR_ANDROID_DATA;
}
/**
* @see #getDataDirectory()
* @hide
*/
public static String getDataDirectoryPath() {
return DIR_ANDROID_DATA_PATH;
}
/** {@hide} */
public static File getDataDirectory(String volumeUuid) {
if (TextUtils.isEmpty(volumeUuid)) {
@@ -366,6 +392,15 @@ public class Environment {
}
}
/** @hide */
public static String getDataDirectoryPath(String volumeUuid) {
if (TextUtils.isEmpty(volumeUuid)) {
return DIR_ANDROID_DATA_PATH;
} else {
return getExpandDirectory().getAbsolutePath() + File.separator + volumeUuid;
}
}
/** {@hide} */
public static File getExpandDirectory() {
return DIR_ANDROID_EXPAND;
@@ -489,7 +524,7 @@ public class Environment {
/** {@hide} */
public static File getDataUserCeDirectory(String volumeUuid) {
return new File(getDataDirectory(volumeUuid), "user");
return new File(getDataDirectory(volumeUuid), DIR_USER_CE);
}
/** {@hide} */
@@ -506,7 +541,7 @@ public class Environment {
/** {@hide} */
public static File getDataUserDeDirectory(String volumeUuid) {
return new File(getDataDirectory(volumeUuid), "user_de");
return new File(getDataDirectory(volumeUuid), DIR_USER_DE);
}
/** {@hide} */
@@ -1372,6 +1407,12 @@ public class Environment {
return path == null ? new File(defaultPath) : new File(path);
}
@NonNull
static String getDirectoryPath(@NonNull String variableName, @NonNull String defaultPath) {
String path = System.getenv(variableName);
return path == null ? defaultPath : path;
}
/** {@hide} */
public static void setUserRequired(boolean userRequired) {
sUserRequired = userRequired;

View File

@@ -29,6 +29,15 @@
"exclude-annotation": "androidx.test.filters.FlakyTest"
}
]
},
{
"file_patterns": ["Environment\\.java"],
"name": "FrameworksServicesTests",
"options": [
{
"include-filter": "com.android.server.pm.parsing.PackageInfoUserFieldsTest"
}
]
}
],
"postsubmit": [

View File

@@ -9531,10 +9531,16 @@ public class PackageManagerService extends IPackageManager.Stub
}
}
// The version of the application on the /system partition is less than or
// equal to the version on the /data partition. Throw an exception and use
// the application already installed on the /data partition.
if (scanSystemPartition && isSystemPkgUpdated && !isSystemPkgBetter) {
// The version of the application on the /system partition is less than or
// equal to the version on the /data partition. Throw an exception and use
// the application already installed on the /data partition.
// In the case of a skipped package, commitReconciledScanResultLocked is not called to
// add the object to the "live" data structures, so this is the final mutation step
// for the package. Which means it needs to be finalized here to cache derived fields.
// This is relevant for cases where the disabled system package is used for flags or
// other metadata.
((ParsedPackage) parsedPackage).hideAsFinal();
throw new PackageManagerException(Log.WARN, "Package " + parsedPackage.getPackageName()
+ " at " + parsedPackage.getPath() + " ignored: updated version "
+ pkgSetting.versionCode + " better than this "

View File

@@ -42,7 +42,9 @@ import java.util.Set;
* Settings data for a particular package we know about.
*/
public class PackageSetting extends PackageSettingBase {
int appId;
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
public int appId;
/**
* This can be null whenever a physical APK on device is missing. This can be the result of

View File

@@ -19,6 +19,7 @@ package com.android.server.pm.parsing;
import android.annotation.CheckResult;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.apex.ApexInfo;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
@@ -56,6 +57,7 @@ import com.android.internal.util.ArrayUtils;
import com.android.server.pm.PackageSetting;
import com.android.server.pm.parsing.pkg.AndroidPackage;
import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
import com.android.server.pm.parsing.pkg.PackageImpl;
import com.android.server.pm.pkg.PackageStateUnserialized;
import libcore.util.EmptyArray;
@@ -218,7 +220,9 @@ public class PackageInfoUtils {
}
ApplicationInfo info = PackageInfoWithoutStateUtils.generateApplicationInfoUnchecked(pkg,
flags, state, userId);
flags, state, userId, false /* assignUserFields */);
initForUser(info, pkg, userId);
if (pkgSetting != null) {
// TODO(b/135203078): Remove PackageParser1/toAppInfoWithoutState and clean all this up
@@ -349,7 +353,11 @@ public class PackageInfoUtils {
if (i == null) return null;
InstrumentationInfo info =
PackageInfoWithoutStateUtils.generateInstrumentationInfo(i, pkg, flags, userId);
PackageInfoWithoutStateUtils.generateInstrumentationInfo(i, pkg, flags, userId,
false /* assignUserFields */);
initForUser(info, pkg, userId);
if (info == null) {
return null;
}
@@ -496,6 +504,90 @@ public class PackageInfoUtils {
// @formatter:on
}
private static void initForUser(ApplicationInfo output, AndroidPackage input,
@UserIdInt int userId) {
PackageImpl pkg = ((PackageImpl) input);
String packageName = input.getPackageName();
output.uid = UserHandle.getUid(userId, UserHandle.getAppId(input.getUid()));
if ("android".equals(packageName)) {
output.dataDir = PackageInfoWithoutStateUtils.SYSTEM_DATA_PATH;
return;
}
// For performance reasons, all these paths are built as strings
if (userId == UserHandle.USER_SYSTEM) {
output.credentialProtectedDataDir =
pkg.getBaseAppDataCredentialProtectedDirForSystemUser() + packageName;
output.deviceProtectedDataDir =
pkg.getBaseAppDataDeviceProtectedDirForSystemUser() + packageName;
} else {
// Convert /data/user/0/ -> /data/user/1/com.example.app
String userIdString = String.valueOf(userId);
int credentialLength = pkg.getBaseAppDataCredentialProtectedDirForSystemUser().length();
output.credentialProtectedDataDir =
new StringBuilder(pkg.getBaseAppDataCredentialProtectedDirForSystemUser())
.replace(credentialLength - 2, credentialLength - 1, userIdString)
.append(packageName)
.toString();
int deviceLength = pkg.getBaseAppDataDeviceProtectedDirForSystemUser().length();
output.deviceProtectedDataDir =
new StringBuilder(pkg.getBaseAppDataDeviceProtectedDirForSystemUser())
.replace(deviceLength - 2, deviceLength - 1, userIdString)
.append(packageName)
.toString();
}
if (input.isDefaultToDeviceProtectedStorage()
&& PackageManager.APPLY_DEFAULT_TO_DEVICE_PROTECTED_STORAGE) {
output.dataDir = output.deviceProtectedDataDir;
} else {
output.dataDir = output.credentialProtectedDataDir;
}
}
// This duplicates the ApplicationInfo variant because it uses field assignment and the classes
// don't inherit from each other, unfortunately. Consolidating logic would introduce overhead.
private static void initForUser(InstrumentationInfo output, AndroidPackage input,
@UserIdInt int userId) {
PackageImpl pkg = ((PackageImpl) input);
String packageName = input.getPackageName();
if ("android".equals(packageName)) {
output.dataDir = PackageInfoWithoutStateUtils.SYSTEM_DATA_PATH;
return;
}
// For performance reasons, all these paths are built as strings
if (userId == UserHandle.USER_SYSTEM) {
output.credentialProtectedDataDir =
pkg.getBaseAppDataCredentialProtectedDirForSystemUser() + packageName;
output.deviceProtectedDataDir =
pkg.getBaseAppDataDeviceProtectedDirForSystemUser() + packageName;
} else {
// Convert /data/user/0/ -> /data/user/1/com.example.app
String userIdString = String.valueOf(userId);
int credentialLength = pkg.getBaseAppDataCredentialProtectedDirForSystemUser().length();
output.credentialProtectedDataDir =
new StringBuilder(pkg.getBaseAppDataCredentialProtectedDirForSystemUser())
.replace(credentialLength - 2, credentialLength - 1, userIdString)
.append(packageName)
.toString();
int deviceLength = pkg.getBaseAppDataDeviceProtectedDirForSystemUser().length();
output.deviceProtectedDataDir =
new StringBuilder(pkg.getBaseAppDataDeviceProtectedDirForSystemUser())
.replace(deviceLength - 2, deviceLength - 1, userIdString)
.append(packageName)
.toString();
}
if (input.isDefaultToDeviceProtectedStorage()
&& PackageManager.APPLY_DEFAULT_TO_DEVICE_PROTECTED_STORAGE) {
output.dataDir = output.deviceProtectedDataDir;
} else {
output.dataDir = output.credentialProtectedDataDir;
}
}
/**
* Wraps {@link PackageInfoUtils#generateApplicationInfo} with a cache.
*/

File diff suppressed because one or more lines are too long

View File

@@ -105,6 +105,9 @@ public interface ParsedPackage extends AndroidPackage {
ParsedPackage setSecondaryNativeLibraryDir(String secondaryNativeLibraryDir);
/**
* This is an appId, the uid if the userId is == USER_SYSTEM
*/
ParsedPackage setUid(int uid);
ParsedPackage setVersionCode(int versionCode);

View File

@@ -155,8 +155,8 @@ public class PackageParserTest {
@Test
public void test_serializePackage() throws Exception {
try (PackageParser2 pp = PackageParser2.forParsingFileWithDefaults()) {
ParsedPackage pkg = pp.parsePackage(FRAMEWORK, 0 /* parseFlags */,
true /* useCaches */);
AndroidPackage pkg = pp.parsePackage(FRAMEWORK, 0 /* parseFlags */,
true /* useCaches */).hideAsFinal();
Parcel p = Parcel.obtain();
pkg.writeToParcel(p, 0 /* flags */);
@@ -591,7 +591,7 @@ public class PackageParserTest {
null
)
.setUse32BitAbi(true)
.setVolumeUuid("foo3")
.setVolumeUuid("d52ef59a-7def-4541-bf21-4c28ed4b65a0")
.addPermission(permission)
.addPermissionGroup(new ParsedPermissionGroup())
.addActivity(new ParsedActivity())

View File

@@ -470,11 +470,16 @@ public class ScanTests {
private PackageManagerService.ScanResult executeScan(
PackageManagerService.ScanRequest scanRequest) throws PackageManagerException {
return PackageManagerService.scanPackageOnlyLI(
PackageManagerService.ScanResult result = PackageManagerService.scanPackageOnlyLI(
scanRequest,
mMockInjector,
false /*isUnderFactoryTest*/,
System.currentTimeMillis());
// Need to call hideAsFinal to cache derived fields. This is normally done in PMS, but not
// in this cut down flow used for the test.
((ParsedPackage) result.pkgSetting.pkg).hideAsFinal();
return result;
}
private static String createCodePath(String packageName) {

View File

@@ -59,7 +59,9 @@ class AndroidPackageInfoFlagBehaviorTest : AndroidPackageParsingTestBase() {
fun appInfo(flag: Int, fieldFunction: (ApplicationInfo) -> List<Any?>) = Param(
flag, ApplicationInfo::class.java.simpleName,
::oldAppInfo, ::newAppInfo, fieldFunction
{ pkg, flags -> oldAppInfo(pkg, flags) },
{ pkg, flags -> newAppInfo(pkg, flags) },
fieldFunction
)
}

View File

@@ -32,6 +32,7 @@ import android.content.pm.ServiceInfo
import android.os.Bundle
import android.os.Debug
import android.os.Environment
import android.os.Process
import android.util.SparseArray
import androidx.test.platform.app.InstrumentationRegistry
import com.android.server.pm.PackageManagerService
@@ -54,7 +55,7 @@ open class AndroidPackageParsingTestBase {
private const val VERIFY_ALL_APKS = true
/** For auditing memory usage differences */
// For auditing memory usage differences to /sdcard/AndroidPackageParsingTestBase.hprof
private const val DUMP_HPROF_TO_EXTERNAL = false
val context: Context = InstrumentationRegistry.getInstrumentation().getContext()
@@ -104,10 +105,12 @@ open class AndroidPackageParsingTestBase {
@JvmStatic
@BeforeClass
fun setUpPackages() {
var uid = Process.FIRST_APPLICATION_UID
apks.mapNotNull {
try {
packageParser.parsePackage(it, PackageParser.PARSE_IS_SYSTEM_DIR, false) to
packageParser2.parsePackage(it, PackageParser.PARSE_IS_SYSTEM_DIR, false)
packageParser2.parsePackage(it, PackageParser.PARSE_IS_SYSTEM_DIR,
false)
} catch (ignored: Exception) {
// It is intentional that a failure of either call here will result in failing
// both. Having null on one side would mean nothing to compare. Due to the
@@ -117,8 +120,15 @@ open class AndroidPackageParsingTestBase {
null
}
}.forEach { (old, new) ->
// Assign an arbitrary UID. This is normally done after parsing completes, inside
// PackageManagerService, but since that code isn't run here, need to mock it. This
// is equivalent to what the system would assign.
old.applicationInfo.uid = uid
new.uid = uid
uid++
oldPackages += old
newPackages += new
newPackages += new.hideAsFinal()
}
if (DUMP_HPROF_TO_EXTERNAL) {
@@ -131,12 +141,29 @@ open class AndroidPackageParsingTestBase {
}
}
fun oldAppInfo(pkg: PackageParser.Package, flags: Int = 0): ApplicationInfo? {
return PackageParser.generateApplicationInfo(pkg, flags, dummyUserState, 0)
fun oldAppInfo(
pkg: PackageParser.Package,
flags: Int = 0,
userId: Int = 0
): ApplicationInfo? {
return PackageParser.generateApplicationInfo(pkg, flags, dummyUserState, userId)
}
fun newAppInfo(pkg: AndroidPackage, flags: Int = 0): ApplicationInfo? {
return PackageInfoUtils.generateApplicationInfo(pkg, flags, dummyUserState, 0,
fun newAppInfo(
pkg: AndroidPackage,
flags: Int = 0,
userId: Int = 0
): ApplicationInfo? {
return PackageInfoUtils.generateApplicationInfo(pkg, flags, dummyUserState, userId,
mockPkgSetting(pkg))
}
fun newAppInfoWithoutState(
pkg: AndroidPackage,
flags: Int = 0,
userId: Int = 0
): ApplicationInfo? {
return PackageInfoUtils.generateApplicationInfo(pkg, flags, dummyUserState, userId,
mockPkgSetting(pkg))
}
@@ -152,6 +179,7 @@ open class AndroidPackageParsingTestBase {
private fun mockPkgSetting(aPkg: AndroidPackage) = mockThrowOnUnmocked<PackageSetting> {
this.pkg = aPkg
this.appId = aPkg.uid
whenever(pkgState) { PackageStateUnserialized() }
whenever(readUserState(anyInt())) { dummyUserState }
}

View File

@@ -0,0 +1,145 @@
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.pm.parsing
import android.content.pm.ApplicationInfo
import android.content.pm.PackageParser
import android.os.Environment
import android.os.UserHandle
import android.platform.test.annotations.Presubmit
import com.google.common.truth.Truth.assertWithMessage
import org.junit.Test
/**
* As a performance optimization, the new parsing code builds the user data directories manually
* using string concatenation. This tries to mirror the logic that [Environment] uses, but it is
* still fragile to changes and potentially different device configurations.
*
* This compares the resultant values against the old [PackageParser] outputs as well as
* [ApplicationInfo]'s own [ApplicationInfo.initForUser].
*/
@Presubmit
class PackageInfoUserFieldsTest : AndroidPackageParsingTestBase() {
@Test
fun userEnvironmentValues() {
// Specifically use a large user ID to test assumptions about single character IDs
val userId = 110
oldPackages.zip(newPackages)
.map { (old, new) ->
(old to oldAppInfo(pkg = old, userId = userId)!!) to
(new to newAppInfo(pkg = new, userId = userId)!!)
}
.forEach { (oldPair, newPair) ->
val (oldPkg, oldInfo) = oldPair
val (newPkg, newInfo) = newPair
val oldValuesActual = extractActual(oldInfo)
val newValuesActual = extractActual(newInfo)
val oldValuesExpected: Values
val newValuesExpected: Values
val packageName = oldPkg.packageName
if (packageName == "android") {
val systemDataDir = Environment.getDataSystemDirectory().absolutePath
oldValuesExpected = Values(
uid = UserHandle.getUid(userId,
UserHandle.getAppId(oldPkg.applicationInfo.uid)),
userDe = null,
userCe = null,
dataDir = systemDataDir
)
newValuesExpected = Values(
uid = UserHandle.getUid(userId, UserHandle.getAppId(newPkg.uid)),
userDe = null,
userCe = null,
dataDir = systemDataDir
)
} else {
oldValuesExpected = extractExpected(oldInfo, oldInfo.uid, userId)
newValuesExpected = extractExpected(newInfo, newPkg.uid, userId)
}
// Calls the internal ApplicationInfo logic to compare against. This must be
// done after saving the original values, since this will overwrite them.
oldInfo.initForUser(userId)
newInfo.initForUser(userId)
val oldInitValues = extractActual(oldInfo)
val newInitValues = extractActual(newInfo)
// The optimization is also done for the no state API that isn't used by the
// system. This API is still exposed publicly, so for this test we should
// verify it.
val newNoStateValues = extractActual(
newAppInfoWithoutState(newPkg, 0, userId)!!)
assertAllEquals(packageName,
oldValuesActual, oldValuesExpected, oldInitValues,
newValuesActual, newValuesExpected, newInitValues, newNoStateValues)
}
}
private fun assertAllEquals(packageName: String, vararg values: Values) {
// Local function to avoid accidentally calling wrong type
fun assertAllEquals(message: String, vararg values: Any?) {
values.forEachIndexed { index, value ->
if (index == 0) return@forEachIndexed
assertWithMessage("$message $index").that(values[0]).isEqualTo(value)
}
}
assertAllEquals("$packageName mismatched uid", values.map { it.uid })
assertAllEquals("$packageName mismatched userDe", values.map { it.userDe })
assertAllEquals("$packageName mismatched userCe", values.map { it.userCe })
assertAllEquals("$packageName mismatched dataDir", values.map { it.dataDir })
}
private fun extractActual(appInfo: ApplicationInfo) = Values(
uid = appInfo.uid,
userDe = appInfo.deviceProtectedDataDir,
userCe = appInfo.credentialProtectedDataDir,
dataDir = appInfo.dataDir
)
private fun extractExpected(appInfo: ApplicationInfo, appIdUid: Int, userId: Int): Values {
val userDe = Environment.getDataUserDePackageDirectory(appInfo.volumeUuid, userId,
appInfo.packageName).absolutePath
val userCe = Environment.getDataUserCePackageDirectory(appInfo.volumeUuid, userId,
appInfo.packageName).absolutePath
val dataDir = if (appInfo.isDefaultToDeviceProtectedStorage) {
appInfo.deviceProtectedDataDir
} else {
appInfo.credentialProtectedDataDir
}
return Values(
uid = UserHandle.getUid(userId, UserHandle.getAppId(appIdUid)),
userDe = userDe,
userCe = userCe,
dataDir = dataDir
)
}
data class Values(
val uid: Int,
val userDe: String?,
val userCe: String?,
val dataDir: String?
)
}