Merge changes from topic "cp_calin_framework_2017"

* changes:
  Allow public profile compilation for primary apks
  [framework] Pass .dm files to dexopt at install time
  Extend the ArtManager profiling API to cover boot image profiling
  [framework] Extend profile operations to take the profile name
  Accept UserHandle.USER_ALL during profile preparation
  [framework] Prepare profile for app code paths
  Perform a non strict matching of .dm files when computing the size
  Support installation of DexMetadata files (.dm)
  Rename snapshotProfile to createProfileSnapshot for consistency
  Implement ArtManager#snapshotProfile API
  Add SystemApis to expose runtime profile information
  Log SHA256 of secondary dex files during reconcile.
This commit is contained in:
Treehugger Robot
2018-02-15 16:17:06 +00:00
committed by Gerrit Code Review
35 changed files with 1763 additions and 92 deletions

View File

@@ -133,6 +133,8 @@ java_library {
"core/java/android/content/pm/IPackageStatsObserver.aidl",
"core/java/android/content/pm/IPinItemRequest.aidl",
"core/java/android/content/pm/IShortcutService.aidl",
"core/java/android/content/pm/dex/IArtManager.aidl",
"core/java/android/content/pm/dex/ISnapshotRuntimeProfileCallback.aidl",
"core/java/android/content/pm/permission/IRuntimePermissionPresenter.aidl",
"core/java/android/database/IContentObserver.aidl",
":libcamera_client_aidl",

View File

@@ -126,6 +126,7 @@ package android {
field public static final java.lang.String READ_PRINT_SERVICES = "android.permission.READ_PRINT_SERVICES";
field public static final java.lang.String READ_PRINT_SERVICE_RECOMMENDATIONS = "android.permission.READ_PRINT_SERVICE_RECOMMENDATIONS";
field public static final java.lang.String READ_PRIVILEGED_PHONE_STATE = "android.permission.READ_PRIVILEGED_PHONE_STATE";
field public static final java.lang.String READ_RUNTIME_PROFILES = "android.permission.READ_RUNTIME_PROFILES";
field public static final java.lang.String READ_SEARCH_INDEXABLES = "android.permission.READ_SEARCH_INDEXABLES";
field public static final java.lang.String READ_WALLPAPER_INTERNAL = "android.permission.READ_WALLPAPER_INTERNAL";
field public static final java.lang.String READ_WIFI_CREDENTIAL = "android.permission.READ_WIFI_CREDENTIAL";
@@ -847,6 +848,7 @@ package android.content.pm {
public abstract class PackageManager {
method public abstract void addOnPermissionsChangeListener(android.content.pm.PackageManager.OnPermissionsChangedListener);
method public abstract java.util.List<android.content.IntentFilter> getAllIntentFilters(java.lang.String);
method public android.content.pm.dex.ArtManager getArtManager();
method public abstract java.lang.String getDefaultBrowserPackageNameAsUser(int);
method public abstract java.util.List<android.content.pm.PackageInfo> getInstalledPackagesAsUser(int, int);
method public abstract android.graphics.drawable.Drawable getInstantAppIcon(java.lang.String);
@@ -948,6 +950,26 @@ package android.content.pm {
}
package android.content.pm.dex {
public class ArtManager {
method public boolean isRuntimeProfilingEnabled(int);
method public void snapshotRuntimeProfile(int, java.lang.String, java.lang.String, java.util.concurrent.Executor, android.content.pm.dex.ArtManager.SnapshotRuntimeProfileCallback);
field public static final int PROFILE_APPS = 0; // 0x0
field public static final int PROFILE_BOOT_IMAGE = 1; // 0x1
field public static final int SNAPSHOT_FAILED_CODE_PATH_NOT_FOUND = 1; // 0x1
field public static final int SNAPSHOT_FAILED_INTERNAL_ERROR = 2; // 0x2
field public static final int SNAPSHOT_FAILED_PACKAGE_NOT_FOUND = 0; // 0x0
}
public static abstract class ArtManager.SnapshotRuntimeProfileCallback {
ctor public ArtManager.SnapshotRuntimeProfileCallback();
method public abstract void onError(int);
method public abstract void onSuccess(android.os.ParcelFileDescriptor);
}
}
package android.content.pm.permission {
public final class RuntimePermissionPresentationInfo implements android.os.Parcelable {

View File

@@ -56,6 +56,7 @@ import android.content.pm.ServiceInfo;
import android.content.pm.SharedLibraryInfo;
import android.content.pm.VerifierDeviceIdentity;
import android.content.pm.VersionedPackage;
import android.content.pm.dex.ArtManager;
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
import android.graphics.Bitmap;
@@ -122,6 +123,8 @@ public class ApplicationPackageManager extends PackageManager {
private UserManager mUserManager;
@GuardedBy("mLock")
private PackageInstaller mInstaller;
@GuardedBy("mLock")
private ArtManager mArtManager;
@GuardedBy("mDelegates")
private final ArrayList<MoveCallbackDelegate> mDelegates = new ArrayList<>();
@@ -2763,4 +2766,18 @@ public class ApplicationPackageManager extends PackageManager {
throw e.rethrowAsRuntimeException();
}
}
@Override
public ArtManager getArtManager() {
synchronized (mLock) {
if (mArtManager == null) {
try {
mArtManager = new ArtManager(mPM.getArtManager());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
return mArtManager;
}
}
}

View File

@@ -28,6 +28,7 @@ import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.dex.ArtManager;
import android.content.pm.split.SplitDependencyLoader;
import android.content.res.AssetManager;
import android.content.res.CompatibilityInfo;
@@ -35,7 +36,6 @@ import android.content.res.Configuration;
import android.content.res.Resources;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.FileUtils;
import android.os.Handler;
import android.os.IBinder;
@@ -49,13 +49,15 @@ import android.text.TextUtils;
import android.util.AndroidRuntimeException;
import android.util.ArrayMap;
import android.util.Log;
import android.util.LogPrinter;
import android.util.Slog;
import android.util.SparseArray;
import android.view.Display;
import android.view.DisplayAdjustments;
import com.android.internal.util.ArrayUtils;
import dalvik.system.VMRuntime;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
@@ -729,13 +731,6 @@ public final class LoadedApk {
}
}
// Keep in sync with installd (frameworks/native/cmds/installd/commands.cpp).
private static File getPrimaryProfileFile(String packageName) {
File profileDir = Environment.getDataProfilesDePackageDirectory(
UserHandle.myUserId(), packageName);
return new File(profileDir, "primary.prof");
}
private void setupJitProfileSupport() {
if (!SystemProperties.getBoolean("dalvik.vm.usejitprofiles", false)) {
return;
@@ -763,10 +758,12 @@ public final class LoadedApk {
return;
}
final File profileFile = getPrimaryProfileFile(mPackageName);
VMRuntime.registerAppInfo(profileFile.getPath(),
codePaths.toArray(new String[codePaths.size()]));
for (int i = codePaths.size() - 1; i >= 0; i--) {
String splitName = i == 0 ? null : mApplicationInfo.splitNames[i - 1];
String profileFile = ArtManager.getCurrentProfilePath(
mPackageName, UserHandle.myUserId(), splitName);
VMRuntime.registerAppInfo(profileFile, new String[] {codePaths.get(i)});
}
// Register the app data directory with the reporter. It will
// help deciding whether or not a dex file is the primary apk or a

View File

@@ -48,6 +48,7 @@ import android.content.pm.ServiceInfo;
import android.content.pm.UserInfo;
import android.content.pm.VerifierDeviceIdentity;
import android.content.pm.VersionedPackage;
import android.content.pm.dex.IArtManager;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Bundle;
@@ -664,4 +665,6 @@ interface IPackageManager {
ComponentName getInstantAppInstallerComponent();
String getInstantAppAndroidId(String packageName, int userId);
IArtManager getArtManager();
}

View File

@@ -42,6 +42,7 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.content.IntentSender;
import android.content.pm.PackageParser.PackageParserException;
import android.content.pm.dex.ArtManager;
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
import android.graphics.Rect;
@@ -1316,6 +1317,15 @@ public abstract class PackageManager {
*/
public static final int INSTALL_FAILED_INSTANT_APP_INVALID = -116;
/**
* Installation parse return code: this is passed in the
* {@link PackageInstaller#EXTRA_LEGACY_STATUS} if the dex metadata file is invalid or
* if there was no matching apk file for a dex metadata file.
*
* @hide
*/
public static final int INSTALL_FAILED_BAD_DEX_METADATA = -117;
/** @hide */
@IntDef(flag = true, prefix = { "DELETE_" }, value = {
DELETE_KEEP_DATA,
@@ -5632,6 +5642,8 @@ public abstract class PackageManager {
case INSTALL_FAILED_DUPLICATE_PERMISSION: return "INSTALL_FAILED_DUPLICATE_PERMISSION";
case INSTALL_FAILED_NO_MATCHING_ABIS: return "INSTALL_FAILED_NO_MATCHING_ABIS";
case INSTALL_FAILED_ABORTED: return "INSTALL_FAILED_ABORTED";
case INSTALL_FAILED_BAD_DEX_METADATA:
return "INSTALL_FAILED_BAD_DEX_METADATA";
default: return Integer.toString(status);
}
}
@@ -5676,6 +5688,7 @@ public abstract class PackageManager {
case INSTALL_PARSE_FAILED_BAD_SHARED_USER_ID: return PackageInstaller.STATUS_FAILURE_INVALID;
case INSTALL_PARSE_FAILED_MANIFEST_MALFORMED: return PackageInstaller.STATUS_FAILURE_INVALID;
case INSTALL_PARSE_FAILED_MANIFEST_EMPTY: return PackageInstaller.STATUS_FAILURE_INVALID;
case INSTALL_FAILED_BAD_DEX_METADATA: return PackageInstaller.STATUS_FAILURE_INVALID;
case INSTALL_FAILED_INTERNAL_ERROR: return PackageInstaller.STATUS_FAILURE;
case INSTALL_FAILED_USER_RESTRICTED: return PackageInstaller.STATUS_FAILURE_INCOMPATIBLE;
case INSTALL_FAILED_DUPLICATE_PERMISSION: return PackageInstaller.STATUS_FAILURE_CONFLICT;
@@ -5877,4 +5890,14 @@ public abstract class PackageManager {
@SystemApi
public abstract void registerDexModule(String dexModulePath,
@Nullable DexModuleRegisterCallback callback);
/**
* Returns the {@link ArtManager} associated with this package manager.
*
* @hide
*/
@SystemApi
public @NonNull ArtManager getArtManager() {
throw new UnsupportedOperationException("getArtManager not implemented in subclass");
}
}

View File

@@ -249,6 +249,9 @@ public class PackageParser {
SAFE_BROADCASTS.add(Intent.ACTION_BOOT_COMPLETED);
}
/** @hide */
public static final String APK_FILE_EXTENSION = ".apk";
/** @hide */
public static class NewPermissionInfo {
public final String name;
@@ -616,7 +619,7 @@ public class PackageParser {
}
public static boolean isApkPath(String path) {
return path.endsWith(".apk");
return path.endsWith(APK_FILE_EXTENSION);
}
/**

View File

@@ -0,0 +1,209 @@
/**
* Copyright 2017 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 android.content.pm.dex;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.os.Environment;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.util.Slog;
import java.io.File;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.concurrent.Executor;
/**
* Class for retrieving various kinds of information related to the runtime artifacts of
* packages that are currently installed on the device.
*
* @hide
*/
@SystemApi
public class ArtManager {
private static final String TAG = "ArtManager";
/** The snapshot failed because the package was not found. */
public static final int SNAPSHOT_FAILED_PACKAGE_NOT_FOUND = 0;
/** The snapshot failed because the package code path does not exist. */
public static final int SNAPSHOT_FAILED_CODE_PATH_NOT_FOUND = 1;
/** The snapshot failed because of an internal error (e.g. error during opening profiles). */
public static final int SNAPSHOT_FAILED_INTERNAL_ERROR = 2;
/** Constant used for applications profiles. */
public static final int PROFILE_APPS = 0;
/** Constant used for the boot image profile. */
public static final int PROFILE_BOOT_IMAGE = 1;
/** @hide */
@IntDef(flag = true, prefix = { "PROFILE_" }, value = {
PROFILE_APPS,
PROFILE_BOOT_IMAGE,
})
@Retention(RetentionPolicy.SOURCE)
public @interface ProfileType {}
private IArtManager mArtManager;
/**
* @hide
*/
public ArtManager(@NonNull IArtManager manager) {
mArtManager = manager;
}
/**
* Snapshots a runtime profile according to the {@code profileType} parameter.
*
* If {@code profileType} is {@link ArtManager#PROFILE_APPS} the method will snapshot
* the profile for for an apk belonging to the package {@code packageName}.
* The apk is identified by {@code codePath}.
*
* If {@code profileType} is {@code ArtManager.PROFILE_BOOT_IMAGE} the method will snapshot
* the profile for the boot image. In this case {@code codePath can be null}. The parameters
* {@code packageName} and {@code codePath} are ignored.
*u
* The calling process must have {@code android.permission.READ_RUNTIME_PROFILE} permission.
*
* The result will be posted on the {@code executor} using the given {@code callback}.
* The profile will be available as a read-only {@link android.os.ParcelFileDescriptor}.
*
* This method will throw {@link IllegalStateException} if
* {@link ArtManager#isRuntimeProfilingEnabled(int)} does not return true for the given
* {@code profileType}.
*
* @param profileType the type of profile that should be snapshot (boot image or app)
* @param packageName the target package name or null if the target is the boot image
* @param codePath the code path for which the profile should be retrieved or null if
* the target is the boot image
* @param callback the callback which should be used for the result
* @param executor the executor which should be used to post the result
*/
@RequiresPermission(android.Manifest.permission.READ_RUNTIME_PROFILES)
public void snapshotRuntimeProfile(@ProfileType int profileType, @Nullable String packageName,
@Nullable String codePath, @NonNull Executor executor,
@NonNull SnapshotRuntimeProfileCallback callback) {
Slog.d(TAG, "Requesting profile snapshot for " + packageName + ":" + codePath);
SnapshotRuntimeProfileCallbackDelegate delegate =
new SnapshotRuntimeProfileCallbackDelegate(callback, executor);
try {
mArtManager.snapshotRuntimeProfile(profileType, packageName, codePath, delegate);
} catch (RemoteException e) {
e.rethrowAsRuntimeException();
}
}
/**
* Returns true if runtime profiles are enabled for the given type, false otherwise.
*
* The calling process must have {@code android.permission.READ_RUNTIME_PROFILE} permission.
*
* @param profileType can be either {@link ArtManager#PROFILE_APPS}
* or {@link ArtManager#PROFILE_BOOT_IMAGE}
*/
@RequiresPermission(android.Manifest.permission.READ_RUNTIME_PROFILES)
public boolean isRuntimeProfilingEnabled(@ProfileType int profileType) {
try {
return mArtManager.isRuntimeProfilingEnabled(profileType);
} catch (RemoteException e) {
e.rethrowAsRuntimeException();
}
return false;
}
/**
* Callback used for retrieving runtime profiles.
*/
public abstract static class SnapshotRuntimeProfileCallback {
/**
* Called when the profile snapshot finished with success.
*
* @param profileReadFd the file descriptor that can be used to read the profile. Note that
* the file might be empty (which is valid profile).
*/
public abstract void onSuccess(ParcelFileDescriptor profileReadFd);
/**
* Called when the profile snapshot finished with an error.
*
* @param errCode the error code {@see SNAPSHOT_FAILED_PACKAGE_NOT_FOUND,
* SNAPSHOT_FAILED_CODE_PATH_NOT_FOUND, SNAPSHOT_FAILED_INTERNAL_ERROR}.
*/
public abstract void onError(int errCode);
}
private static class SnapshotRuntimeProfileCallbackDelegate
extends android.content.pm.dex.ISnapshotRuntimeProfileCallback.Stub {
private final ArtManager.SnapshotRuntimeProfileCallback mCallback;
private final Executor mExecutor;
private SnapshotRuntimeProfileCallbackDelegate(
ArtManager.SnapshotRuntimeProfileCallback callback, Executor executor) {
mCallback = callback;
mExecutor = executor;
}
@Override
public void onSuccess(final ParcelFileDescriptor profileReadFd) {
mExecutor.execute(() -> mCallback.onSuccess(profileReadFd));
}
@Override
public void onError(int errCode) {
mExecutor.execute(() -> mCallback.onError(errCode));
}
}
/**
* Return the profile name for the given split. If {@code splitName} is null the
* method returns the profile name for the base apk.
*
* @hide
*/
public static String getProfileName(String splitName) {
return splitName == null ? "primary.prof" : splitName + ".split.prof";
}
/**
* Return the path to the current profile corresponding to given package and split.
*
* @hide
*/
public static String getCurrentProfilePath(String packageName, int userId, String splitName) {
File profileDir = Environment.getDataProfilesDePackageDirectory(userId, packageName);
return new File(profileDir, getProfileName(splitName)).getAbsolutePath();
}
/**
* Return the snapshot profile file for the given package and profile name.
*
* KEEP in sync with installd dexopt.cpp.
* TODO(calin): inject the snapshot profile name from PM to avoid the dependency.
*
* @hide
*/
public static File getProfileSnapshotFileForName(String packageName, String profileName) {
File profileDir = Environment.getDataRefProfilesDePackageDirectory(packageName);
return new File(profileDir, profileName + ".snapshot");
}
}

View File

@@ -0,0 +1,230 @@
/**
* Copyright 2018 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 android.content.pm.dex;
import static android.content.pm.PackageManager.INSTALL_FAILED_BAD_DEX_METADATA;
import static android.content.pm.PackageParser.APK_FILE_EXTENSION;
import android.content.pm.PackageParser;
import android.content.pm.PackageParser.PackageLite;
import android.content.pm.PackageParser.PackageParserException;
import android.util.ArrayMap;
import android.util.jar.StrictJarFile;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
/**
* Helper class used to compute and validate the location of dex metadata files.
*
* @hide
*/
public class DexMetadataHelper {
private static final String DEX_METADATA_FILE_EXTENSION = ".dm";
private DexMetadataHelper() {}
/** Return true if the given file is a dex metadata file. */
public static boolean isDexMetadataFile(File file) {
return isDexMetadataPath(file.getName());
}
/** Return true if the given path is a dex metadata path. */
private static boolean isDexMetadataPath(String path) {
return path.endsWith(DEX_METADATA_FILE_EXTENSION);
}
/**
* Return the size (in bytes) of all dex metadata files associated with the given package.
*/
public static long getPackageDexMetadataSize(PackageLite pkg) {
long sizeBytes = 0;
Collection<String> dexMetadataList = DexMetadataHelper.getPackageDexMetadata(pkg).values();
for (String dexMetadata : dexMetadataList) {
sizeBytes += new File(dexMetadata).length();
}
return sizeBytes;
}
/**
* Search for the dex metadata file associated with the given target file.
* If it exists, the method returns the dex metadata file; otherwise it returns null.
*
* Note that this performs a loose matching suitable to be used in the InstallerSession logic.
* i.e. the method will attempt to match the {@code dmFile} regardless of {@code targetFile}
* extension (e.g. 'foo.dm' will match 'foo' or 'foo.apk').
*/
public static File findDexMetadataForFile(File targetFile) {
String dexMetadataPath = buildDexMetadataPathForFile(targetFile);
File dexMetadataFile = new File(dexMetadataPath);
return dexMetadataFile.exists() ? dexMetadataFile : null;
}
/**
* Return the dex metadata files for the given package as a map
* [code path -> dex metadata path].
*
* NOTE: involves I/O checks.
*/
public static Map<String, String> getPackageDexMetadata(PackageParser.Package pkg) {
return buildPackageApkToDexMetadataMap(pkg.getAllCodePaths());
}
/**
* Return the dex metadata files for the given package as a map
* [code path -> dex metadata path].
*
* NOTE: involves I/O checks.
*/
private static Map<String, String> getPackageDexMetadata(PackageLite pkg) {
return buildPackageApkToDexMetadataMap(pkg.getAllCodePaths());
}
/**
* Look up the dex metadata files for the given code paths building the map
* [code path -> dex metadata].
*
* For each code path (.apk) the method checks if a matching dex metadata file (.dm) exists.
* If it does it adds the pair to the returned map.
*
* Note that this method will do a loose
* matching based on the extension ('foo.dm' will match 'foo.apk' or 'foo').
*
* This should only be used for code paths extracted from a package structure after the naming
* was enforced in the installer.
*/
private static Map<String, String> buildPackageApkToDexMetadataMap(
List<String> codePaths) {
ArrayMap<String, String> result = new ArrayMap<>();
for (int i = codePaths.size() - 1; i >= 0; i--) {
String codePath = codePaths.get(i);
String dexMetadataPath = buildDexMetadataPathForFile(new File(codePath));
if (Files.exists(Paths.get(dexMetadataPath))) {
result.put(codePath, dexMetadataPath);
}
}
return result;
}
/**
* Return the dex metadata path associated with the given code path.
* (replaces '.apk' extension with '.dm')
*
* @throws IllegalArgumentException if the code path is not an .apk.
*/
public static String buildDexMetadataPathForApk(String codePath) {
if (!PackageParser.isApkPath(codePath)) {
throw new IllegalStateException(
"Corrupted package. Code path is not an apk " + codePath);
}
return codePath.substring(0, codePath.length() - APK_FILE_EXTENSION.length())
+ DEX_METADATA_FILE_EXTENSION;
}
/**
* Return the dex metadata path corresponding to the given {@code targetFile} using a loose
* matching.
* i.e. the method will attempt to match the {@code dmFile} regardless of {@code targetFile}
* extension (e.g. 'foo.dm' will match 'foo' or 'foo.apk').
*/
private static String buildDexMetadataPathForFile(File targetFile) {
return PackageParser.isApkFile(targetFile)
? buildDexMetadataPathForApk(targetFile.getPath())
: targetFile.getPath() + DEX_METADATA_FILE_EXTENSION;
}
/**
* Validate the dex metadata files installed for the given package.
*
* @throws PackageParserException in case of errors.
*/
public static void validatePackageDexMetadata(PackageParser.Package pkg)
throws PackageParserException {
Collection<String> apkToDexMetadataList = getPackageDexMetadata(pkg).values();
for (String dexMetadata : apkToDexMetadataList) {
validateDexMetadataFile(dexMetadata);
}
}
/**
* Validate that the given file is a dex metadata archive.
* This is just a sanity validation that the file is a zip archive.
*
* @throws PackageParserException if the file is not a .dm file.
*/
private static void validateDexMetadataFile(String dmaPath) throws PackageParserException {
StrictJarFile jarFile = null;
try {
jarFile = new StrictJarFile(dmaPath, false, false);
} catch (IOException e) {
throw new PackageParserException(INSTALL_FAILED_BAD_DEX_METADATA,
"Error opening " + dmaPath, e);
} finally {
if (jarFile != null) {
try {
jarFile.close();
} catch (IOException ignored) {
}
}
}
}
/**
* Validates that all dex metadata paths in the given list have a matching apk.
* (for any foo.dm there should be either a 'foo' of a 'foo.apk' file).
* If that's not the case it throws {@code IllegalStateException}.
*
* This is used to perform a basic sanity check during adb install commands.
* (The installer does not support stand alone .dm files)
*/
public static void validateDexPaths(String[] paths) {
ArrayList<String> apks = new ArrayList<>();
for (int i = 0; i < paths.length; i++) {
if (PackageParser.isApkPath(paths[i])) {
apks.add(paths[i]);
}
}
ArrayList<String> unmatchedDmFiles = new ArrayList<>();
for (int i = 0; i < paths.length; i++) {
String dmPath = paths[i];
if (isDexMetadataPath(dmPath)) {
boolean valid = false;
for (int j = apks.size() - 1; j >= 0; j--) {
if (dmPath.equals(buildDexMetadataPathForFile(new File(apks.get(j))))) {
valid = true;
break;
}
}
if (!valid) {
unmatchedDmFiles.add(dmPath);
}
}
}
if (!unmatchedDmFiles.isEmpty()) {
throw new IllegalStateException("Unmatched .dm files: " + unmatchedDmFiles);
}
}
}

View File

@@ -0,0 +1,58 @@
/*
** Copyright 2017, 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 android.content.pm.dex;
import android.content.pm.dex.ISnapshotRuntimeProfileCallback;
/**
* A system service that provides access to runtime and compiler artifacts.
*
* @hide
*/
interface IArtManager {
/**
* Snapshots a runtime profile according to the {@code profileType} parameter.
*
* If {@code profileType} is {@link ArtManager#PROFILE_APPS} the method will snapshot
* the profile for for an apk belonging to the package {@code packageName}.
* The apk is identified by {@code codePath}.
*
* If {@code profileType} is {@code ArtManager.PROFILE_BOOT_IMAGE} the method will snapshot
* the profile for the boot image. In this case {@code codePath can be null}. The parameters
* {@code packageName} and {@code codePath} are ignored.
*
* The calling process must have {@code android.permission.READ_RUNTIME_PROFILE} permission.
*
* The result will be posted on the {@code executor} using the given {@code callback}.
* The profile will be available as a read-only {@link android.os.ParcelFileDescriptor}.
*
* This method will throw {@link IllegalStateException} if
* {@link ArtManager#isRuntimeProfilingEnabled(int)} does not return true for the given
* {@code profileType}.
*/
oneway void snapshotRuntimeProfile(int profileType, in String packageName,
in String codePath, in ISnapshotRuntimeProfileCallback callback);
/**
* Returns true if runtime profiles are enabled for the given type, false otherwise.
* The type can be can be either {@code ArtManager.PROFILE_APPS}
* or {@code ArtManager.PROFILE_BOOT_IMAGE}.
*
* @param profileType
*/
boolean isRuntimeProfilingEnabled(int profileType);
}

View File

@@ -0,0 +1,29 @@
/*
** Copyright 2017, 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 android.content.pm.dex;
import android.os.ParcelFileDescriptor;
/**
* Callback used to post the result of a profile-snapshot operation.
*
* @hide
*/
oneway interface ISnapshotRuntimeProfileCallback {
void onSuccess(in ParcelFileDescriptor profileReadFd);
void onError(int errCode);
}

View File

@@ -291,7 +291,7 @@ public class Environment {
}
/** {@hide} */
public static File getReferenceProfile(String packageName) {
public static File getDataRefProfilesDePackageDirectory(String packageName) {
return buildPath(getDataDirectory(), "misc", "profiles", "ref", packageName);
}

View File

@@ -26,6 +26,7 @@ import android.content.pm.PackageInstaller.SessionParams;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.PackageParser.PackageLite;
import android.content.pm.dex.DexMetadataHelper;
import android.os.Environment;
import android.os.FileUtils;
import android.os.IBinder;
@@ -663,6 +664,9 @@ public class PackageHelper {
}
}
// Include raw dex metadata files
sizeBytes += DexMetadataHelper.getPackageDexMetadataSize(pkg);
// Include all relevant native code
sizeBytes += NativeLibraryHelper.sumNativeBinariesWithOverride(handle, abiOverride);

View File

@@ -576,7 +576,7 @@ public class ZygoteInit {
installd.dexopt(classPathElement, Process.SYSTEM_UID, packageName,
instructionSet, dexoptNeeded, outputPath, dexFlags, compilerFilter,
uuid, classLoaderContext, seInfo, false /* downgrade */,
targetSdkVersion);
targetSdkVersion, /*profileName*/ null, /*dexMetadataPath*/ null);
} catch (RemoteException | ServiceSpecificException e) {
// Ignore (but log), we need this on the classpath for fallback mode.
Log.w(TAG, "Failed compiling classpath element for system server: "

View File

@@ -3613,6 +3613,11 @@
<permission android:name="android.permission.INSTANT_APP_FOREGROUND_SERVICE"
android:protectionLevel="signature|development|instant|appop" />
<!-- @SystemApi Allows an application to read the runtime profiles of other apps.
@hide <p>Not for use by third-party applications. -->
<permission android:name="android.permission.READ_RUNTIME_PROFILES"
android:protectionLevel="signature|privileged" />
<application android:process="system"
android:persistent="true"
android:hasCode="false"

View File

@@ -0,0 +1,10 @@
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := tests
LOCAL_SRC_FILES := $(call all-subdir-java-files)
LOCAL_PACKAGE_NAME := install_split_base
include $(FrameworkCoreTests_BUILD_PACKAGE)

View File

@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2018 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.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.frameworks.coretests.install_split"
android:isolatedSplits="true">
<application android:label="ClassloaderSplitApp">
<activity android:name=".BaseActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>

View File

@@ -0,0 +1,23 @@
/**
* Copyright 2018 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.google.android.dexapis.splitapp;
import android.app.Activity;
/** Main activity */
public class BaseActivity extends Activity {
}

View File

@@ -0,0 +1,14 @@
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := tests
LOCAL_SRC_FILES := $(call all-subdir-java-files)
LOCAL_PACKAGE_NAME := install_split_feature_a
LOCAL_USE_AAPT2 := true
LOCAL_AAPT_FLAGS += --custom-package com.google.android.dexapis.splitapp.feature_a
LOCAL_AAPT_FLAGS += --package-id 0x80
include $(FrameworkCoreTests_BUILD_PACKAGE)

View File

@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2018 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.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.frameworks.coretests.install_split"
featureSplit="feature_a">
<application>
<activity android:name=".feature_a.FeatureAActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>

View File

@@ -0,0 +1,23 @@
/**
* Copyright 2018 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.google.android.dexapis.splitapp.feature_a;
import android.app.Activity;
/** Main activity */
public class FeatureAActivity extends Activity {
}

View File

@@ -0,0 +1,235 @@
/**
* Copyright (C) 2018 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 android.content.pm.dex;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.pm.PackageParser;
import android.content.pm.PackageParser.ApkLite;
import android.content.pm.PackageParser.Package;
import android.content.pm.PackageParser.PackageLite;
import android.content.pm.PackageParser.PackageParserException;
import android.os.FileUtils;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.SmallTest;
import com.android.frameworks.coretests.R;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import libcore.io.IoUtils;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@SmallTest
@RunWith(AndroidJUnit4.class)
public class DexMetadataHelperTest {
private static final String APK_FILE_EXTENSION = ".apk";
private static final String DEX_METADATA_FILE_EXTENSION = ".dm";
private File mTmpDir = null;
@Before
public void setUp() {
mTmpDir = IoUtils.createTemporaryDirectory("DexMetadataHelperTest");
}
@After
public void tearDown() {
if (mTmpDir != null) {
File[] files = mTmpDir.listFiles();
for (File f : files) {
f.delete();
}
}
}
private File createDexMetadataFile(String apkFileName) throws IOException {
File dmFile = new File(mTmpDir, apkFileName.replace(APK_FILE_EXTENSION,
DEX_METADATA_FILE_EXTENSION));
try (FileOutputStream fos = new FileOutputStream(dmFile)) {
try (ZipOutputStream zipOs = new ZipOutputStream(fos)) {
zipOs.putNextEntry(new ZipEntry("primary.prof"));
zipOs.closeEntry();
}
}
return dmFile;
}
private File copyApkToToTmpDir(String apkFileName, int apkResourceId) throws IOException {
Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
File outFile = new File(mTmpDir, apkFileName);
try (InputStream is = context.getResources().openRawResource(apkResourceId)) {
FileUtils.copyToFileOrThrow(is, outFile);
}
return outFile;
}
@Test
public void testParsePackageWithDmFileValid() throws IOException, PackageParserException {
copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base);
createDexMetadataFile("install_split_base.apk");
Package pkg = new PackageParser().parsePackage(mTmpDir, 0 /* flags */);
Map<String, String> packageDexMetadata = DexMetadataHelper.getPackageDexMetadata(pkg);
assertEquals(1, packageDexMetadata.size());
String baseDexMetadata = packageDexMetadata.get(pkg.baseCodePath);
assertNotNull(baseDexMetadata);
assertTrue(isDexMetadataForApk(baseDexMetadata, pkg.baseCodePath));
}
@Test
public void testParsePackageSplitsWithDmFileValid()
throws IOException, PackageParserException {
copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base);
copyApkToToTmpDir("install_split_feature_a.apk", R.raw.install_split_feature_a);
createDexMetadataFile("install_split_base.apk");
createDexMetadataFile("install_split_feature_a.apk");
Package pkg = new PackageParser().parsePackage(mTmpDir, 0 /* flags */);
Map<String, String> packageDexMetadata = DexMetadataHelper.getPackageDexMetadata(pkg);
assertEquals(2, packageDexMetadata.size());
String baseDexMetadata = packageDexMetadata.get(pkg.baseCodePath);
assertNotNull(baseDexMetadata);
assertTrue(isDexMetadataForApk(baseDexMetadata, pkg.baseCodePath));
String splitDexMetadata = packageDexMetadata.get(pkg.splitCodePaths[0]);
assertNotNull(splitDexMetadata);
assertTrue(isDexMetadataForApk(splitDexMetadata, pkg.splitCodePaths[0]));
}
@Test
public void testParsePackageSplitsNoBaseWithDmFileValid()
throws IOException, PackageParserException {
copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base);
copyApkToToTmpDir("install_split_feature_a.apk", R.raw.install_split_feature_a);
createDexMetadataFile("install_split_feature_a.apk");
Package pkg = new PackageParser().parsePackage(mTmpDir, 0 /* flags */);
Map<String, String> packageDexMetadata = DexMetadataHelper.getPackageDexMetadata(pkg);
assertEquals(1, packageDexMetadata.size());
String splitDexMetadata = packageDexMetadata.get(pkg.splitCodePaths[0]);
assertNotNull(splitDexMetadata);
assertTrue(isDexMetadataForApk(splitDexMetadata, pkg.splitCodePaths[0]));
}
@Test
public void testParsePackageWithDmFileInvalid() throws IOException {
copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base);
File invalidDmFile = new File(mTmpDir, "install_split_base.dm");
Files.createFile(invalidDmFile.toPath());
try {
PackageParser.Package pkg = new PackageParser().parsePackage(mTmpDir, 0 /* flags */);
DexMetadataHelper.validatePackageDexMetadata(pkg);
} catch (PackageParserException e) {
assertEquals(e.error, PackageManager.INSTALL_FAILED_BAD_DEX_METADATA);
}
}
@Test
public void testParsePackageSplitsWithDmFileInvalid()
throws IOException, PackageParserException {
copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base);
copyApkToToTmpDir("install_split_feature_a.apk", R.raw.install_split_feature_a);
createDexMetadataFile("install_split_base.apk");
File invalidDmFile = new File(mTmpDir, "install_split_feature_a.dm");
Files.createFile(invalidDmFile.toPath());
try {
PackageParser.Package pkg = new PackageParser().parsePackage(mTmpDir, 0 /* flags */);
DexMetadataHelper.validatePackageDexMetadata(pkg);
} catch (PackageParserException e) {
assertEquals(e.error, PackageManager.INSTALL_FAILED_BAD_DEX_METADATA);
}
}
@Test
public void testPackageWithDmFileNoMatch() throws IOException {
copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base);
createDexMetadataFile("non_existent.apk");
try {
DexMetadataHelper.validateDexPaths(mTmpDir.list());
fail("Should fail validation");
} catch (IllegalStateException e) {
// expected.
}
}
@Test
public void testPackageSplitsWithDmFileNoMatch()
throws IOException, PackageParserException {
copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base);
copyApkToToTmpDir("install_split_feature_a.apk", R.raw.install_split_feature_a);
createDexMetadataFile("install_split_base.apk");
createDexMetadataFile("install_split_feature_a.mistake.apk");
try {
DexMetadataHelper.validateDexPaths(mTmpDir.list());
fail("Should fail validation");
} catch (IllegalStateException e) {
// expected.
}
}
@Test
public void testPackageSizeWithDmFile()
throws IOException, PackageParserException {
copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base);
File dm = createDexMetadataFile("install_split_base.apk");
PackageParser.PackageLite pkg = new PackageParser().parsePackageLite(mTmpDir,
0 /* flags */);
Assert.assertEquals(dm.length(), DexMetadataHelper.getPackageDexMetadataSize(pkg));
}
// This simulates the 'adb shell pm install' flow.
@Test
public void testPackageSizeWithPartialPackageLite() throws IOException, PackageParserException {
File base = copyApkToToTmpDir("install_split_base", R.raw.install_split_base);
File dm = createDexMetadataFile("install_split_base.apk");
ApkLite baseApk = PackageParser.parseApkLite(base, 0);
PackageLite pkgLite = new PackageLite(null, baseApk, null, null, null, null,
null, null);
Assert.assertEquals(dm.length(), DexMetadataHelper.getPackageDexMetadataSize(pkgLite));
}
private static boolean isDexMetadataForApk(String dmaPath, String apkPath) {
return apkPath.substring(0, apkPath.length() - APK_FILE_EXTENSION.length()).equals(
dmaPath.substring(0, dmaPath.length() - DEX_METADATA_FILE_EXTENSION.length()));
}
}

View File

@@ -16,7 +16,9 @@
package com.android.server.pm;
import android.annotation.AppIdInt;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.content.Context;
import android.content.pm.PackageStats;
import android.os.Build;
@@ -283,43 +285,45 @@ public class Installer extends SystemService {
public void dexopt(String apkPath, int uid, @Nullable String pkgName, String instructionSet,
int dexoptNeeded, @Nullable String outputPath, int dexFlags,
String compilerFilter, @Nullable String volumeUuid, @Nullable String sharedLibraries,
@Nullable String seInfo, boolean downgrade, int targetSdkVersion)
@Nullable String seInfo, boolean downgrade, int targetSdkVersion,
@Nullable String profileName, @Nullable String dexMetadataPath)
throws InstallerException {
assertValidInstructionSet(instructionSet);
if (!checkBeforeRemote()) return;
try {
mInstalld.dexopt(apkPath, uid, pkgName, instructionSet, dexoptNeeded, outputPath,
dexFlags, compilerFilter, volumeUuid, sharedLibraries, seInfo, downgrade,
targetSdkVersion);
targetSdkVersion, profileName, dexMetadataPath);
} catch (Exception e) {
throw InstallerException.from(e);
}
}
public boolean mergeProfiles(int uid, String packageName) throws InstallerException {
if (!checkBeforeRemote()) return false;
try {
return mInstalld.mergeProfiles(uid, packageName);
} catch (Exception e) {
throw InstallerException.from(e);
}
}
public boolean dumpProfiles(int uid, String packageName, String codePaths)
public boolean mergeProfiles(int uid, String packageName, String profileName)
throws InstallerException {
if (!checkBeforeRemote()) return false;
try {
return mInstalld.dumpProfiles(uid, packageName, codePaths);
return mInstalld.mergeProfiles(uid, packageName, profileName);
} catch (Exception e) {
throw InstallerException.from(e);
}
}
public boolean copySystemProfile(String systemProfile, int uid, String packageName)
public boolean dumpProfiles(int uid, String packageName, String profileName, String codePath)
throws InstallerException {
if (!checkBeforeRemote()) return false;
try {
return mInstalld.copySystemProfile(systemProfile, uid, packageName);
return mInstalld.dumpProfiles(uid, packageName, profileName, codePath);
} catch (Exception e) {
throw InstallerException.from(e);
}
}
public boolean copySystemProfile(String systemProfile, int uid, String packageName,
String profileName) throws InstallerException {
if (!checkBeforeRemote()) return false;
try {
return mInstalld.copySystemProfile(systemProfile, uid, packageName, profileName);
} catch (Exception e) {
throw InstallerException.from(e);
}
@@ -363,10 +367,10 @@ public class Installer extends SystemService {
}
}
public void clearAppProfiles(String packageName) throws InstallerException {
public void clearAppProfiles(String packageName, String profileName) throws InstallerException {
if (!checkBeforeRemote()) return;
try {
mInstalld.clearAppProfiles(packageName);
mInstalld.clearAppProfiles(packageName, profileName);
} catch (Exception e) {
throw InstallerException.from(e);
}
@@ -489,6 +493,36 @@ public class Installer extends SystemService {
}
}
public byte[] hashSecondaryDexFile(String dexPath, String packageName, int uid,
@Nullable String volumeUuid, int flags) throws InstallerException {
if (!checkBeforeRemote()) return new byte[0];
try {
return mInstalld.hashSecondaryDexFile(dexPath, packageName, uid, volumeUuid, flags);
} catch (Exception e) {
throw InstallerException.from(e);
}
}
public boolean createProfileSnapshot(int appId, String packageName, String profileName,
String classpath) throws InstallerException {
if (!checkBeforeRemote()) return false;
try {
return mInstalld.createProfileSnapshot(appId, packageName, profileName, classpath);
} catch (Exception e) {
throw InstallerException.from(e);
}
}
public void destroyProfileSnapshot(String packageName, String profileName)
throws InstallerException {
if (!checkBeforeRemote()) return;
try {
mInstalld.destroyProfileSnapshot(packageName, profileName);
} catch (Exception e) {
throw InstallerException.from(e);
}
}
public void invalidateMounts() throws InstallerException {
if (!checkBeforeRemote()) return;
try {
@@ -507,6 +541,17 @@ public class Installer extends SystemService {
}
}
public boolean prepareAppProfile(String pkg, @UserIdInt int userId, @AppIdInt int appId,
String profileName, String codePath, String dexMetadataPath) throws InstallerException {
if (!checkBeforeRemote()) return false;
try {
return mInstalld.prepareAppProfile(pkg, userId, appId, profileName, codePath,
dexMetadataPath);
} catch (Exception e) {
throw InstallerException.from(e);
}
}
private static void assertValidInstructionSet(String instructionSet)
throws InstallerException {
for (String abi : Build.SUPPORTED_ABIS) {

View File

@@ -261,12 +261,12 @@ public class OtaDexoptService extends IOtaDexopt.Stub {
String instructionSet, int dexoptNeeded, @Nullable String outputPath,
int dexFlags, String compilerFilter, @Nullable String volumeUuid,
@Nullable String sharedLibraries, @Nullable String seInfo, boolean downgrade,
int targetSdkVersion)
throws InstallerException {
int targetSdkVersion, @Nullable String profileName,
@Nullable String dexMetadataPath) throws InstallerException {
final StringBuilder builder = new StringBuilder();
// The version. Right now it's 4.
builder.append("4 ");
// The version. Right now it's 6.
builder.append("6 ");
builder.append("dexopt");
@@ -283,6 +283,8 @@ public class OtaDexoptService extends IOtaDexopt.Stub {
encodeParameter(builder, seInfo);
encodeParameter(builder, downgrade);
encodeParameter(builder, targetSdkVersion);
encodeParameter(builder, profileName);
encodeParameter(builder, dexMetadataPath);
commands.add(builder.toString());
}

View File

@@ -20,6 +20,8 @@ import android.annotation.Nullable;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageParser;
import android.content.pm.dex.ArtManager;
import android.content.pm.dex.DexMetadataHelper;
import android.os.FileUtils;
import android.os.PowerManager;
import android.os.SystemClock;
@@ -211,12 +213,21 @@ public class PackageDexOptimizer {
}
}
String profileName = ArtManager.getProfileName(i == 0 ? null : pkg.splitNames[i - 1]);
String dexMetadataPath = null;
if (options.isDexoptInstallWithDexMetadata()) {
File dexMetadataFile = DexMetadataHelper.findDexMetadataForFile(new File(path));
dexMetadataPath = dexMetadataFile == null
? null : dexMetadataFile.getAbsolutePath();
}
final boolean isUsedByOtherApps = options.isDexoptAsSharedLibrary()
|| packageUseInfo.isUsedByOtherApps(path);
final String compilerFilter = getRealCompilerFilter(pkg.applicationInfo,
options.getCompilerFilter(), isUsedByOtherApps);
final boolean profileUpdated = options.isCheckForProfileUpdates() &&
isProfileUpdated(pkg, sharedGid, compilerFilter);
isProfileUpdated(pkg, sharedGid, profileName, compilerFilter);
// Get the dexopt flags after getRealCompilerFilter to make sure we get the correct
// flags.
@@ -225,7 +236,7 @@ public class PackageDexOptimizer {
for (String dexCodeIsa : dexCodeInstructionSets) {
int newResult = dexOptPath(pkg, path, dexCodeIsa, compilerFilter,
profileUpdated, classLoaderContexts[i], dexoptFlags, sharedGid,
packageStats, options.isDowngrade());
packageStats, options.isDowngrade(), profileName, dexMetadataPath);
// The end result is:
// - FAILED if any path failed,
// - PERFORMED if at least one path needed compilation,
@@ -249,7 +260,8 @@ public class PackageDexOptimizer {
@GuardedBy("mInstallLock")
private int dexOptPath(PackageParser.Package pkg, String path, String isa,
String compilerFilter, boolean profileUpdated, String classLoaderContext,
int dexoptFlags, int uid, CompilerStats.PackageStats packageStats, boolean downgrade) {
int dexoptFlags, int uid, CompilerStats.PackageStats packageStats, boolean downgrade,
String profileName, String dexMetadataPath) {
int dexoptNeeded = getDexoptNeeded(path, isa, compilerFilter, classLoaderContext,
profileUpdated, downgrade);
if (Math.abs(dexoptNeeded) == DexFile.NO_DEXOPT_NEEDED) {
@@ -275,7 +287,8 @@ public class PackageDexOptimizer {
// primary dex files.
mInstaller.dexopt(path, uid, pkg.packageName, isa, dexoptNeeded, oatDir, dexoptFlags,
compilerFilter, pkg.volumeUuid, classLoaderContext, pkg.applicationInfo.seInfo,
false /* downgrade*/, pkg.applicationInfo.targetSdkVersion);
false /* downgrade*/, pkg.applicationInfo.targetSdkVersion,
profileName, dexMetadataPath);
if (packageStats != null) {
long endTime = System.currentTimeMillis();
@@ -396,7 +409,8 @@ public class PackageDexOptimizer {
mInstaller.dexopt(path, info.uid, info.packageName, isa, /*dexoptNeeded*/ 0,
/*oatDir*/ null, dexoptFlags,
compilerFilter, info.volumeUuid, classLoaderContext, info.seInfoUser,
options.isDowngrade(), info.targetSdkVersion);
options.isDowngrade(), info.targetSdkVersion, /*profileName*/ null,
/*dexMetadataPath*/ null);
}
return DEX_OPT_PERFORMED;
@@ -506,9 +520,13 @@ public class PackageDexOptimizer {
private int getDexFlags(ApplicationInfo info, String compilerFilter, DexoptOptions options) {
int flags = info.flags;
boolean debuggable = (flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
// Profile guide compiled oat files should not be public.
// Profile guide compiled oat files should not be public unles they are based
// on profiles from dex metadata archives.
// The flag isDexoptInstallWithDexMetadata applies only on installs when we know that
// the user does not have an existing profile.
boolean isProfileGuidedFilter = isProfileGuidedCompilerFilter(compilerFilter);
boolean isPublic = !info.isForwardLocked() && !isProfileGuidedFilter;
boolean isPublic = !info.isForwardLocked() &&
(!isProfileGuidedFilter || options.isDexoptInstallWithDexMetadata());
int profileFlag = isProfileGuidedFilter ? DEXOPT_PROFILE_GUIDED : 0;
// Some apps are executed with restrictions on hidden API usage. If this app is one
// of them, pass a flag to dexopt to enable the same restrictions during compilation.
@@ -548,14 +566,15 @@ public class PackageDexOptimizer {
* current profile and the reference profile will be merged and subsequent calls
* may return a different result.
*/
private boolean isProfileUpdated(PackageParser.Package pkg, int uid, String compilerFilter) {
private boolean isProfileUpdated(PackageParser.Package pkg, int uid, String profileName,
String compilerFilter) {
// Check if we are allowed to merge and if the compiler filter is profile guided.
if (!isProfileGuidedCompilerFilter(compilerFilter)) {
return false;
}
// Merge profiles. It returns whether or not there was an updated in the profile info.
try {
return mInstaller.mergeProfiles(uid, pkg.packageName);
return mInstaller.mergeProfiles(uid, pkg.packageName, profileName);
} catch (InstallerException e) {
Slog.w(TAG, "Failed to merge profiles", e);
}

View File

@@ -17,10 +17,12 @@
package com.android.server.pm;
import static android.content.pm.PackageManager.INSTALL_FAILED_ABORTED;
import static android.content.pm.PackageManager.INSTALL_FAILED_BAD_DEX_METADATA;
import static android.content.pm.PackageManager.INSTALL_FAILED_CONTAINER_ERROR;
import static android.content.pm.PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
import static android.content.pm.PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_APK;
import static android.content.pm.PackageParser.APK_FILE_EXTENSION;
import static android.system.OsConstants.O_CREAT;
import static android.system.OsConstants.O_RDONLY;
import static android.system.OsConstants.O_WRONLY;
@@ -94,6 +96,7 @@ import com.android.internal.util.Preconditions;
import com.android.server.pm.Installer.InstallerException;
import com.android.server.pm.PackageInstallerService.PackageInstallObserverAdapter;
import android.content.pm.dex.DexMetadataHelper;
import libcore.io.IoUtils;
import org.xmlpull.v1.XmlPullParser;
@@ -259,6 +262,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
// entries like "lost+found".
if (file.isDirectory()) return false;
if (file.getName().endsWith(REMOVE_SPLIT_MARKER_EXTENSION)) return false;
if (DexMetadataHelper.isDexMetadataFile(file)) return false;
return true;
}
};
@@ -944,6 +948,15 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
mInstallerPackageName, mInstallerUid, user, mCertificates);
}
private static void maybeRenameFile(File from, File to) throws PackageManagerException {
if (!from.equals(to)) {
if (!from.renameTo(to)) {
throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
"Could not rename file " + from + " to " + to);
}
}
}
/**
* Validate install by confirming that all application packages are have
* consistent package name, version code, and signing certificates.
@@ -988,6 +1001,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
if (ArrayUtils.isEmpty(addedFiles) && removeSplitList.size() == 0) {
throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, "No packages staged");
}
// Verify that all staged packages are internally consistent
final ArraySet<String> stagedSplits = new ArraySet<>();
for (File addedFile : addedFiles) {
@@ -1022,9 +1036,9 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
// Take this opportunity to enforce uniform naming
final String targetName;
if (apk.splitName == null) {
targetName = "base.apk";
targetName = "base" + APK_FILE_EXTENSION;
} else {
targetName = "split_" + apk.splitName + ".apk";
targetName = "split_" + apk.splitName + APK_FILE_EXTENSION;
}
if (!FileUtils.isValidExtFilename(targetName)) {
throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
@@ -1032,9 +1046,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
}
final File targetFile = new File(mResolvedStageDir, targetName);
if (!addedFile.equals(targetFile)) {
addedFile.renameTo(targetFile);
}
maybeRenameFile(addedFile, targetFile);
// Base is coming from session
if (apk.splitName == null) {
@@ -1042,6 +1054,18 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
}
mResolvedStagedFiles.add(targetFile);
final File dexMetadataFile = DexMetadataHelper.findDexMetadataForFile(addedFile);
if (dexMetadataFile != null) {
if (!FileUtils.isValidExtFilename(dexMetadataFile.getName())) {
throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
"Invalid filename: " + dexMetadataFile);
}
final File targetDexMetadataFile = new File(mResolvedStageDir,
DexMetadataHelper.buildDexMetadataPathForApk(targetName));
mResolvedStagedFiles.add(targetDexMetadataFile);
maybeRenameFile(dexMetadataFile, targetDexMetadataFile);
}
}
if (removeSplitList.size() > 0) {
@@ -1099,6 +1123,12 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
if (mResolvedBaseFile == null) {
mResolvedBaseFile = new File(appInfo.getBaseCodePath());
mResolvedInheritedFiles.add(mResolvedBaseFile);
// Inherit the dex metadata if present.
final File baseDexMetadataFile =
DexMetadataHelper.findDexMetadataForFile(mResolvedBaseFile);
if (baseDexMetadataFile != null) {
mResolvedInheritedFiles.add(baseDexMetadataFile);
}
}
// Inherit splits if not overridden
@@ -1109,6 +1139,12 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
final boolean splitRemoved = removeSplitList.contains(splitName);
if (!stagedSplits.contains(splitName) && !splitRemoved) {
mResolvedInheritedFiles.add(splitFile);
// Inherit the dex metadata if present.
final File splitDexMetadataFile =
DexMetadataHelper.findDexMetadataForFile(splitFile);
if (splitDexMetadataFile != null) {
mResolvedInheritedFiles.add(splitDexMetadataFile);
}
}
}
}
@@ -1167,7 +1203,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
/**
* Calculate the final install footprint size, combining both staged and
* existing APKs together and including unpacked native code from both.
*/
*/
private long calculateInstalledSize() throws PackageManagerException {
Preconditions.checkNotNull(mResolvedBaseFile);

View File

@@ -106,8 +106,6 @@ import static com.android.server.pm.PermissionsState.PERMISSION_OPERATION_FAILUR
import static com.android.server.pm.PermissionsState.PERMISSION_OPERATION_SUCCESS;
import static com.android.server.pm.PermissionsState.PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED;
import static dalvik.system.DexFile.getNonProfileGuidedCompilerFilter;
import android.Manifest;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -179,6 +177,9 @@ import android.content.pm.UserInfo;
import android.content.pm.VerifierDeviceIdentity;
import android.content.pm.VerifierInfo;
import android.content.pm.VersionedPackage;
import android.content.pm.dex.ArtManager;
import android.content.pm.dex.DexMetadataHelper;
import android.content.pm.dex.IArtManager;
import android.content.res.Resources;
import android.database.ContentObserver;
import android.graphics.Bitmap;
@@ -285,13 +286,14 @@ import com.android.server.pm.Installer.InstallerException;
import com.android.server.pm.PermissionsState.PermissionState;
import com.android.server.pm.Settings.DatabaseVersion;
import com.android.server.pm.Settings.VersionInfo;
import com.android.server.pm.dex.ArtManagerService;
import com.android.server.pm.dex.DexLogger;
import com.android.server.pm.dex.DexManager;
import com.android.server.pm.dex.DexoptOptions;
import com.android.server.pm.dex.PackageDexUsage;
import com.android.server.storage.DeviceStorageMonitorInternal;
import dalvik.system.CloseGuard;
import dalvik.system.DexFile;
import dalvik.system.VMRuntime;
import libcore.io.IoUtils;
@@ -960,6 +962,8 @@ public class PackageManagerService extends IPackageManager.Stub
final PackageInstallerService mInstallerService;
final ArtManagerService mArtManagerService;
private final PackageDexOptimizer mPackageDexOptimizer;
// DexManager handles the usage of dex files (e.g. secondary files, whether or not a package
// is used by other apps).
@@ -2461,7 +2465,11 @@ public class PackageManagerService extends IPackageManager.Stub
mInstaller = installer;
mPackageDexOptimizer = new PackageDexOptimizer(installer, mInstallLock, context,
"*dexopt*");
mDexManager = new DexManager(this, mPackageDexOptimizer, installer, mInstallLock);
DexManager.Listener dexManagerListener = DexLogger.getListener(this,
installer, mInstallLock);
mDexManager = new DexManager(this, mPackageDexOptimizer, installer, mInstallLock,
dexManagerListener);
mArtManagerService = new ArtManagerService(this, installer, mInstallLock);
mMoveCallbacks = new MoveCallbacks(FgThread.get().getLooper());
mOnPermissionChangeListeners = new OnPermissionChangeListeners(
@@ -9753,7 +9761,8 @@ public class PackageManagerService extends IPackageManager.Stub
// PackageDexOptimizer to prevent this happening on first boot. The issue
// is that we don't have a good way to say "do this only once".
if (!mInstaller.copySystemProfile(profileFile.getAbsolutePath(),
pkg.applicationInfo.uid, pkg.packageName)) {
pkg.applicationInfo.uid, pkg.packageName,
ArtManager.getProfileName(null))) {
Log.e(TAG, "Installer failed to copy system profile!");
} else {
// Disabled as this causes speed-profile compilation during first boot
@@ -9788,7 +9797,8 @@ public class PackageManagerService extends IPackageManager.Stub
// issue is that we don't have a good way to say "do this only
// once".
if (!mInstaller.copySystemProfile(profileFile.getAbsolutePath(),
pkg.applicationInfo.uid, pkg.packageName)) {
pkg.applicationInfo.uid, pkg.packageName,
ArtManager.getProfileName(null))) {
Log.e(TAG, "Failed to copy system profile for stub package!");
} else {
useProfileForDexopt = true;
@@ -10213,14 +10223,7 @@ public class PackageManagerService extends IPackageManager.Stub
synchronized (mInstallLock) {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "dump profiles");
final int sharedGid = UserHandle.getSharedAppGid(pkg.applicationInfo.uid);
try {
List<String> allCodePaths = pkg.getAllCodePathsExcludingResourceOnly();
String codePaths = TextUtils.join(";", allCodePaths);
mInstaller.dumpProfiles(sharedGid, packageName, codePaths);
} catch (InstallerException e) {
Slog.w(TAG, "Failed to dump profiles", e);
}
mArtManagerService.dumpProfiles(pkg);
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
}
@@ -10296,6 +10299,8 @@ public class PackageManagerService extends IPackageManager.Stub
for (int i = 0; i < childCount; i++) {
clearAppDataLeafLIF(pkg.childPackages.get(i), userId, flags);
}
clearAppProfilesLIF(pkg, UserHandle.USER_ALL);
}
private void clearAppDataLeafLIF(PackageParser.Package pkg, int userId, int flags) {
@@ -10368,18 +10373,10 @@ public class PackageManagerService extends IPackageManager.Stub
Slog.wtf(TAG, "Package was null!", new Throwable());
return;
}
clearAppProfilesLeafLIF(pkg);
mArtManagerService.clearAppProfiles(pkg);
final int childCount = (pkg.childPackages != null) ? pkg.childPackages.size() : 0;
for (int i = 0; i < childCount; i++) {
clearAppProfilesLeafLIF(pkg.childPackages.get(i));
}
}
private void clearAppProfilesLeafLIF(PackageParser.Package pkg) {
try {
mInstaller.clearAppProfiles(pkg.packageName);
} catch (InstallerException e) {
Slog.w(TAG, String.valueOf(e));
mArtManagerService.clearAppProfiles(pkg.childPackages.get(i));
}
}
@@ -17901,7 +17898,6 @@ public class PackageManagerService extends IPackageManager.Stub
clearAppDataLIF(pkg, UserHandle.USER_ALL, StorageManager.FLAG_STORAGE_DE
| StorageManager.FLAG_STORAGE_CE | Installer.FLAG_CLEAR_CODE_CACHE_ONLY);
clearAppProfilesLIF(deletedPackage, UserHandle.USER_ALL);
try {
final PackageParser.Package newPackage = scanPackageTracedLI(pkg, policyFlags,
@@ -18037,7 +18033,6 @@ public class PackageManagerService extends IPackageManager.Stub
// Successfully disabled the old package. Now proceed with re-installation
clearAppDataLIF(pkg, UserHandle.USER_ALL, StorageManager.FLAG_STORAGE_DE
| StorageManager.FLAG_STORAGE_CE | Installer.FLAG_CLEAR_CODE_CACHE_ONLY);
clearAppProfilesLIF(deletedPackage, UserHandle.USER_ALL);
res.setReturnCode(PackageManager.INSTALL_SUCCEEDED);
pkg.setApplicationInfoFlags(ApplicationInfo.FLAG_UPDATED_SYSTEM_APP,
@@ -18463,6 +18458,7 @@ public class PackageManagerService extends IPackageManager.Stub
final PackageParser.Package pkg;
try {
pkg = pp.parsePackage(tmpPackageFile, parseFlags);
DexMetadataHelper.validatePackageDexMetadata(pkg);
} catch (PackageParserException e) {
res.setError("Failed parse during installPackageLI", e);
return;
@@ -18837,6 +18833,11 @@ public class PackageManagerService extends IPackageManager.Stub
}
}
// Prepare the application profiles for the new code paths.
// This needs to be done before invoking dexopt so that any install-time profile
// can be used for optimizations.
mArtManagerService.prepareAppProfiles(pkg, resolveUserIds(args.user.getIdentifier()));
// Check whether we need to dexopt the app.
//
// NOTE: it is IMPORTANT to call dexopt:
@@ -18871,7 +18872,8 @@ public class PackageManagerService extends IPackageManager.Stub
// Also, don't fail application installs if the dexopt step fails.
DexoptOptions dexoptOptions = new DexoptOptions(pkg.packageName,
REASON_INSTALL,
DexoptOptions.DEXOPT_BOOT_COMPLETE);
DexoptOptions.DEXOPT_BOOT_COMPLETE |
DexoptOptions.DEXOPT_INSTALL_WITH_DEX_METADATA_FILE);
mPackageDexOptimizer.performDexOpt(pkg, pkg.usesLibraryFiles,
null /* instructionSets */,
getOrCreateCompilerPackageStats(pkg),
@@ -22051,7 +22053,6 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName());
}
clearAppDataLIF(newPkg, UserHandle.USER_ALL, FLAG_STORAGE_DE
| FLAG_STORAGE_CE | Installer.FLAG_CLEAR_CODE_CACHE_ONLY);
clearAppProfilesLIF(newPkg, UserHandle.USER_ALL);
mDexManager.notifyPackageUpdated(newPkg.packageName,
newPkg.baseCodePath, newPkg.splitCodePaths);
}
@@ -24211,6 +24212,8 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName());
Slog.e(TAG, "Failed to create app data for " + packageName + ": " + e);
}
}
// Prepare the application profiles.
mArtManagerService.prepareAppProfiles(pkg, userId);
if ((flags & StorageManager.FLAG_STORAGE_CE) != 0 && ceDataInode != -1) {
// TODO: mark this structure as dirty so we persist it!
@@ -24907,6 +24910,11 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName());
return mInstallerService;
}
@Override
public IArtManager getArtManager() {
return mArtManagerService;
}
private boolean userNeedsBadging(int userId) {
int index = mUserNeedsBadging.indexOfKey(userId);
if (index < 0) {

View File

@@ -42,6 +42,7 @@ import android.content.pm.PackageInstaller.SessionParams;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
import android.content.pm.VersionedPackage;
import android.content.pm.dex.DexMetadataHelper;
import android.content.res.AssetManager;
import android.content.res.Resources;
import android.net.Uri;
@@ -1480,6 +1481,14 @@ class PackageManagerShellCommand extends ShellCommand {
session = new PackageInstaller.Session(
mInterface.getPackageInstaller().openSession(sessionId));
// Sanity check that all .dm files match an apk.
// (The installer does not support standalone .dm files and will not process them.)
try {
DexMetadataHelper.validateDexPaths(session.getNames());
} catch (IllegalStateException | IOException e) {
pw.println("Warning [Could not validate the dex paths: " + e.getMessage() + "]");
}
final LocalIntentReceiver receiver = new LocalIntentReceiver();
session.commit(receiver.getIntentSender());

View File

@@ -0,0 +1,400 @@
/*
* Copyright (C) 2017 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.dex;
import android.Manifest;
import android.annotation.UserIdInt;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageParser;
import android.content.pm.dex.ArtManager;
import android.content.pm.dex.ArtManager.ProfileType;
import android.content.pm.dex.DexMetadataHelper;
import android.os.Binder;
import android.os.Build;
import android.os.Handler;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.content.pm.IPackageManager;
import android.content.pm.dex.ISnapshotRuntimeProfileCallback;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.system.Os;
import android.util.ArrayMap;
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.os.BackgroundThread;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.Preconditions;
import com.android.server.pm.Installer;
import com.android.server.pm.Installer.InstallerException;
import java.io.File;
import java.io.FileNotFoundException;
import libcore.io.IoUtils;
import libcore.util.NonNull;
import libcore.util.Nullable;
/**
* A system service that provides access to runtime and compiler artifacts.
*
* This service is not accessed by users directly, instead one uses an instance of
* {@link ArtManager}, which can be accessed via {@link PackageManager} as follows:
* <p/>
* {@code context().getPackageManager().getArtManager();}
* <p class="note">
* Note: Accessing runtime artifacts may require extra permissions. For example querying the
* runtime profiles of apps requires {@link android.Manifest.permission#READ_RUNTIME_PROFILES}
* which is a system-level permission that will not be granted to normal apps.
*/
public class ArtManagerService extends android.content.pm.dex.IArtManager.Stub {
private static final String TAG = "ArtManagerService";
private static boolean DEBUG = false;
private static boolean DEBUG_IGNORE_PERMISSIONS = false;
// Package name used to create the profile directory layout when
// taking a snapshot of the boot image profile.
private static final String BOOT_IMAGE_ANDROID_PACKAGE = "android";
// Profile name used for the boot image profile.
private static final String BOOT_IMAGE_PROFILE_NAME = "android.prof";
private final IPackageManager mPackageManager;
private final Object mInstallLock;
@GuardedBy("mInstallLock")
private final Installer mInstaller;
private final Handler mHandler;
public ArtManagerService(IPackageManager pm, Installer installer, Object installLock) {
mPackageManager = pm;
mInstaller = installer;
mInstallLock = installLock;
mHandler = new Handler(BackgroundThread.getHandler().getLooper());
}
@Override
public void snapshotRuntimeProfile(@ProfileType int profileType, @Nullable String packageName,
@Nullable String codePath, @NonNull ISnapshotRuntimeProfileCallback callback) {
// Sanity checks on the arguments.
Preconditions.checkNotNull(callback);
boolean bootImageProfile = profileType == ArtManager.PROFILE_BOOT_IMAGE;
if (!bootImageProfile) {
Preconditions.checkStringNotEmpty(codePath);
Preconditions.checkStringNotEmpty(packageName);
}
// Verify that the caller has the right permissions and that the runtime profiling is
// enabled. The call to isRuntimePermissions will checkReadRuntimeProfilePermission.
if (!isRuntimeProfilingEnabled(profileType)) {
throw new IllegalStateException("Runtime profiling is not enabled for " + profileType);
}
if (DEBUG) {
Slog.d(TAG, "Requested snapshot for " + packageName + ":" + codePath);
}
if (bootImageProfile) {
snapshotBootImageProfile(callback);
} else {
snapshotAppProfile(packageName, codePath, callback);
}
}
private void snapshotAppProfile(String packageName, String codePath,
ISnapshotRuntimeProfileCallback callback) {
PackageInfo info = null;
try {
// Note that we use the default user 0 to retrieve the package info.
// This doesn't really matter because for user 0 we always get a package back (even if
// it's not installed for the user 0). It is ok because we only care about the code
// paths and not if the package is enabled or not for the user.
// TODO(calin): consider adding an API to PMS which can retrieve the
// PackageParser.Package.
info = mPackageManager.getPackageInfo(packageName, /*flags*/ 0, /*userId*/ 0);
} catch (RemoteException ignored) {
// Should not happen.
}
if (info == null) {
postError(callback, packageName, ArtManager.SNAPSHOT_FAILED_PACKAGE_NOT_FOUND);
return;
}
boolean pathFound = info.applicationInfo.getBaseCodePath().equals(codePath);
String splitName = null;
String[] splitCodePaths = info.applicationInfo.getSplitCodePaths();
if (!pathFound && (splitCodePaths != null)) {
for (int i = splitCodePaths.length - 1; i >= 0; i--) {
if (splitCodePaths[i].equals(codePath)) {
pathFound = true;
splitName = info.applicationInfo.splitNames[i];
break;
}
}
}
if (!pathFound) {
postError(callback, packageName, ArtManager.SNAPSHOT_FAILED_CODE_PATH_NOT_FOUND);
return;
}
// All good, create the profile snapshot.
int appId = UserHandle.getAppId(info.applicationInfo.uid);
if (appId < 0) {
postError(callback, packageName, ArtManager.SNAPSHOT_FAILED_INTERNAL_ERROR);
Slog.wtf(TAG, "AppId is -1 for package: " + packageName);
return;
}
createProfileSnapshot(packageName, ArtManager.getProfileName(splitName), codePath,
appId, callback);
// Destroy the snapshot, we no longer need it.
destroyProfileSnapshot(packageName, ArtManager.getProfileName(splitName));
}
private void createProfileSnapshot(String packageName, String profileName, String classpath,
int appId, ISnapshotRuntimeProfileCallback callback) {
// Ask the installer to snapshot the profile.
synchronized (mInstallLock) {
try {
if (!mInstaller.createProfileSnapshot(appId, packageName, profileName, classpath)) {
postError(callback, packageName, ArtManager.SNAPSHOT_FAILED_INTERNAL_ERROR);
return;
}
} catch (InstallerException e) {
postError(callback, packageName, ArtManager.SNAPSHOT_FAILED_INTERNAL_ERROR);
return;
}
}
// Open the snapshot and invoke the callback.
File snapshotProfile = ArtManager.getProfileSnapshotFileForName(packageName, profileName);
ParcelFileDescriptor fd = null;
try {
fd = ParcelFileDescriptor.open(snapshotProfile, ParcelFileDescriptor.MODE_READ_ONLY);
postSuccess(packageName, fd, callback);
} catch (FileNotFoundException e) {
Slog.w(TAG, "Could not open snapshot profile for " + packageName + ":"
+ snapshotProfile, e);
postError(callback, packageName, ArtManager.SNAPSHOT_FAILED_INTERNAL_ERROR);
} finally {
IoUtils.closeQuietly(fd);
}
}
private void destroyProfileSnapshot(String packageName, String profileName) {
if (DEBUG) {
Slog.d(TAG, "Destroying profile snapshot for" + packageName + ":" + profileName);
}
synchronized (mInstallLock) {
try {
mInstaller.destroyProfileSnapshot(packageName, profileName);
} catch (InstallerException e) {
Slog.e(TAG, "Failed to destroy profile snapshot for " +
packageName + ":" + profileName, e);
}
}
}
@Override
public boolean isRuntimeProfilingEnabled(@ProfileType int profileType) {
// Verify that the caller has the right permissions.
checkReadRuntimeProfilePermission();
switch (profileType) {
case ArtManager.PROFILE_APPS :
return SystemProperties.getBoolean("dalvik.vm.usejitprofiles", false);
case ArtManager.PROFILE_BOOT_IMAGE:
return (Build.IS_USERDEBUG || Build.IS_ENG) &&
SystemProperties.getBoolean("dalvik.vm.usejitprofiles", false) &&
SystemProperties.getBoolean("dalvik.vm.profilebootimage", false);
default:
throw new IllegalArgumentException("Invalid profile type:" + profileType);
}
}
private void snapshotBootImageProfile(ISnapshotRuntimeProfileCallback callback) {
// Combine the profiles for boot classpath and system server classpath.
// This avoids having yet another type of profiles and simplifies the processing.
String classpath = String.join(":", Os.getenv("BOOTCLASSPATH"),
Os.getenv("SYSTEMSERVERCLASSPATH"));
// Create the snapshot.
createProfileSnapshot(BOOT_IMAGE_ANDROID_PACKAGE, BOOT_IMAGE_PROFILE_NAME, classpath,
/*appId*/ -1, callback);
// Destroy the snapshot, we no longer need it.
destroyProfileSnapshot(BOOT_IMAGE_ANDROID_PACKAGE, BOOT_IMAGE_PROFILE_NAME);
}
/**
* Post {@link ISnapshotRuntimeProfileCallback#onError(int)} with the given error message
* on the internal {@code mHandler}.
*/
private void postError(ISnapshotRuntimeProfileCallback callback, String packageName,
int errCode) {
if (DEBUG) {
Slog.d(TAG, "Failed to snapshot profile for " + packageName + " with error: " +
errCode);
}
mHandler.post(() -> {
try {
callback.onError(errCode);
} catch (RemoteException e) {
Slog.w(TAG, "Failed to callback after profile snapshot for " + packageName, e);
}
});
}
private void postSuccess(String packageName, ParcelFileDescriptor fd,
ISnapshotRuntimeProfileCallback callback) {
if (DEBUG) {
Slog.d(TAG, "Successfully snapshot profile for " + packageName);
}
mHandler.post(() -> {
try {
callback.onSuccess(fd);
} catch (RemoteException e) {
Slog.w(TAG,
"Failed to call onSuccess after profile snapshot for " + packageName, e);
}
});
}
/**
* Verify that the binder calling uid has {@code android.permission.READ_RUNTIME_PROFILE}.
* If not, it throws a {@link SecurityException}.
*/
private void checkReadRuntimeProfilePermission() {
if (DEBUG_IGNORE_PERMISSIONS) {
return;
}
try {
int result = mPackageManager.checkUidPermission(
Manifest.permission.READ_RUNTIME_PROFILES, Binder.getCallingUid());
if (result != PackageManager.PERMISSION_GRANTED) {
throw new SecurityException("You need "
+ Manifest.permission.READ_RUNTIME_PROFILES
+ " permission to snapshot profiles.");
}
} catch (RemoteException e) {
// Should not happen.
}
}
/**
* Prepare the application profiles.
* For all code paths:
* - create the current primary profile to save time at app startup time.
* - copy the profiles from the associated dex metadata file to the reference profile.
*/
public void prepareAppProfiles(PackageParser.Package pkg, @UserIdInt int user) {
final int appId = UserHandle.getAppId(pkg.applicationInfo.uid);
if (user < 0) {
Slog.wtf(TAG, "Invalid user id: " + user);
return;
}
if (appId < 0) {
Slog.wtf(TAG, "Invalid app id: " + appId);
return;
}
try {
ArrayMap<String, String> codePathsProfileNames = getPackageProfileNames(pkg);
for (int i = codePathsProfileNames.size() - 1; i >= 0; i--) {
String codePath = codePathsProfileNames.keyAt(i);
String profileName = codePathsProfileNames.valueAt(i);
File dexMetadata = DexMetadataHelper.findDexMetadataForFile(new File(codePath));
String dexMetadataPath = dexMetadata == null ? null : dexMetadata.getAbsolutePath();
synchronized (mInstaller) {
boolean result = mInstaller.prepareAppProfile(pkg.packageName, user, appId,
profileName, codePath, dexMetadataPath);
if (!result) {
Slog.e(TAG, "Failed to prepare profile for " +
pkg.packageName + ":" + codePath);
}
}
}
} catch (InstallerException e) {
Slog.e(TAG, "Failed to prepare profile for " + pkg.packageName, e);
}
}
/**
* Prepares the app profiles for a set of users. {@see ArtManagerService#prepareAppProfiles}.
*/
public void prepareAppProfiles(PackageParser.Package pkg, int[] user) {
for (int i = 0; i < user.length; i++) {
prepareAppProfiles(pkg, user[i]);
}
}
/**
* Clear the profiles for the given package.
*/
public void clearAppProfiles(PackageParser.Package pkg) {
try {
ArrayMap<String, String> packageProfileNames = getPackageProfileNames(pkg);
for (int i = packageProfileNames.size() - 1; i >= 0; i--) {
String profileName = packageProfileNames.valueAt(i);
mInstaller.clearAppProfiles(pkg.packageName, profileName);
}
} catch (InstallerException e) {
Slog.w(TAG, String.valueOf(e));
}
}
/**
* Dumps the profiles for the given package.
*/
public void dumpProfiles(PackageParser.Package pkg) {
final int sharedGid = UserHandle.getSharedAppGid(pkg.applicationInfo.uid);
try {
ArrayMap<String, String> packageProfileNames = getPackageProfileNames(pkg);
for (int i = packageProfileNames.size() - 1; i >= 0; i--) {
String codePath = packageProfileNames.keyAt(i);
String profileName = packageProfileNames.valueAt(i);
synchronized (mInstallLock) {
mInstaller.dumpProfiles(sharedGid, pkg.packageName, profileName, codePath);
}
}
} catch (InstallerException e) {
Slog.w(TAG, "Failed to dump profiles", e);
}
}
/**
* Build the profiles names for all the package code paths (excluding resource only paths).
* Return the map [code path -> profile name].
*/
private ArrayMap<String, String> getPackageProfileNames(PackageParser.Package pkg) {
ArrayMap<String, String> result = new ArrayMap<>();
if ((pkg.applicationInfo.flags & ApplicationInfo.FLAG_HAS_CODE) != 0) {
result.put(pkg.baseCodePath, ArtManager.getProfileName(null));
}
if (!ArrayUtils.isEmpty(pkg.splitCodePaths)) {
for (int i = 0; i < pkg.splitCodePaths.length; i++) {
if ((pkg.splitFlags[i] & ApplicationInfo.FLAG_HAS_CODE) != 0) {
result.put(pkg.splitCodePaths[i], ArtManager.getProfileName(pkg.splitNames[i]));
}
}
}
return result;
}
}

View File

@@ -0,0 +1,116 @@
/*
* Copyright (C) 2017 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.dex;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.os.RemoteException;
import android.util.ArraySet;
import android.util.ByteStringUtils;
import android.util.EventLog;
import android.util.PackageUtils;
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
import com.android.server.pm.Installer;
import com.android.server.pm.Installer.InstallerException;
import java.io.File;
import java.util.Set;
import static com.android.server.pm.dex.PackageDexUsage.DexUseInfo;
/**
* This class is responsible for logging data about secondary dex files.
* The data logged includes hashes of the name and content of each file.
*/
public class DexLogger implements DexManager.Listener {
private static final String TAG = "DexLogger";
// Event log tag & subtag used for SafetyNet logging of dynamic
// code loading (DCL) - see b/63927552.
private static final int SNET_TAG = 0x534e4554;
private static final String DCL_SUBTAG = "dcl";
private final IPackageManager mPackageManager;
private final Object mInstallLock;
@GuardedBy("mInstallLock")
private final Installer mInstaller;
public static DexManager.Listener getListener(IPackageManager pms,
Installer installer, Object installLock) {
return new DexLogger(pms, installer, installLock);
}
private DexLogger(IPackageManager pms, Installer installer, Object installLock) {
mPackageManager = pms;
mInstaller = installer;
mInstallLock = installLock;
}
/**
* Compute and log hashes of the name and content of a secondary dex file.
*/
@Override
public void onReconcileSecondaryDexFile(ApplicationInfo appInfo, DexUseInfo dexUseInfo,
String dexPath, int storageFlags) {
int ownerUid = appInfo.uid;
byte[] hash = null;
synchronized(mInstallLock) {
try {
hash = mInstaller.hashSecondaryDexFile(dexPath, appInfo.packageName,
ownerUid, appInfo.volumeUuid, storageFlags);
} catch (InstallerException e) {
Slog.e(TAG, "Got InstallerException when hashing dex " + dexPath +
" : " + e.getMessage());
}
}
if (hash == null) {
return;
}
String dexFileName = new File(dexPath).getName();
String message = PackageUtils.computeSha256Digest(dexFileName.getBytes());
// Valid SHA256 will be 256 bits, 32 bytes.
if (hash.length == 32) {
message = message + ' ' + ByteStringUtils.toHexString(hash);
}
EventLog.writeEvent(SNET_TAG, DCL_SUBTAG, ownerUid, message);
if (dexUseInfo.isUsedByOtherApps()) {
Set<String> otherPackages = dexUseInfo.getLoadingPackages();
Set<Integer> otherUids = new ArraySet<>(otherPackages.size());
for (String otherPackageName : otherPackages) {
try {
int otherUid = mPackageManager.getPackageUid(
otherPackageName, /*flags*/0, dexUseInfo.getOwnerUserId());
if (otherUid != -1 && otherUid != ownerUid) {
otherUids.add(otherUid);
}
} catch (RemoteException ignore) {
// Can't happen, we're local.
}
}
for (int otherUid : otherUids) {
EventLog.writeEvent(SNET_TAG, DCL_SUBTAG, otherUid, message);
}
}
}
}

View File

@@ -76,6 +76,7 @@ public class DexManager {
private final Object mInstallLock;
@GuardedBy("mInstallLock")
private final Installer mInstaller;
private final Listener mListener;
// Possible outcomes of a dex search.
private static int DEX_SEARCH_NOT_FOUND = 0; // dex file not found
@@ -96,14 +97,24 @@ public class DexManager {
*/
private final static PackageUseInfo DEFAULT_USE_INFO = new PackageUseInfo();
public interface Listener {
/**
* Invoked just before the secondary dex file {@code dexPath} for the specified application
* is reconciled.
*/
void onReconcileSecondaryDexFile(ApplicationInfo appInfo, DexUseInfo dexUseInfo,
String dexPath, int storageFlags);
}
public DexManager(IPackageManager pms, PackageDexOptimizer pdo,
Installer installer, Object installLock) {
Installer installer, Object installLock, Listener listener) {
mPackageCodeLocationsCache = new HashMap<>();
mPackageDexUsage = new PackageDexUsage();
mPackageManager = pms;
mPackageDexOptimizer = pdo;
mInstaller = installer;
mInstallLock = installLock;
mListener = listener;
}
/**
@@ -389,7 +400,7 @@ public class DexManager {
: mPackageDexOptimizer;
String packageName = options.getPackageName();
PackageUseInfo useInfo = getPackageUseInfoOrDefault(packageName);
if (useInfo == null || useInfo.getDexUseInfoMap().isEmpty()) {
if (useInfo.getDexUseInfoMap().isEmpty()) {
if (DEBUG) {
Slog.d(TAG, "No secondary dex use for package:" + packageName);
}
@@ -433,7 +444,7 @@ public class DexManager {
*/
public void reconcileSecondaryDexFiles(String packageName) {
PackageUseInfo useInfo = getPackageUseInfoOrDefault(packageName);
if (useInfo == null || useInfo.getDexUseInfoMap().isEmpty()) {
if (useInfo.getDexUseInfoMap().isEmpty()) {
if (DEBUG) {
Slog.d(TAG, "No secondary dex use for package:" + packageName);
}
@@ -481,12 +492,16 @@ public class DexManager {
continue;
}
if (mListener != null) {
mListener.onReconcileSecondaryDexFile(info, dexUseInfo, dexPath, flags);
}
boolean dexStillExists = true;
synchronized(mInstallLock) {
try {
String[] isas = dexUseInfo.getLoaderIsas().toArray(new String[0]);
dexStillExists = mInstaller.reconcileSecondaryDexFile(dexPath, packageName,
pkg.applicationInfo.uid, isas, pkg.applicationInfo.volumeUuid, flags);
info.uid, isas, info.volumeUuid, flags);
} catch (InstallerException e) {
Slog.e(TAG, "Got InstallerException when reconciling dex " + dexPath +
" : " + e.getMessage());

View File

@@ -59,6 +59,10 @@ public final class DexoptOptions {
// When set, indicates that dexopt is invoked from the background service.
public static final int DEXOPT_IDLE_BACKGROUND_JOB = 1 << 9;
// When set, indicates that dexopt is invoked from the install time flow and
// should get the dex metdata file if present.
public static final int DEXOPT_INSTALL_WITH_DEX_METADATA_FILE = 1 << 10;
// The name of package to optimize.
private final String mPackageName;
@@ -90,7 +94,8 @@ public final class DexoptOptions {
DEXOPT_ONLY_SHARED_DEX |
DEXOPT_DOWNGRADE |
DEXOPT_AS_SHARED_LIBRARY |
DEXOPT_IDLE_BACKGROUND_JOB;
DEXOPT_IDLE_BACKGROUND_JOB |
DEXOPT_INSTALL_WITH_DEX_METADATA_FILE;
if ((flags & (~validityMask)) != 0) {
throw new IllegalArgumentException("Invalid flags : " + Integer.toHexString(flags));
}
@@ -141,6 +146,10 @@ public final class DexoptOptions {
return (mFlags & DEXOPT_IDLE_BACKGROUND_JOB) != 0;
}
public boolean isDexoptInstallWithDexMetadata() {
return (mFlags & DEXOPT_INSTALL_WITH_DEX_METADATA_FILE) != 0;
}
public String getSplitName() {
return mSplitName;
}

View File

@@ -17,12 +17,15 @@
package com.android.server.pm.dex;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.PackageInfo;
import android.os.Build;
import android.os.UserHandle;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
import com.android.server.pm.Installer;
import dalvik.system.DelegateLastClassLoader;
import dalvik.system.PathClassLoader;
import dalvik.system.VMRuntime;
@@ -36,8 +39,13 @@ import java.util.List;
import java.util.Map;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import org.mockito.quality.Strictness;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -45,6 +53,12 @@ import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static com.android.server.pm.dex.PackageDexUsage.PackageUseInfo;
import static com.android.server.pm.dex.PackageDexUsage.DexUseInfo;
@@ -56,6 +70,12 @@ public class DexManagerTests {
private static final String DELEGATE_LAST_CLASS_LOADER_NAME =
DelegateLastClassLoader.class.getName();
@Rule public MockitoRule mockito = MockitoJUnit.rule().strictness(Strictness.STRICT_STUBS);
@Mock Installer mInstaller;
@Mock IPackageManager mPM;
private final Object mInstallLock = new Object();
@Mock DexManager.Listener mListener;
private DexManager mDexManager;
private TestData mFooUser0;
@@ -90,7 +110,8 @@ public class DexManagerTests {
mBarUser0DelegateLastClassLoader = new TestData(bar, isa, mUser0,
DELEGATE_LAST_CLASS_LOADER_NAME);
mDexManager = new DexManager(null, null, null, null);
mDexManager = new DexManager(
mPM, /*PackageDexOptimizer*/ null, mInstaller, mInstallLock, mListener);
// Foo and Bar are available to user0.
// Only Bar is available to user1;
@@ -440,6 +461,20 @@ public class DexManagerTests {
}
@Test
public void testReconcileSecondaryDexFiles_invokesListener() throws Exception {
List<String> fooSecondaries = mFooUser0.getSecondaryDexPathsFromProtectedDirs();
notifyDexLoad(mFooUser0, fooSecondaries, mUser0);
when(mPM.getPackageInfo(mFooUser0.getPackageName(), 0, 0))
.thenReturn(mFooUser0.mPackageInfo);
mDexManager.reconcileSecondaryDexFiles(mFooUser0.getPackageName());
verify(mListener, times(fooSecondaries.size()))
.onReconcileSecondaryDexFile(any(ApplicationInfo.class),
any(DexUseInfo.class), anyString(), anyInt());
}
private void assertSecondaryUse(TestData testData, PackageUseInfo pui,
List<String> secondaries, boolean isUsedByOtherApps, int ownerUserId,
@@ -492,12 +527,12 @@ public class DexManagerTests {
}
private PackageUseInfo getPackageUseInfo(TestData testData) {
assertTrue(mDexManager.hasInfoOnPackage(testData.mPackageInfo.packageName));
return mDexManager.getPackageUseInfoOrDefault(testData.mPackageInfo.packageName);
assertTrue(mDexManager.hasInfoOnPackage(testData.getPackageName()));
return mDexManager.getPackageUseInfoOrDefault(testData.getPackageName());
}
private void assertNoUseInfo(TestData testData) {
assertFalse(mDexManager.hasInfoOnPackage(testData.mPackageInfo.packageName));
assertFalse(mDexManager.hasInfoOnPackage(testData.getPackageName()));
}
private static PackageInfo getMockPackageInfo(String packageName, int userId) {
@@ -555,8 +590,8 @@ public class DexManagerTests {
List<String> getSecondaryDexPathsFromProtectedDirs() {
List<String> paths = new ArrayList<>();
paths.add(mPackageInfo.applicationInfo.dataDir + "/secondary6.dex");
paths.add(mPackageInfo.applicationInfo.dataDir + "/secondary7.dex");
paths.add(mPackageInfo.applicationInfo.deviceProtectedDataDir + "/secondary6.dex");
paths.add(mPackageInfo.applicationInfo.credentialProtectedDataDir + "/secondary7.dex");
return paths;
}

View File

@@ -53,6 +53,7 @@ public class DexoptOptionsTests {
assertFalse(opt.isDowngrade());
assertFalse(opt.isForce());
assertFalse(opt.isDexoptIdleBackgroundJob());
assertFalse(opt.isDexoptInstallWithDexMetadata());
}
@Test
@@ -65,7 +66,8 @@ public class DexoptOptionsTests {
DexoptOptions.DEXOPT_ONLY_SHARED_DEX |
DexoptOptions.DEXOPT_DOWNGRADE |
DexoptOptions.DEXOPT_AS_SHARED_LIBRARY |
DexoptOptions.DEXOPT_IDLE_BACKGROUND_JOB;
DexoptOptions.DEXOPT_IDLE_BACKGROUND_JOB |
DexoptOptions.DEXOPT_INSTALL_WITH_DEX_METADATA_FILE;
DexoptOptions opt = new DexoptOptions(mPackageName, mCompilerFilter, flags);
assertEquals(mPackageName, opt.getPackageName());
@@ -79,6 +81,7 @@ public class DexoptOptionsTests {
assertTrue(opt.isForce());
assertTrue(opt.isDexoptAsSharedLibrary());
assertTrue(opt.isDexoptIdleBackgroundJob());
assertTrue(opt.isDexoptInstallWithDexMetadata());
}
@Test

View File

@@ -47,6 +47,7 @@ import android.content.pm.ServiceInfo;
import android.content.pm.SharedLibraryInfo;
import android.content.pm.VerifierDeviceIdentity;
import android.content.pm.VersionedPackage;
import android.content.pm.dex.ArtManager;
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
import android.graphics.Rect;
@@ -1184,4 +1185,12 @@ public class MockPackageManager extends PackageManager {
@Nullable DexModuleRegisterCallback callback) {
throw new UnsupportedOperationException();
}
/**
* @hide
*/
@Override
public ArtManager getArtManager() {
throw new UnsupportedOperationException();
}
}