Merge "Ensure prediction can remove from RESTRICTED timeout." into rvc-dev
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
index 249bc52..25d3b4a 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
+++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
@@ -1248,10 +1248,8 @@
             // Don't allow changing bucket if higher than ACTIVE
             if (app.currentBucket < STANDBY_BUCKET_ACTIVE) return;
 
-            // Don't allow prediction to change from/to NEVER or from RESTRICTED.
-            if ((app.currentBucket == STANDBY_BUCKET_NEVER
-                    || app.currentBucket == STANDBY_BUCKET_RESTRICTED
-                    || newBucket == STANDBY_BUCKET_NEVER)
+            // Don't allow prediction to change from/to NEVER.
+            if ((app.currentBucket == STANDBY_BUCKET_NEVER || newBucket == STANDBY_BUCKET_NEVER)
                     && predicted) {
                 return;
             }
@@ -1266,12 +1264,18 @@
             final boolean isForcedByUser =
                     (reason & REASON_MAIN_MASK) == REASON_MAIN_FORCED_BY_USER;
 
-            // If the current bucket is RESTRICTED, only user force or usage should bring it out,
-            // unless the app was put into the bucket due to timing out.
-            if (app.currentBucket == STANDBY_BUCKET_RESTRICTED && !isUserUsage(reason)
-                    && !isForcedByUser
-                    && (app.bucketingReason & REASON_MAIN_MASK) != REASON_MAIN_TIMEOUT) {
-                return;
+            if (app.currentBucket == STANDBY_BUCKET_RESTRICTED) {
+                if ((app.bucketingReason & REASON_MAIN_MASK) == REASON_MAIN_TIMEOUT) {
+                    if (predicted && newBucket >= STANDBY_BUCKET_RARE) {
+                        // Predicting into RARE or below means we don't expect the user to use the
+                        // app anytime soon, so don't elevate it from RESTRICTED.
+                        return;
+                    }
+                } else if (!isUserUsage(reason) && !isForcedByUser) {
+                    // If the current bucket is RESTRICTED, only user force or usage should bring
+                    // it out, unless the app was put into the bucket due to timing out.
+                    return;
+                }
             }
 
             if (newBucket == STANDBY_BUCKET_RESTRICTED) {
diff --git a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
index 387e62d..e8cb325 100644
--- a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
+++ b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
@@ -810,7 +810,7 @@
     }
 
     @Test
-    public void testPredictionRaiseFromRestrictedTimeout() {
+    public void testPredictionRaiseFromRestrictedTimeout_highBucket() {
         reportEvent(mController, USER_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_1);
 
         // Way past all timeouts. App times out into RESTRICTED bucket.
@@ -823,6 +823,24 @@
         mInjector.mElapsedRealtime += RESTRICTED_THRESHOLD;
         mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE,
                 REASON_MAIN_PREDICTED);
+        assertBucket(STANDBY_BUCKET_ACTIVE);
+    }
+
+    @Test
+    public void testPredictionRaiseFromRestrictedTimeout_lowBucket() {
+        reportEvent(mController, USER_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_1);
+
+        // Way past all timeouts. App times out into RESTRICTED bucket.
+        mInjector.mElapsedRealtime += RESTRICTED_THRESHOLD * 4;
+        mController.checkIdleStates(USER_ID);
+        assertBucket(STANDBY_BUCKET_RESTRICTED);
+
+        // Prediction into a low bucket means no expectation of the app being used, so we shouldn't
+        // elevate the app from RESTRICTED.
+        mInjector.mElapsedRealtime += RESTRICTED_THRESHOLD;
+        mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE,
+                REASON_MAIN_PREDICTED);
+        assertBucket(STANDBY_BUCKET_RESTRICTED);
     }
 
     @Test