Merge "[pm/incremental] disable unstartable state and hide related APIs"
diff --git a/api/current.txt b/api/current.txt
index 970cf6d..93eb32c 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -10683,8 +10683,6 @@
     field public static final String ACTION_PACKAGE_REMOVED = "android.intent.action.PACKAGE_REMOVED";
     field public static final String ACTION_PACKAGE_REPLACED = "android.intent.action.PACKAGE_REPLACED";
     field public static final String ACTION_PACKAGE_RESTARTED = "android.intent.action.PACKAGE_RESTARTED";
-    field public static final String ACTION_PACKAGE_STARTABLE = "android.intent.action.PACKAGE_STARTABLE";
-    field public static final String ACTION_PACKAGE_UNSTARTABLE = "android.intent.action.PACKAGE_UNSTARTABLE";
     field public static final String ACTION_PACKAGE_VERIFIED = "android.intent.action.PACKAGE_VERIFIED";
     field public static final String ACTION_PASTE = "android.intent.action.PASTE";
     field public static final String ACTION_PICK = "android.intent.action.PICK";
@@ -12312,9 +12310,6 @@
     field public static final int SYNCHRONOUS = 2; // 0x2
     field @Nullable public static final java.util.List<java.security.cert.Certificate> TRUST_ALL;
     field @NonNull public static final java.util.List<java.security.cert.Certificate> TRUST_NONE;
-    field public static final int UNSTARTABLE_REASON_CONNECTION_ERROR = 1; // 0x1
-    field public static final int UNSTARTABLE_REASON_INSUFFICIENT_STORAGE = 2; // 0x2
-    field public static final int UNSTARTABLE_REASON_UNKNOWN = 0; // 0x0
     field public static final int VERIFICATION_ALLOW = 1; // 0x1
     field public static final int VERIFICATION_REJECT = -1; // 0xffffffff
     field public static final int VERSION_CODE_HIGHEST = -1; // 0xffffffff
diff --git a/core/api/current.txt b/core/api/current.txt
index 5215d58..9329e2a 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -10683,8 +10683,6 @@
     field public static final String ACTION_PACKAGE_REMOVED = "android.intent.action.PACKAGE_REMOVED";
     field public static final String ACTION_PACKAGE_REPLACED = "android.intent.action.PACKAGE_REPLACED";
     field public static final String ACTION_PACKAGE_RESTARTED = "android.intent.action.PACKAGE_RESTARTED";
-    field public static final String ACTION_PACKAGE_STARTABLE = "android.intent.action.PACKAGE_STARTABLE";
-    field public static final String ACTION_PACKAGE_UNSTARTABLE = "android.intent.action.PACKAGE_UNSTARTABLE";
     field public static final String ACTION_PACKAGE_VERIFIED = "android.intent.action.PACKAGE_VERIFIED";
     field public static final String ACTION_PASTE = "android.intent.action.PASTE";
     field public static final String ACTION_PICK = "android.intent.action.PICK";
@@ -12312,9 +12310,6 @@
     field public static final int SYNCHRONOUS = 2; // 0x2
     field @Nullable public static final java.util.List<java.security.cert.Certificate> TRUST_ALL;
     field @NonNull public static final java.util.List<java.security.cert.Certificate> TRUST_NONE;
-    field public static final int UNSTARTABLE_REASON_CONNECTION_ERROR = 1; // 0x1
-    field public static final int UNSTARTABLE_REASON_INSUFFICIENT_STORAGE = 2; // 0x2
-    field public static final int UNSTARTABLE_REASON_UNKNOWN = 0; // 0x0
     field public static final int VERIFICATION_ALLOW = 1; // 0x1
     field public static final int VERIFICATION_REJECT = -1; // 0xffffffff
     field public static final int VERSION_CODE_HIGHEST = -1; // 0xffffffff
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 782f0cd..a03bdf2 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -2740,6 +2740,7 @@
      * </ul>
      *
      * <p class="note">This is a protected intent that can only be sent by the system.
+     * @hide
      */
     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
     public static final String ACTION_PACKAGE_STARTABLE = "android.intent.action.PACKAGE_STARTABLE";
