Support blocking screen on multi-display.

- register UXR listener for each physical display;
- non-physical (likely virtual) displays gets UXR for main display;
- if no display has physical address, assume main display is physical;

Bug: 128456985
Test: verified on device with single display; also verified with
multi-display as 2nd home.
Change-Id: I54b45915e1e32f01613d3477c10b2e8c38e4e94e
diff --git a/service/src/com/android/car/SystemActivityMonitoringService.java b/service/src/com/android/car/SystemActivityMonitoringService.java
index 3894337..d192727 100644
--- a/service/src/com/android/car/SystemActivityMonitoringService.java
+++ b/service/src/com/android/car/SystemActivityMonitoringService.java
@@ -15,6 +15,8 @@
  */
 package com.android.car;
 
+import static com.android.car.pm.CarPackageManagerService.BLOCKING_INTENT_EXTRA_DISPLAY_ID;
+
 import android.app.ActivityManager;
 import android.app.ActivityManager.StackInfo;
 import android.app.ActivityOptions;
@@ -104,7 +106,7 @@
     private final HandlerThread mMonitorHandlerThread;
     private final ActivityMonitorHandler mHandler;
 
-    /** K: stack id, V: top task */
+    /** K: display id, V: top task */
     private final SparseArray<TopTaskInfoContainer> mTopTasks = new SparseArray<>();
     /** K: uid, V : list of pid */
     private final Map<Integer, Set<Integer>> mForegroundUidPids = new ArrayMap<>();
