Return the focus back to the default display when FixedActivity gets it.

Currently, the Activity on the cluster display doesn't need the global focus. So, when FixedActivity gets the focus, we'd like to return it back to the default display.

This CL also fixes that it gets the invalid taskId if the display has two tasks.

Bug: 155126599
Test: atest android.server.wm.StartActivityTests#testStartHomeIfNoActivities
Test: atest com.android.car.am.FixedActivityServiceTest
Change-Id: Iddb608b5f6c4ac291cd19720bf08c421c76c81c7
(cherry picked from commit 414d44872fa1a00d0fe148286d76fab3bf994db6)
Merged-In: Iddb608b5f6c4ac291cd19720bf08c421c76c81c7
diff --git a/service/src/com/android/car/am/FixedActivityService.java b/service/src/com/android/car/am/FixedActivityService.java
index 19d1f70..5e5454b 100644
--- a/service/src/com/android/car/am/FixedActivityService.java
+++ b/service/src/com/android/car/am/FixedActivityService.java
@@ -27,7 +27,9 @@
 import android.app.ActivityManager;
 import android.app.ActivityManager.StackInfo;
 import android.app.ActivityOptions;
+import android.app.ActivityTaskManager;
 import android.app.IActivityManager;
+import android.app.IActivityTaskManager;
 import android.app.IProcessObserver;
 import android.app.Presentation;
 import android.app.TaskStackListener;
@@ -59,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.io.PrintWriter;
 import java.util.List;
@@ -128,6 +131,7 @@
     private final Context mContext;
 
     private final IActivityManager mAm;
+    private final IActivityTaskManager mAtm;
 
     private final DisplayManager mDm;
 
@@ -212,8 +216,47 @@
         public void onTaskRemovalStarted(int taskId) {
             launchIfNecessary();
         }
+
+        @Override
+        public void onTaskFocusChanged(int taskId, boolean focused) {
+            handleTaskFocusChanged(taskId, focused);
+        }
     };
 
