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