Make BackgroundDexOpt aware of thermal state

This change makes the BackgroundDexOpt service consider the thermal
state of the device before running.  If the device is in a moderate thermal
state or worse background dexopt will not run.

Bug: 165935246
Test: Treehugger && atest BackgroundDexOptServiceIntegrationTests
Change-Id: Ie5ccbab7aa6d414241780136407f397d326340bf
diff --git a/services/core/java/com/android/server/pm/BackgroundDexOptService.java b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
index 77c1c1d..49a0a88 100644
--- a/services/core/java/com/android/server/pm/BackgroundDexOptService.java
+++ b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
@@ -31,6 +31,9 @@
 import android.content.pm.PackageInfo;
 import android.os.BatteryManagerInternal;
 import android.os.Environment;
+import android.os.IThermalService;
+import android.os.PowerManager;
+import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemProperties;
 import android.os.UserHandle;
@@ -82,10 +85,15 @@
     private static final int OPTIMIZE_ABORT_BY_JOB_SCHEDULER = 2;
     // Optimizations should be aborted. No space left on device.
     private static final int OPTIMIZE_ABORT_NO_SPACE_LEFT = 3;
+    // Optimizations should be aborted. Thermal throttling level too high.
+    private static final int OPTIMIZE_ABORT_THERMAL = 4;
 
     // Used for calculating space threshold for downgrading unused apps.
     private static final int LOW_THRESHOLD_MULTIPLIER_FOR_DOWNGRADE = 2;
 
+    // Thermal cutoff value used if one isn't defined by a system property.
+    private static final int THERMAL_CUTOFF_DEFAULT = PowerManager.THERMAL_STATUS_MODERATE;
+
     /**
      * Set of failed packages remembered across job runs.
      */
@@ -107,8 +115,14 @@
     private static final long mDowngradeUnusedAppsThresholdInMillis =
             getDowngradeUnusedAppsThresholdInMillis();
 
+    private final IThermalService mThermalService =
+            IThermalService.Stub.asInterface(
+                ServiceManager.getService(Context.THERMAL_SERVICE));
+
     private static List<PackagesUpdatedListener> sPackagesUpdatedListeners = new ArrayList<>();
 
+    private int mThermalStatusCutoff = THERMAL_CUTOFF_DEFAULT;
+
     public static void schedule(Context context) {
         if (isBackgroundDexoptDisabled()) {
             return;
@@ -251,12 +265,18 @@
                     Slog.w(TAG, "Idle optimizations aborted because of space constraints.");
                 } else if (result == OPTIMIZE_ABORT_BY_JOB_SCHEDULER) {
                     Slog.w(TAG, "Idle optimizations aborted by job scheduler.");
+                } else if (result == OPTIMIZE_ABORT_THERMAL) {
+                    Slog.w(TAG, "Idle optimizations aborted by thermal throttling.");
                 } else {
                     Slog.w(TAG, "Idle optimizations ended with unexpected code: " + result);
                 }
-                if (result != OPTIMIZE_ABORT_BY_JOB_SCHEDULER) {
+
+                if (result == OPTIMIZE_ABORT_THERMAL) {
+                    // Abandon our timeslice and reschedule
+                    jobFinished(jobParams, /* wantsReschedule */ true);
+                } else if (result != OPTIMIZE_ABORT_BY_JOB_SCHEDULER) {
                     // Abandon our timeslice and do not reschedule.
-                    jobFinished(jobParams, /* reschedule */ false);
+                    jobFinished(jobParams, /* wantsReschedule */ false);
                 }
             }
         }.start();
@@ -542,6 +562,24 @@
             // JobScheduler requested an early abort.
             return OPTIMIZE_ABORT_BY_JOB_SCHEDULER;
         }
+
+        // Abort background dexopt if the device is in a moderate or stronger thermal throttling
+        // state.
+        try {
+            final int thermalStatus = mThermalService.getCurrentThermalStatus();
+
+            if (DEBUG) {
+                Log.i(TAG, "Thermal throttling status during bgdexopt: " + thermalStatus);
+            }
+
+            if (thermalStatus >= mThermalStatusCutoff) {
+                return OPTIMIZE_ABORT_THERMAL;
+            }
+        } catch (RemoteException ex) {
+            // Because this is a intra-process Binder call it is impossible for a RemoteException
+            // to be raised.
+        }
+
         long usableSpace = mDataDir.getUsableSpace();
         if (usableSpace < lowStorageThreshold) {
             // Rather bail than completely fill up the disk.
@@ -603,6 +641,9 @@
             return false;
         }
 
