Support non-managed profile Status Bar icons
When a managed profile's app is in the foreground, the status bar shows
a work briefcase icon to let the user know that the app is running in
the work profile.
However, this only works for managed (work) profiles, not for other
profile types.
As we explore other profile types, we need this same status bar
indicator. We therefore move the icon from SystemUi to Frameworks, tie
it to the profile type, and thereby allow non-managed profiles to also
display an icon.
For prod, this cl is a no-op, since only managed profiles are supported
in out-of-the-box AOSP. But using adb, other profile types can be
created, and they now can display a status bar icon.
Bug: 170249807
Bug: 274838657
Test: atest UserManagerServiceUserTypeTest
Test: atest com.android.server.pm.UserManagerTest#testProfileTypeInformation
Test: Manually confirmed that managed profiles have no change
Test: adb root; adb shell setprop persist.debug.user_mode_emulation 1 && adb shell pm create-user --user-type android.os.usertype.profile.TEST --profileOf 10 Testname and verify that flask icon appears
Change-Id: Ic2099748b8cdb838730def886c46d990b068652b
diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index 8e1d2d6..063cd99 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -128,6 +128,7 @@
int getUserBadgeLabelResId(int userId);
int getUserBadgeColorResId(int userId);
int getUserBadgeDarkColorResId(int userId);
+ int getUserStatusBarIconResId(int userId);
boolean hasBadge(int userId);
boolean isUserUnlocked(int userId);
boolean isUserRunning(int userId);
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index d40d529..670413d 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -5098,6 +5098,21 @@
}
/**
+ * Returns the Resource ID of the user's status bar icon.
+ *
+ * @return the Resource ID of the user's status bar icon if it has one; otherwise
+ * {@link Resources#ID_NULL}.
+ * @hide
+ */
+ public @DrawableRes int getUserStatusBarIconResId(@UserIdInt int userId) {
+ try {
+ return mService.getUserStatusBarIconResId(userId);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* If the target user is a profile of the calling user or the caller
* is itself a profile, then this returns a badged copy of the given
* icon to be able to distinguish it from the original icon. For badging an
diff --git a/packages/SystemUI/res/drawable/stat_sys_managed_profile_status.xml b/core/res/res/drawable/stat_sys_managed_profile_status.xml
similarity index 100%
rename from packages/SystemUI/res/drawable/stat_sys_managed_profile_status.xml
rename to core/res/res/drawable/stat_sys_managed_profile_status.xml
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 9970790..532cd0f 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1392,6 +1392,7 @@
<java-symbol type="drawable" name="ic_corp_user_badge" />
<java-symbol type="drawable" name="ic_corp_badge_no_background" />
<java-symbol type="drawable" name="ic_corp_statusbar_icon" />
+ <java-symbol type="drawable" name="stat_sys_managed_profile_status" />
<java-symbol type="drawable" name="ic_test_badge_experiment" />
<java-symbol type="drawable" name="ic_test_badge_no_background" />
<java-symbol type="drawable" name="ic_test_icon_badge_experiment" />
diff --git a/packages/EasterEgg/res/values/q_puzzles.xml b/packages/EasterEgg/res/values/q_puzzles.xml
index 7c2eff1..3c9ac0b 100644
--- a/packages/EasterEgg/res/values/q_puzzles.xml
+++ b/packages/EasterEgg/res/values/q_puzzles.xml
@@ -206,7 +206,7 @@
<item>com.android.systemui:drawable/ic_volume_ringer_vibrate</item>
<item>com.android.systemui:drawable/ic_volume_voice</item>
<item>com.android.systemui:drawable/stat_sys_camera</item>
- <item>com.android.systemui:drawable/stat_sys_managed_profile_status</item>
+ <item>android:drawable/stat_sys_managed_profile_status</item>
<item>com.android.systemui:drawable/stat_sys_mic_none</item>
<item>com.android.systemui:drawable/stat_sys_vpn_ic</item>
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
index 17e72e5..8f9cf4b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
@@ -53,7 +53,8 @@
public static final String TILE_SPEC = "work";
- private final Icon mIcon = ResourceIcon.get(R.drawable.stat_sys_managed_profile_status);
+ private final Icon mIcon = ResourceIcon.get(
+ com.android.internal.R.drawable.stat_sys_managed_profile_status);
private final ManagedProfileController mProfileController;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
index e6b76ad..78ad2a7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -158,7 +158,7 @@
private boolean mMuteVisible;
private boolean mCurrentUserSetup;
- private boolean mManagedProfileIconVisible = false;
+ private boolean mProfileIconVisible = false;
private BluetoothController mBluetooth;
private AlarmManager.AlarmClockInfo mNextAlarm;
@@ -247,7 +247,9 @@
filter.addAction(TelecomManager.ACTION_CURRENT_TTY_MODE_CHANGED);
filter.addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE);
filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE);
- filter.addAction(Intent.ACTION_MANAGED_PROFILE_REMOVED);
+ filter.addAction(Intent.ACTION_PROFILE_REMOVED);
+ filter.addAction(Intent.ACTION_PROFILE_ACCESSIBLE);
+ filter.addAction(Intent.ACTION_PROFILE_INACCESSIBLE);
mBroadcastDispatcher.registerReceiverWithHandler(mIntentReceiver, filter, mHandler);
Observer<Integer> observer = ringer -> mHandler.post(this::updateVolumeZen);
@@ -290,8 +292,8 @@
mResources.getString(R.string.accessibility_status_bar_hotspot));
mIconController.setIconVisibility(mSlotHotspot, mHotspot.isHotspotEnabled());
- // managed profile
- updateManagedProfile();
+ // profile
+ updateProfileIcon();
// data saver
mIconController.setIcon(mSlotDataSaver, R.drawable.stat_sys_data_saver,
@@ -518,34 +520,34 @@
}
}
- private void updateManagedProfile() {
+ private void updateProfileIcon() {
// getLastResumedActivityUserId needs to acquire the AM lock, which may be contended in
// some cases. Since it doesn't really matter here whether it's updated in this frame
// or in the next one, we call this method from our UI offload thread.
mUiBgExecutor.execute(() -> {
- final int userId;
try {
- userId = ActivityTaskManager.getService().getLastResumedActivityUserId();
- boolean isManagedProfile = mUserManager.isManagedProfile(userId);
+ final int userId = ActivityTaskManager.getService().getLastResumedActivityUserId();
+ final int iconResId = mUserManager.getUserStatusBarIconResId(userId);
+ // TODO(b/170249807, b/230779281): Handle non-managed-profile String
String accessibilityString = getManagedProfileAccessibilityString();
mHandler.post(() -> {
final boolean showIcon;
- if (isManagedProfile && (!mKeyguardStateController.isShowing()
+ if (iconResId != Resources.ID_NULL && (!mKeyguardStateController.isShowing()
|| mKeyguardStateController.isOccluded())) {
showIcon = true;
mIconController.setIcon(mSlotManagedProfile,
- R.drawable.stat_sys_managed_profile_status,
+ iconResId,
accessibilityString);
} else {
showIcon = false;
}
- if (mManagedProfileIconVisible != showIcon) {
+ if (mProfileIconVisible != showIcon) {
mIconController.setIconVisibility(mSlotManagedProfile, showIcon);
- mManagedProfileIconVisible = showIcon;
+ mProfileIconVisible = showIcon;
}
});
} catch (RemoteException e) {
- Log.w(TAG, "updateManagedProfile: ", e);
+ Log.w(TAG, "updateProfileIcon: ", e);
}
});
}
@@ -561,7 +563,7 @@
public void onUserChanged(int newUser, Context userContext) {
mHandler.post(() -> {
updateAlarm();
- updateManagedProfile();
+ updateProfileIcon();
onUserSetupChanged();
});
}
@@ -604,13 +606,13 @@
public void appTransitionStarting(int displayId, long startTime, long duration,
boolean forced) {
if (mDisplayId == displayId) {
- updateManagedProfile();
+ updateProfileIcon();
}
}
@Override
public void onKeyguardShowingChanged() {
- updateManagedProfile();
+ updateProfileIcon();
}
@Override
@@ -733,8 +735,10 @@
break;
case Intent.ACTION_MANAGED_PROFILE_AVAILABLE:
case Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE:
- case Intent.ACTION_MANAGED_PROFILE_REMOVED:
- updateManagedProfile();
+ case Intent.ACTION_PROFILE_REMOVED:
+ case Intent.ACTION_PROFILE_ACCESSIBLE:
+ case Intent.ACTION_PROFILE_INACCESSIBLE:
+ updateProfileIcon();
break;
case AudioManager.ACTION_HEADSET_PLUG:
updateHeadsetPlug(intent);
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 4321451..7917e51 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -1874,6 +1874,18 @@
return userTypeDetails.getBadgeNoBackground();
}
+ @Override
+ public @DrawableRes int getUserStatusBarIconResId(@UserIdInt int userId) {
+ checkManageOrInteractPermissionIfCallerInOtherProfileGroup(userId,
+ "getUserStatusBarIconResId");
+ final UserTypeDetails userTypeDetails = getUserTypeDetailsNoChecks(userId);
+ if (userTypeDetails == null || !userTypeDetails.hasBadge()) {
+ Slog.w(LOG_TAG, "Requested status bar icon for non-badged user " + userId);
+ return Resources.ID_NULL;
+ }
+ return userTypeDetails.getStatusBarIcon();
+ }
+
public boolean isProfile(@UserIdInt int userId) {
checkQueryOrInteractPermissionIfCallerInOtherProfileGroup(userId, "isProfile");
return isProfileUnchecked(userId);
diff --git a/services/core/java/com/android/server/pm/UserTypeDetails.java b/services/core/java/com/android/server/pm/UserTypeDetails.java
index 6065372..d626be0 100644
--- a/services/core/java/com/android/server/pm/UserTypeDetails.java
+++ b/services/core/java/com/android/server/pm/UserTypeDetails.java
@@ -116,6 +116,9 @@
/** Resource ID of the badge without a background. Should be set if mIconBadge is set. */
private @DrawableRes final int mBadgeNoBackground;
+ /** Resource ID of the status bar icon. */
+ private @DrawableRes final int mStatusBarIcon;
+
/**
* Resource ID ({@link StringRes}) of the of the labels to describe badged apps; should be the
* same format as com.android.internal.R.color.profile_badge_1. These are used for accessibility
@@ -160,6 +163,7 @@
@UserInfoFlag int baseType, @UserInfoFlag int defaultUserInfoPropertyFlags, int label,
int maxAllowedPerParent,
int iconBadge, int badgePlain, int badgeNoBackground,
+ int statusBarIcon,
@Nullable int[] badgeLabels, @Nullable int[] badgeColors,
@Nullable int[] darkThemeBadgeColors,
@Nullable Bundle defaultRestrictions,
@@ -181,6 +185,7 @@
this.mIconBadge = iconBadge;
this.mBadgePlain = badgePlain;
this.mBadgeNoBackground = badgeNoBackground;
+ this.mStatusBarIcon = statusBarIcon;
this.mLabel = label;
this.mBadgeLabels = badgeLabels;
this.mBadgeColors = badgeColors;
@@ -254,6 +259,11 @@
return mBadgeNoBackground;
}
+ /** Resource ID of the status bar icon. */
+ public @DrawableRes int getStatusBarIcon() {
+ return mStatusBarIcon;
+ }
+
/**
* Returns the Resource ID of the badgeIndexth badge label, where the badgeIndex is expected
* to be the {@link UserInfo#profileBadge} of the user.
@@ -375,6 +385,7 @@
pw.print(prefix); pw.print("mIconBadge: "); pw.println(mIconBadge);
pw.print(prefix); pw.print("mBadgePlain: "); pw.println(mBadgePlain);
pw.print(prefix); pw.print("mBadgeNoBackground: "); pw.println(mBadgeNoBackground);
+ pw.print(prefix); pw.print("mStatusBarIcon: "); pw.println(mStatusBarIcon);
pw.print(prefix); pw.print("mBadgeLabels.length: ");
pw.println(mBadgeLabels != null ? mBadgeLabels.length : "0(null)");
pw.print(prefix); pw.print("mBadgeColors.length: ");
@@ -404,6 +415,7 @@
private @DrawableRes int mIconBadge = Resources.ID_NULL;
private @DrawableRes int mBadgePlain = Resources.ID_NULL;
private @DrawableRes int mBadgeNoBackground = Resources.ID_NULL;
+ private @DrawableRes int mStatusBarIcon = Resources.ID_NULL;
// Default UserProperties cannot be null but for efficiency we don't initialize it now.
// If it isn't set explicitly, {@link UserProperties.Builder#build()} will be used.
private @Nullable UserProperties mDefaultUserProperties = null;
@@ -471,6 +483,11 @@
return this;
}
+ public Builder setStatusBarIcon(@DrawableRes int statusBarIcon) {
+ mStatusBarIcon = statusBarIcon;
+ return this;
+ }
+
public Builder setLabel(int label) {
mLabel = label;
return this;
@@ -550,6 +567,7 @@
mIconBadge,
mBadgePlain,
mBadgeNoBackground,
+ mStatusBarIcon,
mBadgeLabels,
mBadgeColors,
mDarkThemeBadgeColors == null ? mBadgeColors : mDarkThemeBadgeColors,
diff --git a/services/core/java/com/android/server/pm/UserTypeFactory.java b/services/core/java/com/android/server/pm/UserTypeFactory.java
index a814ca4..99a2ec1 100644
--- a/services/core/java/com/android/server/pm/UserTypeFactory.java
+++ b/services/core/java/com/android/server/pm/UserTypeFactory.java
@@ -128,6 +128,7 @@
.setBadgePlain(com.android.internal.R.drawable.ic_clone_badge)
// Clone doesn't use BadgeNoBackground, so just set to BadgePlain as a placeholder.
.setBadgeNoBackground(com.android.internal.R.drawable.ic_clone_badge)
+ .setStatusBarIcon(Resources.ID_NULL)
.setBadgeLabels(
com.android.internal.R.string.clone_profile_label_badge)
.setBadgeColors(
@@ -167,6 +168,7 @@
.setIconBadge(com.android.internal.R.drawable.ic_corp_icon_badge_case)
.setBadgePlain(com.android.internal.R.drawable.ic_corp_badge_case)
.setBadgeNoBackground(com.android.internal.R.drawable.ic_corp_badge_no_background)
+ .setStatusBarIcon(com.android.internal.R.drawable.stat_sys_managed_profile_status)
.setBadgeLabels(
com.android.internal.R.string.managed_profile_label_badge,
com.android.internal.R.string.managed_profile_label_badge_2,
@@ -205,6 +207,7 @@
.setIconBadge(com.android.internal.R.drawable.ic_test_icon_badge_experiment)
.setBadgePlain(com.android.internal.R.drawable.ic_test_badge_experiment)
.setBadgeNoBackground(com.android.internal.R.drawable.ic_test_badge_no_background)
+ .setStatusBarIcon(com.android.internal.R.drawable.ic_test_badge_experiment)
.setBadgeLabels(
com.android.internal.R.string.managed_profile_label_badge,
com.android.internal.R.string.managed_profile_label_badge_2,
@@ -426,6 +429,7 @@
setResAttribute(parser, "icon-badge", builder::setIconBadge);
setResAttribute(parser, "badge-plain", builder::setBadgePlain);
setResAttribute(parser, "badge-no-background", builder::setBadgeNoBackground);
+ setResAttribute(parser, "status-bar-icon", builder::setStatusBarIcon);
}
setIntAttribute(parser, "enabled", builder::setEnabled);
diff --git a/services/tests/servicestests/res/xml/usertypes_test_profile.xml b/services/tests/servicestests/res/xml/usertypes_test_profile.xml
index 26d681b..0c5c6e4 100644
--- a/services/tests/servicestests/res/xml/usertypes_test_profile.xml
+++ b/services/tests/servicestests/res/xml/usertypes_test_profile.xml
@@ -20,6 +20,7 @@
icon-badge='@*android:drawable/ic_corp_icon_badge_case'
badge-plain='garbage'
badge-no-background='@*android:drawable/ic_corp_badge_no_background'
+ status-bar-icon='@*android:drawable/ic_test_badge_experiment'
>
<badge-labels>
<item res='@*android:string/managed_profile_label_badge' />
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java
index ff9a79e..77f73cf 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java
@@ -106,6 +106,7 @@
.setBadgeNoBackground(30)
.setLabel(31)
.setMaxAllowedPerParent(32)
+ .setStatusBarIcon(33)
.setDefaultRestrictions(restrictions)
.setDefaultSystemSettings(systemSettings)
.setDefaultSecureSettings(secureSettings)
@@ -122,6 +123,7 @@
assertEquals(30, type.getBadgeNoBackground());
assertEquals(31, type.getLabel());
assertEquals(32, type.getMaxAllowedPerParent());
+ assertEquals(33, type.getStatusBarIcon());
assertTrue(UserRestrictionsUtils.areEqual(restrictions, type.getDefaultRestrictions()));
assertNotSame(restrictions, type.getDefaultRestrictions());
@@ -191,6 +193,7 @@
assertEquals(Resources.ID_NULL, type.getIconBadge());
assertEquals(Resources.ID_NULL, type.getBadgePlain());
assertEquals(Resources.ID_NULL, type.getBadgeNoBackground());
+ assertEquals(Resources.ID_NULL, type.getStatusBarIcon());
assertEquals(Resources.ID_NULL, type.getBadgeLabel(0));
assertEquals(Resources.ID_NULL, type.getBadgeColor(0));
assertEquals(Resources.ID_NULL, type.getLabel());
@@ -348,6 +351,8 @@
assertEquals(Resources.ID_NULL, aospType.getBadgePlain()); // No resId for 'garbage'
assertEquals(com.android.internal.R.drawable.ic_corp_badge_no_background,
aospType.getBadgeNoBackground());
+ assertEquals(com.android.internal.R.drawable.ic_test_badge_experiment,
+ aospType.getStatusBarIcon());
assertEquals(com.android.internal.R.string.managed_profile_label_badge,
aospType.getBadgeLabel(0));
assertEquals(com.android.internal.R.string.managed_profile_label_badge_2,
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
index 6bcda3f..3c35735 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -803,6 +803,8 @@
.isEqualTo(userTypeDetails.getBadgePlain());
assertThat(mUserManager.getUserBadgeNoBackgroundResId(userId))
.isEqualTo(userTypeDetails.getBadgeNoBackground());
+ assertThat(mUserManager.getUserStatusBarIconResId(userId))
+ .isEqualTo(userTypeDetails.getStatusBarIcon());
final int badgeIndex = userInfo.profileBadge;
assertThat(mUserManager.getUserBadgeColor(userId)).isEqualTo(