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