Merge "Optimize (Parsing)PackageImpl implementation"
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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();
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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": [
|
||||
|
||||
@@ -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 "
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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);
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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 }
|
||||
}
|
||||
|
||||
@@ -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?
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user