+        mThermalStatusCutoff =
+            SystemProperties.getInt("dalvik.vm.dexopt.thermal-cutoff", THERMAL_CUTOFF_DEFAULT);
+
         boolean result;
         if (params.getJobId() == JOB_POST_BOOT_UPDATE) {
             result = runPostBootUpdate(params, pm, pkgs);
diff --git a/tests/BackgroundDexOptServiceIntegrationTests/OWNERS b/tests/BackgroundDexOptServiceIntegrationTests/OWNERS
new file mode 100644
index 0000000..3414a74
--- /dev/null
+++ b/tests/BackgroundDexOptServiceIntegrationTests/OWNERS
@@ -0,0 +1 @@
+include platform/art:/OWNERS
diff --git a/tests/BackgroundDexOptServiceIntegrationTests/src/com/android/server/pm/BackgroundDexOptServiceIntegrationTests.java b/tests/BackgroundDexOptServiceIntegrationTests/src/com/android/server/pm/BackgroundDexOptServiceIntegrationTests.java
index e05816e..90ddb6f 100644
--- a/tests/BackgroundDexOptServiceIntegrationTests/src/com/android/server/pm/BackgroundDexOptServiceIntegrationTests.java
+++ b/tests/BackgroundDexOptServiceIntegrationTests/src/com/android/server/pm/BackgroundDexOptServiceIntegrationTests.java
@@ -20,6 +20,7 @@
 import android.content.Context;
 import android.os.Environment;
 import android.os.ParcelFileDescriptor;
+import android.os.PowerManager;
 import android.os.SystemProperties;
 import android.os.storage.StorageManager;
 import android.util.Log;
@@ -201,11 +202,16 @@
         fillUpStorage((long) (getStorageLowBytes() * LOW_STORAGE_MULTIPLIER));
     }
 
-    // TODO(aeubanks): figure out how to get scheduled bg-dexopt to run
     private static void runBackgroundDexOpt() throws IOException {
+        runBackgroundDexOpt("Success");
+    }
+
+    // TODO(aeubanks): figure out how to get scheduled bg-dexopt to run
+    private static void runBackgroundDexOpt(String expectedStatus) throws IOException {
         String result = runShellCommand("cmd package bg-dexopt-job " + PACKAGE_NAME);
-        if (!result.trim().equals("Success")) {
-            throw new IllegalStateException("Expected command success, received >" + result + "<");
+        if (!result.trim().equals(expectedStatus)) {
+            throw new IllegalStateException("Expected status: " + expectedStatus
+                + "; Received: " + result.trim());
         }
     }
 
@@ -242,6 +248,16 @@
         runShellCommand(String.format("cmd package compile -f -m %s %s", filter, pkg));
     }
 
+    // Override the thermal status of the device
+    public static void overrideThermalStatus(int status) throws IOException {
+        runShellCommand("cmd thermalservice override-status " + status);
+    }
+
+    // Reset the thermal status of the device
+    public static void resetThermalStatus() throws IOException {
+        runShellCommand("cmd thermalservice reset");
+    }
+
     // Test that background dexopt under normal conditions succeeds.
     @Test
     public void testBackgroundDexOpt() throws IOException {
@@ -307,4 +323,17 @@
         }
     }
 
+    // Test that background dexopt job doesn't trigger if the device is under thermal throttling.
+    @Test
+    public void testBackgroundDexOptThermalThrottling() throws IOException {
+        try {
+            compilePackageWithFilter(PACKAGE_NAME, "verify");
+            overrideThermalStatus(PowerManager.THERMAL_STATUS_MODERATE);
+            // The bgdexopt task should fail when onStartJob is run
+            runBackgroundDexOpt("Failure");
+            Assert.assertEquals("verify", getCompilerFilter(PACKAGE_NAME));
+        } finally {
+            resetThermalStatus();
+        }
+    }
 }