• 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.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