@@ -2757,6 +2758,7 @@
      * </ul>
      *
      * <p class="note">This is a protected intent that can only be sent by the system.
+     * @hide
      */
     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
     public static final String ACTION_PACKAGE_UNSTARTABLE =
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 946c634..a77d8b1 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -3824,18 +3824,21 @@
      * Unstartable state with no root cause specified. E.g., data loader seeing missing pages but
      * unclear about the cause. This corresponds to a generic alert window shown to the user when
      * the user attempts to launch the app.
+     * @hide
      */
     public static final int UNSTARTABLE_REASON_UNKNOWN = 0;
 
     /**
      * Unstartable state due to connection issues that interrupt package loading.
      * This corresponds to an alert window shown to the user indicating connection errors.
+     * @hide
      */
     public static final int UNSTARTABLE_REASON_CONNECTION_ERROR = 1;
 
     /**
      * Unstartable state after encountering storage limitations.
      * This corresponds to an alert window indicating limited storage.
+     * @hide
      */
     public static final int UNSTARTABLE_REASON_INSUFFICIENT_STORAGE = 2;
 
diff --git a/services/core/java/com/android/server/pm/IncrementalStates.java b/services/core/java/com/android/server/pm/IncrementalStates.java
index 72803ac..780c522 100644
--- a/services/core/java/com/android/server/pm/IncrementalStates.java
+++ b/services/core/java/com/android/server/pm/IncrementalStates.java
@@ -101,21 +101,18 @@
         if (DEBUG) {
             Slog.i(TAG, "received package commit event");
         }
+        final boolean startableStateChanged;
         synchronized (mLock) {
-            if (!mStartableState.isStartable()) {
-                mStartableState.adoptNewStartableStateLocked(true);
-            }
+            startableStateChanged = mStartableState.adoptNewStartableStateLocked(true);
             if (!isIncremental) {
                 updateProgressLocked(1);
             }
         }
-        mHandler.post(PooledLambda.obtainRunnable(
-                IncrementalStates::reportStartableState,
-                IncrementalStates.this).recycleOnUse());
+        if (startableStateChanged) {
+            onStartableStateChanged();
+        }
         if (!isIncremental) {
-            mHandler.post(PooledLambda.obtainRunnable(
-                    IncrementalStates::reportFullyLoaded,
-                    IncrementalStates.this).recycleOnUse());
+            onLoadingStateChanged();
         }
     }
 
@@ -131,8 +128,7 @@
         synchronized (mLock) {
             if (mStartableState.isStartable() && mLoadingState.isLoading()) {
                 // Changing from startable -> unstartable only if app is still loading.
-                mStartableState.adoptNewStartableStateLocked(false);
-                startableStateChanged = true;
+                startableStateChanged = mStartableState.adoptNewStartableStateLocked(false);
             } else {
                 // If the app is fully loaded, the crash or ANR is caused by the app itself, so
                 // we do not change the startable state.
@@ -140,12 +136,15 @@
             }
         }
         if (startableStateChanged) {
-            mHandler.post(PooledLambda.obtainRunnable(
-                    IncrementalStates::reportStartableState,
-                    IncrementalStates.this).recycleOnUse());
+            onStartableStateChanged();
         }
     }
 
+    private void onStartableStateChanged() {
+        // Disable startable state broadcasts
+        // TODO(b/171920377): completely remove unstartable state.
+    }
+
     private void reportStartableState() {
         final Callback callback;
         final boolean startable;
@@ -165,6 +164,12 @@
         }
     }
 