@@ -377,10 +379,14 @@
      * block the current task with the provided new activity.
      */
     private void handleBlockActivity(TopTaskInfoContainer currentTask, Intent newActivityIntent) {
-        // Only block default display.
-        ActivityOptions options = ActivityOptions.makeBasic();
-        options.setLaunchDisplayId(Display.DEFAULT_DISPLAY);
+        int displayId = newActivityIntent.getIntExtra(BLOCKING_INTENT_EXTRA_DISPLAY_ID,
+                Display.DEFAULT_DISPLAY);
+        if (Log.isLoggable(CarLog.TAG_AM, Log.DEBUG)) {
+            Log.d(CarLog.TAG_AM, "Launching blocking activity on display: " + displayId);
+        }
 
+        ActivityOptions options = ActivityOptions.makeBasic();
+        options.setLaunchDisplayId(displayId);
         mContext.startActivityAsUser(newActivityIntent, options.toBundle(),
                 new UserHandle(currentTask.stackInfo.userId));
         // Now make stack with new activity focused.
diff --git a/service/src/com/android/car/pm/CarPackageManagerService.java b/service/src/com/android/car/pm/CarPackageManagerService.java
index 7615d28..0f33ff4 100644
--- a/service/src/com/android/car/pm/CarPackageManagerService.java
+++ b/service/src/com/android/car/pm/CarPackageManagerService.java
@@ -42,6 +42,7 @@
 import android.content.pm.ServiceInfo;
 import android.content.pm.Signature;
 import android.content.res.Resources;
+import android.hardware.display.DisplayManager;
 import android.os.Binder;
 import android.os.Build;
 import android.os.Handler;
@@ -54,7 +55,9 @@
 import android.util.ArraySet;
 import android.util.Log;
 import android.util.Pair;
+import android.util.SparseArray;
 import android.view.Display;
+import android.view.DisplayAddress;
 
 import com.android.car.CarLog;
 import com.android.car.CarServiceBase;
@@ -89,6 +92,7 @@
     private final SystemActivityMonitoringService mSystemActivityMonitoringService;
     private final PackageManager mPackageManager;
     private final ActivityManager mActivityManager;
+    private final DisplayManager mDisplayManager;
 
     private final HandlerThread mHandlerThread;
     private final PackageHandler mHandler;
@@ -110,7 +114,6 @@
     private final HashMap<String, ClientPolicy> mClientPolicies = new HashMap<>();
     @GuardedBy("this")
     private HashMap<String, AppBlockingPackageInfoWrapper> mActivityWhitelistMap = new HashMap<>();
-    // The list corresponding to the one configured in <activityBlacklist>
     @GuardedBy("this")
     private LinkedList<AppBlockingPolicyProxy> mProxies;
 
@@ -122,7 +125,10 @@
     private final ComponentName mActivityBlockingActivity;
 
     private final ActivityLaunchListener mActivityLaunchListener = new ActivityLaunchListener();
-    private final UxRestrictionsListener mUxRestrictionsListener;
+    // K: (logical) display id of a physical display, V: UXR change listener of this display.
+    // For multi-display, monitor UXR change on each display.
+    private final SparseArray<UxRestrictionsListener> mUxRestrictionsListeners =
+            new SparseArray<>();
     private final VendorServiceController mVendorServiceController;
 
     // Information related to when the installed packages should be parsed for building a white and
@@ -171,6 +177,12 @@
      */
     public static final String BLOCKING_INTENT_EXTRA_IS_ROOT_ACTIVITY_DO = "is_root_activity_do";
 
+    /**
+     * int display id of the blocked task.
+     * @hide
+     */
+    public static final String BLOCKING_INTENT_EXTRA_DISPLAY_ID = "display_id";
+
     public CarPackageManagerService(Context context,
             CarUxRestrictionsManagerService uxRestrictionsService,
             SystemActivityMonitoringService systemActivityMonitoringService,
@@ -180,7 +192,7 @@
         mSystemActivityMonitoringService = systemActivityMonitoringService;
         mPackageManager = mContext.getPackageManager();
         mActivityManager = mContext.getSystemService(ActivityManager.class);
-        mUxRestrictionsListener = new UxRestrictionsListener(uxRestrictionsService);
+        mDisplayManager = mContext.getSystemService(DisplayManager.class);
         mHandlerThread = new HandlerThread(CarLog.TAG_PACKAGE);
         mHandlerThread.start();
         mHandler = new PackageHandler(mHandlerThread.getLooper());
@@ -281,14 +293,14 @@
 
     @Override
     public boolean isActivityBackedBySafeActivity(ComponentName activityName) {
-        if (!mUxRestrictionsListener.isRestricted()) {
-            return true;
-        }
         StackInfo info = mSystemActivityMonitoringService.getFocusedStackForTopActivity(
                 activityName);
         if (info == null) { // not top in focused stack
             return true;
         }
+        if (!isUxRestrictedOnDisplay(info.displayId)) {
+            return true;
+        }
         if (info.taskNames.length <= 1) { // nothing below this.
             return false;
         }
@@ -385,8 +397,11 @@
         }
         mContext.unregisterReceiver(mPackageParsingEventReceiver);
         mContext.unregisterReceiver(mUserSwitchedEventReceiver);
-        mCarUxRestrictionsService.unregisterUxRestrictionsChangeListener(mUxRestrictionsListener);
         mSystemActivityMonitoringService.registerActivityLaunchListener(null);
+        for (int i = 0; i < mUxRestrictionsListeners.size(); i++) {
+            UxRestrictionsListener listener = mUxRestrictionsListeners.valueAt(i);
+            mCarUxRestrictionsService.unregisterUxRestrictionsChangeListener(listener);
+        }
     }
 
     // run from HandlerThread
@@ -402,15 +417,22 @@
         pkgParseIntent.addDataScheme("package");
         mContext.registerReceiverAsUser(mPackageParsingEventReceiver, UserHandle.ALL,
                 pkgParseIntent, null, null);
-        try {
-            // TODO(128456985): register listener for each display in order to
-            // properly launch blocking screens.
-            mCarUxRestrictionsService.registerUxRestrictionsChangeListener(
-                    mUxRestrictionsListener, Display.DEFAULT_DISPLAY);
-        } catch (IllegalArgumentException e) {
-            // can happen while mocking is going on while init is still done.
-            Log.w(CarLog.TAG_PACKAGE, "sensor subscription failed", e);
-            return;
+
+        List<Display> physicalDisplays = getPhysicalDisplays();
+
+        // Assume default display (display 0) is always a physical display.
+        Display defaultDisplay = mDisplayManager.getDisplay(Display.DEFAULT_DISPLAY);
+        if (!physicalDisplays.contains(defaultDisplay)) {
+            if (Log.isLoggable(CarLog.TAG_PACKAGE, Log.INFO)) {
+                Log.i(CarLog.TAG_PACKAGE, "Adding default display to physical displays.");
+            }
+            physicalDisplays.add(defaultDisplay);
+        }
+        for (Display physicalDisplay : physicalDisplays) {
+            int displayId = physicalDisplay.getDisplayId();
+            UxRestrictionsListener listener = new UxRestrictionsListener(mCarUxRestrictionsService);
+            mUxRestrictionsListeners.put(displayId, listener);
+            mCarUxRestrictionsService.registerUxRestrictionsChangeListener(listener, displayId);
         }
         mSystemActivityMonitoringService.registerActivityLaunchListener(
                 mActivityLaunchListener);
@@ -423,7 +445,7 @@
         synchronized (this) {
             mHasParsedPackages = true;
         }
-        mUxRestrictionsListener.checkIfTopActivityNeedsBlocking();
+        blockTopActivitiesIfNecessary();
     }
 
     private synchronized void doHandleRelease() {
@@ -901,7 +923,14 @@
             writer.println("*PackageManagementService*");
             writer.println("mEnableActivityBlocking:" + mEnableActivityBlocking);
             writer.println("mHasParsedPackages:" + mHasParsedPackages);
-            writer.println("ActivityRestricted:" + mUxRestrictionsListener.isRestricted());
+            List<String> restrictions = new ArrayList<>(mUxRestrictionsListeners.size());
+            for (int i = 0; i < mUxRestrictionsListeners.size(); i++) {
+                int displayId = mUxRestrictionsListeners.keyAt(i);
+                UxRestrictionsListener listener = mUxRestrictionsListeners.valueAt(i);
+                restrictions.add(String.format("Display %d is %s",
+                        displayId, (listener.isRestricted() ? "restricted" : "unrestricted")));
+            }
+            writer.println("Display Restrictions:\n" + String.join("\n", restrictions));
             writer.println(String.join("\n", mBlockedActivityLogs));
             writer.print(dumpPoliciesLocked(true));
         }
@@ -946,15 +975,55 @@
         return sb.toString();
     }
 
