/*
|
* Copyright (C) 2016 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 com.android.server.pm.InstructionSets.getAppDexInstructionSets;
|
import static com.android.server.pm.InstructionSets.getDexCodeInstructionSets;
|
|
import android.annotation.Nullable;
|
import android.content.Context;
|
import android.content.pm.IOtaDexopt;
|
import android.content.pm.PackageParser;
|
import android.os.Environment;
|
import android.os.RemoteException;
|
import android.os.ResultReceiver;
|
import android.os.ServiceManager;
|
import android.os.ShellCallback;
|
import android.os.storage.StorageManager;
|
import android.util.Log;
|
import android.util.Slog;
|
|
import com.android.internal.logging.MetricsLogger;
|
import com.android.server.pm.Installer.InstallerException;
|
import com.android.server.pm.dex.DexoptOptions;
|
|
import java.io.File;
|
import java.io.FileDescriptor;
|
import java.util.ArrayList;
|
import java.util.Collection;
|
import java.util.Collections;
|
import java.util.List;
|
import java.util.concurrent.TimeUnit;
|
|
/**
|
* A service for A/B OTA dexopting.
|
*
|
* {@hide}
|
*/
|
public class OtaDexoptService extends IOtaDexopt.Stub {
|
private final static String TAG = "OTADexopt";
|
private final static boolean DEBUG_DEXOPT = true;
|
|
// The amount of "available" (free - low threshold) space necessary at the start of an OTA to
|
// not bulk-delete unused apps' odex files.
|
private final static long BULK_DELETE_THRESHOLD = 1024 * 1024 * 1024; // 1GB.
|
|
private final Context mContext;
|
private final PackageManagerService mPackageManagerService;
|
private final MetricsLogger metricsLogger;
|
|
// TODO: Evaluate the need for WeakReferences here.
|
|
/**
|
* The list of dexopt invocations for all work.
|
*/
|
private List<String> mDexoptCommands;
|
|
private int completeSize;
|
|
// MetricsLogger properties.
|
|
// Space before and after.
|
private long availableSpaceBefore;
|
private long availableSpaceAfterBulkDelete;
|
private long availableSpaceAfterDexopt;
|
|
// Packages.
|
private int importantPackageCount;
|
private int otherPackageCount;
|
|
// Number of dexopt commands. This may be different from the count of packages.
|
private int dexoptCommandCountTotal;
|
private int dexoptCommandCountExecuted;
|
|
// For spent time.
|
private long otaDexoptTimeStart;
|
|
|
public OtaDexoptService(Context context, PackageManagerService packageManagerService) {
|
this.mContext = context;
|
this.mPackageManagerService = packageManagerService;
|
metricsLogger = new MetricsLogger();
|
}
|
|
public static OtaDexoptService main(Context context,
|
PackageManagerService packageManagerService) {
|
OtaDexoptService ota = new OtaDexoptService(context, packageManagerService);
|
ServiceManager.addService("otadexopt", ota);
|
|
// Now it's time to check whether we need to move any A/B artifacts.
|
ota.moveAbArtifacts(packageManagerService.mInstaller);
|
|
return ota;
|
}
|
|
@Override
|
public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
|
String[] args, ShellCallback callback, ResultReceiver resultReceiver) {
|
(new OtaDexoptShellCommand(this)).exec(
|
this, in, out, err, args, callback, resultReceiver);
|
}
|
|
@Override
|
public synchronized void prepare() throws RemoteException {
|
if (mDexoptCommands != null) {
|
throw new IllegalStateException("already called prepare()");
|
}
|
final List<PackageParser.Package> important;
|
final List<PackageParser.Package> others;
|
synchronized (mPackageManagerService.mPackages) {
|
// Important: the packages we need to run with ab-ota compiler-reason.
|
important = PackageManagerServiceUtils.getPackagesForDexopt(
|
mPackageManagerService.mPackages.values(), mPackageManagerService,
|
DEBUG_DEXOPT);
|
// Others: we should optimize this with the (first-)boot compiler-reason.
|
others = new ArrayList<>(mPackageManagerService.mPackages.values());
|
others.removeAll(important);
|
|
// Pre-size the array list by over-allocating by a factor of 1.5.
|
mDexoptCommands = new ArrayList<>(3 * mPackageManagerService.mPackages.size() / 2);
|
}
|
|
for (PackageParser.Package p : important) {
|
mDexoptCommands.addAll(generatePackageDexopts(p, PackageManagerService.REASON_AB_OTA));
|
}
|
for (PackageParser.Package p : others) {
|
// We assume here that there are no core apps left.
|
if (p.coreApp) {
|
throw new IllegalStateException("Found a core app that's not important");
|
}
|
mDexoptCommands.addAll(
|
generatePackageDexopts(p, PackageManagerService.REASON_FIRST_BOOT));
|
}
|
completeSize = mDexoptCommands.size();
|
|
long spaceAvailable = getAvailableSpace();
|
if (spaceAvailable < BULK_DELETE_THRESHOLD) {
|
Log.i(TAG, "Low on space, deleting oat files in an attempt to free up space: "
|
+ PackageManagerServiceUtils.packagesToString(others));
|
for (PackageParser.Package pkg : others) {
|
mPackageManagerService.deleteOatArtifactsOfPackage(pkg.packageName);
|
}
|
}
|
long spaceAvailableNow = getAvailableSpace();
|
|
prepareMetricsLogging(important.size(), others.size(), spaceAvailable, spaceAvailableNow);
|
|
if (DEBUG_DEXOPT) {
|
try {
|
// Output some data about the packages.
|
PackageParser.Package lastUsed = Collections.max(important,
|
(pkg1, pkg2) -> Long.compare(
|
pkg1.getLatestForegroundPackageUseTimeInMills(),
|
pkg2.getLatestForegroundPackageUseTimeInMills()));
|
Log.d(TAG, "A/B OTA: lastUsed time = "
|
+ lastUsed.getLatestForegroundPackageUseTimeInMills());
|
Log.d(TAG, "A/B OTA: deprioritized packages:");
|
for (PackageParser.Package pkg : others) {
|
Log.d(TAG, " " + pkg.packageName + " - "
|
+ pkg.getLatestForegroundPackageUseTimeInMills());
|
}
|
} catch (Exception ignored) {
|
}
|
}
|
}
|
|
@Override
|
public synchronized void cleanup() throws RemoteException {
|
if (DEBUG_DEXOPT) {
|
Log.i(TAG, "Cleaning up OTA Dexopt state.");
|
}
|
mDexoptCommands = null;
|
availableSpaceAfterDexopt = getAvailableSpace();
|
|
performMetricsLogging();
|
}
|
|
@Override
|
public synchronized boolean isDone() throws RemoteException {
|
if (mDexoptCommands == null) {
|
throw new IllegalStateException("done() called before prepare()");
|
}
|
|
return mDexoptCommands.isEmpty();
|
}
|
|
@Override
|
public synchronized float getProgress() throws RemoteException {
|
// Approximate the progress by the amount of already completed commands.
|
if (completeSize == 0) {
|
return 1f;
|
}
|
int commandsLeft = mDexoptCommands.size();
|
return (completeSize - commandsLeft) / ((float)completeSize);
|
}
|
|
@Override
|
public synchronized String nextDexoptCommand() throws RemoteException {
|
if (mDexoptCommands == null) {
|
throw new IllegalStateException("dexoptNextPackage() called before prepare()");
|
}
|
|
if (mDexoptCommands.isEmpty()) {
|
return "(all done)";
|
}
|
|
String next = mDexoptCommands.remove(0);
|
|
if (getAvailableSpace() > 0) {
|
dexoptCommandCountExecuted++;
|
|
Log.d(TAG, "Next command: " + next);
|
return next;
|
} else {
|
if (DEBUG_DEXOPT) {
|
Log.w(TAG, "Not enough space for OTA dexopt, stopping with "
|
+ (mDexoptCommands.size() + 1) + " commands left.");
|
}
|
mDexoptCommands.clear();
|
return "(no free space)";
|
}
|
}
|
|
private long getMainLowSpaceThreshold() {
|
File dataDir = Environment.getDataDirectory();
|
@SuppressWarnings("deprecation")
|
long lowThreshold = StorageManager.from(mContext).getStorageLowBytes(dataDir);
|
if (lowThreshold == 0) {
|
throw new IllegalStateException("Invalid low memory threshold");
|
}
|
return lowThreshold;
|
}
|
|
/**
|
* Returns the difference of free space to the low-storage-space threshold. Positive values
|
* indicate free bytes.
|
*/
|
private long getAvailableSpace() {
|
// TODO: If apps are not installed in the internal /data partition, we should compare
|
// against that storage's free capacity.
|
long lowThreshold = getMainLowSpaceThreshold();
|
|
File dataDir = Environment.getDataDirectory();
|
long usableSpace = dataDir.getUsableSpace();
|
|
return usableSpace - lowThreshold;
|
}
|
|
/**
|
* Generate all dexopt commands for the given package.
|
*/
|
private synchronized List<String> generatePackageDexopts(PackageParser.Package pkg,
|
int compilationReason) {
|
// Intercept and collect dexopt requests
|
final List<String> commands = new ArrayList<String>();
|
final Installer collectingInstaller = new Installer(mContext, true) {
|
/**
|
* Encode the dexopt command into a string.
|
*
|
* Note: If you have to change the signature of this function, increase the version
|
* number, and update the counterpart in
|
* frameworks/native/cmds/installd/otapreopt.cpp.
|
*/
|
@Override
|
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 profileName,
|
@Nullable String dexMetadataPath, @Nullable String dexoptCompilationReason)
|
throws InstallerException {
|
final StringBuilder builder = new StringBuilder();
|
|
// The current version. For v10, see b/115993344.
|
builder.append("10 ");
|
|
builder.append("dexopt");
|
|
encodeParameter(builder, apkPath);
|
encodeParameter(builder, uid);
|
encodeParameter(builder, pkgName);
|
encodeParameter(builder, instructionSet);
|
encodeParameter(builder, dexoptNeeded);
|
encodeParameter(builder, outputPath);
|
encodeParameter(builder, dexFlags);
|
encodeParameter(builder, compilerFilter);
|
encodeParameter(builder, volumeUuid);
|
encodeParameter(builder, sharedLibraries);
|
encodeParameter(builder, seInfo);
|
encodeParameter(builder, downgrade);
|
encodeParameter(builder, targetSdkVersion);
|
encodeParameter(builder, profileName);
|
encodeParameter(builder, dexMetadataPath);
|
encodeParameter(builder, dexoptCompilationReason);
|
|
commands.add(builder.toString());
|
}
|
|
/**
|
* Encode a parameter as necessary for the commands string.
|
*/
|
private void encodeParameter(StringBuilder builder, Object arg) {
|
builder.append(' ');
|
|
if (arg == null) {
|
builder.append('!');
|
return;
|
}
|
|
String txt = String.valueOf(arg);
|
if (txt.indexOf('\0') != -1 || txt.indexOf(' ') != -1 || "!".equals(txt)) {
|
throw new IllegalArgumentException(
|
"Invalid argument while executing " + arg);
|
}
|
builder.append(txt);
|
}
|
};
|
|
// Use the package manager install and install lock here for the OTA dex optimizer.
|
PackageDexOptimizer optimizer = new OTADexoptPackageDexOptimizer(
|
collectingInstaller, mPackageManagerService.mInstallLock, mContext);
|
|
optimizer.performDexOpt(pkg,
|
null /* ISAs */,
|
null /* CompilerStats.PackageStats */,
|
mPackageManagerService.getDexManager().getPackageUseInfoOrDefault(pkg.packageName),
|
new DexoptOptions(pkg.packageName, compilationReason,
|
DexoptOptions.DEXOPT_BOOT_COMPLETE));
|
|
return commands;
|
}
|
|
@Override
|
public synchronized void dexoptNextPackage() throws RemoteException {
|
throw new UnsupportedOperationException();
|
}
|
|
private void moveAbArtifacts(Installer installer) {
|
if (mDexoptCommands != null) {
|
throw new IllegalStateException("Should not be ota-dexopting when trying to move.");
|
}
|
|
if (!mPackageManagerService.isDeviceUpgrading()) {
|
Slog.d(TAG, "No upgrade, skipping A/B artifacts check.");
|
return;
|
}
|
|
// Look into all packages.
|
Collection<PackageParser.Package> pkgs = mPackageManagerService.getPackages();
|
int packagePaths = 0;
|
int pathsSuccessful = 0;
|
for (PackageParser.Package pkg : pkgs) {
|
if (pkg == null) {
|
continue;
|
}
|
|
// Does the package have code? If not, there won't be any artifacts.
|
if (!PackageDexOptimizer.canOptimizePackage(pkg)) {
|
continue;
|
}
|
if (pkg.codePath == null) {
|
Slog.w(TAG, "Package " + pkg + " can be optimized but has null codePath");
|
continue;
|
}
|
|
// If the path is in /system, /vendor, /product or /product_services, ignore. It will
|
// have been ota-dexopted into /data/ota and moved into the dalvik-cache already.
|
if (pkg.codePath.startsWith("/system")
|
|| pkg.codePath.startsWith("/vendor")
|
|| pkg.codePath.startsWith("/product")
|
|| pkg.codePath.startsWith("/product_services")) {
|
continue;
|
}
|
|
final String[] instructionSets = getAppDexInstructionSets(pkg.applicationInfo);
|
final List<String> paths = pkg.getAllCodePathsExcludingResourceOnly();
|
final String[] dexCodeInstructionSets = getDexCodeInstructionSets(instructionSets);
|
for (String dexCodeInstructionSet : dexCodeInstructionSets) {
|
for (String path : paths) {
|
String oatDir = PackageDexOptimizer.getOatDir(new File(pkg.codePath)).
|
getAbsolutePath();
|
|
// TODO: Check first whether there is an artifact, to save the roundtrip time.
|
|
packagePaths++;
|
try {
|
installer.moveAb(path, dexCodeInstructionSet, oatDir);
|
pathsSuccessful++;
|
} catch (InstallerException e) {
|
}
|
}
|
}
|
}
|
Slog.i(TAG, "Moved " + pathsSuccessful + "/" + packagePaths);
|
}
|
|
/**
|
* Initialize logging fields.
|
*/
|
private void prepareMetricsLogging(int important, int others, long spaceBegin, long spaceBulk) {
|
availableSpaceBefore = spaceBegin;
|
availableSpaceAfterBulkDelete = spaceBulk;
|
availableSpaceAfterDexopt = 0;
|
|
importantPackageCount = important;
|
otherPackageCount = others;
|
|
dexoptCommandCountTotal = mDexoptCommands.size();
|
dexoptCommandCountExecuted = 0;
|
|
otaDexoptTimeStart = System.nanoTime();
|
}
|
|
private static int inMegabytes(long value) {
|
long in_mega_bytes = value / (1024 * 1024);
|
if (in_mega_bytes > Integer.MAX_VALUE) {
|
Log.w(TAG, "Recording " + in_mega_bytes + "MB of free space, overflowing range");
|
return Integer.MAX_VALUE;
|
}
|
return (int)in_mega_bytes;
|
}
|
|
private void performMetricsLogging() {
|
long finalTime = System.nanoTime();
|
|
metricsLogger.histogram("ota_dexopt_available_space_before_mb",
|
inMegabytes(availableSpaceBefore));
|
metricsLogger.histogram("ota_dexopt_available_space_after_bulk_delete_mb",
|
inMegabytes(availableSpaceAfterBulkDelete));
|
metricsLogger.histogram("ota_dexopt_available_space_after_dexopt_mb",
|
inMegabytes(availableSpaceAfterDexopt));
|
|
metricsLogger.histogram("ota_dexopt_num_important_packages", importantPackageCount);
|
metricsLogger.histogram("ota_dexopt_num_other_packages", otherPackageCount);
|
|
metricsLogger.histogram("ota_dexopt_num_commands", dexoptCommandCountTotal);
|
metricsLogger.histogram("ota_dexopt_num_commands_executed", dexoptCommandCountExecuted);
|
|
final int elapsedTimeSeconds =
|
(int) TimeUnit.NANOSECONDS.toSeconds(finalTime - otaDexoptTimeStart);
|
metricsLogger.histogram("ota_dexopt_time_s", elapsedTimeSeconds);
|
}
|
|
private static class OTADexoptPackageDexOptimizer extends
|
PackageDexOptimizer.ForcedUpdatePackageDexOptimizer {
|
public OTADexoptPackageDexOptimizer(Installer installer, Object installLock,
|
Context context) {
|
super(installer, installLock, context, "*otadexopt*");
|
}
|
}
|
}
|