blob: 751454300f2097167aeb27a32f6f2bf79f33821c [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.art;
import static com.android.server.art.PrimaryDexUtils.DetailedPrimaryDexInfo;
import static com.android.server.art.PrimaryDexUtils.PrimaryDexInfo;
import static com.android.server.art.ReasonMapping.BatchOptimizeReason;
import static com.android.server.art.Utils.Abi;
import static com.android.server.art.model.ArtFlags.DeleteFlags;
import static com.android.server.art.model.ArtFlags.GetStatusFlags;
import static com.android.server.art.model.ArtFlags.ScheduleStatus;
import static com.android.server.art.model.Config.Callback;
import static com.android.server.art.model.OptimizationStatus.DexContainerFileOptimizationStatus;
import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.app.job.JobInfo;
import android.apphibernation.AppHibernationManager;
import android.content.Context;
import android.os.Binder;
import android.os.CancellationSignal;
import android.os.ParcelFileDescriptor;
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
import android.os.UserManager;
import android.text.TextUtils;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.LocalManagerRegistry;
import com.android.server.art.model.ArtFlags;
import com.android.server.art.model.BatchOptimizeParams;
import com.android.server.art.model.Config;
import com.android.server.art.model.DeleteResult;
import com.android.server.art.model.OptimizationStatus;
import com.android.server.art.model.OptimizeParams;
import com.android.server.art.model.OptimizeResult;
import com.android.server.pm.PackageManagerLocal;
import com.android.server.pm.pkg.AndroidPackage;
import com.android.server.pm.pkg.PackageState;
import java.io.File;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;
/**
* This class provides a system API for functionality provided by the ART module.
*
* Note: Although this class is the entry point of ART services, this class is not a {@link
* SystemService}, and it does not publish a binder. Instead, it is a module loaded by the
* system_server process, registered in {@link LocalManagerRegistry}. {@link LocalManagerRegistry}
* specifies that in-process module interfaces should be named with the suffix {@code ManagerLocal}
* for consistency.
*
* @hide
*/
@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
public final class ArtManagerLocal {
private static final String TAG = "ArtService";
private static final String[] CLASSPATHS_FOR_BOOT_IMAGE_PROFILE = {
"BOOTCLASSPATH", "SYSTEMSERVERCLASSPATH", "STANDALONE_SYSTEMSERVER_JARS"};
@NonNull private final Injector mInjector;
@Deprecated
public ArtManagerLocal() {
mInjector = new Injector(this, null /* context */);
}
public ArtManagerLocal(@NonNull Context context) {
mInjector = new Injector(this, context);
}
/** @hide */
@VisibleForTesting
public ArtManagerLocal(@NonNull Injector injector) {
mInjector = injector;
}
/**
* Handles `cmd package art` sub-command.
*
* For debugging purposes only. Intentionally enforces root access to limit the usage.
*
* Note: This method is not an override of {@link Binder#handleShellCommand} because ART
* services does not publish a binder. Instead, it handles the `art` sub-command forwarded by
* the `package` service. The semantics of the parameters are the same as {@link
* Binder#handleShellCommand}.
*
* @return zero on success, non-zero on internal error (e.g., I/O error)
* @throws SecurityException if the caller is not root
* @throws IllegalArgumentException if the arguments are illegal
* @see ArtShellCommand#onHelp()
*/
public int handleShellCommand(@NonNull Binder target, @NonNull ParcelFileDescriptor in,
@NonNull ParcelFileDescriptor out, @NonNull ParcelFileDescriptor err,
@NonNull String[] args) {
return new ArtShellCommand(
this, mInjector.getPackageManagerLocal(), mInjector.getDexUseManager())
.exec(target, in.getFileDescriptor(), out.getFileDescriptor(),
err.getFileDescriptor(), args);
}
/**
* Deletes optimized artifacts of a package.
*
* Uses the default flags ({@link ArtFlags#defaultDeleteFlags()}).
*
* @throws IllegalArgumentException if the package is not found or the flags are illegal
* @throws IllegalStateException if the operation encounters an error that should never happen
* (e.g., an internal logic error).
*/
@NonNull
public DeleteResult deleteOptimizedArtifacts(
@NonNull PackageManagerLocal.FilteredSnapshot snapshot, @NonNull String packageName) {
return deleteOptimizedArtifacts(snapshot, packageName, ArtFlags.defaultDeleteFlags());
}
/**
* Same as above, but allows to specify flags.
*
* @see #deleteOptimizedArtifacts(PackageManagerLocal.FilteredSnapshot, String)
*/
@NonNull
public DeleteResult deleteOptimizedArtifacts(
@NonNull PackageManagerLocal.FilteredSnapshot snapshot, @NonNull String packageName,
@DeleteFlags int flags) {
if ((flags & ArtFlags.FLAG_FOR_PRIMARY_DEX) == 0
&& (flags & ArtFlags.FLAG_FOR_SECONDARY_DEX) == 0) {
throw new IllegalArgumentException("Nothing to delete");
}
PackageState pkgState = Utils.getPackageStateOrThrow(snapshot, packageName);
AndroidPackage pkg = Utils.getPackageOrThrow(pkgState);
try {
long freedBytes = 0;
if ((flags & ArtFlags.FLAG_FOR_PRIMARY_DEX) != 0) {
boolean isInDalvikCache = Utils.isInDalvikCache(pkgState);
for (PrimaryDexInfo dexInfo : PrimaryDexUtils.getDexInfo(pkg)) {
if (!dexInfo.hasCode()) {
continue;
}
for (Abi abi : Utils.getAllAbis(pkgState)) {
freedBytes +=
mInjector.getArtd().deleteArtifacts(AidlUtils.buildArtifactsPath(
dexInfo.dexPath(), abi.isa(), isInDalvikCache));
}
}
}
if ((flags & ArtFlags.FLAG_FOR_SECONDARY_DEX) != 0) {
// TODO(jiakaiz): Implement this.
throw new UnsupportedOperationException(
"Deleting artifacts of secondary dex'es is not implemented yet");
}
return new DeleteResult(freedBytes);
} catch (RemoteException e) {
throw new IllegalStateException("An error occurred when calling artd", e);
}
}
/**
* Returns the optimization status of a package.
*
* Uses the default flags ({@link ArtFlags#defaultGetStatusFlags()}).
*
* @throws IllegalArgumentException if the package is not found or the flags are illegal
* @throws IllegalStateException if the operation encounters an error that should never happen
* (e.g., an internal logic error).
*/
@NonNull
public OptimizationStatus getOptimizationStatus(
@NonNull PackageManagerLocal.FilteredSnapshot snapshot, @NonNull String packageName) {
return getOptimizationStatus(snapshot, packageName, ArtFlags.defaultGetStatusFlags());
}
/**
* Same as above, but allows to specify flags.
*
* @see #getOptimizationStatus(PackageManagerLocal.FilteredSnapshot, String)
*/
@NonNull
public OptimizationStatus getOptimizationStatus(
@NonNull PackageManagerLocal.FilteredSnapshot snapshot, @NonNull String packageName,
@GetStatusFlags int flags) {
if ((flags & ArtFlags.FLAG_FOR_PRIMARY_DEX) == 0
&& (flags & ArtFlags.FLAG_FOR_SECONDARY_DEX) == 0) {
throw new IllegalArgumentException("Nothing to check");
}
PackageState pkgState = Utils.getPackageStateOrThrow(snapshot, packageName);
AndroidPackage pkg = Utils.getPackageOrThrow(pkgState);
try {
List<DexContainerFileOptimizationStatus> statuses = new ArrayList<>();
if ((flags & ArtFlags.FLAG_FOR_PRIMARY_DEX) != 0) {
for (DetailedPrimaryDexInfo dexInfo :
PrimaryDexUtils.getDetailedDexInfo(pkgState, pkg)) {
if (!dexInfo.hasCode()) {
continue;
}
for (Abi abi : Utils.getAllAbis(pkgState)) {
try {
GetOptimizationStatusResult result =
mInjector.getArtd().getOptimizationStatus(dexInfo.dexPath(),
abi.isa(), dexInfo.classLoaderContext());
statuses.add(
DexContainerFileOptimizationStatus.create(dexInfo.dexPath(),
abi.isPrimaryAbi(), abi.name(), result.compilerFilter,
result.compilationReason, result.locationDebugString));
} catch (ServiceSpecificException e) {
statuses.add(DexContainerFileOptimizationStatus.create(
dexInfo.dexPath(), abi.isPrimaryAbi(), abi.name(), "error",
"error", e.getMessage()));
}
}
}
}
if ((flags & ArtFlags.FLAG_FOR_SECONDARY_DEX) != 0) {
// TODO(jiakaiz): Implement this.
throw new UnsupportedOperationException(
"Getting optimization status of secondary dex'es is not implemented yet");
}
return OptimizationStatus.create(statuses);
} catch (RemoteException e) {
throw new IllegalStateException("An error occurred when calling artd", e);
}
}
/**
* Optimizes a package. The time this operation takes ranges from a few milliseconds to several
* minutes, depending on the params and the code size of the package.
*
* When this operation ends (either completed or cancelled), callbacks added by {@link
* #addOptimizePackageDoneCallback(Executor, OptimizePackageDoneCallback)} are called.
*
* @throws IllegalArgumentException if the package is not found or the params are illegal
* @throws IllegalStateException if the operation encounters an error that should never happen
* (e.g., an internal logic error).
*/
@NonNull
public OptimizeResult optimizePackage(@NonNull PackageManagerLocal.FilteredSnapshot snapshot,
@NonNull String packageName, @NonNull OptimizeParams params) {
var cancellationSignal = new CancellationSignal();
return optimizePackage(snapshot, packageName, params, cancellationSignal);
}
/**
* Same as above, but supports cancellation.
*
* @see #optimizePackage(PackageManagerLocal.FilteredSnapshot, String, OptimizeParams)
*/
@NonNull
public OptimizeResult optimizePackage(@NonNull PackageManagerLocal.FilteredSnapshot snapshot,
@NonNull String packageName, @NonNull OptimizeParams params,
@NonNull CancellationSignal cancellationSignal) {
return mInjector.getDexOptHelper().dexopt(
snapshot, List.of(packageName), params, cancellationSignal, Runnable::run);
}
/**
* Runs batch optimization for the given reason.
*
* This is called by ART Service automatically during boot / background dexopt.
*
* The list of packages and options are determined by {@code reason}, and can be overridden by
* {@link #setOptimizePackagesCallback(Executor, OptimizePackagesCallback)}.
*
* The optimization is done in a thread pool. The number of packages being optimized
* simultaneously can be configured by system property {@code pm.dexopt.<reason>.concurrency}
* (e.g., {@code pm.dexopt.bg-dexopt.concurrency=4}), and the number of threads for each {@code
* dex2oat} invocation can be configured by system property {@code dalvik.vm.*dex2oat-threads}
* (e.g., {@code dalvik.vm.background-dex2oat-threads=4}). I.e., the maximum number of
* concurrent threads is the product of the two system properties. Note that the physical core
* usage is always bound by {@code dalvik.vm.*dex2oat-cpu-set} regardless of the number of
* threads.
*
* When this operation ends (either completed or cancelled), callbacks added by {@link
* #addOptimizePackageDoneCallback(Executor, OptimizePackageDoneCallback)} are called.
*
* @param snapshot the snapshot from {@link PackageManagerLocal} to operate on
* @param reason determines the default list of packages and options
* @param cancellationSignal provides the ability to cancel this operation
* @throws IllegalStateException if the operation encounters an error that should never happen
* (e.g., an internal logic error), or the callback set by {@link
* #setOptimizePackagesCallback(Executor, OptimizePackagesCallback)} provides invalid
* params.
*
* @hide
*/
@NonNull
public OptimizeResult optimizePackages(@NonNull PackageManagerLocal.FilteredSnapshot snapshot,
@NonNull @BatchOptimizeReason String reason,
@NonNull CancellationSignal cancellationSignal) {
List<String> defaultPackages =
Collections.unmodifiableList(getDefaultPackages(snapshot, reason));
OptimizeParams defaultOptimizeParams = new OptimizeParams.Builder(reason).build();
var builder = new BatchOptimizeParams.Builder(defaultPackages, defaultOptimizeParams);
Callback<OptimizePackagesCallback> callback =
mInjector.getConfig().getOptimizePackagesCallback();
if (callback != null) {
Utils.executeAndWait(callback.executor(), () -> {
callback.get().onOverrideBatchOptimizeParams(
snapshot, reason, defaultPackages, builder);
});
}
BatchOptimizeParams params = builder.build();
Utils.check(params.getOptimizeParams().getReason().equals(reason));
return mInjector.getDexOptHelper().dexopt(snapshot, params.getPackages(),
params.getOptimizeParams(), cancellationSignal,
Executors.newFixedThreadPool(ReasonMapping.getConcurrencyForReason(reason)));
}
/**
* Overrides the default params for {@link
* #optimizePackages(PackageManagerLocal.FilteredSnapshot, String). This method is thread-safe.
*
* This method gives users the opportunity to change the behavior of {@link
* #optimizePackages(PackageManagerLocal.FilteredSnapshot, String)}, which is called by ART
* Service automatically during boot / background dexopt.
*
* If this method is not called, the default list of packages and options determined by {@code
* reason} will be used.
*/
public void setOptimizePackagesCallback(@NonNull @CallbackExecutor Executor executor,
@NonNull OptimizePackagesCallback callback) {
mInjector.getConfig().setOptimizePackagesCallback(executor, callback);
}
/**
* Clears the callback set by {@link #setOptimizePackagesCallback(Executor,
* OptimizePackagesCallback)}. This method is thread-safe.
*/
public void clearOptimizePackagesCallback() {
mInjector.getConfig().clearOptimizePackagesCallback();
}
/**
* Schedules a background dexopt job. Does nothing if the job is already scheduled.
*
* Use this method if you want the system to automatically determine the best time to run
* dexopt.
*
* The job will be run by the job scheduler. The job scheduling configuration can be overridden
* by {@link #setScheduleBackgroundDexoptJobCallback(Executor,
* ScheduleBackgroundDexoptJobCallback)}. By default, it runs periodically (at most once a day)
* when all the following constraints are meet.
*
* <ul>
* <li>The device is idling. (see {@link JobInfo.Builder#setRequiresDeviceIdle(boolean)})
* <li>The device is charging. (see {@link JobInfo.Builder#setRequiresCharging(boolean)})
* <li>The battery level is not low.
* (see {@link JobInfo.Builder#setRequiresBatteryNotLow(boolean)})
* <li>The free storage space is not low.
* (see {@link JobInfo.Builder#setRequiresStorageNotLow(boolean)})
* </ul>
*
* When the job is running, the job scheduler cancels the job immediately whenever one of the
* constraints above is no longer met, and retries it in the next <i>maintenance window</i>.
* For information about <i>maintenance window</i>, see
* https://developer.android.com/training/monitoring-device-state/doze-standby.
*
* See {@link #optimizePackages(PackageManagerLocal.FilteredSnapshot, String,
* CancellationSignal)} for how to customize the behavior of the job.
*
* When the job ends (either completed or cancelled), the result is sent to the callbacks added
* by {@link #addOptimizePackageDoneCallback(Executor, OptimizePackageDoneCallback)} with the
* reason {@link ReasonMapping#REASON_BG_DEXOPT}.
*/
public @ScheduleStatus int scheduleBackgroundDexoptJob() {
return mInjector.getBackgroundDexOptJob().schedule();
}
/**
* Unschedules the background dexopt job scheduled by {@link #scheduleBackgroundDexoptJob()}.
* Does nothing if the job is not scheduled.
*
* Use this method if you no longer want the system to automatically run dexopt.
*
* If the job is already started by the job scheduler and is running, it will be cancelled
* immediately, and the result sent to the callbacks added by {@link
* #addOptimizePackageDoneCallback(Executor, OptimizePackageDoneCallback)} will contain {@link
* OptimizeResult#OPTIMIZE_CANCELLED}. Note that a job started by {@link
* #startBackgroundDexoptJob()} will not be cancelled by this method.
*/
public void unscheduleBackgroundDexoptJob() {
mInjector.getBackgroundDexOptJob().unschedule();
}
/**
* Overrides the configuration of the background dexopt job. This method is thread-safe.
*/
public void setScheduleBackgroundDexoptJobCallback(@NonNull @CallbackExecutor Executor executor,
@NonNull ScheduleBackgroundDexoptJobCallback callback) {
mInjector.getConfig().setScheduleBackgroundDexoptJobCallback(executor, callback);
}
/**
* Clears the callback set by {@link #setScheduleBackgroundDexoptJobCallback(Executor,
* ScheduleBackgroundDexoptJobCallback)}. This method is thread-safe.
*/
public void clearScheduleBackgroundDexoptJobCallback() {
mInjector.getConfig().clearScheduleBackgroundDexoptJobCallback();
}
/**
* Manually starts a background dexopt job. Does nothing if a job is already started by this
* method or by the job scheduler. This method is not blocking.
*
* Unlike the job started by job scheduler, the job started by this method does not respect
* constraints described in {@link #scheduleBackgroundDexoptJob()}, and hence will not be
* cancelled when they aren't met.
*
* See {@link #optimizePackages(PackageManagerLocal.FilteredSnapshot, String,
* CancellationSignal)} for how to customize the behavior of the job.
*
* When the job ends (either completed or cancelled), the result is sent to the callbacks added
* by {@link #addOptimizePackageDoneCallback(Executor, OptimizePackageDoneCallback)} with the
* reason {@link ReasonMapping#REASON_BG_DEXOPT}.
*/
public void startBackgroundDexoptJob() {
mInjector.getBackgroundDexOptJob().start();
}
/**
* Cancels the running background dexopt job started by the job scheduler or by {@link
* #startBackgroundDexoptJob()}. Does nothing if the job is not running. This method is not
* blocking.
*
* The result sent to the callbacks added by {@link #addOptimizePackageDoneCallback(Executor,
* OptimizePackageDoneCallback)} will contain {@link OptimizeResult#OPTIMIZE_CANCELLED}.
*/
public void cancelBackgroundDexoptJob() {
mInjector.getBackgroundDexOptJob().cancel();
}
/**
* Adds a global listener that listens to any result of optimizing package(s), no matter run
* manually or automatically. Calling this method multiple times with different callbacks is
* allowed. Callbacks are executed in the same order as the one in which they were added. This
* method is thread-safe.
*
* @throws IllegalStateException if the same callback instance is already added
*/
public void addOptimizePackageDoneCallback(@NonNull @CallbackExecutor Executor executor,
@NonNull OptimizePackageDoneCallback callback) {
mInjector.getConfig().addOptimizePackageDoneCallback(executor, callback);
}
/**
* Removes the listener added by {@link #addOptimizePackageDoneCallback(Executor,
* OptimizePackageDoneCallback)}. Does nothing if the callback was not added. This method is
* thread-safe.
*/
public void removeOptimizePackageDoneCallback(@NonNull OptimizePackageDoneCallback callback) {
mInjector.getConfig().removeOptimizePackageDoneCallback(callback);
}
/**
* Snapshots the profile of the given app split. The profile snapshot is the aggregation of all
* existing profiles of the app split (all current user profiles and the reference profile).
*
* @param snapshot the snapshot from {@link PackageManagerLocal} to operate on
* @param packageName the name of the app that owns the profile
* @param splitName see {@link AndroidPackageSplit#getName()}
* @return the file descriptor of the snapshot. It doesn't have any path associated with it. The
* caller is responsible for closing it. Note that the content may be empty.
* @throws IllegalArgumentException if the package or the split is not found
* @throws IllegalStateException if the operation encounters an error that should never happen
* (e.g., an internal logic error).
* @throws SnapshotProfileException if the operation encounters an error that the caller should
* handle (e.g., an I/O error, a sub-process crash).
*/
@NonNull
public ParcelFileDescriptor snapshotAppProfile(
@NonNull PackageManagerLocal.FilteredSnapshot snapshot, @NonNull String packageName,
@Nullable String splitName) throws SnapshotProfileException {
PackageState pkgState = Utils.getPackageStateOrThrow(snapshot, packageName);
AndroidPackage pkg = Utils.getPackageOrThrow(pkgState);
PrimaryDexInfo dexInfo = PrimaryDexUtils.getDexInfoBySplitName(pkg, splitName);
List<ProfilePath> profiles = new ArrayList<>();
profiles.add(PrimaryDexUtils.buildRefProfilePath(pkgState, dexInfo));
profiles.addAll(
PrimaryDexUtils.getCurProfiles(mInjector.getUserManager(), pkgState, dexInfo));
OutputProfile output = PrimaryDexUtils.buildOutputProfile(
pkgState, dexInfo, Process.SYSTEM_UID, Process.SYSTEM_UID, false /* isPublic */);
return mergeProfilesAndGetFd(
profiles, output, List.of(dexInfo.dexPath()), false /* forBootImage */);
}
/**
* Snapshots the boot image profile
* (https://source.android.com/docs/core/bootloader/boot-image-profiles). The profile snapshot
* is the aggregation of all existing profiles on the device (all current user profiles and
* reference profiles) of all apps and the system server filtered by applicable classpaths.
*
* @param snapshot the snapshot from {@link PackageManagerLocal} to operate on
* @return the file descriptor of the snapshot. It doesn't have any path associated with it. The
* caller is responsible for closing it. Note that the content may be empty.
* @throws IllegalStateException if the operation encounters an error that should never happen
* (e.g., an internal logic error).
* @throws SnapshotProfileException if the operation encounters an error that the caller should
* handle (e.g., an I/O error, a sub-process crash).
*/
@NonNull
public ParcelFileDescriptor snapshotBootImageProfile(
@NonNull PackageManagerLocal.FilteredSnapshot snapshot)
throws SnapshotProfileException {
List<ProfilePath> profiles = new ArrayList<>();
// System server profiles.
PackageState pkgState = Utils.getPackageStateOrThrow(snapshot, Utils.PLATFORM_PACKAGE_NAME);
AndroidPackage pkg = Utils.getPackageOrThrow(pkgState);
PrimaryDexInfo dexInfo = PrimaryDexUtils.getDexInfo(pkg).get(0);
profiles.add(PrimaryDexUtils.buildRefProfilePath(pkgState, dexInfo));
profiles.addAll(
PrimaryDexUtils.getCurProfiles(mInjector.getUserManager(), pkgState, dexInfo));
// App profiles.
snapshot.forAllPackageStates((appPkgState) -> {
// Hibernating apps can still provide useful profile contents, so skip the hibernation
// check.
if (Utils.canOptimizePackage(appPkgState, null /* appHibernationManager */)) {
AndroidPackage appPkg = Utils.getPackageOrThrow(appPkgState);
for (PrimaryDexInfo appDexInfo : PrimaryDexUtils.getDexInfo(appPkg)) {
if (!appDexInfo.hasCode()) {
continue;
}
profiles.add(PrimaryDexUtils.buildRefProfilePath(appPkgState, appDexInfo));
profiles.addAll(PrimaryDexUtils.getCurProfiles(
mInjector.getUserManager(), appPkgState, appDexInfo));
}
}
});
OutputProfile output = AidlUtils.buildOutputProfileForPrimary(Utils.PLATFORM_PACKAGE_NAME,
"primary", Process.SYSTEM_UID, Process.SYSTEM_UID, false /* isPublic */);
List<String> dexPaths = Arrays.stream(CLASSPATHS_FOR_BOOT_IMAGE_PROFILE)
.map(envVar -> Constants.getenv(envVar))
.filter(classpath -> !TextUtils.isEmpty(classpath))
.flatMap(classpath -> Arrays.stream(classpath.split(":")))
.collect(Collectors.toList());
return mergeProfilesAndGetFd(profiles, output, dexPaths, true /* forBootImage */);
}
/**
* Should be used by {@link BackgroundDexOptJobService} ONLY.
*
* @hide
*/
@NonNull
BackgroundDexOptJob getBackgroundDexOptJob() {
return mInjector.getBackgroundDexOptJob();
}
@NonNull
private List<String> getDefaultPackages(@NonNull PackageManagerLocal.FilteredSnapshot snapshot,
@NonNull @BatchOptimizeReason String reason) {
var packages = new ArrayList<String>();
snapshot.forAllPackageStates((pkgState) -> {
if (Utils.canOptimizePackage(pkgState, mInjector.getAppHibernationManager())) {
packages.add(pkgState.getPackageName());
}
});
return packages;
}
@NonNull
private ParcelFileDescriptor mergeProfilesAndGetFd(@NonNull List<ProfilePath> profiles,
@NonNull OutputProfile output, @NonNull List<String> dexPaths, boolean forBootImage)
throws SnapshotProfileException {
try {
var options = new MergeProfileOptions();
options.forceMerge = true;
options.forBootImage = forBootImage;
boolean hasContent = false;
try {
hasContent = mInjector.getArtd().mergeProfiles(
profiles, null /* referenceProfile */, output, dexPaths, options);
} catch (ServiceSpecificException e) {
throw new SnapshotProfileException(e);
}
String path = hasContent ? output.profilePath.tmpPath : "/dev/null";
ParcelFileDescriptor fd;
try {
fd = ParcelFileDescriptor.open(new File(path), ParcelFileDescriptor.MODE_READ_ONLY);
} catch (FileNotFoundException e) {
throw new IllegalStateException(
String.format("Failed to open profile snapshot '%s'", path), e);
}
if (hasContent) {
// This is done on the open file so that only the FD keeps a reference to its
// contents.
mInjector.getArtd().deleteProfile(ProfilePath.tmpProfilePath(output.profilePath));
}
return fd;
} catch (RemoteException e) {
throw new IllegalStateException("An error occurred when calling artd", e);
}
}
public interface OptimizePackagesCallback {
/**
* Mutates {@code builder} to override the default params for {@link
* #optimizePackages(PackageManagerLocal.FilteredSnapshot, String). It must ignore unknown
* reasons because more reasons may be added in the future.
*
* If {@code builder.setPackages} is not called, {@code defaultPackages} will be used as the
* list of packages to optimize.
*
* If {@code builder.setOptimizeParams} is not called, the default params built from {@code
* new OptimizeParams.Builder(reason)} will to used as the params for optimizing each
* package.
*
* Changing the reason is not allowed. Doing so will result in {@link IllegalStateException}
* when {@link #optimizePackages(PackageManagerLocal.FilteredSnapshot, String,
* CancellationSignal)} is called.
*/
void onOverrideBatchOptimizeParams(@NonNull PackageManagerLocal.FilteredSnapshot snapshot,
@NonNull @BatchOptimizeReason String reason, @NonNull List<String> defaultPackages,
@NonNull BatchOptimizeParams.Builder builder);
}
public interface ScheduleBackgroundDexoptJobCallback {
/**
* Mutates {@code builder} to override the configuration of the background dexopt job.
*
* The default configuration described in {@link
* ArtManagerLocal#scheduleBackgroundDexoptJob()} is passed to the callback as the {@code
* builder} argument.
*/
void onOverrideJobInfo(@NonNull JobInfo.Builder builder);
}
public interface OptimizePackageDoneCallback {
void onOptimizePackageDone(@NonNull OptimizeResult result);
}
/** Represents an error that happens when snapshotting profiles. */
public static class SnapshotProfileException extends Exception {
public SnapshotProfileException(@NonNull Throwable cause) {
super(cause);
}
}
/**
* Injector pattern for testing purpose.
*
* @hide
*/
@VisibleForTesting
public static class Injector {
@Nullable private final Context mContext;
@Nullable private final PackageManagerLocal mPackageManagerLocal;
@Nullable private final Config mConfig;
@Nullable private final BackgroundDexOptJob mBgDexOptJob;
Injector(@NonNull ArtManagerLocal artManagerLocal, @Nullable Context context) {
mContext = context;
if (context != null) {
// We only need them on Android U and above, where a context is passed.
mPackageManagerLocal = LocalManagerRegistry.getManager(PackageManagerLocal.class);
mConfig = new Config();
mBgDexOptJob = new BackgroundDexOptJob(context, artManagerLocal, mConfig);
} else {
mPackageManagerLocal = null;
mConfig = null;
mBgDexOptJob = null;
}
}
@NonNull
public Context getContext() {
return Objects.requireNonNull(mContext);
}
@NonNull
public PackageManagerLocal getPackageManagerLocal() {
return Objects.requireNonNull(mPackageManagerLocal);
}
@NonNull
public IArtd getArtd() {
return Utils.getArtd();
}
@NonNull
public DexOptHelper getDexOptHelper() {
return new DexOptHelper(getContext(), getConfig());
}
@NonNull
public Config getConfig() {
return mConfig;
}
@NonNull
public AppHibernationManager getAppHibernationManager() {
return Objects.requireNonNull(mContext.getSystemService(AppHibernationManager.class));
}
@NonNull
public BackgroundDexOptJob getBackgroundDexOptJob() {
return Objects.requireNonNull(mBgDexOptJob);
}
@NonNull
public UserManager getUserManager() {
return Objects.requireNonNull(mContext.getSystemService(UserManager.class));
}
@NonNull
public DexUseManagerLocal getDexUseManager() {
return Objects.requireNonNull(
LocalManagerRegistry.getManager(DexUseManagerLocal.class));
}
}
}