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 341 try { 342 final boolean supportSecondaryDex = supportSecondaryDex(); 343 344 if (supportSecondaryDex) { 345 int result = reconcileSecondaryDexFiles(pm.getDexManager()); 346 if (result == OPTIMIZE_ABORT_BY_JOB_SCHEDULER) { 347 return result; 348 } 349 } 350 351 // Only downgrade apps when space is low on device. 352 // Threshold is selected above the lowStorageThreshold so that we can pro-actively clean 353 // up disk before user hits the actual lowStorageThreshold. 354 final long lowStorageThresholdForDowngrade = LOW_THRESHOLD_MULTIPLIER_FOR_DOWNGRADE 355 * lowStorageThreshold; 356 boolean shouldDowngrade = shouldDowngrade(lowStorageThresholdForDowngrade); 357 Log.d(TAG, "Should Downgrade " + shouldDowngrade); 358 if (shouldDowngrade) { 359 Set<String> unusedPackages = 360 pm.getUnusedPackages(mDowngradeUnusedAppsThresholdInMillis); 361 Log.d(TAG, "Unsused Packages " + String.join(",", unusedPackages)); 362 363 if (!unusedPackages.isEmpty()) { 364 for (String pkg : unusedPackages) { 365 int abortCode = abortIdleOptimizations(/*lowStorageThreshold*/ -1); 366 if (abortCode != OPTIMIZE_CONTINUE) { 367 // Should be aborted by the scheduler. 368 return abortCode; 369 } 370 if (downgradePackage(pm, pkg, /*isForPrimaryDex*/ true)) { 371 updatedPackages.add(pkg); 372 } 373 if (supportSecondaryDex) { 374 downgradePackage(pm, pkg, /*isForPrimaryDex*/ false); 375 } 376 } 377 378 pkgs = new ArraySet<>(pkgs); 379 pkgs.removeAll(unusedPackages); 380 } 381 } 382 383 int primaryResult = optimizePackages(pm, pkgs, lowStorageThreshold, 384 /*isForPrimaryDex*/ true, updatedPackages); 385 if (primaryResult != OPTIMIZE_PROCESSED) { 386 return primaryResult; 387 } 388 389 if (!supportSecondaryDex) { 390 return OPTIMIZE_PROCESSED; 391 } 392 393 int secondaryResult = optimizePackages(pm, pkgs, lowStorageThreshold, 394 /*isForPrimaryDex*/ false, updatedPackages); 395 return secondaryResult; 396 } finally { 397 // Always let the pinner service know about changes. 398 notifyPinService(updatedPackages); 399 notifyPackagesUpdated(updatedPackages); 400 } 401 } 402 optimizePackages(PackageManagerService pm, ArraySet<String> pkgs, long lowStorageThreshold, boolean isForPrimaryDex, ArraySet<String> updatedPackages)403 private int optimizePackages(PackageManagerService pm, ArraySet<String> pkgs, 404 long lowStorageThreshold, boolean isForPrimaryDex, ArraySet<String> updatedPackages) { 405 for (String pkg : pkgs) { 406 int abortCode = abortIdleOptimizations(lowStorageThreshold); 407 if (abortCode != OPTIMIZE_CONTINUE) { 408 // Either aborted by the scheduler or no space left. 409 return abortCode; 410 } 411 412 boolean dexOptPerformed = optimizePackage(pm, pkg, isForPrimaryDex); 413 if (dexOptPerformed) { 414 updatedPackages.add(pkg); 415 } 416 } 417 return OPTIMIZE_PROCESSED; 418 } 419 420 /** 421 * Try to downgrade the package to a smaller compilation filter. 422 * eg. if the package is in speed-profile the package will be downgraded to verify. 423 * @param pm PackageManagerService 424 * @param pkg The package to be downgraded. 425 * @param isForPrimaryDex. Apps can have several dex file, primary and secondary. 426 * @return true if the package was downgraded. 427 */ downgradePackage(PackageManagerService pm, String pkg, boolean isForPrimaryDex)428 private boolean downgradePackage(PackageManagerService pm, String pkg, 429 boolean isForPrimaryDex) { 430 Log.d(TAG, "Downgrading " + pkg); 431 boolean dex_opt_performed = false; 432 int reason = PackageManagerService.REASON_INACTIVE_PACKAGE_DOWNGRADE; 433 int dexoptFlags = DexoptOptions.DEXOPT_BOOT_COMPLETE 434 | DexoptOptions.DEXOPT_IDLE_BACKGROUND_JOB 435 | DexoptOptions.DEXOPT_DOWNGRADE; 436 long package_size_before = getPackageSize(pm, pkg); 437 438 if (isForPrimaryDex || PLATFORM_PACKAGE_NAME.equals(pkg)) { 439 // This applies for system apps or if packages location is not a directory, i.e. 440 // monolithic install. 441 if (!pm.canHaveOatDir(pkg)) { 442 // For apps that don't have the oat directory, instead of downgrading, 443 // remove their compiler artifacts from dalvik cache. 444 pm.deleteOatArtifactsOfPackage(pkg); 445 } else { 446 dex_opt_performed = performDexOptPrimary(pm, pkg, reason, dexoptFlags); 447 } 448 } else { 449 dex_opt_performed = performDexOptSecondary(pm, pkg, reason, dexoptFlags); 450 } 451 452 if (dex_opt_performed) { 453 FrameworkStatsLog.write(FrameworkStatsLog.APP_DOWNGRADED, pkg, package_size_before, 454 getPackageSize(pm, pkg), /*aggressive=*/ false); 455 } 456 return dex_opt_performed; 457 } 458 supportSecondaryDex()459 private boolean supportSecondaryDex() { 460 return (SystemProperties.getBoolean("dalvik.vm.dexopt.secondary", false)); 461 } 462 reconcileSecondaryDexFiles(DexManager dm)463 private int reconcileSecondaryDexFiles(DexManager dm) { 464 // TODO(calin): should we blacklist packages for which we fail to reconcile? 465 for (String p : dm.getAllPackagesWithSecondaryDexFiles()) { 466 if (mAbortIdleOptimization.get()) { 467 return OPTIMIZE_ABORT_BY_JOB_SCHEDULER; 468 } 469 dm.reconcileSecondaryDexFiles(p); 470 } 471 return OPTIMIZE_PROCESSED; 472 } 473 474 /** 475 * 476 * Optimize package if needed. Note that there can be no race between 477 * concurrent jobs because PackageDexOptimizer.performDexOpt is synchronized. 478 * @param pm An instance of PackageManagerService 479 * @param pkg The package to be downgraded. 480 * @param isForPrimaryDex. Apps can have several dex file, primary and secondary. 481 * @return true if the package was downgraded. 482 */ optimizePackage(PackageManagerService pm, String pkg, boolean isForPrimaryDex)483 private boolean optimizePackage(PackageManagerService pm, String pkg, 484 boolean isForPrimaryDex) { 485 int reason = PackageManagerService.REASON_BACKGROUND_DEXOPT; 486 int dexoptFlags = DexoptOptions.DEXOPT_CHECK_FOR_PROFILES_UPDATES 487 | DexoptOptions.DEXOPT_BOOT_COMPLETE 488 | DexoptOptions.DEXOPT_IDLE_BACKGROUND_JOB; 489 490 // System server share the same code path as primary dex files. 491 // PackageManagerService will select the right optimization path for it. 492 return (isForPrimaryDex || PLATFORM_PACKAGE_NAME.equals(pkg)) 493 ? performDexOptPrimary(pm, pkg, reason, dexoptFlags) 494 : performDexOptSecondary(pm, pkg, reason, dexoptFlags); 495 } 496 performDexOptPrimary(PackageManagerService pm, String pkg, int reason, int dexoptFlags)497 private boolean performDexOptPrimary(PackageManagerService pm, String pkg, int reason, 498 int dexoptFlags) { 499 int result = trackPerformDexOpt(pkg, /*isForPrimaryDex=*/ false, 500 () -> pm.performDexOptWithStatus(new DexoptOptions(pkg, reason, dexoptFlags))); 501 return result == PackageDexOptimizer.DEX_OPT_PERFORMED; 502 } 503 performDexOptSecondary(PackageManagerService pm, String pkg, int reason, int dexoptFlags)504 private boolean performDexOptSecondary(PackageManagerService pm, String pkg, int reason, 505 int dexoptFlags) { 506 DexoptOptions dexoptOptions = new DexoptOptions(pkg, reason, 507 dexoptFlags | DexoptOptions.DEXOPT_ONLY_SECONDARY_DEX); 508 int result = trackPerformDexOpt(pkg, /*isForPrimaryDex=*/ true, 509 () -> pm.performDexOpt(dexoptOptions) 510 ? PackageDexOptimizer.DEX_OPT_PERFORMED : PackageDexOptimizer.DEX_OPT_FAILED 511 ); 512 return result == PackageDexOptimizer.DEX_OPT_PERFORMED; 513 } 514 515 /** 516 * Execute the dexopt wrapper and make sure that if performDexOpt wrapper fails 517 * the package is added to the list of failed packages. 518 * Return one of following result: 519 * {@link PackageDexOptimizer#DEX_OPT_SKIPPED} 520 * {@link PackageDexOptimizer#DEX_OPT_PERFORMED} 521 * {@link PackageDexOptimizer#DEX_OPT_FAILED} 522 */ trackPerformDexOpt(String pkg, boolean isForPrimaryDex, Supplier<Integer> performDexOptWrapper)523 private int trackPerformDexOpt(String pkg, boolean isForPrimaryDex, 524 Supplier<Integer> performDexOptWrapper) { 525 ArraySet<String> sFailedPackageNames = 526 isForPrimaryDex ? sFailedPackageNamesPrimary : sFailedPackageNamesSecondary; 527 synchronized (sFailedPackageNames) { 528 if (sFailedPackageNames.contains(pkg)) { 529 // Skip previously failing package 530 return PackageDexOptimizer.DEX_OPT_SKIPPED; 531 } 532 sFailedPackageNames.add(pkg); 533 } 534 int result = performDexOptWrapper.get(); 535 if (result != PackageDexOptimizer.DEX_OPT_FAILED) { 536 synchronized (sFailedPackageNames) { 537 sFailedPackageNames.remove(pkg); 538 } 539 } 540 return result; 541 } 542 543 // Evaluate whether or not idle optimizations should continue. abortIdleOptimizations(long lowStorageThreshold)544 private int abortIdleOptimizations(long lowStorageThreshold) { 545 if (mAbortIdleOptimization.get()) { 546 // JobScheduler requested an early abort. 547 return OPTIMIZE_ABORT_BY_JOB_SCHEDULER; 548 } 549 long usableSpace = mDataDir.getUsableSpace(); 550 if (usableSpace < lowStorageThreshold) { 551 // Rather bail than completely fill up the disk. 552 Log.w(TAG, "Aborting background dex opt job due to low storage: " + usableSpace); 553 return OPTIMIZE_ABORT_NO_SPACE_LEFT; 554 } 555 556 return OPTIMIZE_CONTINUE; 557 } 558 559 // Evaluate whether apps should be downgraded. shouldDowngrade(long lowStorageThresholdForDowngrade)560 private boolean shouldDowngrade(long lowStorageThresholdForDowngrade) { 561 long usableSpace = mDataDir.getUsableSpace(); 562 if (usableSpace < lowStorageThresholdForDowngrade) { 563 return true; 564 } 565 566 return false; 567 } 568 569 /** 570 * Execute idle optimizations immediately on packages in packageNames. If packageNames is null, 571 * then execute on all packages. 572 */ runIdleOptimizationsNow(PackageManagerService pm, Context context, @Nullable List<String> packageNames)573 public static boolean runIdleOptimizationsNow(PackageManagerService pm, Context context, 574 @Nullable List<String> packageNames) { 575 // Create a new object to make sure we don't interfere with the scheduled jobs. 576 // Note that this may still run at the same time with the job scheduled by the 577 // JobScheduler but the scheduler will not be able to cancel it. 578 BackgroundDexOptService bdos = new BackgroundDexOptService(); 579 ArraySet<String> packagesToOptimize; 580 if (packageNames == null) { 581 packagesToOptimize = pm.getOptimizablePackages(); 582 } else { 583 packagesToOptimize = new ArraySet<>(packageNames); 584 } 585 int result = bdos.idleOptimization(pm, packagesToOptimize, context); 586 return result == OPTIMIZE_PROCESSED; 587 } 588 589 @Override onStartJob(JobParameters params)590 public boolean onStartJob(JobParameters params) { 591 if (DEBUG_DEXOPT) { 592 Log.i(TAG, "onStartJob"); 593 } 594 595 // NOTE: PackageManagerService.isStorageLow uses a different set of criteria from 596 // the checks above. This check is not "live" - the value is determined by a background 597 // restart with a period of ~1 minute. 598 PackageManagerService pm = (PackageManagerService)ServiceManager.getService("package"); 599 if (pm.isStorageLow()) { 600 if (DEBUG_DEXOPT) { 601 Log.i(TAG, "Low storage, skipping this run"); 602 } 603 return false; 604 } 605 606 final ArraySet<String> pkgs = pm.getOptimizablePackages(); 607 if (pkgs.isEmpty()) { 608 if (DEBUG_DEXOPT) { 609 Log.i(TAG, "No packages to optimize"); 610 } 611 return false; 612 } 613 614 boolean result; 615 if (params.getJobId() == JOB_POST_BOOT_UPDATE) { 616 result = runPostBootUpdate(params, pm, pkgs); 617 } else { 618 result = runIdleOptimization(params, pm, pkgs); 619 } 620 621 return result; 622 } 623 624 @Override onStopJob(JobParameters params)625 public boolean onStopJob(JobParameters params) { 626 if (DEBUG_DEXOPT) { 627 Log.i(TAG, "onStopJob"); 628 } 629 630 if (params.getJobId() == JOB_POST_BOOT_UPDATE) { 631 mAbortPostBootUpdate.set(true); 632 633 // Do not reschedule. 634 // TODO: We should reschedule if we didn't process all apps, yet. 635 return false; 636 } else { 637 mAbortIdleOptimization.set(true); 638 639 // Reschedule the run. 640 // TODO: Should this be dependent on the stop reason? 641 return true; 642 } 643 } 644 notifyPinService(ArraySet<String> updatedPackages)645 private void notifyPinService(ArraySet<String> updatedPackages) { 646 PinnerService pinnerService = LocalServices.getService(PinnerService.class); 647 if (pinnerService != null) { 648 Log.i(TAG, "Pinning optimized code " + updatedPackages); 649 pinnerService.update(updatedPackages, false /* force */); 650 } 651 } 652 653 public static interface PackagesUpdatedListener { 654 /** Callback when packages have been updated by the bg-dexopt service. */ onPackagesUpdated(ArraySet<String> updatedPackages)655 public void onPackagesUpdated(ArraySet<String> updatedPackages); 656 } 657 addPackagesUpdatedListener(PackagesUpdatedListener listener)658 public static void addPackagesUpdatedListener(PackagesUpdatedListener listener) { 659 synchronized (sPackagesUpdatedListeners) { 660 sPackagesUpdatedListeners.add(listener); 661 } 662 } 663 removePackagesUpdatedListener(PackagesUpdatedListener listener)664 public static void removePackagesUpdatedListener(PackagesUpdatedListener listener) { 665 synchronized (sPackagesUpdatedListeners) { 666 sPackagesUpdatedListeners.remove(listener); 667 } 668 } 669 670 /** Notify all listeners (#addPackagesUpdatedListener) that packages have been updated. */ notifyPackagesUpdated(ArraySet<String> updatedPackages)671 private void notifyPackagesUpdated(ArraySet<String> updatedPackages) { 672 synchronized (sPackagesUpdatedListeners) { 673 for (PackagesUpdatedListener listener : sPackagesUpdatedListeners) { 674 listener.onPackagesUpdated(updatedPackages); 675 } 676 } 677 } 678 getDowngradeUnusedAppsThresholdInMillis()679 private static long getDowngradeUnusedAppsThresholdInMillis() { 680 final String sysPropKey = "pm.dexopt.downgrade_after_inactive_days"; 681 String sysPropValue = SystemProperties.get(sysPropKey); 682 if (sysPropValue == null || sysPropValue.isEmpty()) { 683 Log.w(TAG, "SysProp " + sysPropKey + " not set"); 684 return Long.MAX_VALUE; 685 } 686 return TimeUnit.DAYS.toMillis(Long.parseLong(sysPropValue)); 687 } 688 isBackgroundDexoptDisabled()689 private static boolean isBackgroundDexoptDisabled() { 690 return SystemProperties.getBoolean("pm.dexopt.disable_bg_dexopt" /* key */, 691 false /* default */); 692 } 693 } 694