1 /* 2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.art; 18 19 import static com.android.server.art.ArtManagerLocal.DexoptDoneCallback; 20 import static com.android.server.art.model.Config.Callback; 21 import static com.android.server.art.model.DexoptResult.DexContainerFileDexoptResult; 22 import static com.android.server.art.model.DexoptResult.PackageDexoptResult; 23 24 import android.annotation.NonNull; 25 import android.annotation.Nullable; 26 import android.apphibernation.AppHibernationManager; 27 import android.content.Context; 28 import android.os.Binder; 29 import android.os.Build; 30 import android.os.CancellationSignal; 31 import android.os.RemoteException; 32 33 import androidx.annotation.RequiresApi; 34 35 import com.android.internal.annotations.VisibleForTesting; 36 import com.android.server.art.model.ArtFlags; 37 import com.android.server.art.model.Config; 38 import com.android.server.art.model.DexoptParams; 39 import com.android.server.art.model.DexoptResult; 40 import com.android.server.art.model.OperationProgress; 41 import com.android.server.pm.PackageManagerLocal; 42 import com.android.server.pm.pkg.AndroidPackage; 43 import com.android.server.pm.pkg.PackageState; 44 import com.android.server.pm.pkg.SharedLibrary; 45 46 import java.util.ArrayList; 47 import java.util.HashSet; 48 import java.util.LinkedHashMap; 49 import java.util.LinkedList; 50 import java.util.List; 51 import java.util.Objects; 52 import java.util.Queue; 53 import java.util.Set; 54 import java.util.concurrent.CompletableFuture; 55 import java.util.concurrent.Executor; 56 import java.util.concurrent.atomic.AtomicInteger; 57 import java.util.function.Consumer; 58 import java.util.function.Function; 59 60 /** 61 * A helper class to handle dexopt. 62 * 63 * It talks to other components (e.g., PowerManager) and dispatches tasks to dexopters. 64 * 65 * @hide 66 */ 67 @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) 68 public class DexoptHelper { 69 @NonNull private final Injector mInjector; 70 DexoptHelper( @onNull Context context, @NonNull Config config, @NonNull Executor reporterExecutor)71 public DexoptHelper( 72 @NonNull Context context, @NonNull Config config, @NonNull Executor reporterExecutor) { 73 this(new Injector(context, config, reporterExecutor)); 74 } 75 76 @VisibleForTesting DexoptHelper(@onNull Injector injector)77 public DexoptHelper(@NonNull Injector injector) { 78 mInjector = injector; 79 } 80 81 /** 82 * DO NOT use this method directly. Use {@link ArtManagerLocal#dexoptPackage} or {@link 83 * ArtManagerLocal#dexoptPackages}. 84 */ 85 @NonNull dexopt(@onNull PackageManagerLocal.FilteredSnapshot snapshot, @NonNull List<String> packageNames, @NonNull DexoptParams params, @NonNull CancellationSignal cancellationSignal, @NonNull Executor dexoptExecutor)86 public DexoptResult dexopt(@NonNull PackageManagerLocal.FilteredSnapshot snapshot, 87 @NonNull List<String> packageNames, @NonNull DexoptParams params, 88 @NonNull CancellationSignal cancellationSignal, @NonNull Executor dexoptExecutor) { 89 return dexopt(snapshot, packageNames, params, cancellationSignal, dexoptExecutor, 90 null /* progressCallbackExecutor */, null /* progressCallback */); 91 } 92 93 /** 94 * DO NOT use this method directly. Use {@link ArtManagerLocal#dexoptPackage} or {@link 95 * ArtManagerLocal#dexoptPackages}. 96 */ 97 @NonNull dexopt(@onNull PackageManagerLocal.FilteredSnapshot snapshot, @NonNull List<String> packageNames, @NonNull DexoptParams params, @NonNull CancellationSignal cancellationSignal, @NonNull Executor dexoptExecutor, @Nullable Executor progressCallbackExecutor, @Nullable Consumer<OperationProgress> progressCallback)98 public DexoptResult dexopt(@NonNull PackageManagerLocal.FilteredSnapshot snapshot, 99 @NonNull List<String> packageNames, @NonNull DexoptParams params, 100 @NonNull CancellationSignal cancellationSignal, @NonNull Executor dexoptExecutor, 101 @Nullable Executor progressCallbackExecutor, 102 @Nullable Consumer<OperationProgress> progressCallback) { 103 return dexoptPackages( 104 getPackageStates(snapshot, packageNames, 105 (params.getFlags() & ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES) != 0), 106 params, cancellationSignal, dexoptExecutor, progressCallbackExecutor, 107 progressCallback); 108 } 109 110 /** 111 * DO NOT use this method directly. Use {@link ArtManagerLocal#dexoptPackage} or {@link 112 * ArtManagerLocal#dexoptPackages}. 113 */ 114 @NonNull dexoptPackages(@onNull List<PackageState> pkgStates, @NonNull DexoptParams params, @NonNull CancellationSignal cancellationSignal, @NonNull Executor dexoptExecutor, @Nullable Executor progressCallbackExecutor, @Nullable Consumer<OperationProgress> progressCallback)115 private DexoptResult dexoptPackages(@NonNull List<PackageState> pkgStates, 116 @NonNull DexoptParams params, @NonNull CancellationSignal cancellationSignal, 117 @NonNull Executor dexoptExecutor, @Nullable Executor progressCallbackExecutor, 118 @Nullable Consumer<OperationProgress> progressCallback) { 119 // TODO(jiakaiz): Find out whether this is still needed. 120 long identityToken = Binder.clearCallingIdentity(); 121 122 try { 123 List<CompletableFuture<PackageDexoptResult>> futures = new ArrayList<>(); 124 125 // Child threads will set their own listeners on the cancellation signal, so we must 126 // create a separate cancellation signal for each of them so that the listeners don't 127 // overwrite each other. 128 List<CancellationSignal> childCancellationSignals = 129 pkgStates.stream().map(pkgState -> new CancellationSignal()).toList(); 130 cancellationSignal.setOnCancelListener(() -> { 131 for (CancellationSignal childCancellationSignal : childCancellationSignals) { 132 childCancellationSignal.cancel(); 133 } 134 }); 135 136 for (int i = 0; i < pkgStates.size(); i++) { 137 PackageState pkgState = pkgStates.get(i); 138 CancellationSignal childCancellationSignal = childCancellationSignals.get(i); 139 futures.add(CompletableFuture.supplyAsync(() -> { 140 AndroidPackage pkg = Utils.getPackageOrThrow(pkgState); 141 if (canDexoptPackage(pkgState) 142 && (params.getFlags() & ArtFlags.FLAG_FOR_SINGLE_SPLIT) != 0) { 143 // Throws if the split is not found. 144 PrimaryDexUtils.getDexInfoBySplitName(pkg, params.getSplitName()); 145 } 146 try { 147 return dexoptPackage(pkgState, pkg, params, childCancellationSignal); 148 } catch (RuntimeException e) { 149 AsLog.wtf("Unexpected package-level exception during dexopt", e); 150 return PackageDexoptResult.create(pkgState.getPackageName(), 151 new ArrayList<>() /* dexContainerFileDexoptResults */, 152 DexoptResult.DEXOPT_FAILED); 153 } 154 }, dexoptExecutor)); 155 } 156 157 if (progressCallback != null) { 158 CompletableFuture.runAsync(() -> { 159 progressCallback.accept(OperationProgress.create( 160 0 /* current */, futures.size(), null /* packageDexoptResult */)); 161 }, progressCallbackExecutor); 162 AtomicInteger current = new AtomicInteger(0); 163 for (CompletableFuture<PackageDexoptResult> future : futures) { 164 future.thenAcceptAsync(result -> { 165 progressCallback.accept(OperationProgress.create( 166 current.incrementAndGet(), futures.size(), result)); 167 }, progressCallbackExecutor).exceptionally(t -> { 168 AsLog.e("Failed to update progress", t); 169 return null; 170 }); 171 } 172 } 173 174 List<PackageDexoptResult> results = futures.stream().map(Utils::getFuture).toList(); 175 176 var result = 177 DexoptResult.create(params.getCompilerFilter(), params.getReason(), results); 178 179 for (Callback<DexoptDoneCallback, Boolean> doneCallback : 180 mInjector.getConfig().getDexoptDoneCallbacks()) { 181 boolean onlyIncludeUpdates = doneCallback.extra(); 182 if (onlyIncludeUpdates) { 183 List<PackageDexoptResult> filteredResults = 184 results.stream() 185 .filter(PackageDexoptResult::hasUpdatedArtifacts) 186 .toList(); 187 if (!filteredResults.isEmpty()) { 188 var resultForCallback = DexoptResult.create( 189 params.getCompilerFilter(), params.getReason(), filteredResults); 190 CompletableFuture.runAsync(() -> { 191 doneCallback.get().onDexoptDone(resultForCallback); 192 }, doneCallback.executor()); 193 } 194 } else { 195 CompletableFuture.runAsync(() -> { 196 doneCallback.get().onDexoptDone(result); 197 }, doneCallback.executor()); 198 } 199 } 200 201 return result; 202 } finally { 203 Binder.restoreCallingIdentity(identityToken); 204 // Make sure nothing leaks even if the caller holds `cancellationSignal` forever. 205 cancellationSignal.setOnCancelListener(null); 206 } 207 } 208 209 /** 210 * DO NOT use this method directly. Use {@link ArtManagerLocal#dexoptPackage} or {@link 211 * ArtManagerLocal#dexoptPackages}. 212 */ 213 @NonNull dexoptPackage(@onNull PackageState pkgState, @NonNull AndroidPackage pkg, @NonNull DexoptParams params, @NonNull CancellationSignal cancellationSignal)214 private PackageDexoptResult dexoptPackage(@NonNull PackageState pkgState, 215 @NonNull AndroidPackage pkg, @NonNull DexoptParams params, 216 @NonNull CancellationSignal cancellationSignal) { 217 List<DexContainerFileDexoptResult> results = new ArrayList<>(); 218 Function<Integer, PackageDexoptResult> createResult = (packageLevelStatus) 219 -> PackageDexoptResult.create( 220 pkgState.getPackageName(), results, packageLevelStatus); 221 222 if (!canDexoptPackage(pkgState)) { 223 return createResult.apply(null /* packageLevelStatus */); 224 } 225 226 try (var tracing = new Utils.Tracing("dexopt")) { 227 if ((params.getFlags() & ArtFlags.FLAG_FOR_PRIMARY_DEX) != 0) { 228 if (cancellationSignal.isCanceled()) { 229 return createResult.apply(DexoptResult.DEXOPT_CANCELLED); 230 } 231 232 results.addAll( 233 mInjector.getPrimaryDexopter(pkgState, pkg, params, cancellationSignal) 234 .dexopt()); 235 } 236 237 if (((params.getFlags() & ArtFlags.FLAG_FOR_SECONDARY_DEX) != 0) 238 && pkgState.getAppId() > 0) { 239 if (cancellationSignal.isCanceled()) { 240 return createResult.apply(DexoptResult.DEXOPT_CANCELLED); 241 } 242 243 results.addAll( 244 mInjector.getSecondaryDexopter(pkgState, pkg, params, cancellationSignal) 245 .dexopt()); 246 } 247 } catch (RemoteException e) { 248 Utils.logArtdException(e); 249 return createResult.apply(DexoptResult.DEXOPT_FAILED); 250 } 251 252 return createResult.apply(null /* packageLevelStatus */); 253 } 254 canDexoptPackage(@onNull PackageState pkgState)255 private boolean canDexoptPackage(@NonNull PackageState pkgState) { 256 // getAppHibernationManager may return null here during boot time compilation, which will 257 // make this function return true incorrectly for packages that shouldn't be dexopted due to 258 // hibernation. Further discussion in comments in ArtManagerLocal.getDefaultPackages. 259 return Utils.canDexoptPackage(pkgState, mInjector.getAppHibernationManager()); 260 } 261 262 @NonNull getPackageStates( @onNull PackageManagerLocal.FilteredSnapshot snapshot, @NonNull List<String> packageNames, boolean includeDependencies)263 private List<PackageState> getPackageStates( 264 @NonNull PackageManagerLocal.FilteredSnapshot snapshot, 265 @NonNull List<String> packageNames, boolean includeDependencies) { 266 var pkgStates = new LinkedHashMap<String, PackageState>(); 267 Set<String> visitedLibraries = new HashSet<>(); 268 Queue<SharedLibrary> queue = new LinkedList<>(); 269 270 Consumer<SharedLibrary> maybeEnqueue = library -> { 271 // The package name is not null if the library is an APK. 272 // TODO(jiakaiz): Support JAR libraries. 273 if (library.getPackageName() != null && !library.isNative() 274 && !visitedLibraries.contains(library.getName())) { 275 visitedLibraries.add(library.getName()); 276 queue.add(library); 277 } 278 }; 279 280 for (String packageName : packageNames) { 281 PackageState pkgState = Utils.getPackageStateOrThrow(snapshot, packageName); 282 Utils.getPackageOrThrow(pkgState); 283 pkgStates.put(packageName, pkgState); 284 if (includeDependencies && canDexoptPackage(pkgState)) { 285 for (SharedLibrary library : pkgState.getSharedLibraryDependencies()) { 286 maybeEnqueue.accept(library); 287 } 288 } 289 } 290 291 SharedLibrary library; 292 while ((library = queue.poll()) != null) { 293 String packageName = library.getPackageName(); 294 PackageState pkgState = Utils.getPackageStateOrThrow(snapshot, packageName); 295 if (canDexoptPackage(pkgState)) { 296 pkgStates.put(packageName, pkgState); 297 298 // Note that `library.getDependencies()` is different from 299 // `pkgState.getUsesLibraries()`. Different libraries can belong to the same 300 // package. `pkgState.getUsesLibraries()` returns a union of dependencies of 301 // libraries that belong to the same package, which is not what we want here. 302 // Therefore, this loop cannot be unified with the one above. 303 for (SharedLibrary dep : library.getDependencies()) { 304 maybeEnqueue.accept(dep); 305 } 306 } 307 } 308 309 // `LinkedHashMap` guarantees deterministic order. 310 return new ArrayList<>(pkgStates.values()); 311 } 312 313 /** 314 * Injector pattern for testing purpose. 315 * 316 * @hide 317 */ 318 @VisibleForTesting 319 public static class Injector { 320 @NonNull private final Context mContext; 321 @NonNull private final Config mConfig; 322 @NonNull private final Executor mReporterExecutor; 323 Injector(@onNull Context context, @NonNull Config config, @NonNull Executor reporterExecutor)324 Injector(@NonNull Context context, @NonNull Config config, 325 @NonNull Executor reporterExecutor) { 326 mContext = context; 327 mConfig = config; 328 mReporterExecutor = reporterExecutor; 329 330 // Call the getters for the dependencies that aren't optional, to ensure correct 331 // initialization order. 332 getAppHibernationManager(); 333 } 334 335 @NonNull getPrimaryDexopter(@onNull PackageState pkgState, @NonNull AndroidPackage pkg, @NonNull DexoptParams params, @NonNull CancellationSignal cancellationSignal)336 PrimaryDexopter getPrimaryDexopter(@NonNull PackageState pkgState, 337 @NonNull AndroidPackage pkg, @NonNull DexoptParams params, 338 @NonNull CancellationSignal cancellationSignal) { 339 return new PrimaryDexopter(mContext, mConfig, mReporterExecutor, pkgState, pkg, params, 340 cancellationSignal); 341 } 342 343 @NonNull getSecondaryDexopter(@onNull PackageState pkgState, @NonNull AndroidPackage pkg, @NonNull DexoptParams params, @NonNull CancellationSignal cancellationSignal)344 SecondaryDexopter getSecondaryDexopter(@NonNull PackageState pkgState, 345 @NonNull AndroidPackage pkg, @NonNull DexoptParams params, 346 @NonNull CancellationSignal cancellationSignal) { 347 return new SecondaryDexopter(mContext, mConfig, mReporterExecutor, pkgState, pkg, 348 params, cancellationSignal); 349 } 350 351 @NonNull getAppHibernationManager()352 public AppHibernationManager getAppHibernationManager() { 353 return Objects.requireNonNull(mContext.getSystemService(AppHibernationManager.class)); 354 } 355 356 @NonNull getConfig()357 public Config getConfig() { 358 return mConfig; 359 } 360 } 361 } 362