+    @VisibleForTesting
+    void handleTaskFocusChanged(int taskId, boolean focused) {
+        if (DBG) Log.d(TAG_AM, "handleTaskFocusChanged taskId=" + taskId + ", focused=" + focused);
+        if (!focused) {
+            return;  // We don't care the focus losing events.
+        }
+        synchronized (mLock) {
+            for (int i = mRunningActivities.size() - 1; i >= 0; i--) {
+                RunningActivityInfo info = mRunningActivities.valueAt(i);
+                if (DBG) Log.d(TAG_AM, "Checking " + info);
+                if (info.taskId == taskId) {
+                    moveFocusBackToDefaultDisplay(taskId);
+                    return;
+                }
+            }
+        }
+    }
+
+    private void moveFocusBackToDefaultDisplay(int taskId) {
+        try {
+            List<StackInfo> tasks = mAtm.getAllStackInfosOnDisplay(Display.DEFAULT_DISPLAY);
+            if (tasks.size() == 0) return;
+            StackInfo topStack = tasks.get(0);
+            int topTaskIdInDefaultDisplay = topStack.taskIds[topStack.taskIds.length - 1];
+            if (DBG) {
+                Log.d(TAG_AM, "FixedActivity #" + taskId + " got the focus, return back to #"
+                        + topTaskIdInDefaultDisplay);
+            }
+            mAtm.setFocusedTask(topTaskIdInDefaultDisplay);
+        } catch (RemoteException e) {
+            Log.e(TAG_AM, "remote exception from ATM", e);
+        }
+    }
+
     private final IProcessObserver mProcessObserver = new IProcessObserver.Stub() {
         @Override
         public void onForegroundActivitiesChanged(int pid, int uid, boolean foregroundActivities) {
@@ -267,14 +310,17 @@
     };
 
     public FixedActivityService(Context context) {
-        this(context, ActivityManager.getService(), context.getSystemService(UserManager.class),
+        this(context, ActivityManager.getService(), ActivityTaskManager.getService(),
+                context.getSystemService(UserManager.class),
                 context.getSystemService(DisplayManager.class));
     }
 
     FixedActivityService(Context context, IActivityManager activityManager,
+            IActivityTaskManager activityTaskManager,
             UserManager userManager, DisplayManager displayManager) {
         mContext = context;
         mAm = activityManager;
+        mAtm = activityTaskManager;
         mUm = userManager;
         mDm = displayManager;
         mHandlerThread = CarServiceUtils.getHandlerThread(
@@ -453,7 +499,15 @@
                 }
                 mRunningActivities.removeAt(i);
             }
+            int previousDisplayId = Display.INVALID_DISPLAY;
             for (StackInfo stackInfo : infos) {
+                // In the current ATMS implementation which enumerates the display first, then
+                // enumerates the stack/task, so the tasks in the same display come consecutively.
+                if (stackInfo.displayId == previousDisplayId) {
+                    // 2nd+ tasks, skip it.
+                    continue;
+                }
+                previousDisplayId = stackInfo.displayId;
                 RunningActivityInfo activityInfo = mRunningActivities.get(stackInfo.displayId);
                 if (activityInfo == null) {
                     continue;
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 6d1cc76..9432cae 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
@@ -23,6 +23,7 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
@@ -33,6 +34,7 @@
 import android.app.ActivityManager.StackInfo;
 import android.app.ActivityOptions;
 import android.app.IActivityManager;
+import android.app.IActivityTaskManager;
 import android.app.TaskStackListener;
 import android.car.hardware.power.CarPowerManager;
 import android.car.test.mocks.AbstractExtendedMockitoTestCase;
@@ -78,6 +80,8 @@
     @Mock
     private IActivityManager mActivityManager;
     @Mock
+    private IActivityTaskManager mActivityTaskManager;
+    @Mock
     private UserManager mUserManager;
     @Mock
     private DisplayManager mDisplayManager;
@@ -102,8 +106,8 @@
         when(mContext.getPackageManager()).thenReturn(mPackageManager);
         doReturn(mCarUserService).when(() -> CarLocalServices.getService(CarUserService.class));
         doReturn(mCarPowerManager).when(() -> CarLocalServices.createCarPowerManager(mContext));
-        mFixedActivityService = new FixedActivityService(mContext, mActivityManager, mUserManager,
-                mDisplayManager);
+        mFixedActivityService = new FixedActivityService(mContext, mActivityManager,
+                mActivityTaskManager, mUserManager, mDisplayManager);
     }
 
     @After
@@ -142,7 +146,7 @@
         mockAmGetCurrentUser(userId);
         expectActivityStackInfo(
                 createEmptyStackInfo(),
-                createStackInfo(intent, userIds, mValidDisplayId, taskIds)
+                createStackInfoList(intent, userIds, mValidDisplayId, taskIds)
         );
 
         // No running activities
@@ -172,7 +176,7 @@
         mockAmGetCurrentUser(userId);
         expectActivityStackInfo(
                 createEmptyStackInfo(),
-                createStackInfo(intent, userIds, mValidDisplayId, taskIds)
+                createStackInfoList(intent, userIds, mValidDisplayId, taskIds)
         );
 
         // No running activities
@@ -202,7 +206,7 @@
         mockAmGetCurrentUser(userId);
         expectActivityStackInfo(
                 createEmptyStackInfo(),
-                createStackInfo(intent, userIds, mValidDisplayId, taskIds)
+                createStackInfoList(intent, userIds, mValidDisplayId, taskIds)
         );
 
         // No running activities
@@ -323,6 +327,88 @@
         verify(mActivityManager, never()).unregisterTaskStackListener(any(TaskStackListener.class));
     }
 
+    @Test
+    public void testHandleTaskFocusChanged_returnForcusBack() throws Exception {
+        int userId = 10;
+        int testTaskId = 1234;
+        int[] userIds = new int[]{userId};
+        int[] taskIds = new int[]{testTaskId};
+        ActivityOptions options = new ActivityOptions(new Bundle());
+        Intent intent = expectComponentAvailable("test_package", "com.test.dude", userId);
+        mockAmGetCurrentUser(userId);
+        expectActivityStackInfo(createStackInfoList(intent, userIds, mValidDisplayId, taskIds));
+
+        // Make FixedActivityService to update the taskIds.
+        boolean ret = mFixedActivityService.startFixedActivityModeForDisplayAndUser(intent,
+                options, mValidDisplayId, userId);
+        assertThat(ret).isTrue();
+
+        Intent homeIntent = expectComponentAvailable("home", "homeActivity", userId);
+        int homeTaskId = 4567;
+        when(mActivityTaskManager.getAllStackInfosOnDisplay(Display.DEFAULT_DISPLAY)).thenReturn(
+                createStackInfoList(
+                        homeIntent, userIds, Display.DEFAULT_DISPLAY, new int[]{homeTaskId}));
+
+        mFixedActivityService.handleTaskFocusChanged(testTaskId, true);
+
+        verify(mActivityTaskManager).setFocusedTask(homeTaskId);
+    }
+
+    // If there are two tasks in one display id, then there is a bug to invalidate
+    // RunningActivityInfo.taskId examining 2nd task and it prevents from checking FixedActivity
+    // in handleTaskFocusChanged(). This test makes sure if the bug doesn't exist.
+    @Test
+    public void testHandleTaskFocusChanged_returnForcusBackInTwoStackInfos() throws Exception {
+        int userId = 10;
+        int testTaskId = 1234;
+        int[] userIds = new int[]{userId};
+        int[] taskIds = new int[]{testTaskId};
+        int[] taskIds2 = new int[]{testTaskId + 1111};
+        ActivityOptions options = new ActivityOptions(new Bundle());
+        Intent intent = expectComponentAvailable("test_package", "com.test.dude", userId);
+        Intent intent2 = expectComponentAvailable("test_package2", "com.test.others", userId);
+        mockAmGetCurrentUser(userId);
+        expectActivityStackInfo(Arrays.asList(
+                createStackInfo(intent, userIds, mValidDisplayId, taskIds),
+                createStackInfo(intent2, userIds, mValidDisplayId, taskIds2)));
+
+        // Make FixedActivityService to update the taskIds.
+        boolean ret = mFixedActivityService.startFixedActivityModeForDisplayAndUser(intent,
+                options, mValidDisplayId, userId);
+        assertThat(ret).isTrue();
+
+        Intent homeIntent = expectComponentAvailable("home", "homeActivity", userId);
+        int homeTaskId = 4567;
+        when(mActivityTaskManager.getAllStackInfosOnDisplay(Display.DEFAULT_DISPLAY)).thenReturn(
+                createStackInfoList(
+                        homeIntent, userIds, Display.DEFAULT_DISPLAY, new int[]{homeTaskId}));
+
+        mFixedActivityService.handleTaskFocusChanged(testTaskId, true);
+
+        verify(mActivityTaskManager).setFocusedTask(homeTaskId);
+    }
+
+    @Test
+    public void testHandleTaskFocusChanged_noForcusBack() throws Exception {
+        int userId = 10;
+        int testTaskId = 1234;
+        int[] userIds = new int[]{userId};
+        int[] taskIds = new int[]{testTaskId};
+        ActivityOptions options = new ActivityOptions(new Bundle());
+        Intent intent = expectComponentAvailable("test_package", "com.test.dude", userId);
+        mockAmGetCurrentUser(userId);
+        expectActivityStackInfo(createStackInfoList(intent, userIds, mValidDisplayId, taskIds));
+
+        // Make FixedActivityService to update the taskIds.
+        boolean ret = mFixedActivityService.startFixedActivityModeForDisplayAndUser(intent,
+                options, mValidDisplayId, userId);
+        assertThat(ret).isTrue();
+
+        mFixedActivityService.handleTaskFocusChanged(testTaskId + 1, true);
+
+        verify(mActivityTaskManager, never()).setFocusedTask(anyInt());
+    }
+
     private void expectNoProfileUser(@UserIdInt int userId) {
         when(mUserManager.getEnabledProfileIds(userId)).thenReturn(new int[0]);
     }
@@ -371,7 +457,7 @@
         return new ArrayList<StackInfo>();
     }
 
-    private List<StackInfo> createStackInfo(Intent intent, @UserIdInt int[] userIds, int displayId,
+    private StackInfo createStackInfo(Intent intent, @UserIdInt int[] userIds, int displayId,
             int[] taskIds) {
         StackInfo stackInfo = new StackInfo();
         stackInfo.taskUserIds = userIds;
@@ -379,6 +465,11 @@
         stackInfo.visible = true;
         stackInfo.displayId = displayId;
         stackInfo.taskIds = taskIds;
-        return Arrays.asList(stackInfo);
+        return stackInfo;
+    }
+
+    private List<StackInfo> createStackInfoList(Intent intent, @UserIdInt int[] userIds,
+            int displayId, int[] taskIds) {
+        return Arrays.asList(createStackInfo(intent, userIds, displayId, taskIds));
     }
 }