• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 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.pm;
18 
19 import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
20 
21 import static com.android.server.pm.ApexManager.ActiveApexInfo;
22 import static com.android.server.pm.InstructionSets.getAppDexInstructionSets;
23 import static com.android.server.pm.PackageManagerService.DEBUG_DEXOPT;
24 import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
25 import static com.android.server.pm.PackageManagerService.REASON_BOOT_AFTER_OTA;
26 import static com.android.server.pm.PackageManagerService.REASON_CMDLINE;
27 import static com.android.server.pm.PackageManagerService.REASON_FIRST_BOOT;
28 import static com.android.server.pm.PackageManagerService.STUB_SUFFIX;
29 import static com.android.server.pm.PackageManagerService.TAG;
30 import static com.android.server.pm.PackageManagerServiceCompilerMapping.getCompilerFilterForReason;
31 import static com.android.server.pm.PackageManagerServiceCompilerMapping.getDefaultCompilerFilter;
32 import static com.android.server.pm.PackageManagerServiceUtils.REMOVE_IF_NULL_PKG;
33 
34 import android.Manifest;
35 import android.annotation.NonNull;
36 import android.annotation.RequiresPermission;
37 import android.app.ActivityManager;
38 import android.app.AppGlobals;
39 import android.content.Intent;
40 import android.content.pm.ResolveInfo;
41 import android.content.pm.SharedLibraryInfo;
42 import android.content.pm.dex.ArtManager;
43 import android.os.Binder;
44 import android.os.RemoteException;
45 import android.os.SystemClock;
46 import android.os.SystemProperties;
47 import android.os.Trace;
48 import android.os.UserHandle;
49 import android.provider.DeviceConfig;
50 import android.text.TextUtils;
51 import android.util.ArraySet;
52 import android.util.Log;
53 import android.util.Slog;
54 
55 import com.android.internal.R;
56 import com.android.internal.annotations.GuardedBy;
57 import com.android.internal.logging.MetricsLogger;
58 import com.android.server.pm.dex.DexManager;
59 import com.android.server.pm.dex.DexoptOptions;
60 import com.android.server.pm.parsing.pkg.AndroidPackage;
61 import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
62 import com.android.server.pm.pkg.PackageStateInternal;
63 
64 import dalvik.system.DexFile;
65 
66 import java.io.File;
67 import java.nio.file.Path;
68 import java.nio.file.Paths;
69 import java.util.ArrayList;
70 import java.util.Collection;
71 import java.util.Collections;
72 import java.util.Comparator;
73 import java.util.HashSet;
74 import java.util.LinkedList;
75 import java.util.List;
76 import java.util.Set;
77 import java.util.concurrent.TimeUnit;
78 import java.util.function.Predicate;
79 
80 final class DexOptHelper {
81     private static final long SEVEN_DAYS_IN_MILLISECONDS = 7 * 24 * 60 * 60 * 1000;
82 
83     private final PackageManagerService mPm;
84 
isDexOptDialogShown()85     public boolean isDexOptDialogShown() {
86         synchronized (mLock) {
87             return mDexOptDialogShown;
88         }
89     }
90 
91     // TODO: Is this lock really necessary?
92     private final Object mLock = new Object();
93 
94     @GuardedBy("mLock")
95     private boolean mDexOptDialogShown;
96 
DexOptHelper(PackageManagerService pm)97     DexOptHelper(PackageManagerService pm) {
98         mPm = pm;
99     }
100 
101     /*
102      * Return the prebuilt profile path given a package base code path.
103      */
getPrebuildProfilePath(AndroidPackage pkg)104     private static String getPrebuildProfilePath(AndroidPackage pkg) {
105         return pkg.getBaseApkPath() + ".prof";
106     }
107 
108     /**
109      * Performs dexopt on the set of packages in {@code packages} and returns an int array
110      * containing statistics about the invocation. The array consists of three elements,
111      * which are (in order) {@code numberOfPackagesOptimized}, {@code numberOfPackagesSkipped}
112      * and {@code numberOfPackagesFailed}.
113      */
performDexOptUpgrade(List<AndroidPackage> pkgs, boolean showDialog, final int compilationReason, boolean bootComplete)114     public int[] performDexOptUpgrade(List<AndroidPackage> pkgs, boolean showDialog,
115             final int compilationReason, boolean bootComplete) {
116 
117         int numberOfPackagesVisited = 0;
118         int numberOfPackagesOptimized = 0;
119         int numberOfPackagesSkipped = 0;
120         int numberOfPackagesFailed = 0;
121         final int numberOfPackagesToDexopt = pkgs.size();
122 
123         for (AndroidPackage pkg : pkgs) {
124             numberOfPackagesVisited++;
125 
126             boolean useProfileForDexopt = false;
127 
128             if ((mPm.isFirstBoot() || mPm.isDeviceUpgrading()) && pkg.isSystem()) {
129                 // Copy over initial preopt profiles since we won't get any JIT samples for methods
130                 // that are already compiled.
131                 File profileFile = new File(getPrebuildProfilePath(pkg));
132                 // Copy profile if it exists.
133                 if (profileFile.exists()) {
134                     try {
135                         // We could also do this lazily before calling dexopt in
136                         // PackageDexOptimizer to prevent this happening on first boot. The issue
137                         // is that we don't have a good way to say "do this only once".
138                         if (!mPm.mInstaller.copySystemProfile(profileFile.getAbsolutePath(),
139                                 pkg.getUid(), pkg.getPackageName(),
140                                 ArtManager.getProfileName(null))) {
141                             Log.e(TAG, "Installer failed to copy system profile!");
142                         } else {
143                             // Disabled as this causes speed-profile compilation during first boot
144                             // even if things are already compiled.
145                             // useProfileForDexopt = true;
146                         }
147                     } catch (Exception e) {
148                         Log.e(TAG, "Failed to copy profile " + profileFile.getAbsolutePath() + " ",
149                                 e);
150                     }
151                 } else {
152                     PackageSetting disabledPs = mPm.mSettings.getDisabledSystemPkgLPr(
153                             pkg.getPackageName());
154                     // Handle compressed APKs in this path. Only do this for stubs with profiles to
155                     // minimize the number off apps being speed-profile compiled during first boot.
156                     // The other paths will not change the filter.
157                     if (disabledPs != null && disabledPs.getPkg().isStub()) {
158                         // The package is the stub one, remove the stub suffix to get the normal
159                         // package and APK names.
160                         String systemProfilePath = getPrebuildProfilePath(disabledPs.getPkg())
161                                 .replace(STUB_SUFFIX, "");
162                         profileFile = new File(systemProfilePath);
163                         // If we have a profile for a compressed APK, copy it to the reference
164                         // location.
165                         // Note that copying the profile here will cause it to override the
166                         // reference profile every OTA even though the existing reference profile
167                         // may have more data. We can't copy during decompression since the
168                         // directories are not set up at that point.
169                         if (profileFile.exists()) {
170                             try {
171                                 // We could also do this lazily before calling dexopt in
172                                 // PackageDexOptimizer to prevent this happening on first boot. The
173                                 // issue is that we don't have a good way to say "do this only
174                                 // once".
175                                 if (!mPm.mInstaller.copySystemProfile(profileFile.getAbsolutePath(),
176                                         pkg.getUid(), pkg.getPackageName(),
177                                         ArtManager.getProfileName(null))) {
178                                     Log.e(TAG, "Failed to copy system profile for stub package!");
179                                 } else {
180                                     useProfileForDexopt = true;
181                                 }
182                             } catch (Exception e) {
183                                 Log.e(TAG, "Failed to copy profile "
184                                         + profileFile.getAbsolutePath() + " ", e);
185                             }
186                         }
187                     }
188                 }
189             }
190 
191             if (!mPm.mPackageDexOptimizer.canOptimizePackage(pkg)) {
192                 if (DEBUG_DEXOPT) {
193                     Log.i(TAG, "Skipping update of non-optimizable app " + pkg.getPackageName());
194                 }
195                 numberOfPackagesSkipped++;
196                 continue;
197             }
198 
199             if (DEBUG_DEXOPT) {
200                 Log.i(TAG, "Updating app " + numberOfPackagesVisited + " of "
201                         + numberOfPackagesToDexopt + ": " + pkg.getPackageName());
202             }
203 
204             if (showDialog) {
205                 try {
206                     ActivityManager.getService().showBootMessage(
207                             mPm.mContext.getResources().getString(R.string.android_upgrading_apk,
208                                     numberOfPackagesVisited, numberOfPackagesToDexopt), true);
209                 } catch (RemoteException e) {
210                 }
211                 synchronized (mLock) {
212                     mDexOptDialogShown = true;
213                 }
214             }
215 
216             int pkgCompilationReason = compilationReason;
217             if (useProfileForDexopt) {
218                 // Use background dexopt mode to try and use the profile. Note that this does not
219                 // guarantee usage of the profile.
220                 pkgCompilationReason = PackageManagerService.REASON_BACKGROUND_DEXOPT;
221             }
222 
223             if (SystemProperties.getBoolean(mPm.PRECOMPILE_LAYOUTS, false)) {
224                 mPm.mArtManagerService.compileLayouts(pkg);
225             }
226 
227             // checkProfiles is false to avoid merging profiles during boot which
228             // might interfere with background compilation (b/28612421).
229             // Unfortunately this will also means that "pm.dexopt.boot=speed-profile" will
230             // behave differently than "pm.dexopt.bg-dexopt=speed-profile" but that's a
231             // trade-off worth doing to save boot time work.
232             int dexoptFlags = bootComplete ? DexoptOptions.DEXOPT_BOOT_COMPLETE : 0;
233             if (compilationReason == REASON_FIRST_BOOT) {
234                 // TODO: This doesn't cover the upgrade case, we should check for this too.
235                 dexoptFlags |= DexoptOptions.DEXOPT_INSTALL_WITH_DEX_METADATA_FILE;
236             }
237             int primaryDexOptStatus = performDexOptTraced(new DexoptOptions(
238                     pkg.getPackageName(),
239                     pkgCompilationReason,
240                     dexoptFlags));
241 
242             switch (primaryDexOptStatus) {
243                 case PackageDexOptimizer.DEX_OPT_PERFORMED:
244                     numberOfPackagesOptimized++;
245                     break;
246                 case PackageDexOptimizer.DEX_OPT_SKIPPED:
247                     numberOfPackagesSkipped++;
248                     break;
249                 case PackageDexOptimizer.DEX_OPT_CANCELLED:
250                     // ignore this case
251                     break;
252                 case PackageDexOptimizer.DEX_OPT_FAILED:
253                     numberOfPackagesFailed++;
254                     break;
255                 default:
256                     Log.e(TAG, "Unexpected dexopt return code " + primaryDexOptStatus);
257                     break;
258             }
259         }
260 
261         return new int[]{numberOfPackagesOptimized, numberOfPackagesSkipped,
262                 numberOfPackagesFailed};
263     }
264 
265     /**
266      * Checks if system UI package (typically "com.android.systemui") needs to be re-compiled, and
267      * compiles it if needed.
268      */
checkAndDexOptSystemUi()269     private void checkAndDexOptSystemUi() {
270         Computer snapshot = mPm.snapshotComputer();
271         String sysUiPackageName =
272                 mPm.mContext.getString(com.android.internal.R.string.config_systemUi);
273         AndroidPackage pkg = snapshot.getPackage(sysUiPackageName);
274         if (pkg == null) {
275             Log.w(TAG, "System UI package " + sysUiPackageName + " is not found for dexopting");
276             return;
277         }
278 
279         // It could also be after mainline update, but we're not introducing a new reason just for
280         // this special case.
281         int reason = REASON_BOOT_AFTER_OTA;
282 
283         String defaultCompilerFilter = getCompilerFilterForReason(reason);
284         String targetCompilerFilter =
285                 SystemProperties.get("dalvik.vm.systemuicompilerfilter", defaultCompilerFilter);
286         String compilerFilter;
287 
288         if (DexFile.isProfileGuidedCompilerFilter(targetCompilerFilter)) {
289             compilerFilter = defaultCompilerFilter;
290             File profileFile = new File(getPrebuildProfilePath(pkg));
291 
292             // Copy the profile to the reference profile path if it exists. Installd can only use a
293             // profile at the reference profile path for dexopt.
294             if (profileFile.exists()) {
295                 try {
296                     synchronized (mPm.mInstallLock) {
297                         if (mPm.mInstaller.copySystemProfile(profileFile.getAbsolutePath(),
298                                     pkg.getUid(), pkg.getPackageName(),
299                                     ArtManager.getProfileName(null))) {
300                             compilerFilter = targetCompilerFilter;
301                         } else {
302                             Log.e(TAG, "Failed to copy profile " + profileFile.getAbsolutePath());
303                         }
304                     }
305                 } catch (Exception e) {
306                     Log.e(TAG, "Failed to copy profile " + profileFile.getAbsolutePath(), e);
307                 }
308             }
309         } else {
310             compilerFilter = targetCompilerFilter;
311         }
312 
313         performDexOptTraced(new DexoptOptions(pkg.getPackageName(), REASON_BOOT_AFTER_OTA,
314                 compilerFilter, null /* splitName */, 0 /* dexoptFlags */));
315     }
316 
317     @RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG)
performPackageDexOptUpgradeIfNeeded()318     public void performPackageDexOptUpgradeIfNeeded() {
319         PackageManagerServiceUtils.enforceSystemOrRoot(
320                 "Only the system can request package update");
321 
322         // The default is "true".
323         if (!"false".equals(DeviceConfig.getProperty("runtime", "dexopt_system_ui_on_boot"))) {
324             // System UI is important to user experience, so we check it after a mainline update or
325             // an OTA. It may need to be re-compiled in these cases.
326             if (hasBcpApexesChanged() || mPm.isDeviceUpgrading()) {
327                 checkAndDexOptSystemUi();
328             }
329         }
330 
331         // We need to re-extract after an OTA.
332         boolean causeUpgrade = mPm.isDeviceUpgrading();
333 
334         // First boot or factory reset.
335         // Note: we also handle devices that are upgrading to N right now as if it is their
336         //       first boot, as they do not have profile data.
337         boolean causeFirstBoot = mPm.isFirstBoot() || mPm.isPreNUpgrade();
338 
339         if (!causeUpgrade && !causeFirstBoot) {
340             return;
341         }
342 
343         final Computer snapshot = mPm.snapshotComputer();
344         List<PackageStateInternal> pkgSettings =
345                 getPackagesForDexopt(snapshot.getPackageStates().values(), mPm);
346 
347         List<AndroidPackage> pkgs = new ArrayList<>(pkgSettings.size());
348         for (int index = 0; index < pkgSettings.size(); index++) {
349             pkgs.add(pkgSettings.get(index).getPkg());
350         }
351 
352         final long startTime = System.nanoTime();
353         final int[] stats = performDexOptUpgrade(pkgs, mPm.isPreNUpgrade() /* showDialog */,
354                 causeFirstBoot ? REASON_FIRST_BOOT : REASON_BOOT_AFTER_OTA,
355                 false /* bootComplete */);
356 
357         final int elapsedTimeSeconds =
358                 (int) TimeUnit.NANOSECONDS.toSeconds(System.nanoTime() - startTime);
359 
360         final Computer newSnapshot = mPm.snapshotComputer();
361 
362         MetricsLogger.histogram(mPm.mContext, "opt_dialog_num_dexopted", stats[0]);
363         MetricsLogger.histogram(mPm.mContext, "opt_dialog_num_skipped", stats[1]);
364         MetricsLogger.histogram(mPm.mContext, "opt_dialog_num_failed", stats[2]);
365         MetricsLogger.histogram(mPm.mContext, "opt_dialog_num_total",
366                 getOptimizablePackages(newSnapshot).size());
367         MetricsLogger.histogram(mPm.mContext, "opt_dialog_time_s", elapsedTimeSeconds);
368     }
369 
getOptimizablePackages(@onNull Computer snapshot)370     public List<String> getOptimizablePackages(@NonNull Computer snapshot) {
371         ArrayList<String> pkgs = new ArrayList<>();
372         mPm.forEachPackageState(snapshot, packageState -> {
373             final AndroidPackage pkg = packageState.getPkg();
374             if (pkg != null && mPm.mPackageDexOptimizer.canOptimizePackage(pkg)) {
375                 pkgs.add(packageState.getPackageName());
376             }
377         });
378         return pkgs;
379     }
380 
performDexOpt(DexoptOptions options)381     /*package*/ boolean performDexOpt(DexoptOptions options) {
382         final Computer snapshot = mPm.snapshotComputer();
383         if (snapshot.getInstantAppPackageName(Binder.getCallingUid()) != null) {
384             return false;
385         } else if (snapshot.isInstantApp(options.getPackageName(), UserHandle.getCallingUserId())) {
386             return false;
387         }
388 
389         if (options.isDexoptOnlySecondaryDex()) {
390             return mPm.getDexManager().dexoptSecondaryDex(options);
391         } else {
392             int dexoptStatus = performDexOptWithStatus(options);
393             return dexoptStatus != PackageDexOptimizer.DEX_OPT_FAILED;
394         }
395     }
396 
397     /**
398      * Perform dexopt on the given package and return one of following result:
399      * {@link PackageDexOptimizer#DEX_OPT_SKIPPED}
400      * {@link PackageDexOptimizer#DEX_OPT_PERFORMED}
401      * {@link PackageDexOptimizer#DEX_OPT_CANCELLED}
402      * {@link PackageDexOptimizer#DEX_OPT_FAILED}
403      */
404     @PackageDexOptimizer.DexOptResult
performDexOptWithStatus(DexoptOptions options)405     /* package */ int performDexOptWithStatus(DexoptOptions options) {
406         return performDexOptTraced(options);
407     }
408 
performDexOptTraced(DexoptOptions options)409     private int performDexOptTraced(DexoptOptions options) {
410         Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "dexopt");
411         try {
412             return performDexOptInternal(options);
413         } finally {
414             Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
415         }
416     }
417 
418     // Run dexopt on a given package. Returns true if dexopt did not fail, i.e.
419     // if the package can now be considered up to date for the given filter.
performDexOptInternal(DexoptOptions options)420     private int performDexOptInternal(DexoptOptions options) {
421         AndroidPackage p;
422         PackageSetting pkgSetting;
423         synchronized (mPm.mLock) {
424             p = mPm.mPackages.get(options.getPackageName());
425             pkgSetting = mPm.mSettings.getPackageLPr(options.getPackageName());
426             if (p == null || pkgSetting == null) {
427                 // Package could not be found. Report failure.
428                 return PackageDexOptimizer.DEX_OPT_FAILED;
429             }
430             mPm.getPackageUsage().maybeWriteAsync(mPm.mSettings.getPackagesLocked());
431             mPm.mCompilerStats.maybeWriteAsync();
432         }
433         final long callingId = Binder.clearCallingIdentity();
434         try {
435             return performDexOptInternalWithDependenciesLI(p, pkgSetting, options);
436         } finally {
437             Binder.restoreCallingIdentity(callingId);
438         }
439     }
440 
performDexOptInternalWithDependenciesLI(AndroidPackage p, @NonNull PackageStateInternal pkgSetting, DexoptOptions options)441     private int performDexOptInternalWithDependenciesLI(AndroidPackage p,
442             @NonNull PackageStateInternal pkgSetting, DexoptOptions options) {
443         // System server gets a special path.
444         if (PLATFORM_PACKAGE_NAME.equals(p.getPackageName())) {
445             return mPm.getDexManager().dexoptSystemServer(options);
446         }
447 
448         // Select the dex optimizer based on the force parameter.
449         // Note: The force option is rarely used (cmdline input for testing, mostly), so it's OK to
450         //       allocate an object here.
451         PackageDexOptimizer pdo = options.isForce()
452                 ? new PackageDexOptimizer.ForcedUpdatePackageDexOptimizer(mPm.mPackageDexOptimizer)
453                 : mPm.mPackageDexOptimizer;
454 
455         // Dexopt all dependencies first. Note: we ignore the return value and march on
456         // on errors.
457         // Note that we are going to call performDexOpt on those libraries as many times as
458         // they are referenced in packages. When we do a batch of performDexOpt (for example
459         // at boot, or background job), the passed 'targetCompilerFilter' stays the same,
460         // and the first package that uses the library will dexopt it. The
461         // others will see that the compiled code for the library is up to date.
462         Collection<SharedLibraryInfo> deps = SharedLibraryUtils.findSharedLibraries(pkgSetting);
463         final String[] instructionSets = getAppDexInstructionSets(
464                 AndroidPackageUtils.getPrimaryCpuAbi(p, pkgSetting),
465                 AndroidPackageUtils.getSecondaryCpuAbi(p, pkgSetting));
466         if (!deps.isEmpty()) {
467             DexoptOptions libraryOptions = new DexoptOptions(options.getPackageName(),
468                     options.getCompilationReason(), options.getCompilerFilter(),
469                     options.getSplitName(),
470                     options.getFlags() | DexoptOptions.DEXOPT_AS_SHARED_LIBRARY);
471             for (SharedLibraryInfo info : deps) {
472                 AndroidPackage depPackage = null;
473                 PackageSetting depPackageSetting = null;
474                 synchronized (mPm.mLock) {
475                     depPackage = mPm.mPackages.get(info.getPackageName());
476                     depPackageSetting = mPm.mSettings.getPackageLPr(info.getPackageName());
477                 }
478                 if (depPackage != null && depPackageSetting != null) {
479                     // TODO: Analyze and investigate if we (should) profile libraries.
480                     pdo.performDexOpt(depPackage, depPackageSetting, instructionSets,
481                             mPm.getOrCreateCompilerPackageStats(depPackage),
482                             mPm.getDexManager().getPackageUseInfoOrDefault(
483                                     depPackage.getPackageName()), libraryOptions);
484                 } else {
485                     // TODO(ngeoffray): Support dexopting system shared libraries.
486                 }
487             }
488         }
489 
490         return pdo.performDexOpt(p, pkgSetting, instructionSets,
491                 mPm.getOrCreateCompilerPackageStats(p),
492                 mPm.getDexManager().getPackageUseInfoOrDefault(p.getPackageName()), options);
493     }
494 
forceDexOpt(@onNull Computer snapshot, String packageName)495     public void forceDexOpt(@NonNull Computer snapshot, String packageName) {
496         PackageManagerServiceUtils.enforceSystemOrRoot("forceDexOpt");
497 
498         final PackageStateInternal packageState = snapshot.getPackageStateInternal(packageName);
499         final AndroidPackage pkg = packageState == null ? null : packageState.getPkg();
500         if (packageState == null || pkg == null) {
501             throw new IllegalArgumentException("Unknown package: " + packageName);
502         }
503 
504         Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "dexopt");
505 
506         // Whoever is calling forceDexOpt wants a compiled package.
507         // Don't use profiles since that may cause compilation to be skipped.
508         final int res = performDexOptInternalWithDependenciesLI(pkg, packageState,
509                 new DexoptOptions(packageName, REASON_CMDLINE,
510                         getDefaultCompilerFilter(), null /* splitName */,
511                         DexoptOptions.DEXOPT_FORCE | DexoptOptions.DEXOPT_BOOT_COMPLETE));
512 
513         Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
514         if (res != PackageDexOptimizer.DEX_OPT_PERFORMED) {
515             throw new IllegalStateException("Failed to dexopt: " + res);
516         }
517     }
518 
performDexOptMode(@onNull Computer snapshot, String packageName, boolean checkProfiles, String targetCompilerFilter, boolean force, boolean bootComplete, String splitName)519     public boolean performDexOptMode(@NonNull Computer snapshot, String packageName,
520             boolean checkProfiles, String targetCompilerFilter, boolean force,
521             boolean bootComplete, String splitName) {
522         if (!PackageManagerServiceUtils.isSystemOrRootOrShell()
523                 && !isCallerInstallerForPackage(snapshot, packageName)) {
524             throw new SecurityException("performDexOptMode");
525         }
526 
527         int flags = (checkProfiles ? DexoptOptions.DEXOPT_CHECK_FOR_PROFILES_UPDATES : 0)
528                 | (force ? DexoptOptions.DEXOPT_FORCE : 0)
529                 | (bootComplete ? DexoptOptions.DEXOPT_BOOT_COMPLETE : 0);
530         return performDexOpt(new DexoptOptions(packageName, REASON_CMDLINE,
531                 targetCompilerFilter, splitName, flags));
532     }
533 
isCallerInstallerForPackage(@onNull Computer snapshot, String packageName)534     private boolean isCallerInstallerForPackage(@NonNull Computer snapshot, String packageName) {
535         final PackageStateInternal packageState = snapshot.getPackageStateInternal(packageName);
536         if (packageState == null) {
537             return false;
538         }
539         final InstallSource installSource = packageState.getInstallSource();
540 
541         final PackageStateInternal installerPackageState =
542                 snapshot.getPackageStateInternal(installSource.installerPackageName);
543         if (installerPackageState == null) {
544             return false;
545         }
546         final AndroidPackage installerPkg = installerPackageState.getPkg();
547         return installerPkg.getUid() == Binder.getCallingUid();
548     }
549 
performDexOptSecondary(String packageName, String compilerFilter, boolean force)550     public boolean performDexOptSecondary(String packageName, String compilerFilter,
551             boolean force) {
552         int flags = DexoptOptions.DEXOPT_ONLY_SECONDARY_DEX
553                 | DexoptOptions.DEXOPT_CHECK_FOR_PROFILES_UPDATES
554                 | DexoptOptions.DEXOPT_BOOT_COMPLETE
555                 | (force ? DexoptOptions.DEXOPT_FORCE : 0);
556         return performDexOpt(new DexoptOptions(packageName, REASON_CMDLINE,
557                 compilerFilter, null /* splitName */, flags));
558     }
559 
560     // Sort apps by importance for dexopt ordering. Important apps are given
561     // more priority in case the device runs out of space.
getPackagesForDexopt( Collection<? extends PackageStateInternal> packages, PackageManagerService packageManagerService)562     public static List<PackageStateInternal> getPackagesForDexopt(
563             Collection<? extends PackageStateInternal> packages,
564             PackageManagerService packageManagerService) {
565         return getPackagesForDexopt(packages, packageManagerService, DEBUG_DEXOPT);
566     }
567 
getPackagesForDexopt( Collection<? extends PackageStateInternal> pkgSettings, PackageManagerService packageManagerService, boolean debug)568     public static List<PackageStateInternal> getPackagesForDexopt(
569             Collection<? extends PackageStateInternal> pkgSettings,
570             PackageManagerService packageManagerService,
571             boolean debug) {
572         List<PackageStateInternal> result = new LinkedList<>();
573         ArrayList<PackageStateInternal> remainingPkgSettings = new ArrayList<>(pkgSettings);
574 
575         // First, remove all settings without available packages
576         remainingPkgSettings.removeIf(REMOVE_IF_NULL_PKG);
577 
578         ArrayList<PackageStateInternal> sortTemp = new ArrayList<>(remainingPkgSettings.size());
579 
580         final Computer snapshot = packageManagerService.snapshotComputer();
581 
582         // Give priority to core apps.
583         applyPackageFilter(snapshot, pkgSetting -> pkgSetting.getPkg().isCoreApp(), result,
584                 remainingPkgSettings, sortTemp, packageManagerService);
585 
586         // Give priority to system apps that listen for pre boot complete.
587         Intent intent = new Intent(Intent.ACTION_PRE_BOOT_COMPLETED);
588         final ArraySet<String> pkgNames = getPackageNamesForIntent(intent, UserHandle.USER_SYSTEM);
589         applyPackageFilter(snapshot, pkgSetting -> pkgNames.contains(pkgSetting.getPackageName()), result,
590                 remainingPkgSettings, sortTemp, packageManagerService);
591 
592         // Give priority to apps used by other apps.
593         DexManager dexManager = packageManagerService.getDexManager();
594         applyPackageFilter(snapshot, pkgSetting ->
595                         dexManager.getPackageUseInfoOrDefault(pkgSetting.getPackageName())
596                                 .isAnyCodePathUsedByOtherApps(),
597                 result, remainingPkgSettings, sortTemp, packageManagerService);
598 
599         // Filter out packages that aren't recently used, add all remaining apps.
600         // TODO: add a property to control this?
601         Predicate<PackageStateInternal> remainingPredicate;
602         if (!remainingPkgSettings.isEmpty()
603                 && packageManagerService.isHistoricalPackageUsageAvailable()) {
604             if (debug) {
605                 Log.i(TAG, "Looking at historical package use");
606             }
607             // Get the package that was used last.
608             PackageStateInternal lastUsed = Collections.max(remainingPkgSettings,
609                     Comparator.comparingLong(
610                             pkgSetting -> pkgSetting.getTransientState()
611                                     .getLatestForegroundPackageUseTimeInMills()));
612             if (debug) {
613                 Log.i(TAG, "Taking package " + lastUsed.getPackageName()
614                         + " as reference in time use");
615             }
616             long estimatedPreviousSystemUseTime = lastUsed.getTransientState()
617                     .getLatestForegroundPackageUseTimeInMills();
618             // Be defensive if for some reason package usage has bogus data.
619             if (estimatedPreviousSystemUseTime != 0) {
620                 final long cutoffTime = estimatedPreviousSystemUseTime - SEVEN_DAYS_IN_MILLISECONDS;
621                 remainingPredicate = pkgSetting -> pkgSetting.getTransientState()
622                         .getLatestForegroundPackageUseTimeInMills() >= cutoffTime;
623             } else {
624                 // No meaningful historical info. Take all.
625                 remainingPredicate = pkgSetting -> true;
626             }
627             sortPackagesByUsageDate(remainingPkgSettings, packageManagerService);
628         } else {
629             // No historical info. Take all.
630             remainingPredicate = pkgSetting -> true;
631         }
632         applyPackageFilter(snapshot, remainingPredicate, result, remainingPkgSettings, sortTemp,
633                 packageManagerService);
634 
635         if (debug) {
636             Log.i(TAG, "Packages to be dexopted: " + packagesToString(result));
637             Log.i(TAG, "Packages skipped from dexopt: " + packagesToString(remainingPkgSettings));
638         }
639 
640         return result;
641     }
642 
643     // Apply the given {@code filter} to all packages in {@code packages}. If tested positive, the
644     // package will be removed from {@code packages} and added to {@code result} with its
645     // dependencies. If usage data is available, the positive packages will be sorted by usage
646     // data (with {@code sortTemp} as temporary storage).
applyPackageFilter(@onNull Computer snapshot, Predicate<PackageStateInternal> filter, Collection<PackageStateInternal> result, Collection<PackageStateInternal> packages, @NonNull List<PackageStateInternal> sortTemp, PackageManagerService packageManagerService)647     private static void applyPackageFilter(@NonNull Computer snapshot,
648             Predicate<PackageStateInternal> filter,
649             Collection<PackageStateInternal> result,
650             Collection<PackageStateInternal> packages,
651             @NonNull List<PackageStateInternal> sortTemp,
652             PackageManagerService packageManagerService) {
653         for (PackageStateInternal pkgSetting : packages) {
654             if (filter.test(pkgSetting)) {
655                 sortTemp.add(pkgSetting);
656             }
657         }
658 
659         sortPackagesByUsageDate(sortTemp, packageManagerService);
660         packages.removeAll(sortTemp);
661 
662         for (PackageStateInternal pkgSetting : sortTemp) {
663             result.add(pkgSetting);
664 
665             List<PackageStateInternal> deps = snapshot.findSharedNonSystemLibraries(pkgSetting);
666             if (!deps.isEmpty()) {
667                 deps.removeAll(result);
668                 result.addAll(deps);
669                 packages.removeAll(deps);
670             }
671         }
672 
673         sortTemp.clear();
674     }
675 
676     // Sort a list of apps by their last usage, most recently used apps first. The order of
677     // packages without usage data is undefined (but they will be sorted after the packages
678     // that do have usage data).
sortPackagesByUsageDate(List<PackageStateInternal> pkgSettings, PackageManagerService packageManagerService)679     private static void sortPackagesByUsageDate(List<PackageStateInternal> pkgSettings,
680             PackageManagerService packageManagerService) {
681         if (!packageManagerService.isHistoricalPackageUsageAvailable()) {
682             return;
683         }
684 
685         Collections.sort(pkgSettings, (pkgSetting1, pkgSetting2) ->
686                 Long.compare(
687                         pkgSetting2.getTransientState().getLatestForegroundPackageUseTimeInMills(),
688                         pkgSetting1.getTransientState().getLatestForegroundPackageUseTimeInMills())
689         );
690     }
691 
getPackageNamesForIntent(Intent intent, int userId)692     private static ArraySet<String> getPackageNamesForIntent(Intent intent, int userId) {
693         List<ResolveInfo> ris = null;
694         try {
695             ris = AppGlobals.getPackageManager().queryIntentReceivers(intent, null, 0, userId)
696                     .getList();
697         } catch (RemoteException e) {
698         }
699         ArraySet<String> pkgNames = new ArraySet<String>();
700         if (ris != null) {
701             for (ResolveInfo ri : ris) {
702                 pkgNames.add(ri.activityInfo.packageName);
703             }
704         }
705         return pkgNames;
706     }
707 
packagesToString(List<PackageStateInternal> pkgSettings)708     public static String packagesToString(List<PackageStateInternal> pkgSettings) {
709         StringBuilder sb = new StringBuilder();
710         for (int index = 0; index < pkgSettings.size(); index++) {
711             if (sb.length() > 0) {
712                 sb.append(", ");
713             }
714             sb.append(pkgSettings.get(index).getPackageName());
715         }
716         return sb.toString();
717     }
718 
719      /**
720      * Requests that files preopted on a secondary system partition be copied to the data partition
721      * if possible.  Note that the actual copying of the files is accomplished by init for security
722      * reasons. This simply requests that the copy takes place and awaits confirmation of its
723      * completion. See platform/system/extras/cppreopt/ for the implementation of the actual copy.
724      */
requestCopyPreoptedFiles()725     public static void requestCopyPreoptedFiles() {
726         final int WAIT_TIME_MS = 100;
727         final String CP_PREOPT_PROPERTY = "sys.cppreopt";
728         if (SystemProperties.getInt("ro.cp_system_other_odex", 0) == 1) {
729             SystemProperties.set(CP_PREOPT_PROPERTY, "requested");
730             // We will wait for up to 100 seconds.
731             final long timeStart = SystemClock.uptimeMillis();
732             final long timeEnd = timeStart + 100 * 1000;
733             long timeNow = timeStart;
734             while (!SystemProperties.get(CP_PREOPT_PROPERTY).equals("finished")) {
735                 try {
736                     Thread.sleep(WAIT_TIME_MS);
737                 } catch (InterruptedException e) {
738                     // Do nothing
739                 }
740                 timeNow = SystemClock.uptimeMillis();
741                 if (timeNow > timeEnd) {
742                     SystemProperties.set(CP_PREOPT_PROPERTY, "timed-out");
743                     Slog.wtf(TAG, "cppreopt did not finish!");
744                     break;
745                 }
746             }
747 
748             Slog.i(TAG, "cppreopts took " + (timeNow - timeStart) + " ms");
749         }
750     }
751 
controlDexOptBlocking(boolean block)752     /*package*/ void controlDexOptBlocking(boolean block) {
753         mPm.mPackageDexOptimizer.controlDexOptBlocking(block);
754     }
755 
756     /**
757      * Returns the module names of the APEXes that contribute to bootclasspath.
758      */
getBcpApexes()759     private static List<String> getBcpApexes() {
760         String bcp = System.getenv("BOOTCLASSPATH");
761         if (TextUtils.isEmpty(bcp)) {
762             Log.e(TAG, "Unable to get BOOTCLASSPATH");
763             return List.of();
764         }
765 
766         ArrayList<String> bcpApexes = new ArrayList<>();
767         for (String pathStr : bcp.split(":")) {
768             Path path = Paths.get(pathStr);
769             // Check if the path is in the format of `/apex/<apex-module-name>/...` and extract the
770             // apex module name from the path.
771             if (path.getNameCount() >= 2 && path.getName(0).toString().equals("apex")) {
772                 bcpApexes.add(path.getName(1).toString());
773             }
774         }
775 
776         return bcpApexes;
777     }
778 
779     /**
780      * Returns true of any of the APEXes that contribute to bootclasspath has changed during this
781      * boot.
782      */
hasBcpApexesChanged()783     private static boolean hasBcpApexesChanged() {
784         Set<String> bcpApexes = new HashSet<>(getBcpApexes());
785         ApexManager apexManager = ApexManager.getInstance();
786         for (ActiveApexInfo apexInfo : apexManager.getActiveApexInfos()) {
787             if (bcpApexes.contains(apexInfo.apexModuleName) && apexInfo.activeApexChanged) {
788                 return true;
789             }
790         }
791         return false;
792     }
793 }
794