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