• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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