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:
@@ -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",
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
209
core/java/android/content/pm/dex/ArtManager.java
Normal file
209
core/java/android/content/pm/dex/ArtManager.java
Normal 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");
|
||||
}
|
||||
}
|
||||
230
core/java/android/content/pm/dex/DexMetadataHelper.java
Normal file
230
core/java/android/content/pm/dex/DexMetadataHelper.java
Normal 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
58
core/java/android/content/pm/dex/IArtManager.aidl
Normal file
58
core/java/android/content/pm/dex/IArtManager.aidl
Normal 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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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: "
|
||||
|
||||
@@ -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"
|
||||
|
||||
10
core/tests/coretests/apks/install-split-base/Android.mk
Normal file
10
core/tests/coretests/apks/install-split-base/Android.mk
Normal 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)
|
||||
@@ -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>
|
||||
@@ -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 {
|
||||
}
|
||||
14
core/tests/coretests/apks/install-split-feature-a/Android.mk
Normal file
14
core/tests/coretests/apks/install-split-feature-a/Android.mk
Normal 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)
|
||||
@@ -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>
|
||||
@@ -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 {
|
||||
}
|
||||
@@ -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()));
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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());
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
116
services/core/java/com/android/server/pm/dex/DexLogger.java
Normal file
116
services/core/java/com/android/server/pm/dex/DexLogger.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user