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