+    /**
+     * Returns display with physical address.
+     */
+    private List<Display> getPhysicalDisplays() {
+        List<Display> displays = new ArrayList<>();
+        for (Display display : mDisplayManager.getDisplays()) {
+            if (display.getAddress() instanceof DisplayAddress.Physical) {
+                displays.add(display);
+            }
+        }
+        return displays;
+    }
+
+    /**
+     * Returns whether UX restrictions is required for display.
+     *
+     * Non-physical display will use restrictions for {@link Display#DEFAULT_DISPLAY}.
+     */
+    private boolean isUxRestrictedOnDisplay(int displayId) {
+        UxRestrictionsListener listenerForTopTaskDisplay;
+        if (mUxRestrictionsListeners.indexOfKey(displayId) < 0) {
+            listenerForTopTaskDisplay = mUxRestrictionsListeners.get(Display.DEFAULT_DISPLAY);
+            if (listenerForTopTaskDisplay == null) {
+                // This should never happen.
+                Log.e(CarLog.TAG_PACKAGE, "Missing listener for default display.");
+                return true;
+            }
+        } else {
+            listenerForTopTaskDisplay = mUxRestrictionsListeners.get(displayId);
+        }
+
+        return listenerForTopTaskDisplay.isRestricted();
+    }
+
+    private void blockTopActivitiesIfNecessary() {
+        List<TopTaskInfoContainer> topTasks = mSystemActivityMonitoringService.getTopTasks();
+        for (TopTaskInfoContainer topTask : topTasks) {
+            if (topTask == null) {
+                Log.e(CarLog.TAG_PACKAGE, "Top tasks contains null.");
+                continue;
+            }
+            blockTopActivityIfNecessary(topTask);
+        }
+    }
+
     private void blockTopActivityIfNecessary(TopTaskInfoContainer topTask) {
-        // Only block activities launched on default display.
-        if (topTask.displayId != Display.DEFAULT_DISPLAY) {
-            return;
+        if (isUxRestrictedOnDisplay(topTask.displayId)) {
+            doBlockTopActivityIfNotAllowed(topTask);
         }
-        if (!mUxRestrictionsListener.isRestricted()) {
-            return;
-        }
-        doBlockTopActivityIfNotAllowed(topTask);
     }
 
     private void doBlockTopActivityIfNotAllowed(TopTaskInfoContainer topTask) {
@@ -1004,8 +1073,9 @@
         }
 
         Intent newActivityIntent = createBlockingActivityIntent(
-                mActivityBlockingActivity, topTask.topActivity.flattenToShortString(),
-                topTask.taskId, taskRootActivity, isRootDO);
+                mActivityBlockingActivity, topTask.displayId,
+                topTask.topActivity.flattenToShortString(), topTask.taskId, taskRootActivity,
+                isRootDO);
 
         // Intent contains all info to debug what is blocked - log into both logcat and dumpsys.
         String log = "Starting blocking activity with intent: " + newActivityIntent.toUri(0);
