1 /* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.pm; 18 19 import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME; 20 import static com.android.server.pm.dex.ArtStatsLogUtils.BackgroundDexoptJobStatsLogger; 21 22 import android.annotation.IntDef; 23 import android.annotation.NonNull; 24 import android.annotation.Nullable; 25 import android.app.job.JobInfo; 26 import android.app.job.JobParameters; 27 import android.app.job.JobScheduler; 28 import android.content.BroadcastReceiver; 29 import android.content.ComponentName; 30 import android.content.Context; 31 import android.content.Intent; 32 import android.content.IntentFilter; 33 import android.content.pm.PackageInfo; 34 import android.os.BatteryManagerInternal; 35 import android.os.Binder; 36 import android.os.Environment; 37 import android.os.IThermalService; 38 import android.os.PowerManager; 39 import android.os.Process; 40 import android.os.RemoteException; 41 import android.os.ServiceManager; 42 import android.os.SystemClock; 43 import android.os.SystemProperties; 44 import android.os.Trace; 45 import android.os.UserHandle; 46 import android.os.storage.StorageManager; 47 import android.util.ArraySet; 48 import android.util.Log; 49 import android.util.Slog; 50 51 import com.android.internal.annotations.GuardedBy; 52 import com.android.internal.annotations.VisibleForTesting; 53 import com.android.internal.util.ArrayUtils; 54 import com.android.internal.util.FrameworkStatsLog; 55 import com.android.internal.util.IndentingPrintWriter; 56 import com.android.server.LocalServices; 57 import com.android.server.PinnerService; 58 import com.android.server.pm.PackageDexOptimizer.DexOptResult; 59 import com.android.server.pm.dex.DexManager; 60 import com.android.server.pm.dex.DexoptOptions; 61 import com.android.server.utils.TimingsTraceAndSlog; 62 63 import java.io.File; 64 import java.lang.annotation.Retention; 65 import java.lang.annotation.RetentionPolicy; 66 import java.nio.file.Paths; 67 import java.util.ArrayList; 68 import java.util.List; 69 import java.util.Set; 70 import java.util.concurrent.TimeUnit; 71 import java.util.function.Supplier; 72 73 /** 74 * Controls background dex optimization run as idle job or command line. 75 */ 76 public final class BackgroundDexOptService { 77 private static final String TAG = "BackgroundDexOptService"; 78 79 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); 80 81 @VisibleForTesting static final int JOB_IDLE_OPTIMIZE = 800; 82 @VisibleForTesting static final int JOB_POST_BOOT_UPDATE = 801; 83 84 private static final long IDLE_OPTIMIZATION_PERIOD = TimeUnit.DAYS.toMillis(1); 85 86 private static final long CANCELLATION_WAIT_CHECK_INTERVAL_MS = 200; 87 88 private static ComponentName sDexoptServiceName = 89 new ComponentName("android", BackgroundDexOptJobService.class.getName()); 90 91 // Possible return codes of individual optimization steps. 92 /** Ok status: Optimizations finished, All packages were processed, can continue */ 93 public static final int STATUS_OK = 0; 94 /** Optimizations should be aborted. Job scheduler requested it. */ 95 public static final int STATUS_ABORT_BY_CANCELLATION = 1; 96 /** Optimizations should be aborted. No space left on device. */ 97 public static final int STATUS_ABORT_NO_SPACE_LEFT = 2; 98 /** Optimizations should be aborted. Thermal throttling level too high. */ 99 public static final int STATUS_ABORT_THERMAL = 3; 100 /** Battery level too low */ 101 public static final int STATUS_ABORT_BATTERY = 4; 102 /** 103 * {@link PackageDexOptimizer#DEX_OPT_FAILED} case. This state means some packages have failed 104 * compilation during the job. Note that the failure will not be permanent as the next dexopt 105 * job will exclude those failed packages. 106 */ 107 public static final int STATUS_DEX_OPT_FAILED = 5; 108 109 @IntDef(prefix = {"STATUS_"}, 110 value = 111 { 112 STATUS_OK, 113 STATUS_ABORT_BY_CANCELLATION, 114 STATUS_ABORT_NO_SPACE_LEFT, 115 STATUS_ABORT_THERMAL, 116 STATUS_ABORT_BATTERY, 117 STATUS_DEX_OPT_FAILED, 118 }) 119 @Retention(RetentionPolicy.SOURCE) 120 public @interface Status {} 121 122 // Used for calculating space threshold for downgrading unused apps. 123 private static final int LOW_THRESHOLD_MULTIPLIER_FOR_DOWNGRADE = 2; 124 125 // Thermal cutoff value used if one isn't defined by a system property. 126 private static final int THERMAL_CUTOFF_DEFAULT = PowerManager.THERMAL_STATUS_MODERATE; 127 128 private final Injector mInjector; 129 130 private final DexOptHelper mDexOptHelper; 131 132 private final BackgroundDexoptJobStatsLogger mStatsLogger = 133 new BackgroundDexoptJobStatsLogger(); 134 135 private final Object mLock = new Object(); 136 137 // Thread currently running dexopt. This will be null if dexopt is not running. 138 // The thread running dexopt make sure to set this into null when the pending dexopt is 139 // completed. 140 @GuardedBy("mLock") @Nullable private Thread mDexOptThread; 141 142 // Thread currently cancelling dexopt. This thread is in blocked wait state until 143 // cancellation is done. Only this thread can change states for control. The other threads, if 144 // need to wait for cancellation, should just wait without doing any control. 145 @GuardedBy("mLock") @Nullable private Thread mDexOptCancellingThread; 146 147 // Tells whether post boot update is completed or not. 148 @GuardedBy("mLock") private boolean mFinishedPostBootUpdate; 149 150 @GuardedBy("mLock") @Status private int mLastExecutionStatus = STATUS_OK; 151 152 @GuardedBy("mLock") private long mLastExecutionStartTimeMs; 153 @GuardedBy("mLock") private long mLastExecutionDurationIncludingSleepMs; 154 @GuardedBy("mLock") private long mLastExecutionStartUptimeMs; 155 @GuardedBy("mLock") private long mLastExecutionDurationMs; 156 157 // Keeps packages cancelled from PDO for last session. This is for debugging. 158 @GuardedBy("mLock") 159 private final ArraySet<String> mLastCancelledPackages = new ArraySet<String>(); 160 161 /** 162 * Set of failed packages remembered across job runs. 163 */ 164 @GuardedBy("mLock") 165 private final ArraySet<String> mFailedPackageNamesPrimary = new ArraySet<String>(); 166 @GuardedBy("mLock") 167 private final ArraySet<String> mFailedPackageNamesSecondary = new ArraySet<String>(); 168 169 private final long mDowngradeUnusedAppsThresholdInMillis; 170 171 private List<PackagesUpdatedListener> mPackagesUpdatedListeners = new ArrayList<>(); 172 173 private int mThermalStatusCutoff = THERMAL_CUTOFF_DEFAULT; 174 175 /** Listener for monitoring package change due to dexopt. */ 176 public interface PackagesUpdatedListener { 177 /** Called when the packages are updated through dexopt */ onPackagesUpdated(ArraySet<String> updatedPackages)178 void onPackagesUpdated(ArraySet<String> updatedPackages); 179 } 180 BackgroundDexOptService( Context context, DexManager dexManager, PackageManagerService pm)181 public BackgroundDexOptService( 182 Context context, DexManager dexManager, PackageManagerService pm) { 183 this(new Injector(context, dexManager, pm)); 184 } 185 186 @VisibleForTesting BackgroundDexOptService(Injector injector)187 public BackgroundDexOptService(Injector injector) { 188 mInjector = injector; 189 mDexOptHelper = mInjector.getDexOptHelper(); 190 LocalServices.addService(BackgroundDexOptService.class, this); 191 mDowngradeUnusedAppsThresholdInMillis = mInjector.getDowngradeUnusedAppsThresholdInMillis(); 192 } 193 194 /** Start scheduling job after boot completion */ systemReady()195 public void systemReady() { 196 if (mInjector.isBackgroundDexOptDisabled()) { 197 return; 198 } 199 200 mInjector.getContext().registerReceiver(new BroadcastReceiver() { 201 @Override 202 public void onReceive(Context context, Intent intent) { 203 mInjector.getContext().unregisterReceiver(this); 204 // queue both job. JOB_IDLE_OPTIMIZE will not start until JOB_POST_BOOT_UPDATE is 205 // completed. 206 scheduleAJob(JOB_POST_BOOT_UPDATE); 207 scheduleAJob(JOB_IDLE_OPTIMIZE); 208 if (DEBUG) { 209 Slog.d(TAG, "BootBgDexopt scheduled"); 210 } 211 } 212 }, new IntentFilter(Intent.ACTION_BOOT_COMPLETED)); 213 } 214 215 /** Dump the current state */ dump(IndentingPrintWriter writer)216 public void dump(IndentingPrintWriter writer) { 217 boolean disabled = mInjector.isBackgroundDexOptDisabled(); 218 writer.print("enabled:"); 219 writer.println(!disabled); 220 if (disabled) { 221 return; 222 } 223 synchronized (mLock) { 224 writer.print("mDexOptThread:"); 225 writer.println(mDexOptThread); 226 writer.print("mDexOptCancellingThread:"); 227 writer.println(mDexOptCancellingThread); 228 writer.print("mFinishedPostBootUpdate:"); 229 writer.println(mFinishedPostBootUpdate); 230 writer.print("mLastExecutionStatus:"); 231 writer.println(mLastExecutionStatus); 232 writer.print("mLastExecutionStartTimeMs:"); 233 writer.println(mLastExecutionStartTimeMs); 234 writer.print("mLastExecutionDurationIncludingSleepMs:"); 235 writer.println(mLastExecutionDurationIncludingSleepMs); 236 writer.print("mLastExecutionStartUptimeMs:"); 237 writer.println(mLastExecutionStartUptimeMs); 238 writer.print("mLastExecutionDurationMs:"); 239 writer.println(mLastExecutionDurationMs); 240 writer.print("now:"); 241 writer.println(SystemClock.elapsedRealtime()); 242 writer.print("mLastCancelledPackages:"); 243 writer.println(String.join(",", mLastCancelledPackages)); 244 writer.print("mFailedPackageNamesPrimary:"); 245 writer.println(String.join(",", mFailedPackageNamesPrimary)); 246 writer.print("mFailedPackageNamesSecondary:"); 247 writer.println(String.join(",", mFailedPackageNamesSecondary)); 248 } 249 } 250 251 /** Gets the instance of the service */ getService()252 public static BackgroundDexOptService getService() { 253 return LocalServices.getService(BackgroundDexOptService.class); 254 } 255 256 /** 257 * Executes the background dexopt job immediately for selected packages or all packages. 258 * 259 * <p>This is only for shell command and only root or shell user can use this. 260 * 261 * @param packageNames dex optimize the passed packages in the given order, or all packages in 262 * the default order if null 263 * 264 * @return true if dex optimization is complete. false if the task is cancelled or if there was 265 * an error. 266 */ runBackgroundDexoptJob(@ullable List<String> packageNames)267 public boolean runBackgroundDexoptJob(@Nullable List<String> packageNames) { 268 enforceRootOrShell(); 269 long identity = Binder.clearCallingIdentity(); 270 try { 271 synchronized (mLock) { 272 // Do not cancel and wait for completion if there is pending task. 273 waitForDexOptThreadToFinishLocked(); 274 resetStatesForNewDexOptRunLocked(Thread.currentThread()); 275 } 276 PackageManagerService pm = mInjector.getPackageManagerService(); 277 List<String> packagesToOptimize; 278 if (packageNames == null) { 279 packagesToOptimize = mDexOptHelper.getOptimizablePackages(pm.snapshotComputer()); 280 } else { 281 packagesToOptimize = packageNames; 282 } 283 return runIdleOptimization(pm, packagesToOptimize, /* isPostBootUpdate= */ false); 284 } finally { 285 Binder.restoreCallingIdentity(identity); 286 markDexOptCompleted(); 287 } 288 } 289 290 /** 291 * Cancels currently running any idle optimization tasks started from JobScheduler 292 * or runIdleOptimizationsNow call. 293 * 294 * <p>This is only for shell command and only root or shell user can use this. 295 */ cancelBackgroundDexoptJob()296 public void cancelBackgroundDexoptJob() { 297 enforceRootOrShell(); 298 Binder.withCleanCallingIdentity(() -> cancelDexOptAndWaitForCompletion()); 299 } 300 301 /** Adds listener for package update */ addPackagesUpdatedListener(PackagesUpdatedListener listener)302 public void addPackagesUpdatedListener(PackagesUpdatedListener listener) { 303 synchronized (mLock) { 304 mPackagesUpdatedListeners.add(listener); 305 } 306 } 307 308 /** Removes package update listener */ removePackagesUpdatedListener(PackagesUpdatedListener listener)309 public void removePackagesUpdatedListener(PackagesUpdatedListener listener) { 310 synchronized (mLock) { 311 mPackagesUpdatedListeners.remove(listener); 312 } 313 } 314 315 /** 316 * Notifies package change and removes the package from the failed package list so that 317 * the package can run dexopt again. 318 */ notifyPackageChanged(String packageName)319 public void notifyPackageChanged(String packageName) { 320 // The idle maintenance job skips packages which previously failed to 321 // compile. The given package has changed and may successfully compile 322 // now. Remove it from the list of known failing packages. 323 synchronized (mLock) { 324 mFailedPackageNamesPrimary.remove(packageName); 325 mFailedPackageNamesSecondary.remove(packageName); 326 } 327 } 328 329 /** For BackgroundDexOptJobService to dispatch onStartJob event */ onStartJob(BackgroundDexOptJobService job, JobParameters params)330 /* package */ boolean onStartJob(BackgroundDexOptJobService job, JobParameters params) { 331 Slog.i(TAG, "onStartJob:" + params.getJobId()); 332 333 boolean isPostBootUpdateJob = params.getJobId() == JOB_POST_BOOT_UPDATE; 334 // NOTE: PackageManagerService.isStorageLow uses a different set of criteria from 335 // the checks above. This check is not "live" - the value is determined by a background 336 // restart with a period of ~1 minute. 337 PackageManagerService pm = mInjector.getPackageManagerService(); 338 if (pm.isStorageLow()) { 339 Slog.w(TAG, "Low storage, skipping this run"); 340 markPostBootUpdateCompleted(params); 341 return false; 342 } 343 344 List<String> pkgs = mDexOptHelper.getOptimizablePackages(pm.snapshotComputer()); 345 if (pkgs.isEmpty()) { 346 Slog.i(TAG, "No packages to optimize"); 347 markPostBootUpdateCompleted(params); 348 return false; 349 } 350 351 mThermalStatusCutoff = mInjector.getDexOptThermalCutoff(); 352 353 synchronized (mLock) { 354 if (mDexOptThread != null && mDexOptThread.isAlive()) { 355 // Other task is already running. 356 return false; 357 } 358 if (!isPostBootUpdateJob && !mFinishedPostBootUpdate) { 359 // Post boot job not finished yet. Run post boot job first. 360 return false; 361 } 362 resetStatesForNewDexOptRunLocked(mInjector.createAndStartThread( 363 "BackgroundDexOptService_" + (isPostBootUpdateJob ? "PostBoot" : "Idle"), 364 () -> { 365 TimingsTraceAndSlog tr = 366 new TimingsTraceAndSlog(TAG, Trace.TRACE_TAG_PACKAGE_MANAGER); 367 tr.traceBegin("jobExecution"); 368 boolean completed = false; 369 try { 370 completed = runIdleOptimization( 371 pm, pkgs, params.getJobId() == JOB_POST_BOOT_UPDATE); 372 } finally { // Those cleanup should be done always. 373 tr.traceEnd(); 374 Slog.i(TAG, 375 "dexopt finishing. jobid:" + params.getJobId() 376 + " completed:" + completed); 377 378 writeStatsLog(params); 379 380 if (params.getJobId() == JOB_POST_BOOT_UPDATE) { 381 if (completed) { 382 markPostBootUpdateCompleted(params); 383 } 384 // Reschedule when cancelled 385 job.jobFinished(params, !completed); 386 } else { 387 // Periodic job 388 job.jobFinished(params, false /* reschedule */); 389 } 390 markDexOptCompleted(); 391 } 392 })); 393 } 394 return true; 395 } 396 397 /** For BackgroundDexOptJobService to dispatch onStopJob event */ onStopJob(BackgroundDexOptJobService job, JobParameters params)398 /* package */ boolean onStopJob(BackgroundDexOptJobService job, JobParameters params) { 399 Slog.i(TAG, "onStopJob:" + params.getJobId()); 400 // This cannot block as it is in main thread, thus dispatch to a newly created thread and 401 // cancel it from there. 402 // As this event does not happen often, creating a new thread is justified rather than 403 // having one thread kept permanently. 404 mInjector.createAndStartThread("DexOptCancel", this::cancelDexOptAndWaitForCompletion); 405 // Always reschedule for cancellation. 406 return true; 407 } 408 409 /** 410 * Cancels pending dexopt and wait for completion of the cancellation. This can block the caller 411 * until cancellation is done. 412 */ cancelDexOptAndWaitForCompletion()413 private void cancelDexOptAndWaitForCompletion() { 414 synchronized (mLock) { 415 if (mDexOptThread == null) { 416 return; 417 } 418 if (mDexOptCancellingThread != null && mDexOptCancellingThread.isAlive()) { 419 // No control, just wait 420 waitForDexOptThreadToFinishLocked(); 421 // Do not wait for other cancellation's complete. That will be handled by the next 422 // start flow. 423 return; 424 } 425 mDexOptCancellingThread = Thread.currentThread(); 426 // Take additional caution to make sure that we do not leave this call 427 // with controlDexOptBlockingLocked(true) state. 428 try { 429 controlDexOptBlockingLocked(true); 430 waitForDexOptThreadToFinishLocked(); 431 } finally { 432 // Reset to default states regardless of previous states 433 mDexOptCancellingThread = null; 434 mDexOptThread = null; 435 controlDexOptBlockingLocked(false); 436 mLock.notifyAll(); 437 } 438 } 439 } 440 441 @GuardedBy("mLock") waitForDexOptThreadToFinishLocked()442 private void waitForDexOptThreadToFinishLocked() { 443 TimingsTraceAndSlog tr = new TimingsTraceAndSlog(TAG, Trace.TRACE_TAG_PACKAGE_MANAGER); 444 tr.traceBegin("waitForDexOptThreadToFinishLocked"); 445 try { 446 // Wait but check in regular internal to see if the thread is still alive. 447 while (mDexOptThread != null && mDexOptThread.isAlive()) { 448 mLock.wait(CANCELLATION_WAIT_CHECK_INTERVAL_MS); 449 } 450 } catch (InterruptedException e) { 451 Slog.w(TAG, "Interrupted while waiting for dexopt thread"); 452 Thread.currentThread().interrupt(); 453 } 454 tr.traceEnd(); 455 } 456 markDexOptCompleted()457 private void markDexOptCompleted() { 458 synchronized (mLock) { 459 if (mDexOptThread != Thread.currentThread()) { 460 throw new IllegalStateException( 461 "Only mDexOptThread can mark completion, mDexOptThread:" + mDexOptThread 462 + " current:" + Thread.currentThread()); 463 } 464 mDexOptThread = null; 465 // Other threads may be waiting for completion. 466 mLock.notifyAll(); 467 } 468 } 469 470 @GuardedBy("mLock") resetStatesForNewDexOptRunLocked(Thread thread)471 private void resetStatesForNewDexOptRunLocked(Thread thread) { 472 mDexOptThread = thread; 473 mLastCancelledPackages.clear(); 474 controlDexOptBlockingLocked(false); 475 } 476 enforceRootOrShell()477 private void enforceRootOrShell() { 478 int uid = Binder.getCallingUid(); 479 if (uid != Process.ROOT_UID && uid != Process.SHELL_UID) { 480 throw new SecurityException("Should be shell or root user"); 481 } 482 } 483 484 @GuardedBy("mLock") controlDexOptBlockingLocked(boolean block)485 private void controlDexOptBlockingLocked(boolean block) { 486 PackageManagerService pm = mInjector.getPackageManagerService(); 487 mDexOptHelper.controlDexOptBlocking(block); 488 } 489 scheduleAJob(int jobId)490 private void scheduleAJob(int jobId) { 491 JobScheduler js = mInjector.getJobScheduler(); 492 JobInfo.Builder builder = 493 new JobInfo.Builder(jobId, sDexoptServiceName).setRequiresDeviceIdle(true); 494 if (jobId == JOB_IDLE_OPTIMIZE) { 495 builder.setRequiresCharging(true).setPeriodic(IDLE_OPTIMIZATION_PERIOD); 496 } 497 js.schedule(builder.build()); 498 } 499 getLowStorageThreshold()500 private long getLowStorageThreshold() { 501 long lowThreshold = mInjector.getDataDirStorageLowBytes(); 502 if (lowThreshold == 0) { 503 Slog.e(TAG, "Invalid low storage threshold"); 504 } 505 506 return lowThreshold; 507 } 508 logStatus(int status)509 private void logStatus(int status) { 510 switch (status) { 511 case STATUS_OK: 512 Slog.i(TAG, "Idle optimizations completed."); 513 break; 514 case STATUS_ABORT_NO_SPACE_LEFT: 515 Slog.w(TAG, "Idle optimizations aborted because of space constraints."); 516 break; 517 case STATUS_ABORT_BY_CANCELLATION: 518 Slog.w(TAG, "Idle optimizations aborted by cancellation."); 519 break; 520 case STATUS_ABORT_THERMAL: 521 Slog.w(TAG, "Idle optimizations aborted by thermal throttling."); 522 break; 523 case STATUS_ABORT_BATTERY: 524 Slog.w(TAG, "Idle optimizations aborted by low battery."); 525 break; 526 case STATUS_DEX_OPT_FAILED: 527 Slog.w(TAG, "Idle optimizations failed from dexopt."); 528 break; 529 default: 530 Slog.w(TAG, "Idle optimizations ended with unexpected code: " + status); 531 break; 532 } 533 } 534 535 /** 536 * Returns whether we've successfully run the job. Note that it will return true even if some 537 * packages may have failed compiling. 538 */ runIdleOptimization( PackageManagerService pm, List<String> pkgs, boolean isPostBootUpdate)539 private boolean runIdleOptimization( 540 PackageManagerService pm, List<String> pkgs, boolean isPostBootUpdate) { 541 synchronized (mLock) { 542 mLastExecutionStartTimeMs = SystemClock.elapsedRealtime(); 543 mLastExecutionDurationIncludingSleepMs = -1; 544 mLastExecutionStartUptimeMs = SystemClock.uptimeMillis(); 545 mLastExecutionDurationMs = -1; 546 } 547 long lowStorageThreshold = getLowStorageThreshold(); 548 int status = idleOptimizePackages(pm, pkgs, lowStorageThreshold, isPostBootUpdate); 549 logStatus(status); 550 synchronized (mLock) { 551 mLastExecutionStatus = status; 552 mLastExecutionDurationIncludingSleepMs = 553 SystemClock.elapsedRealtime() - mLastExecutionStartTimeMs; 554 mLastExecutionDurationMs = SystemClock.uptimeMillis() - mLastExecutionStartUptimeMs; 555 } 556 557 return status == STATUS_OK || status == STATUS_DEX_OPT_FAILED; 558 } 559 560 /** Gets the size of the directory. It uses recursion to go over all files. */ getDirectorySize(File f)561 private long getDirectorySize(File f) { 562 long size = 0; 563 if (f.isDirectory()) { 564 for (File file : f.listFiles()) { 565 size += getDirectorySize(file); 566 } 567 } else { 568 size = f.length(); 569 } 570 return size; 571 } 572 573 /** Gets the size of a package. */ getPackageSize(@onNull Computer snapshot, String pkg)574 private long getPackageSize(@NonNull Computer snapshot, String pkg) { 575 PackageInfo info = snapshot.getPackageInfo(pkg, 0, UserHandle.USER_SYSTEM); 576 long size = 0; 577 if (info != null && info.applicationInfo != null) { 578 File path = Paths.get(info.applicationInfo.sourceDir).toFile(); 579 if (path.isFile()) { 580 path = path.getParentFile(); 581 } 582 size += getDirectorySize(path); 583 if (!ArrayUtils.isEmpty(info.applicationInfo.splitSourceDirs)) { 584 for (String splitSourceDir : info.applicationInfo.splitSourceDirs) { 585 path = Paths.get(splitSourceDir).toFile(); 586 if (path.isFile()) { 587 path = path.getParentFile(); 588 } 589 size += getDirectorySize(path); 590 } 591 } 592 return size; 593 } 594 return 0; 595 } 596 597 @Status idleOptimizePackages(PackageManagerService pm, List<String> pkgs, long lowStorageThreshold, boolean isPostBootUpdate)598 private int idleOptimizePackages(PackageManagerService pm, List<String> pkgs, 599 long lowStorageThreshold, boolean isPostBootUpdate) { 600 ArraySet<String> updatedPackages = new ArraySet<>(); 601 602 try { 603 boolean supportSecondaryDex = mInjector.supportSecondaryDex(); 604 605 if (supportSecondaryDex) { 606 @Status int result = reconcileSecondaryDexFiles(); 607 if (result != STATUS_OK) { 608 return result; 609 } 610 } 611 612 // Only downgrade apps when space is low on device. 613 // Threshold is selected above the lowStorageThreshold so that we can pro-actively clean 614 // up disk before user hits the actual lowStorageThreshold. 615 long lowStorageThresholdForDowngrade = 616 LOW_THRESHOLD_MULTIPLIER_FOR_DOWNGRADE * lowStorageThreshold; 617 boolean shouldDowngrade = shouldDowngrade(lowStorageThresholdForDowngrade); 618 if (DEBUG) { 619 Slog.d(TAG, "Should Downgrade " + shouldDowngrade); 620 } 621 if (shouldDowngrade) { 622 final Computer snapshot = pm.snapshotComputer(); 623 Set<String> unusedPackages = 624 snapshot.getUnusedPackages(mDowngradeUnusedAppsThresholdInMillis); 625 if (DEBUG) { 626 Slog.d(TAG, "Unsused Packages " + String.join(",", unusedPackages)); 627 } 628 629 if (!unusedPackages.isEmpty()) { 630 for (String pkg : unusedPackages) { 631 @Status int abortCode = abortIdleOptimizations(/*lowStorageThreshold*/ -1); 632 if (abortCode != STATUS_OK) { 633 // Should be aborted by the scheduler. 634 return abortCode; 635 } 636 @DexOptResult 637 int downgradeResult = downgradePackage(snapshot, pm, pkg, 638 /* isForPrimaryDex= */ true, isPostBootUpdate); 639 if (downgradeResult == PackageDexOptimizer.DEX_OPT_PERFORMED) { 640 updatedPackages.add(pkg); 641 } 642 @Status 643 int status = convertPackageDexOptimizerStatusToInternal(downgradeResult); 644 if (status != STATUS_OK) { 645 return status; 646 } 647 if (supportSecondaryDex) { 648 downgradeResult = downgradePackage(snapshot, pm, pkg, 649 /* isForPrimaryDex= */ false, isPostBootUpdate); 650 status = convertPackageDexOptimizerStatusToInternal(downgradeResult); 651 if (status != STATUS_OK) { 652 return status; 653 } 654 } 655 } 656 657 pkgs = new ArrayList<>(pkgs); 658 pkgs.removeAll(unusedPackages); 659 } 660 } 661 662 return optimizePackages(pkgs, lowStorageThreshold, updatedPackages, isPostBootUpdate); 663 } finally { 664 // Always let the pinner service know about changes. 665 notifyPinService(updatedPackages); 666 // Only notify IORap the primary dex opt, because we don't want to 667 // invalidate traces unnecessary due to b/161633001 and that it's 668 // better to have a trace than no trace at all. 669 notifyPackagesUpdated(updatedPackages); 670 } 671 } 672 673 @Status optimizePackages(List<String> pkgs, long lowStorageThreshold, ArraySet<String> updatedPackages, boolean isPostBootUpdate)674 private int optimizePackages(List<String> pkgs, long lowStorageThreshold, 675 ArraySet<String> updatedPackages, boolean isPostBootUpdate) { 676 boolean supportSecondaryDex = mInjector.supportSecondaryDex(); 677 678 // Keep the error if there is any error from any package. 679 @Status int status = STATUS_OK; 680 681 // Other than cancellation, all packages will be processed even if an error happens 682 // in a package. 683 for (String pkg : pkgs) { 684 int abortCode = abortIdleOptimizations(lowStorageThreshold); 685 if (abortCode != STATUS_OK) { 686 // Either aborted by the scheduler or no space left. 687 return abortCode; 688 } 689 690 @DexOptResult 691 int primaryResult = optimizePackage(pkg, true /* isForPrimaryDex */, isPostBootUpdate); 692 if (primaryResult == PackageDexOptimizer.DEX_OPT_CANCELLED) { 693 return STATUS_ABORT_BY_CANCELLATION; 694 } 695 if (primaryResult == PackageDexOptimizer.DEX_OPT_PERFORMED) { 696 updatedPackages.add(pkg); 697 } else if (primaryResult == PackageDexOptimizer.DEX_OPT_FAILED) { 698 status = convertPackageDexOptimizerStatusToInternal(primaryResult); 699 } 700 701 if (!supportSecondaryDex) { 702 continue; 703 } 704 705 @DexOptResult 706 int secondaryResult = 707 optimizePackage(pkg, false /* isForPrimaryDex */, isPostBootUpdate); 708 if (secondaryResult == PackageDexOptimizer.DEX_OPT_CANCELLED) { 709 return STATUS_ABORT_BY_CANCELLATION; 710 } 711 if (secondaryResult == PackageDexOptimizer.DEX_OPT_FAILED) { 712 status = convertPackageDexOptimizerStatusToInternal(secondaryResult); 713 } 714 } 715 return status; 716 } 717 718 /** 719 * Try to downgrade the package to a smaller compilation filter. 720 * eg. if the package is in speed-profile the package will be downgraded to verify. 721 * @param pm PackageManagerService 722 * @param pkg The package to be downgraded. 723 * @param isForPrimaryDex Apps can have several dex file, primary and secondary. 724 * @return PackageDexOptimizer.DEX_* 725 */ 726 @DexOptResult downgradePackage(@onNull Computer snapshot, PackageManagerService pm, String pkg, boolean isForPrimaryDex, boolean isPostBootUpdate)727 private int downgradePackage(@NonNull Computer snapshot, PackageManagerService pm, String pkg, 728 boolean isForPrimaryDex, boolean isPostBootUpdate) { 729 if (DEBUG) { 730 Slog.d(TAG, "Downgrading " + pkg); 731 } 732 if (isCancelling()) { 733 return PackageDexOptimizer.DEX_OPT_CANCELLED; 734 } 735 int reason = PackageManagerService.REASON_INACTIVE_PACKAGE_DOWNGRADE; 736 int dexoptFlags = DexoptOptions.DEXOPT_BOOT_COMPLETE | DexoptOptions.DEXOPT_DOWNGRADE; 737 if (!isPostBootUpdate) { 738 dexoptFlags |= DexoptOptions.DEXOPT_IDLE_BACKGROUND_JOB; 739 } 740 long package_size_before = getPackageSize(snapshot, pkg); 741 int result = PackageDexOptimizer.DEX_OPT_SKIPPED; 742 if (isForPrimaryDex || PLATFORM_PACKAGE_NAME.equals(pkg)) { 743 // This applies for system apps or if packages location is not a directory, i.e. 744 // monolithic install. 745 if (!pm.canHaveOatDir(snapshot, pkg)) { 746 // For apps that don't have the oat directory, instead of downgrading, 747 // remove their compiler artifacts from dalvik cache. 748 pm.deleteOatArtifactsOfPackage(snapshot, pkg); 749 } else { 750 result = performDexOptPrimary(pkg, reason, dexoptFlags); 751 } 752 } else { 753 result = performDexOptSecondary(pkg, reason, dexoptFlags); 754 } 755 756 if (result == PackageDexOptimizer.DEX_OPT_PERFORMED) { 757 final Computer newSnapshot = pm.snapshotComputer(); 758 FrameworkStatsLog.write(FrameworkStatsLog.APP_DOWNGRADED, pkg, package_size_before, 759 getPackageSize(newSnapshot, pkg), /*aggressive=*/false); 760 } 761 return result; 762 } 763 764 @Status reconcileSecondaryDexFiles()765 private int reconcileSecondaryDexFiles() { 766 // TODO(calin): should we denylist packages for which we fail to reconcile? 767 for (String p : mInjector.getDexManager().getAllPackagesWithSecondaryDexFiles()) { 768 if (isCancelling()) { 769 return STATUS_ABORT_BY_CANCELLATION; 770 } 771 mInjector.getDexManager().reconcileSecondaryDexFiles(p); 772 } 773 return STATUS_OK; 774 } 775 776 /** 777 * 778 * Optimize package if needed. Note that there can be no race between 779 * concurrent jobs because PackageDexOptimizer.performDexOpt is synchronized. 780 * @param pkg The package to be downgraded. 781 * @param isForPrimaryDex Apps can have several dex file, primary and secondary. 782 * @param isPostBootUpdate is post boot update or not. 783 * @return PackageDexOptimizer#DEX_OPT_* 784 */ 785 @DexOptResult optimizePackage(String pkg, boolean isForPrimaryDex, boolean isPostBootUpdate)786 private int optimizePackage(String pkg, boolean isForPrimaryDex, boolean isPostBootUpdate) { 787 int reason = isPostBootUpdate ? PackageManagerService.REASON_POST_BOOT 788 : PackageManagerService.REASON_BACKGROUND_DEXOPT; 789 int dexoptFlags = DexoptOptions.DEXOPT_BOOT_COMPLETE; 790 if (!isPostBootUpdate) { 791 dexoptFlags |= DexoptOptions.DEXOPT_CHECK_FOR_PROFILES_UPDATES 792 | DexoptOptions.DEXOPT_IDLE_BACKGROUND_JOB; 793 } 794 795 // System server share the same code path as primary dex files. 796 // PackageManagerService will select the right optimization path for it. 797 if (isForPrimaryDex || PLATFORM_PACKAGE_NAME.equals(pkg)) { 798 return performDexOptPrimary(pkg, reason, dexoptFlags); 799 } else { 800 return performDexOptSecondary(pkg, reason, dexoptFlags); 801 } 802 } 803 804 @DexOptResult performDexOptPrimary(String pkg, int reason, int dexoptFlags)805 private int performDexOptPrimary(String pkg, int reason, int dexoptFlags) { 806 DexoptOptions dexoptOptions = new DexoptOptions(pkg, reason, dexoptFlags); 807 return trackPerformDexOpt(pkg, /*isForPrimaryDex=*/true, 808 () -> mDexOptHelper.performDexOptWithStatus(dexoptOptions)); 809 } 810 811 @DexOptResult performDexOptSecondary(String pkg, int reason, int dexoptFlags)812 private int performDexOptSecondary(String pkg, int reason, int dexoptFlags) { 813 DexoptOptions dexoptOptions = new DexoptOptions( 814 pkg, reason, dexoptFlags | DexoptOptions.DEXOPT_ONLY_SECONDARY_DEX); 815 return trackPerformDexOpt(pkg, /*isForPrimaryDex=*/false, 816 () 817 -> mDexOptHelper.performDexOpt(dexoptOptions) 818 ? PackageDexOptimizer.DEX_OPT_PERFORMED 819 : PackageDexOptimizer.DEX_OPT_FAILED); 820 } 821 822 /** 823 * Execute the dexopt wrapper and make sure that if performDexOpt wrapper fails 824 * the package is added to the list of failed packages. 825 * Return one of following result: 826 * {@link PackageDexOptimizer#DEX_OPT_SKIPPED} 827 * {@link PackageDexOptimizer#DEX_OPT_CANCELLED} 828 * {@link PackageDexOptimizer#DEX_OPT_PERFORMED} 829 * {@link PackageDexOptimizer#DEX_OPT_FAILED} 830 */ 831 @DexOptResult trackPerformDexOpt( String pkg, boolean isForPrimaryDex, Supplier<Integer> performDexOptWrapper)832 private int trackPerformDexOpt( 833 String pkg, boolean isForPrimaryDex, Supplier<Integer> performDexOptWrapper) { 834 ArraySet<String> failedPackageNames; 835 synchronized (mLock) { 836 failedPackageNames = 837 isForPrimaryDex ? mFailedPackageNamesPrimary : mFailedPackageNamesSecondary; 838 if (failedPackageNames.contains(pkg)) { 839 // Skip previously failing package 840 return PackageDexOptimizer.DEX_OPT_SKIPPED; 841 } 842 } 843 int result = performDexOptWrapper.get(); 844 if (result == PackageDexOptimizer.DEX_OPT_FAILED) { 845 synchronized (mLock) { 846 failedPackageNames.add(pkg); 847 } 848 } else if (result == PackageDexOptimizer.DEX_OPT_CANCELLED) { 849 synchronized (mLock) { 850 mLastCancelledPackages.add(pkg); 851 } 852 } 853 return result; 854 } 855 856 @Status convertPackageDexOptimizerStatusToInternal(@exOptResult int pdoStatus)857 private int convertPackageDexOptimizerStatusToInternal(@DexOptResult int pdoStatus) { 858 switch (pdoStatus) { 859 case PackageDexOptimizer.DEX_OPT_CANCELLED: 860 return STATUS_ABORT_BY_CANCELLATION; 861 case PackageDexOptimizer.DEX_OPT_FAILED: 862 return STATUS_DEX_OPT_FAILED; 863 case PackageDexOptimizer.DEX_OPT_PERFORMED: 864 case PackageDexOptimizer.DEX_OPT_SKIPPED: 865 return STATUS_OK; 866 default: 867 Slog.e(TAG, "Unkknown error code from PackageDexOptimizer:" + pdoStatus, 868 new RuntimeException()); 869 return STATUS_DEX_OPT_FAILED; 870 } 871 } 872 873 /** Evaluate whether or not idle optimizations should continue. */ 874 @Status abortIdleOptimizations(long lowStorageThreshold)875 private int abortIdleOptimizations(long lowStorageThreshold) { 876 if (isCancelling()) { 877 // JobScheduler requested an early abort. 878 return STATUS_ABORT_BY_CANCELLATION; 879 } 880 881 // Abort background dexopt if the device is in a moderate or stronger thermal throttling 882 // state. 883 int thermalStatus = mInjector.getCurrentThermalStatus(); 884 if (DEBUG) { 885 Log.d(TAG, "Thermal throttling status during bgdexopt: " + thermalStatus); 886 } 887 if (thermalStatus >= mThermalStatusCutoff) { 888 return STATUS_ABORT_THERMAL; 889 } 890 891 if (mInjector.isBatteryLevelLow()) { 892 return STATUS_ABORT_BATTERY; 893 } 894 895 long usableSpace = mInjector.getDataDirUsableSpace(); 896 if (usableSpace < lowStorageThreshold) { 897 // Rather bail than completely fill up the disk. 898 Slog.w(TAG, "Aborting background dex opt job due to low storage: " + usableSpace); 899 return STATUS_ABORT_NO_SPACE_LEFT; 900 } 901 902 return STATUS_OK; 903 } 904 905 // Evaluate whether apps should be downgraded. shouldDowngrade(long lowStorageThresholdForDowngrade)906 private boolean shouldDowngrade(long lowStorageThresholdForDowngrade) { 907 if (mInjector.getDataDirUsableSpace() < lowStorageThresholdForDowngrade) { 908 return true; 909 } 910 911 return false; 912 } 913 isCancelling()914 private boolean isCancelling() { 915 synchronized (mLock) { 916 return mDexOptCancellingThread != null; 917 } 918 } 919 markPostBootUpdateCompleted(JobParameters params)920 private void markPostBootUpdateCompleted(JobParameters params) { 921 if (params.getJobId() != JOB_POST_BOOT_UPDATE) { 922 return; 923 } 924 synchronized (mLock) { 925 if (!mFinishedPostBootUpdate) { 926 mFinishedPostBootUpdate = true; 927 } 928 } 929 // Safe to do this outside lock. 930 mInjector.getJobScheduler().cancel(JOB_POST_BOOT_UPDATE); 931 } 932 notifyPinService(ArraySet<String> updatedPackages)933 private void notifyPinService(ArraySet<String> updatedPackages) { 934 PinnerService pinnerService = mInjector.getPinnerService(); 935 if (pinnerService != null) { 936 Slog.i(TAG, "Pinning optimized code " + updatedPackages); 937 pinnerService.update(updatedPackages, false /* force */); 938 } 939 } 940 941 /** Notify all listeners (#addPackagesUpdatedListener) that packages have been updated. */ notifyPackagesUpdated(ArraySet<String> updatedPackages)942 private void notifyPackagesUpdated(ArraySet<String> updatedPackages) { 943 synchronized (mLock) { 944 for (PackagesUpdatedListener listener : mPackagesUpdatedListeners) { 945 listener.onPackagesUpdated(updatedPackages); 946 } 947 } 948 } 949 writeStatsLog(JobParameters params)950 private void writeStatsLog(JobParameters params) { 951 @Status int status; 952 long durationMs; 953 long durationIncludingSleepMs; 954 synchronized (mLock) { 955 status = mLastExecutionStatus; 956 durationMs = mLastExecutionDurationMs; 957 durationIncludingSleepMs = mLastExecutionDurationIncludingSleepMs; 958 } 959 960 mStatsLogger.write(status, params.getStopReason(), durationMs, durationIncludingSleepMs); 961 } 962 963 /** Injector pattern for testing purpose */ 964 @VisibleForTesting 965 static final class Injector { 966 private final Context mContext; 967 private final DexManager mDexManager; 968 private final PackageManagerService mPackageManagerService; 969 private final File mDataDir = Environment.getDataDirectory(); 970 Injector(Context context, DexManager dexManager, PackageManagerService pm)971 Injector(Context context, DexManager dexManager, PackageManagerService pm) { 972 mContext = context; 973 mDexManager = dexManager; 974 mPackageManagerService = pm; 975 } 976 getContext()977 Context getContext() { 978 return mContext; 979 } 980 getPackageManagerService()981 PackageManagerService getPackageManagerService() { 982 return mPackageManagerService; 983 } 984 getDexOptHelper()985 DexOptHelper getDexOptHelper() { 986 return new DexOptHelper(getPackageManagerService()); 987 } 988 getJobScheduler()989 JobScheduler getJobScheduler() { 990 return mContext.getSystemService(JobScheduler.class); 991 } 992 getDexManager()993 DexManager getDexManager() { 994 return mDexManager; 995 } 996 getPinnerService()997 PinnerService getPinnerService() { 998 return LocalServices.getService(PinnerService.class); 999 } 1000 isBackgroundDexOptDisabled()1001 boolean isBackgroundDexOptDisabled() { 1002 return SystemProperties.getBoolean( 1003 "pm.dexopt.disable_bg_dexopt" /* key */, false /* default */); 1004 } 1005 isBatteryLevelLow()1006 boolean isBatteryLevelLow() { 1007 return LocalServices.getService(BatteryManagerInternal.class).getBatteryLevelLow(); 1008 } 1009 getDowngradeUnusedAppsThresholdInMillis()1010 long getDowngradeUnusedAppsThresholdInMillis() { 1011 String sysPropKey = "pm.dexopt.downgrade_after_inactive_days"; 1012 String sysPropValue = SystemProperties.get(sysPropKey); 1013 if (sysPropValue == null || sysPropValue.isEmpty()) { 1014 Slog.w(TAG, "SysProp " + sysPropKey + " not set"); 1015 return Long.MAX_VALUE; 1016 } 1017 return TimeUnit.DAYS.toMillis(Long.parseLong(sysPropValue)); 1018 } 1019 supportSecondaryDex()1020 boolean supportSecondaryDex() { 1021 return (SystemProperties.getBoolean("dalvik.vm.dexopt.secondary", false)); 1022 } 1023 getDataDirUsableSpace()1024 long getDataDirUsableSpace() { 1025 return mDataDir.getUsableSpace(); 1026 } 1027 getDataDirStorageLowBytes()1028 long getDataDirStorageLowBytes() { 1029 return mContext.getSystemService(StorageManager.class).getStorageLowBytes(mDataDir); 1030 } 1031 getCurrentThermalStatus()1032 int getCurrentThermalStatus() { 1033 IThermalService thermalService = IThermalService.Stub.asInterface( 1034 ServiceManager.getService(Context.THERMAL_SERVICE)); 1035 try { 1036 return thermalService.getCurrentThermalStatus(); 1037 } catch (RemoteException e) { 1038 return STATUS_ABORT_THERMAL; 1039 } 1040 } 1041 getDexOptThermalCutoff()1042 int getDexOptThermalCutoff() { 1043 return SystemProperties.getInt( 1044 "dalvik.vm.dexopt.thermal-cutoff", THERMAL_CUTOFF_DEFAULT); 1045 } 1046 createAndStartThread(String name, Runnable target)1047 Thread createAndStartThread(String name, Runnable target) { 1048 Thread thread = new Thread(target, name); 1049 Slog.i(TAG, "Starting thread:" + name); 1050 thread.start(); 1051 return thread; 1052 } 1053 } 1054 } 1055