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