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