Show error dialogs(ANR, crash) for the visible background users
In a car environment, error dialogs are not displayed by default when
an ANR or a crash occurs. However, this can confuse the passenger users
and leave them uninformed when an app is terminated by an ANR or a 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.
Accordingly, this patch fixes
CtsAppTestCases:ActivityManagerTest#testGetProcessInErrorState fail for
visible background users.
Bug: 342315883
Flag: EXEMPT bugfix (does not affect phones)
Test: manually verified that error dialogs are shown when an ANR or a
crash occurs for the visible background user.
Test: atest --user-type secondary_user_on_secondary_display CtsAppTestCases:ActivityManagerTest#testGetProcessInErrorState
Test: atest CtsAppTestCases:ActivityManagerTest#testGetProcessInErrorState
Test: atest ActivityTaskManagerServiceTests
Signed-off-by: Dohyun Lee <dohyun.lee@lge.com>
(cherry picked from https://partner-android-review.git.corp.google.com/c/platform/frameworks/base/+/2837587)
Change-Id: If0733679d2fb8622634568827d7238aab43df800
Signed-off-by: Dohyun Lee <dohyun.lee@lge.com>
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) {