Merge "Show error dialogs(ANR, crash) for the visible background users" into main
diff --git a/services/core/java/com/android/server/am/AppErrors.java b/services/core/java/com/android/server/am/AppErrors.java
index 061bcd7..ee7033e 100644
--- a/services/core/java/com/android/server/am/AppErrors.java
+++ b/services/core/java/com/android/server/am/AppErrors.java
@@ -47,6 +47,7 @@
 import android.os.Process;
 import android.os.SystemClock;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.provider.Settings;
 import android.util.ArrayMap;
 import android.util.ArraySet;
@@ -63,6 +64,8 @@
 import com.android.internal.logging.nano.MetricsProto;
 import com.android.server.LocalServices;
 import com.android.server.PackageWatchdog;
+import com.android.server.pm.UserManagerInternal;
+import com.android.server.pm.UserManagerService;
 import com.android.server.usage.AppStandbyInternal;
 import com.android.server.wm.WindowProcessController;
 
@@ -868,9 +871,6 @@
     private boolean handleAppCrashLSPB(ProcessRecord app, String reason,
             String shortMsg, String longMsg, String stackTrace, AppErrorDialog.Data data) {
         final long now = SystemClock.uptimeMillis();
-        final boolean showBackground = Settings.Secure.getIntForUser(mContext.getContentResolver(),
-                Settings.Secure.ANR_SHOW_BACKGROUND, 0,
-                mService.mUserController.getCurrentUserId()) != 0;
 
         Long crashTime;
         Long crashTimePersistent;
@@ -881,6 +881,8 @@
         final boolean persistent = app.isPersistent();
         final WindowProcessController proc = app.getWindowProcessController();
         final ProcessErrorStateRecord errState = app.mErrorState;
+        final boolean showBackground = Settings.Secure.getIntForUser(mContext.getContentResolver(),
+                Settings.Secure.ANR_SHOW_BACKGROUND, 0, getVisibleUserId(userId)) != 0;
 
         if (!app.isolated) {
             crashTime = mProcessCrashTimes.get(processName, uid);
@@ -1000,9 +1002,6 @@
 
     void handleShowAppErrorUi(Message msg) {
         AppErrorDialog.Data data = (AppErrorDialog.Data) msg.obj;
-        boolean showBackground = Settings.Secure.getIntForUser(mContext.getContentResolver(),
-                Settings.Secure.ANR_SHOW_BACKGROUND, 0,
-                mService.mUserController.getCurrentUserId()) != 0;
 
         final int userId;
         synchronized (mProcLock) {
@@ -1027,7 +1026,11 @@
             for (int profileId : mService.mUserController.getCurrentProfileIds()) {
                 isBackground &= (userId != profileId);
             }
-            if (isBackground && !showBackground) {
+            int visibleUserId = getVisibleUserId(userId);
+            boolean isVisibleUser = isVisibleBackgroundUser(visibleUserId);
+            boolean showBackground = Settings.Secure.getIntForUser(mContext.getContentResolver(),
+                    Settings.Secure.ANR_SHOW_BACKGROUND, 0, visibleUserId) != 0;
+            if (isBackground && !showBackground && !isVisibleUser) {
                 Slog.w(TAG, "Skipping crash dialog of " + proc + ": background");
                 if (res != null) {
                     res.set(AppErrorDialog.BACKGROUND_USER);
@@ -1054,7 +1057,7 @@
                 final long now = SystemClock.uptimeMillis();
                 final boolean shouldThottle = crashShowErrorTime != null
                         && now < crashShowErrorTime + ActivityManagerConstants.MIN_CRASH_INTERVAL;
-                if ((mService.mAtmInternal.canShowErrorDialogs() || showBackground)
+                if ((mService.mAtmInternal.canShowErrorDialogs(visibleUserId) || showBackground)
                         && !crashSilenced && !shouldThottle
                         && (showFirstCrash || showFirstCrashDevOption || data.repeating)) {
                     Slog.i(TAG, "Showing crash dialog for package " + packageName + " u" + userId);
@@ -1103,10 +1106,10 @@
                 return;
             }
 
+            int visibleUserId = getVisibleUserId(proc.userId);
             boolean showBackground = Settings.Secure.getIntForUser(mContext.getContentResolver(),
-                    Settings.Secure.ANR_SHOW_BACKGROUND, 0,
-                    mService.mUserController.getCurrentUserId()) != 0;
-            if (mService.mAtmInternal.canShowErrorDialogs() || showBackground) {
+                    Settings.Secure.ANR_SHOW_BACKGROUND, 0, visibleUserId) != 0;
+            if (mService.mAtmInternal.canShowErrorDialogs(visibleUserId) || showBackground) {
                 AnrController anrController = errState.getDialogController().getAnrController();
                 if (anrController == null) {
                     errState.getDialogController().showAnrDialogs(data);
@@ -1163,6 +1166,43 @@
     }
 
     /**
+     * Returns the user ID of the visible user associated with the error occurrence.
+     *
+     * <p>For most cases it will return the current foreground user ID, but on devices that
+     * {@link UserManager#isVisibleBackgroundUsersEnabled() support visible background users},
+     * it will return the given app user ID passed as parameter.
+     *
+     * @param appUserId The user ID of the app where the error occurred.
+     * @return The ID of the visible user associated with the error.
+     */
+    private int getVisibleUserId(int appUserId) {
+        if (!UserManager.isVisibleBackgroundUsersEnabled()) {
+            return mService.mUserController.getCurrentUserId();
+        }
+        return appUserId;
+    }
+
+    /**
+     * Checks if the given user is a visible background user, which is a full, background user
+     * assigned to secondary displays on the devices that have
+     * {@link UserManager#isVisibleBackgroundUsersEnabled()
+     * config_multiuserVisibleBackgroundUsers enabled} (for example, passenger users on
+     * automotive builds, using the display associated with their seats).
+     *
+     * @see UserManager#isUserVisible()
+     */
+    private boolean isVisibleBackgroundUser(int userId) {
+        if (!UserManager.isVisibleBackgroundUsersEnabled()) {
+            return false;
+        }
+        boolean isForeground = mService.mUserController.getCurrentUserId() == userId;
+        boolean isProfile = UserManagerService.getInstance().isProfile(userId);
+        boolean isVisible = LocalServices.getService(UserManagerInternal.class)
+                .isUserVisible(userId);
+        return isVisible && !isForeground && !isProfile;
+    }
+
+    /**
      * Information about a process that is currently marked as bad.
      */
     static final class BadProcessInfo {
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
index 3b0b727..26a6b00 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
@@ -584,7 +584,7 @@
 
     public abstract void clearLockedTasks(String reason);
     public abstract void updateUserConfiguration();
-    public abstract boolean canShowErrorDialogs();
+    public abstract boolean canShowErrorDialogs(int userId);
 
     public abstract void setProfileApp(String profileApp);
     public abstract void setProfileProc(WindowProcessController wpc);
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index ff46b33..a84598d 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -190,6 +190,7 @@
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.ParceledListSlice;
 import android.content.pm.ResolveInfo;
+import android.content.pm.UserInfo;
 import android.content.res.CompatibilityInfo;
 import android.content.res.Configuration;
 import android.content.res.Resources;
@@ -4899,14 +4900,21 @@
      * dialog / global actions also might want different behaviors.
      */
     private void updateShouldShowDialogsLocked(Configuration config) {
+        mShowDialogs = shouldShowDialogs(config, /* checkUiMode= */ true);
+    }
+
+    private boolean shouldShowDialogs(Configuration config, boolean checkUiMode) {
         final boolean inputMethodExists = !(config.keyboard == Configuration.KEYBOARD_NOKEYS
                 && config.touchscreen == Configuration.TOUCHSCREEN_NOTOUCH
                 && config.navigation == Configuration.NAVIGATION_NONAV);
         final boolean hideDialogsSet = Settings.Global.getInt(mContext.getContentResolver(),
                 HIDE_ERROR_DIALOGS, 0) != 0;
-        mShowDialogs = inputMethodExists
-                && ActivityTaskManager.currentUiModeSupportsErrorDialogs(config)
-                && !hideDialogsSet;
+        boolean showDialogs = inputMethodExists && !hideDialogsSet;
+        if (checkUiMode) {
+            showDialogs = showDialogs
+                    && ActivityTaskManager.currentUiModeSupportsErrorDialogs(config);
+        }
+        return showDialogs;
     }
 
     private void updateFontScaleIfNeeded(@UserIdInt int userId) {
@@ -7148,17 +7156,69 @@
         }
 
         @Override
-        public boolean canShowErrorDialogs() {
+        public boolean canShowErrorDialogs(int userId) {
             synchronized (mGlobalLock) {
-                return mShowDialogs && !mSleeping && !mShuttingDown
+                final boolean showDialogs = mShowDialogs
+                        || shouldShowDialogsForVisibleBackgroundUserLocked(userId);
+                final UserInfo userInfo = getUserManager().getUserInfo(userId);
+                if (userInfo == null) {
+                    // Unable to retrieve user information. Returning false, assuming there is
+                    // no valid user with the given id.
+                    return false;
+                }
+                return showDialogs && !mSleeping && !mShuttingDown
                         && !mKeyguardController.isKeyguardOrAodShowing(DEFAULT_DISPLAY)
-                        && !hasUserRestriction(UserManager.DISALLOW_SYSTEM_ERROR_DIALOGS,
-                        mAmInternal.getCurrentUserId())
+                        && !hasUserRestriction(UserManager.DISALLOW_SYSTEM_ERROR_DIALOGS, userId)
                         && !(UserManager.isDeviceInDemoMode(mContext)
-                        && mAmInternal.getCurrentUser().isDemo());
+                        && userInfo.isDemo());
             }
         }
 
+        /**
+         * Checks if the given user is a visible background user, which is a full, background user
+         * assigned to secondary displays on the devices that have
+         * {@link UserManager#isVisibleBackgroundUsersEnabled()
+         * config_multiuserVisibleBackgroundUsers enabled} (for example, passenger users on
+         * automotive builds, using the display associated with their seats).
+         *
+         * @see UserManager#isUserVisible()
+         */
+        private boolean isVisibleBackgroundUser(int userId) {
+            if (!UserManager.isVisibleBackgroundUsersEnabled()) {
+                return false;
+            }
+            boolean isForeground = getCurrentUserId() == userId;
+            boolean isProfile = getUserManager().isProfile(userId);
+            boolean isVisible = mWindowManager.mUmInternal.isUserVisible(userId);
+            return isVisible && !isForeground && !isProfile;
+        }
+
+        /**
+         * In a car environment, {@link ActivityTaskManagerService#mShowDialogs} is always set to
+         * {@code false} from {@link ActivityTaskManagerService#updateShouldShowDialogsLocked}
+         * because its UI mode is {@link Configuration#UI_MODE_TYPE_CAR}. Thus, error dialogs are
+         * not displayed when an ANR or a crash occurs. However, in the automotive multi-user
+         * multi-display environment, this can confuse the passenger users and leave them
+         * uninformed when an app is terminated by the ANR or crash without any notification.
+         * To address this, error dialogs are allowed for the passenger users who have UI access
+         * on assigned displays (a.k.a. visible background users) on devices that have
+         * config_multiuserVisibleBackgroundUsers enabled even though the UI mode is
+         * {@link Configuration#UI_MODE_TYPE_CAR}.
+         *
+         * @see ActivityTaskManagerService#updateShouldShowDialogsLocked
+         */
+        private boolean shouldShowDialogsForVisibleBackgroundUserLocked(int userId) {
+            if (!isVisibleBackgroundUser(userId)) {
+                return false;
+            }
+            final int displayId = mWindowManager.mUmInternal.getMainDisplayAssignedToUser(userId);
+            final DisplayContent dc = mRootWindowContainer.getDisplayContent(displayId);
+            if (dc == null) {
+                return false;
+            }
+            return shouldShowDialogs(dc.getConfiguration(), /* checkUiMode= */ false);
+        }
+
         @Override
         public void setProfileApp(String profileApp) {
             synchronized (mGlobalLock) {