1 /* 2 * Copyright (C) 2014 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 com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME; 20 21 import android.annotation.Nullable; 22 import android.app.job.JobInfo; 23 import android.app.job.JobParameters; 24 import android.app.job.JobScheduler; 25 import android.app.job.JobService; 26 import android.content.BroadcastReceiver; 27 import android.content.ComponentName; 28 import android.content.Context; 29 import android.content.Intent; 30 import android.content.IntentFilter; 31 import android.content.pm.PackageInfo; 32 import android.os.BatteryManagerInternal; 33 import android.os.Environment; 34 import android.os.IThermalService; 35 import android.os.PowerManager; 36 import android.os.RemoteException; 37 import android.os.ServiceManager; 38 import android.os.SystemProperties; 39 import android.os.UserHandle; 40 import android.os.storage.StorageManager; 41 import android.util.ArraySet; 42 import android.util.Log; 43 import android.util.Slog; 44 45 import com.android.internal.util.ArrayUtils; 46 import com.android.internal.util.FrameworkStatsLog; 47 import com.android.server.LocalServices; 48 import com.android.server.PinnerService; 49 import com.android.server.pm.dex.DexManager; 50 import com.android.server.pm.dex.DexoptOptions; 51 52 import java.io.File; 53 import java.nio.file.Paths; 54 import java.util.ArrayList; 55 import java.util.List; 56 import java.util.Set; 57 import java.util.concurrent.TimeUnit; 58 import java.util.concurrent.atomic.AtomicBoolean; 59 import java.util.function.Supplier; 60 61 /** 62 * {@hide} 63 */ 64 public class BackgroundDexOptService extends JobService { 65 private static final String TAG = "BackgroundDexOptService"; 66 67 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); 68 69 private static final int JOB_IDLE_OPTIMIZE = 800; 70 private static final int JOB_POST_BOOT_UPDATE = 801; 71 72 private static final long IDLE_OPTIMIZATION_PERIOD = TimeUnit.DAYS.toMillis(1); 73 74 private static ComponentName sDexoptServiceName = new ComponentName( 75 "android", 76 BackgroundDexOptService.class.getName()); 77 78 // Possible return codes of individual optimization steps. 79 80 // Optimizations finished. All packages were processed. 81 private static final int OPTIMIZE_PROCESSED = 0; 82 // Optimizations should continue. Issued after checking the scheduler, disk space or battery. 83 private static final int OPTIMIZE_CONTINUE = 1; 84 // Optimizations should be aborted. Job scheduler requested it. 85 private static final int OPTIMIZE_ABORT_BY_JOB_SCHEDULER = 2; 86 // Optimizations should be aborted. No space left on device. 87 private static final int OPTIMIZE_ABORT_NO_SPACE_LEFT = 3; 88 // Optimizations should be aborted. Thermal throttling level too high. 89 private static final int OPTIMIZE_ABORT_THERMAL = 4; 90 91 // Used for calculating space threshold for downgrading unused apps. 92 private static final int LOW_THRESHOLD_MULTIPLIER_FOR_DOWNGRADE = 2; 93 94 // Thermal cutoff value used if one isn't defined by a system property. 95 private static final int THERMAL_CUTOFF_DEFAULT = PowerManager.THERMAL_STATUS_MODERATE; 96 97 /** 98 * Set of failed packages remembered across job runs. 99 */ 100 static final ArraySet<String> sFailedPackageNamesPrimary = new ArraySet<String>(); 101 static final ArraySet<String> sFailedPackageNamesSecondary = new ArraySet<String>(); 102 103 /** 104 * Atomics set to true if the JobScheduler requests an abort. 105 */ 106 private final AtomicBoolean mAbortPostBootUpdate = new AtomicBoolean(false); 107 private final AtomicBoolean mAbortIdleOptimization = new AtomicBoolean(false); 108 109 /** 110 * Atomic set to true if one job should exit early because another job was started. 111 */ 112 private final AtomicBoolean mExitPostBootUpdate = new AtomicBoolean(false); 113 114 private final File mDataDir = Environment.getDataDirectory(); 115 private static final long mDowngradeUnusedAppsThresholdInMillis = 116 getDowngradeUnusedAppsThresholdInMillis(); 117 118 private final IThermalService mThermalService = 119 IThermalService.Stub.asInterface( 120 ServiceManager.getService(Context.THERMAL_SERVICE)); 121 122 private static List<PackagesUpdatedListener> sPackagesUpdatedListeners = new ArrayList<>(); 123 124 private int mThermalStatusCutoff = THERMAL_CUTOFF_DEFAULT; 125 schedule(Context context)126 public static void schedule(Context context) { 127 if (isBackgroundDexoptDisabled()) { 128 return; 129 } 130 131 final JobScheduler js = context.getSystemService(JobScheduler.class); 132 133 // Schedule a one-off job which scans installed packages and updates 134 // out-of-date oat files. Schedule it 10 minutes after the boot complete event, 135 // so that we don't overload the boot with additional dex2oat compilations. 136 context.registerReceiver(new BroadcastReceiver() { 137 @Override 138 public void onReceive(Context context, Intent intent) { 139 js.schedule(new JobInfo.Builder(JOB_POST_BOOT_UPDATE, sDexoptServiceName) 140 .setMinimumLatency(TimeUnit.MINUTES.toMillis(10)) 141 .setOverrideDeadline(TimeUnit.MINUTES.toMillis(60)) 142 .build()); 143 context.unregisterReceiver(this); 144 if (DEBUG) { 145 Slog.i(TAG, "BootBgDexopt scheduled"); 146 } 147 } 148 }, new IntentFilter(Intent.ACTION_BOOT_COMPLETED)); 149 150 // Schedule a daily job which scans installed packages and compiles 151 // those with fresh profiling data. 152 js.schedule(new JobInfo.Builder(JOB_IDLE_OPTIMIZE, sDexoptServiceName) 153 .setRequiresDeviceIdle(true) 154 .setRequiresCharging(true) 155 .setPeriodic(IDLE_OPTIMIZATION_PERIOD) 156 .build()); 157 158 if (DEBUG) { 159 Slog.d(TAG, "BgDexopt scheduled"); 160 } 161 } 162 notifyPackageChanged(String packageName)163 public static void notifyPackageChanged(String packageName) { 164 // The idle maintanance job skips packages which previously failed to 165 // compile. The given package has changed and may successfully compile 166 // now. Remove it from the list of known failing packages. 167 synchronized (sFailedPackageNamesPrimary) { 168 sFailedPackageNamesPrimary.remove(packageName); 169 } 170 synchronized (sFailedPackageNamesSecondary) { 171 sFailedPackageNamesSecondary.remove(packageName); 172 } 173 } 174 getLowStorageThreshold(Context context)175 private long getLowStorageThreshold(Context context) { 176 @SuppressWarnings("deprecation") 177 final long lowThreshold = StorageManager.from(context).getStorageLowBytes(mDataDir); 178 if (lowThreshold == 0) { 179 Slog.e(TAG, "Invalid low storage threshold"); 180 } 181 182 return lowThreshold; 183 } 184 runPostBootUpdate(final JobParameters jobParams, final PackageManagerService pm, final ArraySet<String> pkgs)185 private boolean runPostBootUpdate(final JobParameters jobParams, 186 final PackageManagerService pm, final ArraySet<String> pkgs) { 187 if (mExitPostBootUpdate.get()) { 188 // This job has already been superseded. Do not start it. 189 return false; 190 } 191 new Thread("BackgroundDexOptService_PostBootUpdate") { 192 @Override 193 public void run() { 194 postBootUpdate(jobParams, pm, pkgs); 195 } 196 197 }.start(); 198 return true; 199 } 200 postBootUpdate(JobParameters jobParams, PackageManagerService pm, ArraySet<String> pkgs)201 private void postBootUpdate(JobParameters jobParams, PackageManagerService pm, 202 ArraySet<String> pkgs) { 203 final BatteryManagerInternal batteryManagerInternal = 204 LocalServices.getService(BatteryManagerInternal.class); 205 final long lowThreshold = getLowStorageThreshold(this); 206 207 mAbortPostBootUpdate.set(false); 208 209 ArraySet<String> updatedPackages = new ArraySet<>(); 210 for (String pkg : pkgs) { 211 if (mAbortPostBootUpdate.get()) { 212 // JobScheduler requested an early abort. 213 return; 214 } 215 if (mExitPostBootUpdate.get()) { 216 // Different job, which supersedes this one, is running. 217 break; 218 } 219 if (batteryManagerInternal.getBatteryLevelLow()) { 220 // Rather bail than completely drain the battery. 221 break; 222 } 223 long usableSpace = mDataDir.getUsableSpace(); 224 if (usableSpace < lowThreshold) { 225 // Rather bail than completely fill up the disk. 226 Slog.w(TAG, "Aborting background dex opt job due to low storage: " + 227 usableSpace); 228 break; 229 } 230 if (DEBUG) { 231 Slog.i(TAG, "Updating package " + pkg); 232 } 233 234 // Update package if needed. Note that there can be no race between concurrent 235 // jobs because PackageDexOptimizer.performDexOpt is synchronized. 236 237 // checkProfiles is false to avoid merging profiles during boot which 238 // might interfere with background compilation (b/28612421). 239 // Unfortunately this will also means that "pm.dexopt.boot=speed-profile" will 240 // behave differently than "pm.dexopt.bg-dexopt=speed-profile" but that's a 241 // trade-off worth doing to save boot time work. 242 int result = pm.performDexOptWithStatus(new DexoptOptions( 243 pkg, 244 PackageManagerService.REASON_POST_BOOT, 245 DexoptOptions.DEXOPT_BOOT_COMPLETE)); 246 if (result == PackageDexOptimizer.DEX_OPT_PERFORMED) { 247 updatedPackages.add(pkg); 248 } 249 } 250 notifyPinService(updatedPackages); 251 notifyPackagesUpdated(updatedPackages); 252 // Ran to completion, so we abandon our timeslice and do not reschedule. 253 jobFinished(jobParams, /* reschedule */ false); 254 } 255 runIdleOptimization(final JobParameters jobParams, final PackageManagerService pm, final ArraySet<String> pkgs)256 private boolean runIdleOptimization(final JobParameters jobParams, 257 final PackageManagerService pm, final ArraySet<String> pkgs) { 258 new Thread("BackgroundDexOptService_IdleOptimization") { 259 @Override 260 public void run() { 261 int result = idleOptimization(pm, pkgs, BackgroundDexOptService.this); 262 if (result == OPTIMIZE_PROCESSED) { 263 Slog.i(TAG, "Idle optimizations completed."); 264 } else if (result == OPTIMIZE_ABORT_NO_SPACE_LEFT) { 265 Slog.w(TAG, "Idle optimizations aborted because of space constraints."); 266 } else if (result == OPTIMIZE_ABORT_BY_JOB_SCHEDULER) { 267 Slog.w(TAG, "Idle optimizations aborted by job scheduler."); 268 } else if (result == OPTIMIZE_ABORT_THERMAL) { 269 Slog.w(TAG, "Idle optimizations aborted by thermal throttling."); 270 } else { 271 Slog.w(TAG, "Idle optimizations ended with unexpected code: " + result); 272 } 273 274 if (result == OPTIMIZE_ABORT_THERMAL) { 275 // Abandon our timeslice and reschedule 276 jobFinished(jobParams, /* wantsReschedule */ true); 277 } else if (result != OPTIMIZE_ABORT_BY_JOB_SCHEDULER) { 278 // Abandon our timeslice and do not reschedule. 279 jobFinished(jobParams, /* wantsReschedule */ false); 280 } 281 } 282 }.start(); 283 return true; 284 } 285 286 // Optimize the given packages and return the optimization result (one of the OPTIMIZE_* codes). idleOptimization(PackageManagerService pm, ArraySet<String> pkgs, Context context)287 private int idleOptimization(PackageManagerService pm, ArraySet<String> pkgs, 288 Context context) { 289 Slog.i(TAG, "Performing idle optimizations"); 290 // If post-boot update is still running, request that it exits early. 291 mExitPostBootUpdate.set(true); 292 mAbortIdleOptimization.set(false); 293 294 long lowStorageThreshold = getLowStorageThreshold(context); 295 int result = idleOptimizePackages(pm, pkgs, lowStorageThreshold); 296 return result; 297 } 298 299 /** 300 * Get the size of the directory. It uses recursion to go over all files. 301 * @param f 302 * @return 303 */ getDirectorySize(File f)304 private long getDirectorySize(File f) { 305 long size = 0; 306 if (f.isDirectory()) { 307 for (File file: f.listFiles()) { 308 size += getDirectorySize(file); 309 } 310 } else { 311 size = f.length(); 312 } 313 return size; 314 } 315 316 /** 317 * Get the size of a package. 318 * @param pkg 319 */ getPackageSize(PackageManagerService pm, String pkg)320 private long getPackageSize(PackageManagerService pm, String pkg) { 321 PackageInfo info = pm.getPackageInfo(pkg, 0, UserHandle.USER_SYSTEM); 322 long size = 0; 323 if (info != null && info.applicationInfo != null) { 324 File path = Paths.get(info.applicationInfo.sourceDir).toFile(); 325 if (path.isFile()) { 326 path = path.getParentFile(); 327 } 328 size += getDirectorySize(path); 329 if (!ArrayUtils.isEmpty(info.applicationInfo.splitSourceDirs)) { 330 for (String splitSourceDir : info.applicationInfo.splitSourceDirs) { 331 path = Paths.get(splitSourceDir).toFile(); 332 if (path.isFile()) { 333 path = path.getParentFile(); 334 } 335 size += getDirectorySize(path); 336 } 337 } 338 return size; 339 } 340 return 0; 341 } 342 idleOptimizePackages(PackageManagerService pm, ArraySet<String> pkgs, long lowStorageThreshold)343 private int idleOptimizePackages(PackageManagerService pm, ArraySet<String> pkgs, 344 long lowStorageThreshold) { 345 ArraySet<String> updatedPackages = new ArraySet<>(); 346 ArraySet<String> updatedPackagesDueToSecondaryDex = new ArraySet<>(); 347 348 try { 349 final boolean supportSecondaryDex = supportSecondaryDex(); 350 351 if (supportSecondaryDex) { 352 int result = reconcileSecondaryDexFiles(pm.getDexManager()); 353 if (result == OPTIMIZE_ABORT_BY_JOB_SCHEDULER) { 354 return result; 355 } 356 } 357 358 // Only downgrade apps when space is low on device. 359 // Threshold is selected above the lowStorageThreshold so that we can pro-actively clean 360 // up disk before user hits the actual lowStorageThreshold. 361 final long lowStorageThresholdForDowngrade = LOW_THRESHOLD_MULTIPLIER_FOR_DOWNGRADE 362 * lowStorageThreshold; 363 boolean shouldDowngrade = shouldDowngrade(lowStorageThresholdForDowngrade); 364 if (DEBUG) { 365 Slog.d(TAG, "Should Downgrade " + shouldDowngrade); 366 } 367 if (shouldDowngrade) { 368 Set<String> unusedPackages = 369 pm.getUnusedPackages(mDowngradeUnusedAppsThresholdInMillis); 370 if (DEBUG) { 371 Slog.d(TAG, "Unsused Packages " + String.join(",", unusedPackages)); 372 } 373 374 if (!unusedPackages.isEmpty()) { 375 for (String pkg : unusedPackages) { 376 int abortCode = abortIdleOptimizations(/*lowStorageThreshold*/ -1); 377 if (abortCode != OPTIMIZE_CONTINUE) { 378 // Should be aborted by the scheduler. 379 return abortCode; 380 } 381 if (downgradePackage(pm, pkg, /*isForPrimaryDex*/ true)) { 382 updatedPackages.add(pkg); 383 } 384 if (supportSecondaryDex) { 385 downgradePackage(pm, pkg, /*isForPrimaryDex*/ false); 386 } 387 } 388 389 pkgs = new ArraySet<>(pkgs); 390 pkgs.removeAll(unusedPackages); 391 } 392 } 393 394 int primaryResult = optimizePackages(pm, pkgs, lowStorageThreshold, 395 /*isForPrimaryDex*/ true, updatedPackages); 396 if (primaryResult != OPTIMIZE_PROCESSED) { 397 return primaryResult; 398 } 399 400 if (!supportSecondaryDex) { 401 return OPTIMIZE_PROCESSED; 402 } 403 404 int secondaryResult = optimizePackages(pm, pkgs, lowStorageThreshold, 405 /*isForPrimaryDex*/ false, updatedPackagesDueToSecondaryDex); 406 return secondaryResult; 407 } finally { 408 // Always let the pinner service know about changes. 409 notifyPinService(updatedPackages); 410 // Only notify IORap the primary dex opt, because we don't want to 411 // invalidate traces unnecessary due to b/161633001 and that it's 412 // better to have a trace than no trace at all. 413 notifyPackagesUpdated(updatedPackages); 414 } 415 } 416 optimizePackages(PackageManagerService pm, ArraySet<String> pkgs, long lowStorageThreshold, boolean isForPrimaryDex, ArraySet<String> updatedPackages)417 private int optimizePackages(PackageManagerService pm, ArraySet<String> pkgs, 418 long lowStorageThreshold, boolean isForPrimaryDex, ArraySet<String> updatedPackages) { 419 for (String pkg : pkgs) { 420 int abortCode = abortIdleOptimizations(lowStorageThreshold); 421 if (abortCode != OPTIMIZE_CONTINUE) { 422 // Either aborted by the scheduler or no space left. 423 return abortCode; 424 } 425 426 boolean dexOptPerformed = optimizePackage(pm, pkg, isForPrimaryDex); 427 if (dexOptPerformed) { 428 updatedPackages.add(pkg); 429 } 430 } 431 return OPTIMIZE_PROCESSED; 432 } 433 434 /** 435 * Try to downgrade the package to a smaller compilation filter. 436 * eg. if the package is in speed-profile the package will be downgraded to verify. 437 * @param pm PackageManagerService 438 * @param pkg The package to be downgraded. 439 * @param isForPrimaryDex. Apps can have several dex file, primary and secondary. 440 * @return true if the package was downgraded. 441 */ downgradePackage(PackageManagerService pm, String pkg, boolean isForPrimaryDex)442 private boolean downgradePackage(PackageManagerService pm, String pkg, 443 boolean isForPrimaryDex) { 444 if (DEBUG) { 445 Slog.d(TAG, "Downgrading " + pkg); 446 } 447 boolean dex_opt_performed = false; 448 int reason = PackageManagerService.REASON_INACTIVE_PACKAGE_DOWNGRADE; 449 int dexoptFlags = DexoptOptions.DEXOPT_BOOT_COMPLETE 450 | DexoptOptions.DEXOPT_IDLE_BACKGROUND_JOB 451 | DexoptOptions.DEXOPT_DOWNGRADE; 452 long package_size_before = getPackageSize(pm, pkg); 453 454 if (isForPrimaryDex || PLATFORM_PACKAGE_NAME.equals(pkg)) { 455 // This applies for system apps or if packages location is not a directory, i.e. 456 // monolithic install. 457 if (!pm.canHaveOatDir(pkg)) { 458 // For apps that don't have the oat directory, instead of downgrading, 459 // remove their compiler artifacts from dalvik cache. 460 pm.deleteOatArtifactsOfPackage(pkg); 461 } else { 462 dex_opt_performed = performDexOptPrimary(pm, pkg, reason, dexoptFlags); 463 } 464 } else { 465 dex_opt_performed = performDexOptSecondary(pm, pkg, reason, dexoptFlags); 466 } 467 468 if (dex_opt_performed) { 469 FrameworkStatsLog.write(FrameworkStatsLog.APP_DOWNGRADED, pkg, package_size_before, 470 getPackageSize(pm, pkg), /*aggressive=*/ false); 471 } 472 return dex_opt_performed; 473 } 474 supportSecondaryDex()475 private boolean supportSecondaryDex() { 476 return (SystemProperties.getBoolean("dalvik.vm.dexopt.secondary", false)); 477 } 478 reconcileSecondaryDexFiles(DexManager dm)479 private int reconcileSecondaryDexFiles(DexManager dm) { 480 // TODO(calin): should we denylist packages for which we fail to reconcile? 481 for (String p : dm.getAllPackagesWithSecondaryDexFiles()) { 482 if (mAbortIdleOptimization.get()) { 483 return OPTIMIZE_ABORT_BY_JOB_SCHEDULER; 484 } 485 dm.reconcileSecondaryDexFiles(p); 486 } 487 return OPTIMIZE_PROCESSED; 488 } 489 490 /** 491 * 492 * Optimize package if needed. Note that there can be no race between 493 * concurrent jobs because PackageDexOptimizer.performDexOpt is synchronized. 494 * @param pm An instance of PackageManagerService 495 * @param pkg The package to be downgraded. 496 * @param isForPrimaryDex. Apps can have several dex file, primary and secondary. 497 * @return true if the package was downgraded. 498 */ optimizePackage(PackageManagerService pm, String pkg, boolean isForPrimaryDex)499 private boolean optimizePackage(PackageManagerService pm, String pkg, 500 boolean isForPrimaryDex) { 501 int reason = PackageManagerService.REASON_BACKGROUND_DEXOPT; 502 int dexoptFlags = DexoptOptions.DEXOPT_CHECK_FOR_PROFILES_UPDATES 503 | DexoptOptions.DEXOPT_BOOT_COMPLETE 504 | DexoptOptions.DEXOPT_IDLE_BACKGROUND_JOB; 505 506 // System server share the same code path as primary dex files. 507 // PackageManagerService will select the right optimization path for it. 508 return (isForPrimaryDex || PLATFORM_PACKAGE_NAME.equals(pkg)) 509 ? performDexOptPrimary(pm, pkg, reason, dexoptFlags) 510 : performDexOptSecondary(pm, pkg, reason, dexoptFlags); 511 } 512 performDexOptPrimary(PackageManagerService pm, String pkg, int reason, int dexoptFlags)513 private boolean performDexOptPrimary(PackageManagerService pm, String pkg, int reason, 514 int dexoptFlags) { 515 int result = trackPerformDexOpt(pkg, /*isForPrimaryDex=*/ false, 516 () -> pm.performDexOptWithStatus(new DexoptOptions(pkg, reason, dexoptFlags))); 517 return result == PackageDexOptimizer.DEX_OPT_PERFORMED; 518 } 519 performDexOptSecondary(PackageManagerService pm, String pkg, int reason, int dexoptFlags)520 private boolean performDexOptSecondary(PackageManagerService pm, String pkg, int reason, 521 int dexoptFlags) { 522 DexoptOptions dexoptOptions = new DexoptOptions(pkg, reason, 523 dexoptFlags | DexoptOptions.DEXOPT_ONLY_SECONDARY_DEX); 524 int result = trackPerformDexOpt(pkg, /*isForPrimaryDex=*/ true, 525 () -> pm.performDexOpt(dexoptOptions) 526 ? PackageDexOptimizer.DEX_OPT_PERFORMED : PackageDexOptimizer.DEX_OPT_FAILED 527 ); 528 return result == PackageDexOptimizer.DEX_OPT_PERFORMED; 529 } 530 531 /** 532 * Execute the dexopt wrapper and make sure that if performDexOpt wrapper fails 533 * the package is added to the list of failed packages. 534 * Return one of following result: 535 * {@link PackageDexOptimizer#DEX_OPT_SKIPPED} 536 * {@link PackageDexOptimizer#DEX_OPT_PERFORMED} 537 * {@link PackageDexOptimizer#DEX_OPT_FAILED} 538 */ trackPerformDexOpt(String pkg, boolean isForPrimaryDex, Supplier<Integer> performDexOptWrapper)539 private int trackPerformDexOpt(String pkg, boolean isForPrimaryDex, 540 Supplier<Integer> performDexOptWrapper) { 541 ArraySet<String> sFailedPackageNames = 542 isForPrimaryDex ? sFailedPackageNamesPrimary : sFailedPackageNamesSecondary; 543 synchronized (sFailedPackageNames) { 544 if (sFailedPackageNames.contains(pkg)) { 545 // Skip previously failing package 546 return PackageDexOptimizer.DEX_OPT_SKIPPED; 547 } 548 sFailedPackageNames.add(pkg); 549 } 550 int result = performDexOptWrapper.get(); 551 if (result != PackageDexOptimizer.DEX_OPT_FAILED) { 552 synchronized (sFailedPackageNames) { 553 sFailedPackageNames.remove(pkg); 554 } 555 } 556 return result; 557 } 558 559 // Evaluate whether or not idle optimizations should continue. abortIdleOptimizations(long lowStorageThreshold)560 private int abortIdleOptimizations(long lowStorageThreshold) { 561 if (mAbortIdleOptimization.get()) { 562 // JobScheduler requested an early abort. 563 return OPTIMIZE_ABORT_BY_JOB_SCHEDULER; 564 } 565 566 // Abort background dexopt if the device is in a moderate or stronger thermal throttling 567 // state. 568 try { 569 final int thermalStatus = mThermalService.getCurrentThermalStatus(); 570 571 if (DEBUG) { 572 Log.i(TAG, "Thermal throttling status during bgdexopt: " + thermalStatus); 573 } 574 575 if (thermalStatus >= mThermalStatusCutoff) { 576 return OPTIMIZE_ABORT_THERMAL; 577 } 578 } catch (RemoteException ex) { 579 // Because this is a intra-process Binder call it is impossible for a RemoteException 580 // to be raised. 581 } 582 583 long usableSpace = mDataDir.getUsableSpace(); 584 if (usableSpace < lowStorageThreshold) { 585 // Rather bail than completely fill up the disk. 586 Slog.w(TAG, "Aborting background dex opt job due to low storage: " + usableSpace); 587 return OPTIMIZE_ABORT_NO_SPACE_LEFT; 588 } 589 590 return OPTIMIZE_CONTINUE; 591 } 592 593 // Evaluate whether apps should be downgraded. shouldDowngrade(long lowStorageThresholdForDowngrade)594 private boolean shouldDowngrade(long lowStorageThresholdForDowngrade) { 595 long usableSpace = mDataDir.getUsableSpace(); 596 if (usableSpace < lowStorageThresholdForDowngrade) { 597 return true; 598 } 599 600 return false; 601 } 602 603 /** 604 * Execute idle optimizations immediately on packages in packageNames. If packageNames is null, 605 * then execute on all packages. 606 */ runIdleOptimizationsNow(PackageManagerService pm, Context context, @Nullable List<String> packageNames)607 public static boolean runIdleOptimizationsNow(PackageManagerService pm, Context context, 608 @Nullable List<String> packageNames) { 609 // Create a new object to make sure we don't interfere with the scheduled jobs. 610 // Note that this may still run at the same time with the job scheduled by the 611 // JobScheduler but the scheduler will not be able to cancel it. 612 BackgroundDexOptService bdos = new BackgroundDexOptService(); 613 ArraySet<String> packagesToOptimize; 614 if (packageNames == null) { 615 packagesToOptimize = pm.getOptimizablePackages(); 616 } else { 617 packagesToOptimize = new ArraySet<>(packageNames); 618 } 619 int result = bdos.idleOptimization(pm, packagesToOptimize, context); 620 return result == OPTIMIZE_PROCESSED; 621 } 622 623 @Override onStartJob(JobParameters params)624 public boolean onStartJob(JobParameters params) { 625 if (DEBUG) { 626 Slog.i(TAG, "onStartJob"); 627 } 628 629 // NOTE: PackageManagerService.isStorageLow uses a different set of criteria from 630 // the checks above. This check is not "live" - the value is determined by a background 631 // restart with a period of ~1 minute. 632 PackageManagerService pm = (PackageManagerService)ServiceManager.getService("package"); 633 if (pm.isStorageLow()) { 634 Slog.i(TAG, "Low storage, skipping this run"); 635 return false; 636 } 637 638 final ArraySet<String> pkgs = pm.getOptimizablePackages(); 639 if (pkgs.isEmpty()) { 640 Slog.i(TAG, "No packages to optimize"); 641 return false; 642 } 643 644 mThermalStatusCutoff = 645 SystemProperties.getInt("dalvik.vm.dexopt.thermal-cutoff", THERMAL_CUTOFF_DEFAULT); 646 647 boolean result; 648 if (params.getJobId() == JOB_POST_BOOT_UPDATE) { 649 result = runPostBootUpdate(params, pm, pkgs); 650 } else { 651 result = runIdleOptimization(params, pm, pkgs); 652 } 653 654 return result; 655 } 656 657 @Override onStopJob(JobParameters params)658 public boolean onStopJob(JobParameters params) { 659 if (DEBUG) { 660 Slog.d(TAG, "onStopJob"); 661 } 662 663 if (params.getJobId() == JOB_POST_BOOT_UPDATE) { 664 mAbortPostBootUpdate.set(true); 665 666 // Do not reschedule. 667 // TODO: We should reschedule if we didn't process all apps, yet. 668 return false; 669 } else { 670 mAbortIdleOptimization.set(true); 671 672 // Reschedule the run. 673 // TODO: Should this be dependent on the stop reason? 674 return true; 675 } 676 } 677 notifyPinService(ArraySet<String> updatedPackages)678 private void notifyPinService(ArraySet<String> updatedPackages) { 679 PinnerService pinnerService = LocalServices.getService(PinnerService.class); 680 if (pinnerService != null) { 681 Slog.i(TAG, "Pinning optimized code " + updatedPackages); 682 pinnerService.update(updatedPackages, false /* force */); 683 } 684 } 685 686 public static interface PackagesUpdatedListener { 687 /** Callback when packages have been updated by the bg-dexopt service. */ onPackagesUpdated(ArraySet<String> updatedPackages)688 public void onPackagesUpdated(ArraySet<String> updatedPackages); 689 } 690 addPackagesUpdatedListener(PackagesUpdatedListener listener)691 public static void addPackagesUpdatedListener(PackagesUpdatedListener listener) { 692 synchronized (sPackagesUpdatedListeners) { 693 sPackagesUpdatedListeners.add(listener); 694 } 695 } 696 removePackagesUpdatedListener(PackagesUpdatedListener listener)697 public static void removePackagesUpdatedListener(PackagesUpdatedListener listener) { 698 synchronized (sPackagesUpdatedListeners) { 699 sPackagesUpdatedListeners.remove(listener); 700 } 701 } 702 703 /** Notify all listeners (#addPackagesUpdatedListener) that packages have been updated. */ notifyPackagesUpdated(ArraySet<String> updatedPackages)704 private void notifyPackagesUpdated(ArraySet<String> updatedPackages) { 705 synchronized (sPackagesUpdatedListeners) { 706 for (PackagesUpdatedListener listener : sPackagesUpdatedListeners) { 707 listener.onPackagesUpdated(updatedPackages); 708 } 709 } 710 } 711 getDowngradeUnusedAppsThresholdInMillis()712 private static long getDowngradeUnusedAppsThresholdInMillis() { 713 final String sysPropKey = "pm.dexopt.downgrade_after_inactive_days"; 714 String sysPropValue = SystemProperties.get(sysPropKey); 715 if (sysPropValue == null || sysPropValue.isEmpty()) { 716 Slog.w(TAG, "SysProp " + sysPropKey + " not set"); 717 return Long.MAX_VALUE; 718 } 719 return TimeUnit.DAYS.toMillis(Long.parseLong(sysPropValue)); 720 } 721 isBackgroundDexoptDisabled()722 private static boolean isBackgroundDexoptDisabled() { 723 return SystemProperties.getBoolean("pm.dexopt.disable_bg_dexopt" /* key */, 724 false /* default */); 725 } 726 } 727