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