blob: 2792a2d934a33bbd11ddeb5a77cd733f48898b14 [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.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.OptimizationStatus.DexContainerFileOptimizationStatus;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.content.Context;
import android.os.Binder;
import android.os.CancellationSignal;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.art.IArtd;
import com.android.server.art.model.ArtFlags;
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.art.wrapper.AndroidPackageApi;
import com.android.server.art.wrapper.PackageManagerLocal;
import com.android.server.art.wrapper.PackageState;
import com.android.server.pm.snapshot.PackageDataSnapshot;
import java.util.ArrayList;
import java.util.List;
/**
* 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";
@NonNull private final Injector mInjector;
@Deprecated
public ArtManagerLocal() {
this(new Injector(null /* context */));
}
public ArtManagerLocal(@NonNull Context context) {
this(new Injector(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())
.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 an internal error occurs
*/
@NonNull
public DeleteResult deleteOptimizedArtifacts(
@NonNull PackageDataSnapshot snapshot, @NonNull String packageName) {
return deleteOptimizedArtifacts(snapshot, packageName, ArtFlags.defaultDeleteFlags());
}
/**
* Same as above, but allows to specify flags.
*
* @see #deleteOptimizedArtifacts(PackageDataSnapshot, String)
*/
@NonNull
public DeleteResult deleteOptimizedArtifacts(@NonNull PackageDataSnapshot 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 = getPackageStateOrThrow(snapshot, packageName);
AndroidPackageApi pkg = 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 an internal error occurs
*/
@NonNull
public OptimizationStatus getOptimizationStatus(
@NonNull PackageDataSnapshot snapshot, @NonNull String packageName) {
return getOptimizationStatus(snapshot, packageName, ArtFlags.defaultGetStatusFlags());
}
/**
* Same as above, but allows to specify flags.
*
* @see #getOptimizationStatus(PackageDataSnapshot, String)
*/
@NonNull
public OptimizationStatus getOptimizationStatus(@NonNull PackageDataSnapshot 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 = getPackageStateOrThrow(snapshot, packageName);
AndroidPackageApi pkg = 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.
*
* @throws IllegalArgumentException if the package is not found or the params are illegal
* @throws IllegalStateException if an internal error occurs
*/
@NonNull
public OptimizeResult optimizePackage(@NonNull PackageDataSnapshot 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(PackageDataSnapshot, String, OptimizeParams)
*/
@NonNull
public OptimizeResult optimizePackage(@NonNull PackageDataSnapshot snapshot,
@NonNull String packageName, @NonNull OptimizeParams params,
@NonNull CancellationSignal cancellationSignal) {
if ((params.getFlags() & ArtFlags.FLAG_FOR_PRIMARY_DEX) == 0
&& (params.getFlags() & ArtFlags.FLAG_FOR_SECONDARY_DEX) == 0) {
throw new IllegalArgumentException("Nothing to optimize");
}
PackageState pkgState = getPackageStateOrThrow(snapshot, packageName);
AndroidPackageApi pkg = getPackageOrThrow(pkgState);
try {
return mInjector.getDexOptHelper().dexopt(
snapshot, pkgState, pkg, params, cancellationSignal);
} catch (RemoteException e) {
throw new IllegalStateException("An error occurred when calling artd", e);
}
}
private PackageState getPackageStateOrThrow(
@NonNull PackageDataSnapshot snapshot, @NonNull String packageName) {
PackageState pkgState = mInjector.getPackageManagerLocal().getPackageState(
snapshot, Binder.getCallingUid(), packageName);
if (pkgState == null) {
throw new IllegalArgumentException("Package not found: " + packageName);
}
return pkgState;
}
private AndroidPackageApi getPackageOrThrow(@NonNull PackageState pkgState) {
AndroidPackageApi pkg = pkgState.getAndroidPackage();
if (pkg == null) {
throw new IllegalArgumentException(
"Unable to get package " + pkgState.getPackageName());
}
return pkg;
}
/**
* Injector pattern for testing purpose.
*
* @hide
*/
@VisibleForTesting
public static class Injector {
@Nullable private final Context mContext;
@Nullable private final PackageManagerLocal mPackageManagerLocal;
Injector(@Nullable Context context) {
mContext = context;
PackageManagerLocal packageManagerLocal = null;
try {
packageManagerLocal = PackageManagerLocal.getInstance();
} catch (Exception e) {
// This is not a serious error. The reflection-based approach can be broken in some
// cases. This is fine because ART services is under development and no one depends
// on it.
// TODO(b/177273468): Make this a serious error when we switch to using the real
// APIs.
Log.w(TAG, "Unable to get fake PackageManagerLocal", e);
}
mPackageManagerLocal = packageManagerLocal;
}
@NonNull
public Context getContext() {
if (mContext == null) {
throw new IllegalStateException("Context is null");
}
return mContext;
}
@NonNull
public PackageManagerLocal getPackageManagerLocal() {
if (mPackageManagerLocal == null) {
throw new IllegalStateException("PackageManagerLocal is null");
}
return mPackageManagerLocal;
}
@NonNull
public IArtd getArtd() {
return Utils.getArtd();
}
@NonNull
public DexOptHelper getDexOptHelper() {
return new DexOptHelper(getContext());
}
}
}