blob: e061608138a508f91c7d43ac75ea9f9248b7ec66 [file] [log] [blame]
/*
* Copyright (C) 2021 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;
import static android.os.Trace.TRACE_TAG_DALVIK;
import static com.android.server.LocalManagerRegistry.ManagerNotFoundException;
import static com.android.server.pm.ApexManager.ActiveApexInfo;
import static com.android.server.pm.InstructionSets.getAppDexInstructionSets;
import static com.android.server.pm.PackageManagerService.DEBUG_DEXOPT;
import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
import static com.android.server.pm.PackageManagerService.REASON_BOOT_AFTER_MAINLINE_UPDATE;
import static com.android.server.pm.PackageManagerService.REASON_BOOT_AFTER_OTA;
import static com.android.server.pm.PackageManagerService.REASON_CMDLINE;
import static com.android.server.pm.PackageManagerService.REASON_FIRST_BOOT;
import static com.android.server.pm.PackageManagerService.STUB_SUFFIX;
import static com.android.server.pm.PackageManagerService.TAG;
import static com.android.server.pm.PackageManagerServiceCompilerMapping.getCompilerFilterForReason;
import static com.android.server.pm.PackageManagerServiceCompilerMapping.getDefaultCompilerFilter;
import static com.android.server.pm.PackageManagerServiceUtils.REMOVE_IF_APEX_PKG;
import static com.android.server.pm.PackageManagerServiceUtils.REMOVE_IF_NULL_PKG;
import static com.android.server.pm.PackageManagerServiceUtils.getPackageManagerLocal;
import static dalvik.system.DexFile.isProfileGuidedCompilerFilter;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.AppGlobals;
import android.app.role.RoleManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ApexStagedEvent;
import android.content.pm.IPackageManagerNative;
import android.content.pm.IStagedApexObserver;
import android.content.pm.ResolveInfo;
import android.content.pm.SharedLibraryInfo;
import android.content.pm.dex.ArtManager;
import android.os.Binder;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.Trace;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.ArraySet;
import android.util.Log;
import android.util.Slog;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.IndentingPrintWriter;
import com.android.server.LocalManagerRegistry;
import com.android.server.LocalServices;
import com.android.server.PinnerService;
import com.android.server.art.ArtManagerLocal;
import com.android.server.art.DexUseManagerLocal;
import com.android.server.art.ReasonMapping;
import com.android.server.art.model.ArtFlags;
import com.android.server.art.model.DexoptParams;
import com.android.server.art.model.DexoptResult;
import com.android.server.pm.Installer.InstallerException;
import com.android.server.pm.Installer.LegacyDexoptDisabledException;
import com.android.server.pm.PackageDexOptimizer.DexOptResult;
import com.android.server.pm.dex.DexManager;
import com.android.server.pm.dex.DexoptOptions;
import com.android.server.pm.pkg.AndroidPackage;
import com.android.server.pm.pkg.PackageState;
import com.android.server.pm.pkg.PackageStateInternal;
import java.io.File;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.function.Predicate;
/**
* Helper class for dex optimization operations in PackageManagerService.
*/
public final class DexOptHelper {
private static final long SEVEN_DAYS_IN_MILLISECONDS = 7 * 24 * 60 * 60 * 1000;
private static boolean sArtManagerLocalIsInitialized = false;
private final PackageManagerService mPm;
// Start time for the boot dexopt in performPackageDexOptUpgradeIfNeeded when ART Service is
// used, to make it available to the onDexoptDone callback.
private volatile long mBootDexoptStartTime;
DexOptHelper(PackageManagerService pm) {
mPm = pm;
}
/*
* Return the prebuilt profile path given a package base code path.
*/
private static String getPrebuildProfilePath(AndroidPackage pkg) {
return pkg.getBaseApkPath() + ".prof";
}
/**
* Performs dexopt on the set of packages in {@code packages} and returns an int array
* containing statistics about the invocation. The array consists of three elements,
* which are (in order) {@code numberOfPackagesOptimized}, {@code numberOfPackagesSkipped}
* and {@code numberOfPackagesFailed}.
*/
public int[] performDexOptUpgrade(List<PackageStateInternal> packageStates,
final int compilationReason, boolean bootComplete)
throws LegacyDexoptDisabledException {
Installer.checkLegacyDexoptDisabled();
int numberOfPackagesVisited = 0;
int numberOfPackagesOptimized = 0;
int numberOfPackagesSkipped = 0;
int numberOfPackagesFailed = 0;
final int numberOfPackagesToDexopt = packageStates.size();
for (var packageState : packageStates) {
var pkg = packageState.getAndroidPackage();
numberOfPackagesVisited++;
boolean useProfileForDexopt = false;
if ((mPm.isFirstBoot() || mPm.isDeviceUpgrading()) && packageState.isSystem()) {
// Copy over initial preopt profiles since we won't get any JIT samples for methods
// that are already compiled.
File profileFile = new File(getPrebuildProfilePath(pkg));
// Copy profile if it exists.
if (profileFile.exists()) {
try {
// We could also do this lazily before calling dexopt in
// 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 (!mPm.mInstaller.copySystemProfile(profileFile.getAbsolutePath(),
pkg.getUid(), pkg.getPackageName(),
ArtManager.getProfileName(null))) {
Log.e(TAG, "Installer failed to copy system profile!");
} else {
// Disabled as this causes speed-profile compilation during first boot
// even if things are already compiled.
// useProfileForDexopt = true;
}
} catch (InstallerException | RuntimeException e) {
Log.e(TAG, "Failed to copy profile " + profileFile.getAbsolutePath() + " ",
e);
}
} else {
PackageSetting disabledPs = mPm.mSettings.getDisabledSystemPkgLPr(
pkg.getPackageName());
// Handle compressed APKs in this path. Only do this for stubs with profiles to
// minimize the number off apps being speed-profile compiled during first boot.
// The other paths will not change the filter.
if (disabledPs != null && disabledPs.getPkg().isStub()) {
// The package is the stub one, remove the stub suffix to get the normal
// package and APK names.
String systemProfilePath = getPrebuildProfilePath(disabledPs.getPkg())
.replace(STUB_SUFFIX, "");
profileFile = new File(systemProfilePath);
// If we have a profile for a compressed APK, copy it to the reference
// location.
// Note that copying the profile here will cause it to override the
// reference profile every OTA even though the existing reference profile
// may have more data. We can't copy during decompression since the
// directories are not set up at that point.
if (profileFile.exists()) {
try {
// We could also do this lazily before calling dexopt in
// 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 (!mPm.mInstaller.copySystemProfile(profileFile.getAbsolutePath(),
pkg.getUid(), pkg.getPackageName(),
ArtManager.getProfileName(null))) {
Log.e(TAG, "Failed to copy system profile for stub package!");
} else {
useProfileForDexopt = true;
}
} catch (InstallerException | RuntimeException e) {
Log.e(TAG, "Failed to copy profile "
+ profileFile.getAbsolutePath() + " ", e);
}
}
}
}
}
if (!mPm.mPackageDexOptimizer.canOptimizePackage(pkg)) {
if (DEBUG_DEXOPT) {
Log.i(TAG, "Skipping update of non-optimizable app " + pkg.getPackageName());
}
numberOfPackagesSkipped++;
continue;
}
if (DEBUG_DEXOPT) {
Log.i(TAG, "Updating app " + numberOfPackagesVisited + " of "
+ numberOfPackagesToDexopt + ": " + pkg.getPackageName());
}
int pkgCompilationReason = compilationReason;
if (useProfileForDexopt) {
// Use background dexopt mode to try and use the profile. Note that this does not
// guarantee usage of the profile.
pkgCompilationReason = PackageManagerService.REASON_BACKGROUND_DEXOPT;
}
int dexoptFlags = bootComplete ? DexoptOptions.DEXOPT_BOOT_COMPLETE : 0;
String filter = getCompilerFilterForReason(pkgCompilationReason);
if (isProfileGuidedCompilerFilter(filter)) {
// DEXOPT_CHECK_FOR_PROFILES_UPDATES used to be false to avoid merging profiles
// during boot which might interfere with background compilation (b/28612421).
// However those problems were related to the verify-profile compiler filter which
// doesn't exist any more, so enable it again.
dexoptFlags |= DexoptOptions.DEXOPT_CHECK_FOR_PROFILES_UPDATES;
}
if (compilationReason == REASON_FIRST_BOOT) {
// TODO: This doesn't cover the upgrade case, we should check for this too.
dexoptFlags |= DexoptOptions.DEXOPT_INSTALL_WITH_DEX_METADATA_FILE;
}
int primaryDexOptStatus = performDexOptTraced(
new DexoptOptions(pkg.getPackageName(), pkgCompilationReason, filter,
/*splitName*/ null, dexoptFlags));
switch (primaryDexOptStatus) {
case PackageDexOptimizer.DEX_OPT_PERFORMED:
numberOfPackagesOptimized++;
break;
case PackageDexOptimizer.DEX_OPT_SKIPPED:
numberOfPackagesSkipped++;
break;
case PackageDexOptimizer.DEX_OPT_CANCELLED:
// ignore this case
break;
case PackageDexOptimizer.DEX_OPT_FAILED:
numberOfPackagesFailed++;
break;
default:
Log.e(TAG, "Unexpected dexopt return code " + primaryDexOptStatus);
break;
}
}
return new int[]{numberOfPackagesOptimized, numberOfPackagesSkipped,
numberOfPackagesFailed};
}
/**
* Checks if system UI package (typically "com.android.systemui") needs to be re-compiled, and
* compiles it if needed.
*/
private void checkAndDexOptSystemUi(int reason) throws LegacyDexoptDisabledException {
Computer snapshot = mPm.snapshotComputer();
String sysUiPackageName =
mPm.mContext.getString(com.android.internal.R.string.config_systemUi);
AndroidPackage pkg = snapshot.getPackage(sysUiPackageName);
if (pkg == null) {
Log.w(TAG, "System UI package " + sysUiPackageName + " is not found for dexopting");
return;
}
String defaultCompilerFilter = getCompilerFilterForReason(reason);
String targetCompilerFilter =
SystemProperties.get("dalvik.vm.systemuicompilerfilter", defaultCompilerFilter);
String compilerFilter;
if (isProfileGuidedCompilerFilter(targetCompilerFilter)) {
compilerFilter = "verify";
File profileFile = new File(getPrebuildProfilePath(pkg));
// Copy the profile to the reference profile path if it exists. Installd can only use a
// profile at the reference profile path for dexopt.
if (profileFile.exists()) {
try {
synchronized (mPm.mInstallLock) {
if (mPm.mInstaller.copySystemProfile(profileFile.getAbsolutePath(),
pkg.getUid(), pkg.getPackageName(),
ArtManager.getProfileName(null))) {
compilerFilter = targetCompilerFilter;
} else {
Log.e(TAG, "Failed to copy profile " + profileFile.getAbsolutePath());
}
}
} catch (InstallerException | RuntimeException e) {
Log.e(TAG, "Failed to copy profile " + profileFile.getAbsolutePath(), e);
}
}
} else {
compilerFilter = targetCompilerFilter;
}
performDexoptPackage(sysUiPackageName, reason, compilerFilter);
}
private void dexoptLauncher(int reason) throws LegacyDexoptDisabledException {
Computer snapshot = mPm.snapshotComputer();
RoleManager roleManager = mPm.mContext.getSystemService(RoleManager.class);
for (var packageName : roleManager.getRoleHolders(RoleManager.ROLE_HOME)) {
AndroidPackage pkg = snapshot.getPackage(packageName);
if (pkg == null) {
Log.w(TAG, "Launcher package " + packageName + " is not found for dexopting");
} else {
performDexoptPackage(packageName, reason, "speed-profile");
}
}
}
private void performDexoptPackage(@NonNull String packageName, int reason,
@NonNull String compilerFilter) throws LegacyDexoptDisabledException {
Installer.checkLegacyDexoptDisabled();
// DEXOPT_CHECK_FOR_PROFILES_UPDATES is set to replicate behaviour that will be
// unconditionally enabled for profile guided filters when ART Service is called instead of
// the legacy PackageDexOptimizer implementation.
int dexoptFlags = isProfileGuidedCompilerFilter(compilerFilter)
? DexoptOptions.DEXOPT_CHECK_FOR_PROFILES_UPDATES
: 0;
performDexOptTraced(new DexoptOptions(
packageName, reason, compilerFilter, null /* splitName */, dexoptFlags));
}
/**
* Called during startup to do any boot time dexopting. This can occasionally be time consuming
* (30+ seconds) and the function will block until it is complete.
*/
public void performPackageDexOptUpgradeIfNeeded() {
PackageManagerServiceUtils.enforceSystemOrRoot(
"Only the system can request package update");
int reason;
if (mPm.isFirstBoot()) {
reason = REASON_FIRST_BOOT; // First boot or factory reset.
} else if (mPm.isDeviceUpgrading()) {
reason = REASON_BOOT_AFTER_OTA;
} else if (hasBcpApexesChanged()) {
reason = REASON_BOOT_AFTER_MAINLINE_UPDATE;
} else {
return;
}
Log.i(TAG,
"Starting boot dexopt for reason "
+ DexoptOptions.convertToArtServiceDexoptReason(reason));
final long startTime = System.nanoTime();
if (useArtService()) {
mBootDexoptStartTime = startTime;
getArtManagerLocal().onBoot(DexoptOptions.convertToArtServiceDexoptReason(reason),
null /* progressCallbackExecutor */, null /* progressCallback */);
} else {
try {
// System UI and the launcher are important to user experience, so we check them
// after a mainline update or OTA. They may need to be re-compiled in these cases.
checkAndDexOptSystemUi(reason);
dexoptLauncher(reason);
if (reason != REASON_BOOT_AFTER_OTA && reason != REASON_FIRST_BOOT) {
return;
}
final Computer snapshot = mPm.snapshotComputer();
// TODO(b/251903639): Align this with how ART Service selects packages for boot
// compilation.
List<PackageStateInternal> pkgSettings =
getPackagesForDexopt(snapshot.getPackageStates().values(), mPm);
final int[] stats =
performDexOptUpgrade(pkgSettings, reason, false /* bootComplete */);
reportBootDexopt(startTime, stats[0], stats[1], stats[2]);
} catch (LegacyDexoptDisabledException e) {
throw new RuntimeException(e);
}
}
}
private void reportBootDexopt(long startTime, int numDexopted, int numSkipped, int numFailed) {
final int elapsedTimeSeconds =
(int) TimeUnit.NANOSECONDS.toSeconds(System.nanoTime() - startTime);
final Computer newSnapshot = mPm.snapshotComputer();
MetricsLogger.histogram(mPm.mContext, "opt_dialog_num_dexopted", numDexopted);
MetricsLogger.histogram(mPm.mContext, "opt_dialog_num_skipped", numSkipped);
MetricsLogger.histogram(mPm.mContext, "opt_dialog_num_failed", numFailed);
// TODO(b/251903639): getOptimizablePackages calls PackageDexOptimizer.canOptimizePackage
// which duplicates logic in ART Service (com.android.server.art.Utils.canDexoptPackage).
MetricsLogger.histogram(mPm.mContext, "opt_dialog_num_total",
getOptimizablePackages(newSnapshot).size());
MetricsLogger.histogram(mPm.mContext, "opt_dialog_time_s", elapsedTimeSeconds);
}
public List<String> getOptimizablePackages(@NonNull Computer snapshot) {
ArrayList<String> pkgs = new ArrayList<>();
mPm.forEachPackageState(snapshot, packageState -> {
final AndroidPackage pkg = packageState.getPkg();
if (pkg != null && mPm.mPackageDexOptimizer.canOptimizePackage(pkg)) {
pkgs.add(packageState.getPackageName());
}
});
return pkgs;
}
/*package*/ boolean performDexOpt(DexoptOptions options) {
final Computer snapshot = mPm.snapshotComputer();
if (snapshot.getInstantAppPackageName(Binder.getCallingUid()) != null) {
return false;
} else if (snapshot.isInstantApp(options.getPackageName(), UserHandle.getCallingUserId())) {
return false;
}
var pkg = snapshot.getPackage(options.getPackageName());
if (pkg != null && pkg.isApex()) {
// skip APEX
return true;
}
@DexOptResult int dexoptStatus;
if (options.isDexoptOnlySecondaryDex()) {
if (useArtService()) {
dexoptStatus = performDexOptWithArtService(options, 0 /* extraFlags */);
} else {
try {
return mPm.getDexManager().dexoptSecondaryDex(options);
} catch (LegacyDexoptDisabledException e) {
throw new RuntimeException(e);
}
}
} else {
dexoptStatus = performDexOptWithStatus(options);
}
return dexoptStatus != PackageDexOptimizer.DEX_OPT_FAILED;
}
/**
* Perform dexopt on the given package and return one of following result:
* {@link PackageDexOptimizer#DEX_OPT_SKIPPED}
* {@link PackageDexOptimizer#DEX_OPT_PERFORMED}
* {@link PackageDexOptimizer#DEX_OPT_CANCELLED}
* {@link PackageDexOptimizer#DEX_OPT_FAILED}
*/
@DexOptResult
/* package */ int performDexOptWithStatus(DexoptOptions options) {
return performDexOptTraced(options);
}
@DexOptResult
private int performDexOptTraced(DexoptOptions options) {
Trace.traceBegin(TRACE_TAG_DALVIK, "dexopt");
try {
return performDexOptInternal(options);
} finally {
Trace.traceEnd(TRACE_TAG_DALVIK);
}
}
// Run dexopt on a given package. Returns true if dexopt did not fail, i.e.
// if the package can now be considered up to date for the given filter.
@DexOptResult
private int performDexOptInternal(DexoptOptions options) {
if (useArtService()) {
return performDexOptWithArtService(options, ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES);
}
AndroidPackage p;
PackageSetting pkgSetting;
synchronized (mPm.mLock) {
p = mPm.mPackages.get(options.getPackageName());
pkgSetting = mPm.mSettings.getPackageLPr(options.getPackageName());
if (p == null || pkgSetting == null) {
// Package could not be found. Report failure.
return PackageDexOptimizer.DEX_OPT_FAILED;
}
if (p.isApex()) {
// APEX needs no dexopt
return PackageDexOptimizer.DEX_OPT_SKIPPED;
}
mPm.getPackageUsage().maybeWriteAsync(mPm.mSettings.getPackagesLocked());
mPm.mCompilerStats.maybeWriteAsync();
}
final long callingId = Binder.clearCallingIdentity();
try {
return performDexOptInternalWithDependenciesLI(p, pkgSetting, options);
} catch (LegacyDexoptDisabledException e) {
throw new RuntimeException(e);
} finally {
Binder.restoreCallingIdentity(callingId);
}
}
/**
* Performs dexopt on the given package using ART Service. May only be called when ART Service
* is enabled, i.e. when {@link useArtService} returns true.
*/
@DexOptResult
private int performDexOptWithArtService(DexoptOptions options,
/*@DexoptFlags*/ int extraFlags) {
try (PackageManagerLocal.FilteredSnapshot snapshot =
getPackageManagerLocal().withFilteredSnapshot()) {
PackageState ops = snapshot.getPackageState(options.getPackageName());
if (ops == null) {
return PackageDexOptimizer.DEX_OPT_FAILED;
}
AndroidPackage oap = ops.getAndroidPackage();
if (oap == null) {
return PackageDexOptimizer.DEX_OPT_FAILED;
}
DexoptParams params = options.convertToDexoptParams(extraFlags);
DexoptResult result =
getArtManagerLocal().dexoptPackage(snapshot, options.getPackageName(), params);
return convertToDexOptResult(result);
}
}
@DexOptResult
private int performDexOptInternalWithDependenciesLI(
AndroidPackage p, @NonNull PackageStateInternal pkgSetting, DexoptOptions options)
throws LegacyDexoptDisabledException {
if (PLATFORM_PACKAGE_NAME.equals(p.getPackageName())) {
// This needs to be done in odrefresh in early boot, for security reasons.
throw new IllegalArgumentException("Cannot dexopt the system server");
}
// Select the dex optimizer based on the force parameter.
// Note: The force option is rarely used (cmdline input for testing, mostly), so it's OK to
// allocate an object here.
PackageDexOptimizer pdo = options.isForce()
? new PackageDexOptimizer.ForcedUpdatePackageDexOptimizer(mPm.mPackageDexOptimizer)
: mPm.mPackageDexOptimizer;
// Dexopt all dependencies first. Note: we ignore the return value and march on
// on errors.
// Note that we are going to call performDexOpt on those libraries as many times as
// they are referenced in packages. When we do a batch of performDexOpt (for example
// at boot, or background job), the passed 'targetCompilerFilter' stays the same,
// and the first package that uses the library will dexopt it. The
// others will see that the compiled code for the library is up to date.
Collection<SharedLibraryInfo> deps = SharedLibraryUtils.findSharedLibraries(pkgSetting);
final String[] instructionSets = getAppDexInstructionSets(
pkgSetting.getPrimaryCpuAbi(),
pkgSetting.getSecondaryCpuAbi());
if (!deps.isEmpty()) {
DexoptOptions libraryOptions = new DexoptOptions(options.getPackageName(),
options.getCompilationReason(), options.getCompilerFilter(),
options.getSplitName(),
options.getFlags() | DexoptOptions.DEXOPT_AS_SHARED_LIBRARY);
for (SharedLibraryInfo info : deps) {
Computer snapshot = mPm.snapshotComputer();
AndroidPackage depPackage = snapshot.getPackage(info.getPackageName());
PackageStateInternal depPackageStateInternal =
snapshot.getPackageStateInternal(info.getPackageName());
if (depPackage != null && depPackageStateInternal != null) {
// TODO: Analyze and investigate if we (should) profile libraries.
pdo.performDexOpt(depPackage, depPackageStateInternal, instructionSets,
mPm.getOrCreateCompilerPackageStats(depPackage),
mPm.getDexManager().getPackageUseInfoOrDefault(
depPackage.getPackageName()), libraryOptions);
} else {
// TODO(ngeoffray): Support dexopting system shared libraries.
}
}
}
return pdo.performDexOpt(p, pkgSetting, instructionSets,
mPm.getOrCreateCompilerPackageStats(p),
mPm.getDexManager().getPackageUseInfoOrDefault(p.getPackageName()), options);
}
/** @deprecated For legacy shell command only. */
@Deprecated
public void forceDexOpt(@NonNull Computer snapshot, String packageName)
throws LegacyDexoptDisabledException {
PackageManagerServiceUtils.enforceSystemOrRoot("forceDexOpt");
final PackageStateInternal packageState = snapshot.getPackageStateInternal(packageName);
final AndroidPackage pkg = packageState == null ? null : packageState.getPkg();
if (packageState == null || pkg == null) {
throw new IllegalArgumentException("Unknown package: " + packageName);
}
if (pkg.isApex()) {
throw new IllegalArgumentException("Can't dexopt APEX package: " + packageName);
}
Trace.traceBegin(TRACE_TAG_DALVIK, "dexopt");
// Whoever is calling forceDexOpt wants a compiled package.
// Don't use profiles since that may cause compilation to be skipped.
DexoptOptions options = new DexoptOptions(packageName, REASON_CMDLINE,
getDefaultCompilerFilter(), null /* splitName */,
DexoptOptions.DEXOPT_FORCE | DexoptOptions.DEXOPT_BOOT_COMPLETE);
@DexOptResult int res = performDexOptInternalWithDependenciesLI(pkg, packageState, options);
Trace.traceEnd(TRACE_TAG_DALVIK);
if (res != PackageDexOptimizer.DEX_OPT_PERFORMED) {
throw new IllegalStateException("Failed to dexopt: " + res);
}
}
public boolean performDexOptMode(@NonNull Computer snapshot, String packageName,
String targetCompilerFilter, boolean force, boolean bootComplete, String splitName) {
if (!PackageManagerServiceUtils.isSystemOrRootOrShell()
&& !isCallerInstallerForPackage(snapshot, packageName)) {
throw new SecurityException("performDexOptMode");
}
int flags = (force ? DexoptOptions.DEXOPT_FORCE : 0)
| (bootComplete ? DexoptOptions.DEXOPT_BOOT_COMPLETE : 0);
if (isProfileGuidedCompilerFilter(targetCompilerFilter)) {
// Set this flag whenever the filter is profile guided, to align with ART Service
// behavior.
flags |= DexoptOptions.DEXOPT_CHECK_FOR_PROFILES_UPDATES;
}
return performDexOpt(new DexoptOptions(packageName, REASON_CMDLINE,
targetCompilerFilter, splitName, flags));
}
private boolean isCallerInstallerForPackage(@NonNull Computer snapshot, String packageName) {
final PackageStateInternal packageState = snapshot.getPackageStateInternal(packageName);
if (packageState == null) {
return false;
}
final InstallSource installSource = packageState.getInstallSource();
final PackageStateInternal installerPackageState =
snapshot.getPackageStateInternal(installSource.mInstallerPackageName);
if (installerPackageState == null) {
return false;
}
final AndroidPackage installerPkg = installerPackageState.getPkg();
return installerPkg.getUid() == Binder.getCallingUid();
}
public boolean performDexOptSecondary(
String packageName, String compilerFilter, boolean force) {
int flags = DexoptOptions.DEXOPT_ONLY_SECONDARY_DEX
| DexoptOptions.DEXOPT_CHECK_FOR_PROFILES_UPDATES
| DexoptOptions.DEXOPT_BOOT_COMPLETE
| (force ? DexoptOptions.DEXOPT_FORCE : 0);
return performDexOpt(new DexoptOptions(packageName, REASON_CMDLINE,
compilerFilter, null /* splitName */, flags));
}
// Sort apps by importance for dexopt ordering. Important apps are given
// more priority in case the device runs out of space.
public static List<PackageStateInternal> getPackagesForDexopt(
Collection<? extends PackageStateInternal> packages,
PackageManagerService packageManagerService) {
return getPackagesForDexopt(packages, packageManagerService, DEBUG_DEXOPT);
}
public static List<PackageStateInternal> getPackagesForDexopt(
Collection<? extends PackageStateInternal> pkgSettings,
PackageManagerService packageManagerService,
boolean debug) {
List<PackageStateInternal> result = new ArrayList<>();
ArrayList<PackageStateInternal> remainingPkgSettings = new ArrayList<>(pkgSettings);
// First, remove all settings without available packages
remainingPkgSettings.removeIf(REMOVE_IF_NULL_PKG);
remainingPkgSettings.removeIf(REMOVE_IF_APEX_PKG);
ArrayList<PackageStateInternal> sortTemp = new ArrayList<>(remainingPkgSettings.size());
final Computer snapshot = packageManagerService.snapshotComputer();
// Give priority to core apps.
applyPackageFilter(snapshot, pkgSetting -> pkgSetting.getPkg().isCoreApp(), result,
remainingPkgSettings, sortTemp, packageManagerService);
// Give priority to system apps that listen for pre boot complete.
Intent intent = new Intent(Intent.ACTION_PRE_BOOT_COMPLETED);
final ArraySet<String> pkgNames = getPackageNamesForIntent(intent, UserHandle.USER_SYSTEM);
applyPackageFilter(snapshot, pkgSetting -> pkgNames.contains(pkgSetting.getPackageName()), result,
remainingPkgSettings, sortTemp, packageManagerService);
// Give priority to apps used by other apps.
DexManager dexManager = packageManagerService.getDexManager();
applyPackageFilter(snapshot, pkgSetting ->
dexManager.getPackageUseInfoOrDefault(pkgSetting.getPackageName())
.isAnyCodePathUsedByOtherApps(),
result, remainingPkgSettings, sortTemp, packageManagerService);
// Filter out packages that aren't recently used, add all remaining apps.
// TODO: add a property to control this?
Predicate<PackageStateInternal> remainingPredicate;
if (!remainingPkgSettings.isEmpty()
&& packageManagerService.isHistoricalPackageUsageAvailable()) {
if (debug) {
Log.i(TAG, "Looking at historical package use");
}
// Get the package that was used last.
PackageStateInternal lastUsed = Collections.max(remainingPkgSettings,
Comparator.comparingLong(
pkgSetting -> pkgSetting.getTransientState()
.getLatestForegroundPackageUseTimeInMills()));
if (debug) {
Log.i(TAG, "Taking package " + lastUsed.getPackageName()
+ " as reference in time use");
}
long estimatedPreviousSystemUseTime = lastUsed.getTransientState()
.getLatestForegroundPackageUseTimeInMills();
// Be defensive if for some reason package usage has bogus data.
if (estimatedPreviousSystemUseTime != 0) {
final long cutoffTime = estimatedPreviousSystemUseTime - SEVEN_DAYS_IN_MILLISECONDS;
remainingPredicate = pkgSetting -> pkgSetting.getTransientState()
.getLatestForegroundPackageUseTimeInMills() >= cutoffTime;
} else {
// No meaningful historical info. Take all.
remainingPredicate = pkgSetting -> true;
}
sortPackagesByUsageDate(remainingPkgSettings, packageManagerService);
} else {
// No historical info. Take all.
remainingPredicate = pkgSetting -> true;
}
applyPackageFilter(snapshot, remainingPredicate, result, remainingPkgSettings, sortTemp,
packageManagerService);
// Make sure the system server isn't in the result, because it can never be dexopted here.
result.removeIf(pkgSetting -> PLATFORM_PACKAGE_NAME.equals(pkgSetting.getPackageName()));
if (debug) {
Log.i(TAG, "Packages to be dexopted: " + packagesToString(result));
Log.i(TAG, "Packages skipped from dexopt: " + packagesToString(remainingPkgSettings));
}
return result;
}
// Apply the given {@code filter} to all packages in {@code packages}. If tested positive, the
// package will be removed from {@code packages} and added to {@code result} with its
// dependencies. If usage data is available, the positive packages will be sorted by usage
// data (with {@code sortTemp} as temporary storage).
private static void applyPackageFilter(@NonNull Computer snapshot,
Predicate<PackageStateInternal> filter,
Collection<PackageStateInternal> result,
Collection<PackageStateInternal> packages,
@NonNull List<PackageStateInternal> sortTemp,
PackageManagerService packageManagerService) {
for (PackageStateInternal pkgSetting : packages) {
if (filter.test(pkgSetting)) {
sortTemp.add(pkgSetting);
}
}
sortPackagesByUsageDate(sortTemp, packageManagerService);
packages.removeAll(sortTemp);
for (PackageStateInternal pkgSetting : sortTemp) {
result.add(pkgSetting);
List<PackageStateInternal> deps = snapshot.findSharedNonSystemLibraries(pkgSetting);
if (!deps.isEmpty()) {
deps.removeAll(result);
result.addAll(deps);
packages.removeAll(deps);
}
}
sortTemp.clear();
}
// Sort a list of apps by their last usage, most recently used apps first. The order of
// packages without usage data is undefined (but they will be sorted after the packages
// that do have usage data).
private static void sortPackagesByUsageDate(List<PackageStateInternal> pkgSettings,
PackageManagerService packageManagerService) {
if (!packageManagerService.isHistoricalPackageUsageAvailable()) {
return;
}
Collections.sort(pkgSettings, (pkgSetting1, pkgSetting2) ->
Long.compare(
pkgSetting2.getTransientState().getLatestForegroundPackageUseTimeInMills(),
pkgSetting1.getTransientState().getLatestForegroundPackageUseTimeInMills())
);
}
private static ArraySet<String> getPackageNamesForIntent(Intent intent, int userId) {
List<ResolveInfo> ris = null;
try {
ris = AppGlobals.getPackageManager().queryIntentReceivers(intent, null, 0, userId)
.getList();
} catch (RemoteException e) {
}
ArraySet<String> pkgNames = new ArraySet<String>();
if (ris != null) {
for (ResolveInfo ri : ris) {
pkgNames.add(ri.activityInfo.packageName);
}
}
return pkgNames;
}
public static String packagesToString(List<PackageStateInternal> pkgSettings) {
StringBuilder sb = new StringBuilder();
for (int index = 0; index < pkgSettings.size(); index++) {
if (sb.length() > 0) {
sb.append(", ");
}
sb.append(pkgSettings.get(index).getPackageName());
}
return sb.toString();
}
/**
* Requests that files preopted on a secondary system partition be copied to the data partition
* if possible. Note that the actual copying of the files is accomplished by init for security
* reasons. This simply requests that the copy takes place and awaits confirmation of its
* completion. See platform/system/extras/cppreopt/ for the implementation of the actual copy.
*/
public static void requestCopyPreoptedFiles() {
final int WAIT_TIME_MS = 100;
final String CP_PREOPT_PROPERTY = "sys.cppreopt";
if (SystemProperties.getInt("ro.cp_system_other_odex", 0) == 1) {
SystemProperties.set(CP_PREOPT_PROPERTY, "requested");
// We will wait for up to 100 seconds.
final long timeStart = SystemClock.uptimeMillis();
final long timeEnd = timeStart + 100 * 1000;
long timeNow = timeStart;
while (!SystemProperties.get(CP_PREOPT_PROPERTY).equals("finished")) {
try {
Thread.sleep(WAIT_TIME_MS);
} catch (InterruptedException e) {
// Do nothing
}
timeNow = SystemClock.uptimeMillis();
if (timeNow > timeEnd) {
SystemProperties.set(CP_PREOPT_PROPERTY, "timed-out");
Slog.wtf(TAG, "cppreopt did not finish!");
break;
}
}
Slog.i(TAG, "cppreopts took " + (timeNow - timeStart) + " ms");
}
}
/*package*/ void controlDexOptBlocking(boolean block) throws LegacyDexoptDisabledException {
mPm.mPackageDexOptimizer.controlDexOptBlocking(block);
}
/**
* Dumps the dexopt state for the given package, or all packages if it is null.
*/
public static void dumpDexoptState(
@NonNull IndentingPrintWriter ipw, @Nullable String packageName) {
try (PackageManagerLocal.FilteredSnapshot snapshot =
getPackageManagerLocal().withFilteredSnapshot()) {
if (packageName != null) {
try {
DexOptHelper.getArtManagerLocal().dumpPackage(ipw, snapshot, packageName);
} catch (IllegalArgumentException e) {
// Package isn't found, but that should only happen due to race.
ipw.println(e);
}
} else {
DexOptHelper.getArtManagerLocal().dump(ipw, snapshot);
}
}
}
/**
* Returns the module names of the APEXes that contribute to bootclasspath.
*/
private static List<String> getBcpApexes() {
String bcp = System.getenv("BOOTCLASSPATH");
if (TextUtils.isEmpty(bcp)) {
Log.e(TAG, "Unable to get BOOTCLASSPATH");
return List.of();
}
ArrayList<String> bcpApexes = new ArrayList<>();
for (String pathStr : bcp.split(":")) {
Path path = Paths.get(pathStr);
// Check if the path is in the format of `/apex/<apex-module-name>/...` and extract the
// apex module name from the path.
if (path.getNameCount() >= 2 && path.getName(0).toString().equals("apex")) {
bcpApexes.add(path.getName(1).toString());
}
}
return bcpApexes;
}
/**
* Returns true of any of the APEXes that contribute to bootclasspath has changed during this
* boot.
*/
private static boolean hasBcpApexesChanged() {
Set<String> bcpApexes = new HashSet<>(getBcpApexes());
ApexManager apexManager = ApexManager.getInstance();
for (ActiveApexInfo apexInfo : apexManager.getActiveApexInfos()) {
if (bcpApexes.contains(apexInfo.apexModuleName) && apexInfo.activeApexChanged) {
return true;
}
}
return false;
}
/**
* Returns true if ART Service should be used for package optimization.
*/
public static boolean useArtService() {
return SystemProperties.getBoolean("dalvik.vm.useartservice", false);
}
/**
* Returns {@link DexUseManagerLocal} if ART Service should be used for package optimization.
*/
public static @Nullable DexUseManagerLocal getDexUseManagerLocal() {
if (!useArtService()) {
return null;
}
try {
return LocalManagerRegistry.getManagerOrThrow(DexUseManagerLocal.class);
} catch (ManagerNotFoundException e) {
throw new RuntimeException(e);
}
}
private class DexoptDoneHandler implements ArtManagerLocal.DexoptDoneCallback {
/**
* Called after every package dexopt operation done by {@link ArtManagerLocal} (when ART
* Service is in use).
*/
@Override
public void onDexoptDone(@NonNull DexoptResult result) {
switch (result.getReason()) {
case ReasonMapping.REASON_FIRST_BOOT:
case ReasonMapping.REASON_BOOT_AFTER_OTA:
case ReasonMapping.REASON_BOOT_AFTER_MAINLINE_UPDATE:
int numDexopted = 0;
int numSkipped = 0;
int numFailed = 0;
for (DexoptResult.PackageDexoptResult pkgRes :
result.getPackageDexoptResults()) {
switch (pkgRes.getStatus()) {
case DexoptResult.DEXOPT_PERFORMED:
numDexopted += 1;
break;
case DexoptResult.DEXOPT_SKIPPED:
numSkipped += 1;
break;
case DexoptResult.DEXOPT_FAILED:
numFailed += 1;
break;
}
}
reportBootDexopt(mBootDexoptStartTime, numDexopted, numSkipped, numFailed);
break;
}
for (DexoptResult.PackageDexoptResult pkgRes : result.getPackageDexoptResults()) {
CompilerStats.PackageStats stats =
mPm.getOrCreateCompilerPackageStats(pkgRes.getPackageName());
for (DexoptResult.DexContainerFileDexoptResult dexRes :
pkgRes.getDexContainerFileDexoptResults()) {
stats.setCompileTime(
dexRes.getDexContainerFile(), dexRes.getDex2oatWallTimeMillis());
}
}
synchronized (mPm.mLock) {
mPm.getPackageUsage().maybeWriteAsync(mPm.mSettings.getPackagesLocked());
mPm.mCompilerStats.maybeWriteAsync();
}
if (result.getReason().equals(ReasonMapping.REASON_INACTIVE)) {
for (DexoptResult.PackageDexoptResult pkgRes : result.getPackageDexoptResults()) {
if (pkgRes.getStatus() == DexoptResult.DEXOPT_PERFORMED) {
long pkgSizeBytes = 0;
long pkgSizeBeforeBytes = 0;
for (DexoptResult.DexContainerFileDexoptResult dexRes :
pkgRes.getDexContainerFileDexoptResults()) {
long dexContainerSize = new File(dexRes.getDexContainerFile()).length();
pkgSizeBytes += dexRes.getSizeBytes() + dexContainerSize;
pkgSizeBeforeBytes += dexRes.getSizeBeforeBytes() + dexContainerSize;
}
FrameworkStatsLog.write(FrameworkStatsLog.APP_DOWNGRADED,
pkgRes.getPackageName(), pkgSizeBeforeBytes, pkgSizeBytes,
false /* aggressive */);
}
}
}
var updatedPackages = new ArraySet<String>();
for (DexoptResult.PackageDexoptResult pkgRes : result.getPackageDexoptResults()) {
if (pkgRes.hasUpdatedArtifacts()) {
updatedPackages.add(pkgRes.getPackageName());
}
}
if (!updatedPackages.isEmpty()) {
LocalServices.getService(PinnerService.class)
.update(updatedPackages, false /* force */);
}
}
}
/**
* Initializes {@link ArtManagerLocal} before {@link getArtManagerLocal} is called.
*/
public static void initializeArtManagerLocal(
@NonNull Context systemContext, @NonNull PackageManagerService pm) {
if (!useArtService()) {
return;
}
ArtManagerLocal artManager = new ArtManagerLocal(systemContext);
artManager.addDexoptDoneCallback(false /* onlyIncludeUpdates */, Runnable::run,
pm.getDexOptHelper().new DexoptDoneHandler());
LocalManagerRegistry.addManager(ArtManagerLocal.class, artManager);
sArtManagerLocalIsInitialized = true;
// Schedule the background job when boot is complete. This decouples us from when
// JobSchedulerService is initialized.
systemContext.registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
context.unregisterReceiver(this);
artManager.scheduleBackgroundDexoptJob();
}
}, new IntentFilter(Intent.ACTION_LOCKED_BOOT_COMPLETED));
StagedApexObserver.registerForStagedApexUpdates(artManager);
}
/**
* Returns true if an {@link ArtManagerLocal} instance has been created.
*
* Avoid this function if at all possible, because it may hide initialization order problems.
*/
public static boolean artManagerLocalIsInitialized() {
return sArtManagerLocalIsInitialized;
}
/**
* Returns the registered {@link ArtManagerLocal} instance, or else throws an unchecked error.
*/
public static @NonNull ArtManagerLocal getArtManagerLocal() {
try {
return LocalManagerRegistry.getManagerOrThrow(ArtManagerLocal.class);
} catch (ManagerNotFoundException e) {
throw new RuntimeException(e);
}
}
/**
* Converts an ART Service {@link DexoptResult} to {@link DexOptResult}.
*
* For interfacing {@link ArtManagerLocal} with legacy dex optimization code in PackageManager.
*/
@DexOptResult
private static int convertToDexOptResult(DexoptResult result) {
/*@DexoptResultStatus*/ int status = result.getFinalStatus();
switch (status) {
case DexoptResult.DEXOPT_SKIPPED:
return PackageDexOptimizer.DEX_OPT_SKIPPED;
case DexoptResult.DEXOPT_FAILED:
return PackageDexOptimizer.DEX_OPT_FAILED;
case DexoptResult.DEXOPT_PERFORMED:
return PackageDexOptimizer.DEX_OPT_PERFORMED;
case DexoptResult.DEXOPT_CANCELLED:
return PackageDexOptimizer.DEX_OPT_CANCELLED;
default:
throw new IllegalArgumentException("DexoptResult for "
+ result.getPackageDexoptResults().get(0).getPackageName()
+ " has unsupported status " + status);
}
}
private static class StagedApexObserver extends IStagedApexObserver.Stub {
private final @NonNull ArtManagerLocal mArtManager;
static void registerForStagedApexUpdates(@NonNull ArtManagerLocal artManager) {
IPackageManagerNative packageNative = IPackageManagerNative.Stub.asInterface(
ServiceManager.getService("package_native"));
if (packageNative == null) {
Log.e(TAG, "No IPackageManagerNative");
return;
}
try {
packageNative.registerStagedApexObserver(new StagedApexObserver(artManager));
} catch (RemoteException e) {
Log.e(TAG, "Failed to register staged apex observer", e);
}
}
private StagedApexObserver(@NonNull ArtManagerLocal artManager) {
mArtManager = artManager;
}
@Override
public void onApexStaged(@NonNull ApexStagedEvent event) {
mArtManager.onApexStaged(event.stagedApexModuleNames);
}
}
}