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_ABORT_BY_JOB_SCHEDULER) { 258 Log.w(TAG, "Idle optimizations aborted because of space constraints."); 259 // If we didn't abort we ran to completion (or stopped because of space). 260 // Abandon our timeslice and do not reschedule. 261 jobFinished(jobParams, /* reschedule */ false); 262 } 263 } 264 }.start(); 265 return true; 266 } 267 268 // Optimize the given packages and return the optimization result (one of the OPTIMIZE_* codes). idleOptimization(PackageManagerService pm, ArraySet<String> pkgs, Context context)269 private int idleOptimization(PackageManagerService pm, ArraySet<String> pkgs, 270 Context context) { 271 Log.i(TAG, "Performing idle optimizations"); 272 // If post-boot update is still running, request that it exits early. 273 mExitPostBootUpdate.set(true); 274 mAbortIdleOptimization.set(false); 275 276 long lowStorageThreshold = getLowStorageThreshold(context); 277 // Optimize primary apks. 278 int result = optimizePackages(pm, pkgs, lowStorageThreshold, 279 /*isForPrimaryDex=*/ true); 280 if (result == OPTIMIZE_ABORT_BY_JOB_SCHEDULER) { 281 return result; 282 } 283 if (supportSecondaryDex()) { 284 result = reconcileSecondaryDexFiles(pm.getDexManager()); 285 if (result == OPTIMIZE_ABORT_BY_JOB_SCHEDULER) { 286 return result; 287 } 288 result = optimizePackages(pm, pkgs, lowStorageThreshold, 289 /*isForPrimaryDex=*/ false); 290 } 291 return result; 292 } 293 294 /** 295 * Get the size of the directory. It uses recursion to go over all files. 296 * @param f 297 * @return 298 */ getDirectorySize(File f)299 private long getDirectorySize(File f) { 300 long size = 0; 301 if (f.isDirectory()) { 302 for (File file: f.listFiles()) { 303 size += getDirectorySize(file); 304 } 305 } else { 306 size = f.length(); 307 } 308 return size; 309 } 310 311 /** 312 * Get the size of a package. 313 * @param pkg 314 */ getPackageSize(PackageManagerService pm, String pkg)315 private long getPackageSize(PackageManagerService pm, String pkg) { 316 PackageInfo info = pm.getPackageInfo(pkg, 0, UserHandle.USER_SYSTEM); 317 long size = 0; 318 if (info != null && info.applicationInfo != null) { 319 File path = Paths.get(info.applicationInfo.sourceDir).toFile(); 320 if (path.isFile()) { 321 path = path.getParentFile(); 322 } 323 size += getDirectorySize(path); 324 if (!ArrayUtils.isEmpty(info.applicationInfo.splitSourceDirs)) { 325 for (String splitSourceDir : info.applicationInfo.splitSourceDirs) { 326 path = Paths.get(splitSourceDir).toFile(); 327 if (path.isFile()) { 328 path = path.getParentFile(); 329 } 330 size += getDirectorySize(path); 331 } 332 } 333 return size; 334 } 335 return 0; 336 } 337 optimizePackages(PackageManagerService pm, ArraySet<String> pkgs, long lowStorageThreshold, boolean isForPrimaryDex)338 private int optimizePackages(PackageManagerService pm, ArraySet<String> pkgs, 339 long lowStorageThreshold, boolean isForPrimaryDex) { 340 ArraySet<String> updatedPackages = new ArraySet<>(); 341 Set<String> unusedPackages = pm.getUnusedPackages(mDowngradeUnusedAppsThresholdInMillis); 342 Log.d(TAG, "Unsused Packages " + String.join(",", unusedPackages)); 343 // Only downgrade apps when space is low on device. 344 // Threshold is selected above the lowStorageThreshold so that we can pro-actively clean 345 // up disk before user hits the actual lowStorageThreshold. 346 final long lowStorageThresholdForDowngrade = LOW_THRESHOLD_MULTIPLIER_FOR_DOWNGRADE * 347 lowStorageThreshold; 348 boolean shouldDowngrade = shouldDowngrade(lowStorageThresholdForDowngrade); 349 Log.d(TAG, "Should Downgrade " + shouldDowngrade); 350 boolean dex_opt_performed = false; 351 for (String pkg : pkgs) { 352 int abort_code = abortIdleOptimizations(lowStorageThreshold); 353 if (abort_code == OPTIMIZE_ABORT_BY_JOB_SCHEDULER) { 354 return abort_code; 355 } 356 // Downgrade unused packages. 357 if (unusedPackages.contains(pkg) && shouldDowngrade) { 358 dex_opt_performed = downgradePackage(pm, pkg, isForPrimaryDex); 359 } else { 360 if (abort_code == OPTIMIZE_ABORT_NO_SPACE_LEFT) { 361 // can't dexopt because of low space. 362 continue; 363 } 364 dex_opt_performed = optimizePackage(pm, pkg, isForPrimaryDex); 365 } 366 if (dex_opt_performed) { 367 updatedPackages.add(pkg); 368 } 369 } 370 371 notifyPinService(updatedPackages); 372 return OPTIMIZE_PROCESSED; 373 } 374 375 376 /** 377 * Try to downgrade the package to a smaller compilation filter. 378 * eg. if the package is in speed-profile the package will be downgraded to verify. 379 * @param pm PackageManagerService 380 * @param pkg The package to be downgraded. 381 * @param isForPrimaryDex. Apps can have several dex file, primary and secondary. 382 * @return true if the package was downgraded. 383 */ downgradePackage(PackageManagerService pm, String pkg, boolean isForPrimaryDex)384 private boolean downgradePackage(PackageManagerService pm, String pkg, 385 boolean isForPrimaryDex) { 386 Log.d(TAG, "Downgrading " + pkg); 387 boolean dex_opt_performed = false; 388 int reason = PackageManagerService.REASON_INACTIVE_PACKAGE_DOWNGRADE; 389 int dexoptFlags = DexoptOptions.DEXOPT_BOOT_COMPLETE 390 | DexoptOptions.DEXOPT_IDLE_BACKGROUND_JOB 391 | DexoptOptions.DEXOPT_DOWNGRADE; 392 long package_size_before = getPackageSize(pm, pkg); 393 394 if (isForPrimaryDex) { 395 // This applies for system apps or if packages location is not a directory, i.e. 396 // monolithic install. 397 if (!pm.canHaveOatDir(pkg)) { 398 // For apps that don't have the oat directory, instead of downgrading, 399 // remove their compiler artifacts from dalvik cache. 400 pm.deleteOatArtifactsOfPackage(pkg); 401 } else { 402 dex_opt_performed = performDexOptPrimary(pm, pkg, reason, dexoptFlags); 403 } 404 } else { 405 dex_opt_performed = performDexOptSecondary(pm, pkg, reason, dexoptFlags); 406 } 407 408 if (dex_opt_performed) { 409 StatsLog.write(StatsLog.APP_DOWNGRADED, pkg, package_size_before, 410 getPackageSize(pm, pkg), /*aggressive=*/ false); 411 } 412 return dex_opt_performed; 413 } 414 supportSecondaryDex()415 private boolean supportSecondaryDex() { 416 return (SystemProperties.getBoolean("dalvik.vm.dexopt.secondary", false)); 417 } 418 reconcileSecondaryDexFiles(DexManager dm)419 private int reconcileSecondaryDexFiles(DexManager dm) { 420 // TODO(calin): should we blacklist packages for which we fail to reconcile? 421 for (String p : dm.getAllPackagesWithSecondaryDexFiles()) { 422 if (mAbortIdleOptimization.get()) { 423 return OPTIMIZE_ABORT_BY_JOB_SCHEDULER; 424 } 425 dm.reconcileSecondaryDexFiles(p); 426 } 427 return OPTIMIZE_PROCESSED; 428 } 429 430 /** 431 * 432 * Optimize package if needed. Note that there can be no race between 433 * concurrent jobs because PackageDexOptimizer.performDexOpt is synchronized. 434 * @param pm An instance of PackageManagerService 435 * @param pkg The package to be downgraded. 436 * @param isForPrimaryDex. Apps can have several dex file, primary and secondary. 437 * @return true if the package was downgraded. 438 */ optimizePackage(PackageManagerService pm, String pkg, boolean isForPrimaryDex)439 private boolean optimizePackage(PackageManagerService pm, String pkg, 440 boolean isForPrimaryDex) { 441 int reason = PackageManagerService.REASON_BACKGROUND_DEXOPT; 442 int dexoptFlags = DexoptOptions.DEXOPT_CHECK_FOR_PROFILES_UPDATES 443 | DexoptOptions.DEXOPT_BOOT_COMPLETE 444 | DexoptOptions.DEXOPT_IDLE_BACKGROUND_JOB; 445 446 return isForPrimaryDex 447 ? performDexOptPrimary(pm, pkg, reason, dexoptFlags) 448 : performDexOptSecondary(pm, pkg, reason, dexoptFlags); 449 } 450 performDexOptPrimary(PackageManagerService pm, String pkg, int reason, int dexoptFlags)451 private boolean performDexOptPrimary(PackageManagerService pm, String pkg, int reason, 452 int dexoptFlags) { 453 int result = trackPerformDexOpt(pkg, /*isForPrimaryDex=*/ false, 454 () -> pm.performDexOptWithStatus(new DexoptOptions(pkg, reason, dexoptFlags))); 455 return result == PackageDexOptimizer.DEX_OPT_PERFORMED; 456 } 457 performDexOptSecondary(PackageManagerService pm, String pkg, int reason, int dexoptFlags)458 private boolean performDexOptSecondary(PackageManagerService pm, String pkg, int reason, 459 int dexoptFlags) { 460 DexoptOptions dexoptOptions = new DexoptOptions(pkg, reason, 461 dexoptFlags | DexoptOptions.DEXOPT_ONLY_SECONDARY_DEX); 462 int result = trackPerformDexOpt(pkg, /*isForPrimaryDex=*/ true, 463 () -> pm.performDexOpt(dexoptOptions) 464 ? PackageDexOptimizer.DEX_OPT_PERFORMED : PackageDexOptimizer.DEX_OPT_FAILED 465 ); 466 return result == PackageDexOptimizer.DEX_OPT_PERFORMED; 467 } 468 469 /** 470 * Execute the dexopt wrapper and make sure that if performDexOpt wrapper fails 471 * the package is added to the list of failed packages. 472 * Return one of following result: 473 * {@link PackageDexOptimizer#DEX_OPT_SKIPPED} 474 * {@link PackageDexOptimizer#DEX_OPT_PERFORMED} 475 * {@link PackageDexOptimizer#DEX_OPT_FAILED} 476 */ trackPerformDexOpt(String pkg, boolean isForPrimaryDex, Supplier<Integer> performDexOptWrapper)477 private int trackPerformDexOpt(String pkg, boolean isForPrimaryDex, 478 Supplier<Integer> performDexOptWrapper) { 479 ArraySet<String> sFailedPackageNames = 480 isForPrimaryDex ? sFailedPackageNamesPrimary : sFailedPackageNamesSecondary; 481 synchronized (sFailedPackageNames) { 482 if (sFailedPackageNames.contains(pkg)) { 483 // Skip previously failing package 484 return PackageDexOptimizer.DEX_OPT_SKIPPED; 485 } 486 sFailedPackageNames.add(pkg); 487 } 488 int result = performDexOptWrapper.get(); 489 if (result != PackageDexOptimizer.DEX_OPT_FAILED) { 490 synchronized (sFailedPackageNames) { 491 sFailedPackageNames.remove(pkg); 492 } 493 } 494 return result; 495 } 496 497 // Evaluate whether or not idle optimizations should continue. abortIdleOptimizations(long lowStorageThreshold)498 private int abortIdleOptimizations(long lowStorageThreshold) { 499 if (mAbortIdleOptimization.get()) { 500 // JobScheduler requested an early abort. 501 return OPTIMIZE_ABORT_BY_JOB_SCHEDULER; 502 } 503 long usableSpace = mDataDir.getUsableSpace(); 504 if (usableSpace < lowStorageThreshold) { 505 // Rather bail than completely fill up the disk. 506 Log.w(TAG, "Aborting background dex opt job due to low storage: " + usableSpace); 507 return OPTIMIZE_ABORT_NO_SPACE_LEFT; 508 } 509 510 return OPTIMIZE_CONTINUE; 511 } 512 513 // Evaluate whether apps should be downgraded. shouldDowngrade(long lowStorageThresholdForDowngrade)514 private boolean shouldDowngrade(long lowStorageThresholdForDowngrade) { 515 long usableSpace = mDataDir.getUsableSpace(); 516 if (usableSpace < lowStorageThresholdForDowngrade) { 517 return true; 518 } 519 520 return false; 521 } 522 523 /** 524 * Execute idle optimizations immediately on packages in packageNames. If packageNames is null, 525 * then execute on all packages. 526 */ runIdleOptimizationsNow(PackageManagerService pm, Context context, @Nullable List<String> packageNames)527 public static boolean runIdleOptimizationsNow(PackageManagerService pm, Context context, 528 @Nullable List<String> packageNames) { 529 // Create a new object to make sure we don't interfere with the scheduled jobs. 530 // Note that this may still run at the same time with the job scheduled by the 531 // JobScheduler but the scheduler will not be able to cancel it. 532 BackgroundDexOptService bdos = new BackgroundDexOptService(); 533 ArraySet<String> packagesToOptimize; 534 if (packageNames == null) { 535 packagesToOptimize = pm.getOptimizablePackages(); 536 } else { 537 packagesToOptimize = new ArraySet<>(packageNames); 538 } 539 int result = bdos.idleOptimization(pm, packagesToOptimize, context); 540 return result == OPTIMIZE_PROCESSED; 541 } 542 543 @Override onStartJob(JobParameters params)544 public boolean onStartJob(JobParameters params) { 545 if (DEBUG_DEXOPT) { 546 Log.i(TAG, "onStartJob"); 547 } 548 549 // NOTE: PackageManagerService.isStorageLow uses a different set of criteria from 550 // the checks above. This check is not "live" - the value is determined by a background 551 // restart with a period of ~1 minute. 552 PackageManagerService pm = (PackageManagerService)ServiceManager.getService("package"); 553 if (pm.isStorageLow()) { 554 if (DEBUG_DEXOPT) { 555 Log.i(TAG, "Low storage, skipping this run"); 556 } 557 return false; 558 } 559 560 final ArraySet<String> pkgs = pm.getOptimizablePackages(); 561 if (pkgs.isEmpty()) { 562 if (DEBUG_DEXOPT) { 563 Log.i(TAG, "No packages to optimize"); 564 } 565 return false; 566 } 567 568 boolean result; 569 if (params.getJobId() == JOB_POST_BOOT_UPDATE) { 570 result = runPostBootUpdate(params, pm, pkgs); 571 } else { 572 result = runIdleOptimization(params, pm, pkgs); 573 } 574 575 return result; 576 } 577 578 @Override onStopJob(JobParameters params)579 public boolean onStopJob(JobParameters params) { 580 if (DEBUG_DEXOPT) { 581 Log.i(TAG, "onStopJob"); 582 } 583 584 if (params.getJobId() == JOB_POST_BOOT_UPDATE) { 585 mAbortPostBootUpdate.set(true); 586 587 // Do not reschedule. 588 // TODO: We should reschedule if we didn't process all apps, yet. 589 return false; 590 } else { 591 mAbortIdleOptimization.set(true); 592 593 // Reschedule the run. 594 // TODO: Should this be dependent on the stop reason? 595 return true; 596 } 597 } 598 notifyPinService(ArraySet<String> updatedPackages)599 private void notifyPinService(ArraySet<String> updatedPackages) { 600 PinnerService pinnerService = LocalServices.getService(PinnerService.class); 601 if (pinnerService != null) { 602 Log.i(TAG, "Pinning optimized code " + updatedPackages); 603 pinnerService.update(updatedPackages, false /* force */); 604 } 605 } 606 getDowngradeUnusedAppsThresholdInMillis()607 private static long getDowngradeUnusedAppsThresholdInMillis() { 608 final String sysPropKey = "pm.dexopt.downgrade_after_inactive_days"; 609 String sysPropValue = SystemProperties.get(sysPropKey); 610 if (sysPropValue == null || sysPropValue.isEmpty()) { 611 Log.w(TAG, "SysProp " + sysPropKey + " not set"); 612 return Long.MAX_VALUE; 613 } 614 return TimeUnit.DAYS.toMillis(Long.parseLong(sysPropValue)); 615 } 616 isBackgroundDexoptDisabled()617 private static boolean isBackgroundDexoptDisabled() { 618 return SystemProperties.getBoolean("pm.dexopt.disable_bg_dexopt" /* key */, 619 false /* default */); 620 } 621 } 622