Stop fixed activity mode when the display is removed.

- Tried to install DisplayListener and remove the removed display only,
  But, because it turns out DisplayListener is so slow, I'd like to
  check displays inside launchIfNecessary().

Bug: 200040668
Test: check if the logcat has the warning to launch an Activity on the invalid display.
Change-Id: Iae79cdfdb8dc31c958bae4afbd0db17f0dddd321
Merged-In: Iae79cdfdb8dc31c958bae4afbd0db17f0dddd321
diff --git a/service/src/com/android/car/am/FixedActivityService.java b/service/src/com/android/car/am/FixedActivityService.java
index bbc76ff..c969fc1 100644
--- a/service/src/com/android/car/am/FixedActivityService.java
+++ b/service/src/com/android/car/am/FixedActivityService.java
@@ -61,6 +61,7 @@
 import com.android.car.R;
 import com.android.car.user.CarUserService;
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 
 import java.util.List;
 
@@ -417,12 +418,20 @@
                 return false;
             }
             for (int i = mRunningActivities.size() - 1; i >= 0; i--) {
+                int displayIdForActivity = mRunningActivities.keyAt(i);
+                Display display = mDm.getDisplay(displayIdForActivity);
+                if (display == null) {
+                    Slog.e(TAG_AM, "Stop fixed activity for non-available display"
+                            + displayIdForActivity);
+                    mRunningActivities.removeAt(i);
+                    continue;
+                }
+
                 RunningActivityInfo activityInfo = mRunningActivities.valueAt(i);
                 activityInfo.isVisible = false;
                 if (isUserAllowedToLaunchActivity(activityInfo.userId)) {
                     continue;
                 }
-                final int displayIdForActivity = mRunningActivities.keyAt(i);
                 if (activityInfo.taskId != INVALID_TASK_ID) {
                     Slog.i(TAG_AM, "Finishing fixed activity on user switching:"
                             + activityInfo);
@@ -432,12 +441,6 @@
                         Slog.e(TAG_AM, "remote exception from AM", e);
                     }
                     CarServiceUtils.runOnMain(() -> {
-                        Display display = mDm.getDisplay(displayIdForActivity);
-                        if (display == null) {
-                            Slog.e(TAG_AM, "Display not available, cannot launnch window:"
-                                    + displayIdForActivity);
-                            return;
-                        }
                         Presentation p = new Presentation(mContext, display,
                                 android.R.style.Theme_Black_NoTitleBar_Fullscreen,
                                 // TYPE_PRESENTATION can't be used in the internal display.
@@ -546,7 +549,8 @@
         }
     }
 
-    private void launchIfNecessary() {
+    @VisibleForTesting
+    void launchIfNecessary() {
         launchIfNecessary(Display.INVALID_DISPLAY);
     }
 
@@ -609,6 +613,13 @@
         return true;
     }
 
+    @VisibleForTesting
+    RunningActivityInfo getRunningFixedActivity(int displayId) {
+        synchronized (mLock) {
+            return mRunningActivities.get(displayId);
+        }
+    }
+
     /**
      * Checks {@link InstrumentClusterRenderingService#startFixedActivityModeForDisplayAndUser(
      * Intent, ActivityOptions, int)}
diff --git a/tests/carservice_unit_test/src/com/android/car/am/FixedActivityServiceTest.java b/tests/carservice_unit_test/src/com/android/car/am/FixedActivityServiceTest.java
index b22831d..4a9f0c7 100644
--- a/tests/carservice_unit_test/src/com/android/car/am/FixedActivityServiceTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/am/FixedActivityServiceTest.java
@@ -87,6 +87,8 @@
     private CarUserService mCarUserService;
     @Mock
     private CarPowerManager mCarPowerManager;
+    @Mock
+    private Display mValidDisplay;
 
     private FixedActivityService mFixedActivityService;
 
@@ -102,6 +104,7 @@
         when(mContext.getPackageManager()).thenReturn(mPackageManager);
         doReturn(mCarUserService).when(() -> CarLocalServices.getService(CarUserService.class));
         doReturn(mCarPowerManager).when(() -> CarLocalServices.createCarPowerManager(mContext));
+        when(mDisplayManager.getDisplay(mValidDisplayId)).thenReturn(mValidDisplay);
         mFixedActivityService = new FixedActivityService(mContext, mActivityManager, mUserManager,
                 mDisplayManager);
     }
@@ -252,6 +255,7 @@
         assertThat(ret).isTrue();
 
         int anotherValidDisplayId = mValidDisplayId + 1;
+        when(mDisplayManager.getDisplay(anotherValidDisplayId)).thenReturn(mValidDisplay);
         ret = mFixedActivityService.startFixedActivityModeForDisplayAndUser(anotherIntent,
                 options, anotherValidDisplayId, userId);
         verify(mContext).startActivityAsUser(eq(anotherIntent), any(Bundle.class),
@@ -272,6 +276,46 @@
     }
 
     @Test
+    public void testStartFixedActivityModeForDisplayAndUser_unavailableDisplay() {
+        int userId = 10;
+        Intent intent = new Intent(Intent.ACTION_MAIN);
+        ActivityOptions options = new ActivityOptions(new Bundle());
+        int unavailableDisplayId = mValidDisplayId + 1;
+
+        boolean started = mFixedActivityService.startFixedActivityModeForDisplayAndUser(
+                intent, options, unavailableDisplayId, userId);
+        assertThat(started).isFalse();
+    }
+
+    @Test
+    public void testStartFixedActivityModeForDisplayAndUser_displayRemoved()
+            throws Exception {
+        int displayToBeRemoved = mValidDisplayId + 1;
+        when(mDisplayManager.getDisplay(displayToBeRemoved)).thenReturn(
+                mValidDisplay, // for startFixedActivityModeForDisplayAndUser
+                mValidDisplay, // for launchIf
+                null);
+        int userId = 10;
+        ActivityOptions options = new ActivityOptions(new Bundle());
+        Intent intent = expectComponentAvailable("test_package", "com.test.dude", userId);
+        mockAmGetCurrentUser(userId);
+        expectNoActivityStack();
+
+        boolean started = mFixedActivityService.startFixedActivityModeForDisplayAndUser(
+                intent, options, displayToBeRemoved, userId);
+        assertThat(started).isTrue();
+        assertThat(mFixedActivityService.getRunningFixedActivity(displayToBeRemoved)).isNotNull();
+
+        // The display is still valid.
+        mFixedActivityService.launchIfNecessary();
+        assertThat(mFixedActivityService.getRunningFixedActivity(displayToBeRemoved)).isNotNull();
+
+        // The display is removed.
+        mFixedActivityService.launchIfNecessary();
+        assertThat(mFixedActivityService.getRunningFixedActivity(displayToBeRemoved)).isNull();
+    }
+
+    @Test
     public void testStartFixedActivityModeForDisplayAndUser_notAllowedUser() {
         int currentUserId = 10;
         int notAllowedUserId = 11;