+    private void onLoadingStateChanged() {
+        mHandler.post(PooledLambda.obtainRunnable(
+                IncrementalStates::reportFullyLoaded,
+                IncrementalStates.this).recycleOnUse());
+    }
+
     private void reportFullyLoaded() {
         final Callback callback;
         synchronized (mLock) {
@@ -178,23 +183,20 @@
     private class StatusConsumer implements Consumer<Integer> {
         @Override
         public void accept(Integer storageStatus) {
-            final boolean oldState, newState;
+            final boolean startableStateChanged;
             synchronized (mLock) {
                 if (!mLoadingState.isLoading()) {
                     // Do nothing if the package is already fully loaded
                     return;
                 }
-                oldState = mStartableState.isStartable();
                 mStorageHealthStatus = storageStatus;
-                updateStartableStateLocked();
-                newState = mStartableState.isStartable();
+                startableStateChanged = updateStartableStateLocked();
             }
-            if (oldState != newState) {
-                mHandler.post(PooledLambda.obtainRunnable(IncrementalStates::reportStartableState,
-                        IncrementalStates.this).recycleOnUse());
+            if (startableStateChanged) {
+                onStartableStateChanged();
             }
         }
-    };
+    }
 
     /**
      * By calling this method, the caller indicates that there issues with the Incremental
@@ -239,14 +241,10 @@
             newStartableState = mStartableState.isStartable();
         }
         if (!newLoadingState) {
-            mHandler.post(PooledLambda.obtainRunnable(
-                    IncrementalStates::reportFullyLoaded,
-                    IncrementalStates.this).recycleOnUse());
+            onLoadingStateChanged();
         }
         if (newStartableState != oldStartableState) {
-            mHandler.post(PooledLambda.obtainRunnable(
-                    IncrementalStates::reportStartableState,
-                    IncrementalStates.this).recycleOnUse());
+            onStartableStateChanged();
         }
     }
 
@@ -284,8 +282,9 @@
      * health
      * status. If the next state is different from the current state, proceed with state
      * change.
+     * @return True if the new startable state is different from the old one.
      */
-    private void updateStartableStateLocked() {
+    private boolean updateStartableStateLocked() {
         final boolean currentState = mStartableState.isStartable();
         boolean nextState = currentState;
         if (!currentState) {
@@ -302,9 +301,9 @@
             }
         }
         if (nextState == currentState) {
-            return;
+            return false;
         }
-        mStartableState.adoptNewStartableStateLocked(nextState);
+        return mStartableState.adoptNewStartableStateLocked(nextState);
     }
 
     private void updateProgressLocked(float progress) {
@@ -343,12 +342,30 @@
             return mUnstartableReason;
         }
 
-        public void adoptNewStartableStateLocked(boolean nextState) {
+        /**
+         * Adopt new startable state if it is different from the current state.
+         * @param nextState True if startable, false if unstartable.
+         * @return True if the state has changed, false otherwise.
+         */
+        public boolean adoptNewStartableStateLocked(boolean nextState) {
+            if (mIsStartable == nextState) {
+                return false;
+            }
+            if (!nextState) {
+                // Do nothing if the next state is "unstartable"; keep package always startable.
+                // TODO(b/171920377): completely remove unstartable state.
+                if (DEBUG) {
+                    Slog.i(TAG, "Attempting to set startable state to false. Abort.");
+                }
+                return false;
+            }
             if (DEBUG) {
-                Slog.i(TAG, "startable state changed from " + mIsStartable + " to " + nextState);
+                Slog.i(TAG,
+                        "startable state changed from " + mIsStartable + " to " + nextState);
             }
             mIsStartable = nextState;
             mUnstartableReason = getUnstartableReasonLocked();
+            return true;
         }
 
         private int getUnstartableReasonLocked() {
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index ab60260..203321e 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -16478,8 +16478,8 @@
                     healthCheckParams.unhealthyTimeoutMs = INCREMENTAL_STORAGE_UNHEALTHY_TIMEOUT_MS;
                     healthCheckParams.unhealthyMonitoringMs =
                             INCREMENTAL_STORAGE_UNHEALTHY_MONITORING_MS;
-                    mIncrementalManager.registerHealthListener(codePath,
-                            new StorageHealthCheckParams(), incrementalHealthListener);
+                    mIncrementalManager.registerHealthListener(codePath, healthCheckParams,
+                            incrementalHealthListener);
                 }
 
                 // Ensure that the uninstall reason is UNKNOWN for users with the package installed.
diff --git a/services/tests/servicestests/src/com/android/server/pm/IncrementalStatesTest.java b/services/tests/servicestests/src/com/android/server/pm/IncrementalStatesTest.java
index 40d959d..9ba0967 100644
--- a/services/tests/servicestests/src/com/android/server/pm/IncrementalStatesTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/IncrementalStatesTest.java
@@ -44,7 +44,6 @@
 public class IncrementalStatesTest {
     private IncrementalStates mIncrementalStates;
     private ConditionVariable mUnstartableCalled = new ConditionVariable();
-    private ConditionVariable mStartableCalled = new ConditionVariable();
     private ConditionVariable mFullyLoadedCalled = new ConditionVariable();
     private AtomicInteger mUnstartableReason = new AtomicInteger(0);
     private static final int WAIT_TIMEOUT_MILLIS = 1000; /* 1 second */
@@ -57,7 +56,6 @@
 
         @Override
         public void onPackageStartable() {
-            mStartableCalled.open();
         }
 
         @Override
@@ -77,24 +75,22 @@
         mIncrementalStates.setCallback(mCallback);
         mIncrementalStates.onCommit(true);
         // Test that package is now startable and loading
-        assertTrue(mStartableCalled.block(WAIT_TIMEOUT_MILLIS));
         assertTrue(mIncrementalStates.isStartable());
         assertTrue(mIncrementalStates.isLoading());
-        mStartableCalled.close();
         mUnstartableCalled.close();
         mFullyLoadedCalled.close();
     }
 
     /**
-     * Test that startable state changes to false when Incremental Storage is unhealthy.
+     * Test that the package is still startable when Incremental Storage is unhealthy.
      */
     @Test
     public void testStartableTransition_IncrementalStorageUnhealthy() {
         mIncrementalStates.onStorageHealthStatusChanged(
                 IStorageHealthListener.HEALTH_STATUS_UNHEALTHY);
-        // Test that package is now unstartable
-        assertTrue(mUnstartableCalled.block(WAIT_TIMEOUT_MILLIS));
-        assertFalse(mIncrementalStates.isStartable());
+        // Test that package is still startable
+        assertFalse(mUnstartableCalled.block(WAIT_TIMEOUT_MILLIS));
+        assertTrue(mIncrementalStates.isStartable());
         assertEquals(PackageManager.UNSTARTABLE_REASON_UNKNOWN, mUnstartableReason.get());
     }
 
@@ -112,73 +108,34 @@
     }
 
     /**
-     * Test that the package becomes unstartable when health status indicate storage issues.
+     * Test that the package is still startable when health status indicate storage issues.
      */
     @Test
     public void testStartableTransition_IncrementalStorageBlocked() {
         mIncrementalStates.onStorageHealthStatusChanged(
                 IStorageHealthListener.HEALTH_STATUS_UNHEALTHY_STORAGE);
-        // Test that package is now unstartable
-        assertTrue(mUnstartableCalled.block(WAIT_TIMEOUT_MILLIS));
-        assertFalse(mIncrementalStates.isStartable());
-        assertEquals(PackageManager.UNSTARTABLE_REASON_INSUFFICIENT_STORAGE,
+        // Test that package is still startable
+        assertFalse(mUnstartableCalled.block(WAIT_TIMEOUT_MILLIS));
+        assertTrue(mIncrementalStates.isStartable());
+        assertEquals(PackageManager.UNSTARTABLE_REASON_UNKNOWN,
                 mUnstartableReason.get());
     }
 
     /**
-     * Test that the package becomes unstartable when health status indicates transport issues.
+     * Test that the package is still startable when health status indicates transport issues.
      */
     @Test
     public void testStartableTransition_DataLoaderIntegrityError() {
         mIncrementalStates.onStorageHealthStatusChanged(
                 IStorageHealthListener.HEALTH_STATUS_UNHEALTHY_TRANSPORT);
-        // Test that package is now unstartable
-        assertTrue(mUnstartableCalled.block(WAIT_TIMEOUT_MILLIS));
-        assertFalse(mIncrementalStates.isStartable());
-        assertEquals(PackageManager.UNSTARTABLE_REASON_CONNECTION_ERROR,
+        // Test that package is still startable
+        assertFalse(mUnstartableCalled.block(WAIT_TIMEOUT_MILLIS));
+        assertTrue(mIncrementalStates.isStartable());
+        assertEquals(PackageManager.UNSTARTABLE_REASON_UNKNOWN,
                 mUnstartableReason.get());
     }
 
     /**
-     * Test that the package becomes unstartable when Incremental Storage is unhealthy, and it
-     * becomes startable again when Incremental Storage is healthy again.
-     */
-    @Test
-    public void testStartableTransition_IncrementalStorageUnhealthyBackToHealthy()
-            throws InterruptedException {
-        mIncrementalStates.onStorageHealthStatusChanged(
-                IStorageHealthListener.HEALTH_STATUS_UNHEALTHY);
-        // Test that package is unstartable
-        assertTrue(mUnstartableCalled.block(WAIT_TIMEOUT_MILLIS));
-        assertFalse(mIncrementalStates.isStartable());
-
-        mIncrementalStates.onStorageHealthStatusChanged(
-                IStorageHealthListener.HEALTH_STATUS_OK);
-        // Test that package is now startable
-        assertTrue(mStartableCalled.block(WAIT_TIMEOUT_MILLIS));
-        assertTrue(mIncrementalStates.isStartable());
-    }
-
-    /**
-     * Test that the package becomes unstartable when health status indicates transportation issue,
-     * and it becomes startable again when health status is ok again.
-     */
-    @Test
-    public void testStartableTransition_DataLoaderUnhealthyBackToHealthy()
-            throws InterruptedException {
-        mIncrementalStates.onStorageHealthStatusChanged(
-                IStorageHealthListener.HEALTH_STATUS_UNHEALTHY_TRANSPORT);
-        // Test that package is unstartable
-        assertTrue(mUnstartableCalled.block(WAIT_TIMEOUT_MILLIS));
-        assertFalse(mIncrementalStates.isStartable());
-
-        mIncrementalStates.onStorageHealthStatusChanged(IStorageHealthListener.HEALTH_STATUS_OK);
-        // Test that package is now startable
-        assertTrue(mStartableCalled.block(WAIT_TIMEOUT_MILLIS));
-        assertTrue(mIncrementalStates.isStartable());
-    }
-
-    /**
      * Test that when loading progress is 1, the package becomes fully loaded, and the change of
      * Incremental Storage health status does not affect the startable state.
      */
@@ -197,43 +154,11 @@
     }
 
     /**
-     * Test that when loading progress is 1, the package becomes fully loaded, and if the package
-     * was unstartable, it becomes startable.
-     */
-    @Test
-    public void testLoadingTransition_FullyLoadedWhenUnstartable() throws InterruptedException {
-        mIncrementalStates.onStorageHealthStatusChanged(
-                IStorageHealthListener.HEALTH_STATUS_UNHEALTHY);
-        // Test that package is unstartable
-        assertTrue(mUnstartableCalled.block(WAIT_TIMEOUT_MILLIS));
-        assertFalse(mIncrementalStates.isStartable());
-        // Test that package is still loading
-        assertTrue(mIncrementalStates.isLoading());
-
-        mIncrementalStates.setProgress(0.5f);
-        // Test that package is still unstartable
-        assertFalse(mStartableCalled.block(WAIT_TIMEOUT_MILLIS));
-        assertFalse(mIncrementalStates.isStartable());
-        mIncrementalStates.setProgress(1.0f);
-        // Test that package is now startable
-        assertTrue(mStartableCalled.block(WAIT_TIMEOUT_MILLIS));
-        assertTrue(mIncrementalStates.isStartable());
-        // Test that package is now fully loaded
-        assertTrue(mFullyLoadedCalled.block(WAIT_TIMEOUT_MILLIS));
-        assertFalse(mIncrementalStates.isLoading());
-    }
-
-    /**
      * Test startability transitions if app crashes or anrs
      */
     @Test
     public void testStartableTransition_AppCrashOrAnr() {
         mIncrementalStates.onCrashOrAnr();
-        assertFalse(mIncrementalStates.isStartable());
-        mIncrementalStates.setProgress(1.0f);
-        assertTrue(mIncrementalStates.isStartable());
-        mIncrementalStates.onCrashOrAnr();
-        // Test that if fully loaded, app remains startable even if it has crashed
         assertTrue(mIncrementalStates.isStartable());
     }
 }