@@ -1027,10 +1097,14 @@
      * @return an intent to launch the blocking activity.
      */
     private static Intent createBlockingActivityIntent(ComponentName blockingActivity,
-            String blockedActivity, int blockedTaskId, String taskRootActivity, boolean isRootDo) {
+            int displayId, String blockedActivity, int blockedTaskId, String taskRootActivity,
+            boolean isRootDo) {
         Intent newActivityIntent = new Intent();
+        newActivityIntent.setFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
         newActivityIntent.setComponent(blockingActivity);
         newActivityIntent.putExtra(
+                BLOCKING_INTENT_EXTRA_DISPLAY_ID, displayId);
+        newActivityIntent.putExtra(
                 BLOCKING_INTENT_EXTRA_BLOCKED_ACTIVITY_NAME, blockedActivity);
         newActivityIntent.putExtra(
                 BLOCKING_INTENT_EXTRA_BLOCKED_TASK_ID, blockedTaskId);
@@ -1042,21 +1116,6 @@
         return newActivityIntent;
     }
 
-    private void blockTopActivitiesIfNecessary() {
-        boolean restricted = mUxRestrictionsListener.isRestricted();
-        if (!restricted) {
-            return;
-        }
-        List<TopTaskInfoContainer> topTasks = mSystemActivityMonitoringService.getTopTasks();
-        for (TopTaskInfoContainer topTask : topTasks) {
-            if (topTask == null) {
-                Log.e(CarLog.TAG_PACKAGE, "Top tasks contains null.");
-                continue;
-            }
-            doBlockTopActivityIfNotAllowed(topTask);
-        }
-    }
-
     /**
      * Enable/Disable activity blocking by correspondingly enabling/disabling broadcasting UXR
      * changes in {@link CarUxRestrictionsManagerService}. This is only available in
@@ -1327,9 +1386,10 @@
                 }
             }
             if (DBG_POLICY_ENFORCEMENT) {
-                Log.d(CarLog.TAG_PACKAGE, "block?: " + shouldCheck);
+                Log.d(CarLog.TAG_PACKAGE, "Should check top tasks?: " + shouldCheck);
             }
             if (shouldCheck) {
+                // Loop over all top tasks to ensure tasks on virtual display can also be blocked.
                 blockTopActivitiesIfNecessary();
             }
         }