Allow overriding max profile in debugable builds.
Support a system property on debugable builds to
override the max number of managed profiles to
allow easier dogfooding of multiple profiles.
Support 3 different badge colours for managed profiles.
Bug: 30473760
Test: runtest -c com.android.server.pm.UserManagerServiceCreateProfileTest frameworks-services
Test: runtest -c com.android.server.pm.UserManagerServiceUserInfoTest frameworks-services
Test: manual - attempting to create 2 profiles with adb fails, passes once I set the property.
Change-Id: Ie7fb19048a04a01572666f229283152254d0ffc3
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index b199984..627e661 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -58,9 +58,11 @@
import android.content.res.XmlResourceParser;
import android.graphics.Bitmap;
import android.graphics.Canvas;
+import android.graphics.Color;
import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
+import android.graphics.drawable.LayerDrawable;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
@@ -81,6 +83,7 @@
import dalvik.system.VMRuntime;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.SomeArgs;
import com.android.internal.util.Preconditions;
import com.android.internal.util.UserIcons;
@@ -1175,21 +1178,21 @@
}
@Override
- public Drawable getManagedUserBadgedDrawable(Drawable drawable, Rect badgeLocation,
- int badgeDensity) {
- Drawable badgeDrawable = getDrawableForDensity(
- com.android.internal.R.drawable.ic_corp_badge, badgeDensity);
- return getBadgedDrawable(drawable, badgeDrawable, badgeLocation, true);
- }
-
- @Override
public Drawable getUserBadgedIcon(Drawable icon, UserHandle user) {
- final int badgeResId = getBadgeResIdForUser(user.getIdentifier());
- if (badgeResId == 0) {
+ if (!isManagedProfile(user.getIdentifier())) {
return icon;
}
- Drawable badgeIcon = getDrawable("system", badgeResId, null);
- return getBadgedDrawable(icon, badgeIcon, null, true);
+ Drawable badgeShadow = getDrawable("system",
+ com.android.internal.R.drawable.ic_corp_icon_badge_shadow, null);
+ Drawable badgeColor = getDrawable("system",
+ com.android.internal.R.drawable.ic_corp_icon_badge_color, null);
+ badgeColor.setTint(getUserBadgeColor(user));
+ Drawable badgeForeground = getDrawable("system",
+ com.android.internal.R.drawable.ic_corp_icon_badge_case, null);
+
+ Drawable badge = new LayerDrawable(
+ new Drawable[] {badgeShadow, badgeColor, badgeForeground });
+ return getBadgedDrawable(icon, badge, null, true);
}
@Override
@@ -1202,16 +1205,53 @@
return getBadgedDrawable(drawable, badgeDrawable, badgeLocation, true);
}
+ // Should have enough colors to cope with UserManagerService.getMaxManagedProfiles()
+ @VisibleForTesting
+ public static final int[] CORP_BADGE_COLORS = new int[] {
+ com.android.internal.R.color.profile_badge_1,
+ com.android.internal.R.color.profile_badge_2,
+ com.android.internal.R.color.profile_badge_3
+ };
+
+ @VisibleForTesting
+ public static final int[] CORP_BADGE_LABEL_RES_ID = new int[] {
+ com.android.internal.R.string.managed_profile_label_badge,
+ com.android.internal.R.string.managed_profile_label_badge_2,
+ com.android.internal.R.string.managed_profile_label_badge_3
+ };
+
+ private int getUserBadgeColor(UserHandle user) {
+ int badge = getUserManager().getManagedProfileBadge(user.getIdentifier());
+ if (badge < 0) {
+ badge = 0;
+ }
+ int resourceId = CORP_BADGE_COLORS[badge % CORP_BADGE_COLORS.length];
+ return Resources.getSystem().getColor(resourceId, null);
+ }
+
@Override
public Drawable getUserBadgeForDensity(UserHandle user, int density) {
- return getManagedProfileIconForDensity(user, com.android.internal.R.drawable.ic_corp_badge,
- density);
+ Drawable badgeColor = getManagedProfileIconForDensity(user,
+ com.android.internal.R.drawable.ic_corp_badge_color, density);
+ if (badgeColor == null) {
+ return null;
+ }
+ badgeColor.setTint(getUserBadgeColor(user));
+ Drawable badgeForeground = getDrawableForDensity(
+ com.android.internal.R.drawable.ic_corp_badge_case, density);
+ Drawable badge = new LayerDrawable(
+ new Drawable[] {badgeColor, badgeForeground });
+ return badge;
}
@Override
public Drawable getUserBadgeForDensityNoBackground(UserHandle user, int density) {
- return getManagedProfileIconForDensity(user,
+ Drawable badge = getManagedProfileIconForDensity(user,
com.android.internal.R.drawable.ic_corp_badge_no_background, density);
+ if (badge != null) {
+ badge.setTint(getUserBadgeColor(user));
+ }
+ return badge;
}
private Drawable getDrawableForDensity(int drawableId, int density) {
@@ -1231,8 +1271,9 @@
@Override
public CharSequence getUserBadgedLabel(CharSequence label, UserHandle user) {
if (isManagedProfile(user.getIdentifier())) {
- return Resources.getSystem().getString(
- com.android.internal.R.string.managed_profile_label_badge, label);
+ int badge = getUserManager().getManagedProfileBadge(user.getIdentifier());
+ int resourceId = CORP_BADGE_LABEL_RES_ID[badge % CORP_BADGE_LABEL_RES_ID.length];
+ return Resources.getSystem().getString(resourceId, label);
}
return label;
}
@@ -2355,14 +2396,6 @@
return drawable;
}
- private int getBadgeResIdForUser(int userId) {
- // Return the framework-provided badge.
- if (isManagedProfile(userId)) {
- return com.android.internal.R.drawable.ic_corp_icon_badge;
- }
- return 0;
- }
-
private boolean isManagedProfile(int userId) {
return getUserManager().isManagedProfile(userId);
}
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index b3dd0e5..5087bc8 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -4522,32 +4522,6 @@
throws NameNotFoundException;
/**
- * Returns a managed-user-style badged copy of the given drawable allowing the user to
- * distinguish it from the original drawable.
- * The caller can specify the location in the bounds of the drawable to be
- * badged where the badge should be applied as well as the density of the
- * badge to be used.
- * <p>
- * If the original drawable is a BitmapDrawable and the backing bitmap is
- * mutable as per {@link android.graphics.Bitmap#isMutable()}, the badging
- * is performed in place and the original drawable is returned.
- * </p>
- *
- * @param drawable The drawable to badge.
- * @param badgeLocation Where in the bounds of the badged drawable to place
- * the badge. If it's {@code null}, the badge is applied on top of the entire
- * drawable being badged.
- * @param badgeDensity The optional desired density for the badge as per
- * {@link android.util.DisplayMetrics#densityDpi}. If it's not positive,
- * the density of the display is used.
- * @return A drawable that combines the original drawable and a badge as
- * determined by the system.
- * @hide
- */
- public abstract Drawable getManagedUserBadgedDrawable(Drawable drawable, Rect badgeLocation,
- int badgeDensity);
-
- /**
* If the target user is a managed profile, then this returns a badged copy of the given icon
* to be able to distinguish it from the original icon. For badging an arbitrary drawable use
* {@link #getUserBadgedDrawableForDensity(
diff --git a/core/java/android/content/pm/UserInfo.java b/core/java/android/content/pm/UserInfo.java
index e796fa7..f34b590 100644
--- a/core/java/android/content/pm/UserInfo.java
+++ b/core/java/android/content/pm/UserInfo.java
@@ -109,6 +109,8 @@
*/
public int profileGroupId;
public int restrictedProfileParentId;
+ /** Which profile badge color/label to use. */
+ public int profileBadge;
/** User is only partially created. */
public boolean partial;
@@ -233,6 +235,7 @@
profileGroupId = orig.profileGroupId;
restrictedProfileParentId = orig.restrictedProfileParentId;
guestToRemove = orig.guestToRemove;
+ profileBadge = orig.profileBadge;
}
public UserHandle getUserHandle() {
@@ -261,6 +264,7 @@
dest.writeInt(profileGroupId);
dest.writeInt(guestToRemove ? 1 : 0);
dest.writeInt(restrictedProfileParentId);
+ dest.writeInt(profileBadge);
}
public static final Parcelable.Creator<UserInfo> CREATOR
@@ -286,5 +290,6 @@
profileGroupId = source.readInt();
guestToRemove = source.readInt() != 0;
restrictedProfileParentId = source.readInt();
+ profileBadge = source.readInt();
}
}
diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index 65c6093..9dafe29 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -86,4 +86,5 @@
UserInfo createProfileForUserEvenWhenDisallowed(in String name, int flags, int userHandle,
in String[] disallowedPackages);
boolean isUserUnlockingOrUnlocked(int userId);
+ int getManagedProfileBadge(int userId);
}
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 50eb7cf..a79b0c4 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -948,6 +948,23 @@
}
/**
+ * Gets badge for a managed profile.
+ * Requires {@link android.Manifest.permission#MANAGE_USERS} permission, otherwise the caller
+ * must be in the same profile group of specified user.
+ *
+ * @return which badge to use for the managed profile badge id will be less than
+ * UserManagerService.getMaxManagedProfiles()
+ * @hide
+ */
+ public int getManagedProfileBadge(@UserIdInt int userId) {
+ try {
+ return mService.getManagedProfileBadge(userId);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Checks if the calling app is running as an ephemeral user.
*
* @return whether the caller is an ephemeral user.
diff --git a/core/res/res/drawable/ic_corp_badge_case.xml b/core/res/res/drawable/ic_corp_badge_case.xml
new file mode 100644
index 0000000..0b6028c
--- /dev/null
+++ b/core/res/res/drawable/ic_corp_badge_case.xml
@@ -0,0 +1,30 @@
+<!--
+Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="20.0dp"
+ android:height="20.0dp"
+ android:viewportWidth="20.0"
+ android:viewportHeight="20.0">
+ <path
+ android:pathData="M15.2,6.2L4.8,6.2c-0.5,0.0 -0.9,0.4 -0.9,1.0L3.9,10.0c0.0,0.5 0.4,1.0 0.9,1.0l3.8,0.0l0.0,-1.0l2.9,0.0l0.0,1.0l3.8,0.0c0.5,0.0 1.0,-0.4 1.0,-1.0L16.3,7.1C16.2,6.6 15.8,6.2 15.2,6.2z"
+ android:fillColor="#FFFFFF"/>
+ <path
+ android:pathData="M8.6,12.9l0.0,-1.0L4.3,11.9l0.0,2.4c0.0,0.5 0.4,0.9 0.9,0.9l9.5,0.0c0.5,0.0 0.9,-0.4 0.9,-0.9l0.0,-2.4l-4.3,0.0l0.0,1.0L8.6,12.9z"
+ android:fillColor="#FFFFFF"/>
+ <path
+ android:pathData="M7.1,5.2l0.0,1.0 1.0,0.0 0.0,-1.0 3.799999,0.0 0.0,1.0 1.0,0.0 0.0,-1.0 -1.0,-0.9 -3.799999,0.0z"
+ android:fillColor="#FFFFFF"/>
+</vector>
diff --git a/core/res/res/drawable/ic_corp_badge_color.xml b/core/res/res/drawable/ic_corp_badge_color.xml
new file mode 100644
index 0000000..b6c7969
--- /dev/null
+++ b/core/res/res/drawable/ic_corp_badge_color.xml
@@ -0,0 +1,24 @@
+<!--
+Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="20.0dp"
+ android:height="20.0dp"
+ android:viewportWidth="20.0"
+ android:viewportHeight="20.0">
+ <path
+ android:pathData="M10.0,10.0m-10.0,0.0a10.0,10.0 0.0,1.0 1.0,20.0 0.0a10.0,10.0 0.0,1.0 1.0,-20.0 0.0"
+ android:fillColor="#FFFFFF"/>
+</vector>
diff --git a/core/res/res/drawable/ic_corp_badge_no_background.xml b/core/res/res/drawable/ic_corp_badge_no_background.xml
index b1bddfc..78322a9 100644
--- a/core/res/res/drawable/ic_corp_badge_no_background.xml
+++ b/core/res/res/drawable/ic_corp_badge_no_background.xml
@@ -20,11 +20,11 @@
android:viewportHeight="24.0">
<path
android:pathData="M20.801,5.981L17.13,5.98l0.001,-1.471l-2.053,-2.055L8.969,2.453L6.915,4.506L6.914,5.977L3.203,5.976c-1.216,0.0 -2.189,0.983 -2.189,2.199L1.0,12.406c0.0,1.216 0.983,2.2 2.199,2.2L10.0,14.608l0.0,-1.644l0.291,0.0l3.351,0.0l0.291,0.0l0.0,1.645l6.863,0.002c1.216,0.0 2.2,-0.983 2.2,-2.199L23.0,8.181C23.0,6.965 22.017,5.981 20.801,5.981zM15.076,5.979L8.968,5.978l0.001,-1.471l6.108,0.001L15.076,5.979z"
- android:fillColor="#FF5722"/>
+ android:fillColor="#FFFFFF"/>
<path
android:pathData="M13.911,16.646L9.978,16.646L9.978,15.48L1.673,15.48l0.0,4.105c0.0,1.216 0.959,2.2 2.175,2.2l16.13,0.004c1.216,0.0 2.203,-0.983 2.203,-2.199l0.0,-4.11l-8.27,0.0L13.910999,16.646z"
- android:fillColor="#FF5722"/>
+ android:fillColor="#FFFFFF"/>
<path
android:pathData="M23.657,6.55 h4.72 v1.137 h-4.72z"
android:fillColor="#00000000"/>
-</vector>
\ No newline at end of file
+</vector>
diff --git a/core/res/res/drawable/ic_corp_icon_badge.xml b/core/res/res/drawable/ic_corp_icon_badge_case.xml
similarity index 71%
rename from core/res/res/drawable/ic_corp_icon_badge.xml
rename to core/res/res/drawable/ic_corp_icon_badge_case.xml
index 0273545..d62eda4 100644
--- a/core/res/res/drawable/ic_corp_icon_badge.xml
+++ b/core/res/res/drawable/ic_corp_icon_badge_case.xml
@@ -1,5 +1,5 @@
<!--
-Copyright (C) 2014 The Android Open Source Project
+Copyright (C) 2016 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -19,17 +19,6 @@
android:viewportWidth="64.0"
android:viewportHeight="64.0">
<path
- android:fillColor="#FF000000"
- android:pathData="M49.1,50.1m-13.9,0.0a13.9,13.9 0.0,1.0 1.0,27.8 0.0a13.9,13.9 0.0,1.0 1.0,-27.8 0.0"
- android:fillAlpha="0.2"/>
- <path
- android:fillColor="#FF000000"
- android:pathData="M49.1,49.4m-13.9,0.0a13.9,13.9 0.0,1.0 1.0,27.8 0.0a13.9,13.9 0.0,1.0 1.0,-27.8 0.0"
- android:fillAlpha="0.2"/>
- <path
- android:pathData="M49.1,48.8m-13.9,0.0a13.9,13.9 0.0,1.0 1.0,27.8 0.0a13.9,13.9 0.0,1.0 1.0,-27.8 0.0"
- android:fillColor="#FF5722"/>
- <path
android:pathData="M56.4,43.5L41.8,43.5c-0.7,0.0 -1.3,0.6 -1.3,1.3l0.0,4.0c0.0,0.7 0.6,1.3 1.3,1.3L47.0,50.1l0.0,-1.3l4.0,0.0l0.0,1.4l5.4,0.0c0.7,0.0 1.3,-0.6 1.3,-1.3l0.0,-4.0C57.6,44.1 57.0,43.5 56.4,43.5z"
android:fillColor="#FFFFFF"/>
<path
diff --git a/core/res/res/drawable/ic_corp_icon_badge_color.xml b/core/res/res/drawable/ic_corp_icon_badge_color.xml
new file mode 100644
index 0000000..3bc4e67
--- /dev/null
+++ b/core/res/res/drawable/ic_corp_icon_badge_color.xml
@@ -0,0 +1,24 @@
+<!--
+Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="64.0dp"
+ android:height="64.0dp"
+ android:viewportWidth="64.0"
+ android:viewportHeight="64.0">
+ <path
+ android:pathData="M49.1,48.8m-13.9,0.0a13.9,13.9 0.0,1.0 1.0,27.8 0.0a13.9,13.9 0.0,1.0 1.0,-27.8 0.0"
+ android:fillColor="#FFFFFF"/>
+</vector>
diff --git a/core/res/res/drawable/ic_corp_icon_badge_shadow.xml b/core/res/res/drawable/ic_corp_icon_badge_shadow.xml
new file mode 100644
index 0000000..a546cdd
--- /dev/null
+++ b/core/res/res/drawable/ic_corp_icon_badge_shadow.xml
@@ -0,0 +1,29 @@
+<!--
+Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="64.0dp"
+ android:height="64.0dp"
+ android:viewportWidth="64.0"
+ android:viewportHeight="64.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M49.1,50.1m-13.9,0.0a13.9,13.9 0.0,1.0 1.0,27.8 0.0a13.9,13.9 0.0,1.0 1.0,-27.8 0.0"
+ android:fillAlpha="0.2"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M49.1,49.4m-13.9,0.0a13.9,13.9 0.0,1.0 1.0,27.8 0.0a13.9,13.9 0.0,1.0 1.0,-27.8 0.0"
+ android:fillAlpha="0.2"/>
+</vector>
diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml
index 0995bc3..fa9cac2 100644
--- a/core/res/res/values/colors.xml
+++ b/core/res/res/values/colors.xml
@@ -165,6 +165,11 @@
<color name="user_icon_default_gray">#ff9e9e9e</color><!-- gray 500 -->
<color name="user_icon_default_white">#ffffffff</color><!-- white -->
+ <!-- Default profile badge colors -->
+ <color name="profile_badge_1">#ffff5722</color><!-- Orange -->
+ <color name="profile_badge_2">#ff000000</color><!-- Black -->
+ <color name="profile_badge_3">#ff22f033</color><!-- Green -->
+
<!-- Multi-sim sim colors -->
<color name="Teal_700">#ff00796b</color>
<color name="Teal_800">#ff00695c</color>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 4070d48..8546aa5 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -4161,6 +4161,8 @@
[CHAR LIMIT=20]
-->
<string name="managed_profile_label_badge">Work <xliff:g id="label" example="Email">%1$s</xliff:g></string>
+ <string name="managed_profile_label_badge_2">2nd Work <xliff:g id="label" example="Email">%1$s</xliff:g></string>
+ <string name="managed_profile_label_badge_3">3rd Work <xliff:g id="label" example="Email">%1$s</xliff:g></string>
<!-- DO NOT TRANSLATE -->
<string name="time_placeholder">--</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index a28a6fd..f6fd64b 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1017,6 +1017,8 @@
<java-symbol type="string" name="action_bar_home_subtitle_description_format" />
<java-symbol type="string" name="wireless_display_route_description" />
<java-symbol type="string" name="managed_profile_label_badge" />
+ <java-symbol type="string" name="managed_profile_label_badge_2" />
+ <java-symbol type="string" name="managed_profile_label_badge_3" />
<java-symbol type="string" name="mediasize_unknown_portrait" />
<java-symbol type="string" name="mediasize_unknown_landscape" />
<java-symbol type="string" name="mediasize_iso_a0" />
@@ -1269,12 +1271,15 @@
<java-symbol type="drawable" name="cling_button" />
<java-symbol type="drawable" name="cling_arrow_up" />
<java-symbol type="drawable" name="cling_bg" />
- <java-symbol type="drawable" name="ic_corp_badge" />
+ <java-symbol type="drawable" name="ic_corp_badge_color" />
+ <java-symbol type="drawable" name="ic_corp_badge_case" />
+ <java-symbol type="drawable" name="ic_corp_icon" />
<java-symbol type="drawable" name="ic_corp_badge_off" />
- <java-symbol type="drawable" name="ic_corp_icon_badge" />
+ <java-symbol type="drawable" name="ic_corp_icon_badge_shadow" />
+ <java-symbol type="drawable" name="ic_corp_icon_badge_color" />
+ <java-symbol type="drawable" name="ic_corp_icon_badge_case" />
<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_icon" />
<java-symbol type="drawable" name="ic_corp_statusbar_icon" />
<java-symbol type="drawable" name="emulator_circular_window_overlay" />
@@ -1301,6 +1306,9 @@
<java-symbol type="color" name="user_icon_8" />
<java-symbol type="color" name="user_icon_default_gray" />
<java-symbol type="color" name="user_icon_default_white" />
+ <java-symbol type="color" name="profile_badge_1" />
+ <java-symbol type="color" name="profile_badge_2" />
+ <java-symbol type="color" name="profile_badge_3" />
<java-symbol type="layout" name="action_bar_home" />
<java-symbol type="layout" name="action_bar_title_item" />
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 5b47b6f..20afed7 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -63,6 +63,7 @@
import android.os.ServiceManager;
import android.os.ShellCallback;
import android.os.ShellCommand;
+import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
import android.os.UserManagerInternal;
@@ -110,6 +111,8 @@
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
@@ -149,6 +152,7 @@
private static final String ATTR_GUEST_TO_REMOVE = "guestToRemove";
private static final String ATTR_USER_VERSION = "version";
private static final String ATTR_PROFILE_GROUP_ID = "profileGroupId";
+ private static final String ATTR_PROFILE_BADGE = "profileBadge";
private static final String ATTR_RESTRICTED_PROFILE_PARENT_ID = "restrictedProfileParentId";
private static final String ATTR_SEED_ACCOUNT_NAME = "seedAccountName";
private static final String ATTR_SEED_ACCOUNT_TYPE = "seedAccountType";
@@ -203,7 +207,8 @@
// Maximum number of managed profiles permitted per user is 1. This cannot be increased
// without first making sure that the rest of the framework is prepared for it.
- private static final int MAX_MANAGED_PROFILES = 1;
+ @VisibleForTesting
+ static final int MAX_MANAGED_PROFILES = 1;
static final int WRITE_USER_MSG = 1;
static final int WRITE_USER_DELAY = 2*1000; // 2 seconds
@@ -232,7 +237,8 @@
* User-related information that is used for persisting to flash. Only UserInfo is
* directly exposed to other system apps.
*/
- private static class UserData {
+ @VisibleForTesting
+ static class UserData {
// Basic user information and properties
UserInfo info;
// Account name used when there is a strong association between a user and an account
@@ -874,6 +880,22 @@
}
@Override
+ public int getManagedProfileBadge(@UserIdInt int userId) {
+ int callingUserId = UserHandle.getCallingUserId();
+ if (callingUserId != userId && !hasManageUsersPermission()) {
+ if (!isSameProfileGroupNoChecks(callingUserId, userId)) {
+ throw new SecurityException(
+ "You need MANAGE_USERS permission to: check if specified user a " +
+ "managed profile outside your profile group");
+ }
+ }
+ synchronized (mUsersLock) {
+ UserInfo userInfo = getUserInfoLU(userId);
+ return userInfo != null ? userInfo.profileBadge : 0;
+ }
+ }
+
+ @Override
public boolean isManagedProfile(int userId) {
int callingUserId = UserHandle.getCallingUserId();
if (callingUserId != userId && !hasManageUsersPermission()) {
@@ -1464,7 +1486,7 @@
// Limit number of managed profiles that can be created
final int managedProfilesCount = getProfiles(userId, true).size() - 1;
final int profilesRemovedCount = managedProfilesCount > 0 && allowedToRemoveOne ? 1 : 0;
- if (managedProfilesCount - profilesRemovedCount >= MAX_MANAGED_PROFILES) {
+ if (managedProfilesCount - profilesRemovedCount >= getMaxManagedProfiles()) {
return false;
}
synchronized(mUsersLock) {
@@ -1859,13 +1881,6 @@
}
}
- /*
- * Writes the user file in this format:
- *
- * <user flags="20039023" id="0">
- * <name>Primary</name>
- * </user>
- */
private void writeUserLP(UserData userData) {
if (DBG) {
debug("writeUserLP " + userData);
@@ -1875,78 +1890,7 @@
try {
fos = userFile.startWrite();
final BufferedOutputStream bos = new BufferedOutputStream(fos);
-
- // XmlSerializer serializer = XmlUtils.serializerInstance();
- final XmlSerializer serializer = new FastXmlSerializer();
- serializer.setOutput(bos, StandardCharsets.UTF_8.name());
- serializer.startDocument(null, true);
- serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
-
- final UserInfo userInfo = userData.info;
- serializer.startTag(null, TAG_USER);
- serializer.attribute(null, ATTR_ID, Integer.toString(userInfo.id));
- serializer.attribute(null, ATTR_SERIAL_NO, Integer.toString(userInfo.serialNumber));
- serializer.attribute(null, ATTR_FLAGS, Integer.toString(userInfo.flags));
- serializer.attribute(null, ATTR_CREATION_TIME, Long.toString(userInfo.creationTime));
- serializer.attribute(null, ATTR_LAST_LOGGED_IN_TIME,
- Long.toString(userInfo.lastLoggedInTime));
- if (userInfo.lastLoggedInFingerprint != null) {
- serializer.attribute(null, ATTR_LAST_LOGGED_IN_FINGERPRINT,
- userInfo.lastLoggedInFingerprint);
- }
- if (userInfo.iconPath != null) {
- serializer.attribute(null, ATTR_ICON_PATH, userInfo.iconPath);
- }
- if (userInfo.partial) {
- serializer.attribute(null, ATTR_PARTIAL, "true");
- }
- if (userInfo.guestToRemove) {
- serializer.attribute(null, ATTR_GUEST_TO_REMOVE, "true");
- }
- if (userInfo.profileGroupId != UserInfo.NO_PROFILE_GROUP_ID) {
- serializer.attribute(null, ATTR_PROFILE_GROUP_ID,
- Integer.toString(userInfo.profileGroupId));
- }
- if (userInfo.restrictedProfileParentId != UserInfo.NO_PROFILE_GROUP_ID) {
- serializer.attribute(null, ATTR_RESTRICTED_PROFILE_PARENT_ID,
- Integer.toString(userInfo.restrictedProfileParentId));
- }
- // Write seed data
- if (userData.persistSeedData) {
- if (userData.seedAccountName != null) {
- serializer.attribute(null, ATTR_SEED_ACCOUNT_NAME, userData.seedAccountName);
- }
- if (userData.seedAccountType != null) {
- serializer.attribute(null, ATTR_SEED_ACCOUNT_TYPE, userData.seedAccountType);
- }
- }
- if (userInfo.name != null) {
- serializer.startTag(null, TAG_NAME);
- serializer.text(userInfo.name);
- serializer.endTag(null, TAG_NAME);
- }
- synchronized (mRestrictionsLock) {
- UserRestrictionsUtils.writeRestrictions(serializer,
- mBaseUserRestrictions.get(userInfo.id), TAG_RESTRICTIONS);
- UserRestrictionsUtils.writeRestrictions(serializer,
- mDevicePolicyLocalUserRestrictions.get(userInfo.id),
- TAG_DEVICE_POLICY_RESTRICTIONS);
- }
-
- if (userData.account != null) {
- serializer.startTag(null, TAG_ACCOUNT);
- serializer.text(userData.account);
- serializer.endTag(null, TAG_ACCOUNT);
- }
-
- if (userData.persistSeedData && userData.seedAccountOptions != null) {
- serializer.startTag(null, TAG_SEED_ACCOUNT_OPTIONS);
- userData.seedAccountOptions.saveToXml(serializer);
- serializer.endTag(null, TAG_SEED_ACCOUNT_OPTIONS);
- }
- serializer.endTag(null, TAG_USER);
-
- serializer.endDocument();
+ writeUserLP(userData, bos);
userFile.finishWrite(fos);
} catch (Exception ioe) {
Slog.e(LOG_TAG, "Error writing user info " + userData.info.id, ioe);
@@ -1955,6 +1899,92 @@
}
/*
+ * Writes the user file in this format:
+ *
+ * <user flags="20039023" id="0">
+ * <name>Primary</name>
+ * </user>
+ */
+ @VisibleForTesting
+ void writeUserLP(UserData userData, OutputStream os)
+ throws IOException, XmlPullParserException {
+ // XmlSerializer serializer = XmlUtils.serializerInstance();
+ final XmlSerializer serializer = new FastXmlSerializer();
+ serializer.setOutput(os, StandardCharsets.UTF_8.name());
+ serializer.startDocument(null, true);
+ serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
+
+ final UserInfo userInfo = userData.info;
+ serializer.startTag(null, TAG_USER);
+ serializer.attribute(null, ATTR_ID, Integer.toString(userInfo.id));
+ serializer.attribute(null, ATTR_SERIAL_NO, Integer.toString(userInfo.serialNumber));
+ serializer.attribute(null, ATTR_FLAGS, Integer.toString(userInfo.flags));
+ serializer.attribute(null, ATTR_CREATION_TIME, Long.toString(userInfo.creationTime));
+ serializer.attribute(null, ATTR_LAST_LOGGED_IN_TIME,
+ Long.toString(userInfo.lastLoggedInTime));
+ if (userInfo.lastLoggedInFingerprint != null) {
+ serializer.attribute(null, ATTR_LAST_LOGGED_IN_FINGERPRINT,
+ userInfo.lastLoggedInFingerprint);
+ }
+ if (userInfo.iconPath != null) {
+ serializer.attribute(null, ATTR_ICON_PATH, userInfo.iconPath);
+ }
+ if (userInfo.partial) {
+ serializer.attribute(null, ATTR_PARTIAL, "true");
+ }
+ if (userInfo.guestToRemove) {
+ serializer.attribute(null, ATTR_GUEST_TO_REMOVE, "true");
+ }
+ if (userInfo.profileGroupId != UserInfo.NO_PROFILE_GROUP_ID) {
+ serializer.attribute(null, ATTR_PROFILE_GROUP_ID,
+ Integer.toString(userInfo.profileGroupId));
+ }
+ serializer.attribute(null, ATTR_PROFILE_BADGE,
+ Integer.toString(userInfo.profileBadge));
+ if (userInfo.restrictedProfileParentId != UserInfo.NO_PROFILE_GROUP_ID) {
+ serializer.attribute(null, ATTR_RESTRICTED_PROFILE_PARENT_ID,
+ Integer.toString(userInfo.restrictedProfileParentId));
+ }
+ // Write seed data
+ if (userData.persistSeedData) {
+ if (userData.seedAccountName != null) {
+ serializer.attribute(null, ATTR_SEED_ACCOUNT_NAME, userData.seedAccountName);
+ }
+ if (userData.seedAccountType != null) {
+ serializer.attribute(null, ATTR_SEED_ACCOUNT_TYPE, userData.seedAccountType);
+ }
+ }
+ if (userInfo.name != null) {
+ serializer.startTag(null, TAG_NAME);
+ serializer.text(userInfo.name);
+ serializer.endTag(null, TAG_NAME);
+ }
+ synchronized (mRestrictionsLock) {
+ UserRestrictionsUtils.writeRestrictions(serializer,
+ mBaseUserRestrictions.get(userInfo.id), TAG_RESTRICTIONS);
+ UserRestrictionsUtils.writeRestrictions(serializer,
+ mDevicePolicyLocalUserRestrictions.get(userInfo.id),
+ TAG_DEVICE_POLICY_RESTRICTIONS);
+ }
+
+ if (userData.account != null) {
+ serializer.startTag(null, TAG_ACCOUNT);
+ serializer.text(userData.account);
+ serializer.endTag(null, TAG_ACCOUNT);
+ }
+
+ if (userData.persistSeedData && userData.seedAccountOptions != null) {
+ serializer.startTag(null, TAG_SEED_ACCOUNT_OPTIONS);
+ userData.seedAccountOptions.saveToXml(serializer);
+ serializer.endTag(null, TAG_SEED_ACCOUNT_OPTIONS);
+ }
+
+ serializer.endTag(null, TAG_USER);
+
+ serializer.endDocument();
+ }
+
+ /*
* Writes the user list file in this format:
*
* <users nextSerialNumber="3">
@@ -2020,6 +2050,25 @@
}
private UserData readUserLP(int id) {
+ FileInputStream fis = null;
+ try {
+ AtomicFile userFile =
+ new AtomicFile(new File(mUsersDir, Integer.toString(id) + XML_SUFFIX));
+ fis = userFile.openRead();
+ return readUserLP(id, fis);
+ } catch (IOException ioe) {
+ Slog.e(LOG_TAG, "Error reading user list");
+ } catch (XmlPullParserException pe) {
+ Slog.e(LOG_TAG, "Error reading user list");
+ } finally {
+ IoUtils.closeQuietly(fis);
+ }
+ return null;
+ }
+
+ @VisibleForTesting
+ UserData readUserLP(int id, InputStream is) throws IOException,
+ XmlPullParserException {
int flags = 0;
int serialNumber = id;
String name = null;
@@ -2029,6 +2078,7 @@
long lastLoggedInTime = 0L;
String lastLoggedInFingerprint = null;
int profileGroupId = UserInfo.NO_PROFILE_GROUP_ID;
+ int profileBadge = 0;
int restrictedProfileParentId = UserInfo.NO_PROFILE_GROUP_ID;
boolean partial = false;
boolean guestToRemove = false;
@@ -2039,120 +2089,106 @@
Bundle baseRestrictions = new Bundle();
Bundle localRestrictions = new Bundle();
- FileInputStream fis = null;
- try {
- AtomicFile userFile =
- new AtomicFile(new File(mUsersDir, Integer.toString(id) + XML_SUFFIX));
- fis = userFile.openRead();
- XmlPullParser parser = Xml.newPullParser();
- parser.setInput(fis, StandardCharsets.UTF_8.name());
- int type;
- while ((type = parser.next()) != XmlPullParser.START_TAG
- && type != XmlPullParser.END_DOCUMENT) {
- // Skip
- }
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(is, StandardCharsets.UTF_8.name());
+ int type;
+ while ((type = parser.next()) != XmlPullParser.START_TAG
+ && type != XmlPullParser.END_DOCUMENT) {
+ // Skip
+ }
- if (type != XmlPullParser.START_TAG) {
- Slog.e(LOG_TAG, "Unable to read user " + id);
+ if (type != XmlPullParser.START_TAG) {
+ Slog.e(LOG_TAG, "Unable to read user " + id);
+ return null;
+ }
+
+ if (type == XmlPullParser.START_TAG && parser.getName().equals(TAG_USER)) {
+ int storedId = readIntAttribute(parser, ATTR_ID, -1);
+ if (storedId != id) {
+ Slog.e(LOG_TAG, "User id does not match the file name");
return null;
}
+ serialNumber = readIntAttribute(parser, ATTR_SERIAL_NO, id);
+ flags = readIntAttribute(parser, ATTR_FLAGS, 0);
+ iconPath = parser.getAttributeValue(null, ATTR_ICON_PATH);
+ creationTime = readLongAttribute(parser, ATTR_CREATION_TIME, 0);
+ lastLoggedInTime = readLongAttribute(parser, ATTR_LAST_LOGGED_IN_TIME, 0);
+ lastLoggedInFingerprint = parser.getAttributeValue(null,
+ ATTR_LAST_LOGGED_IN_FINGERPRINT);
+ profileGroupId = readIntAttribute(parser, ATTR_PROFILE_GROUP_ID,
+ UserInfo.NO_PROFILE_GROUP_ID);
+ profileBadge = readIntAttribute(parser, ATTR_PROFILE_BADGE, 0);
+ restrictedProfileParentId = readIntAttribute(parser,
+ ATTR_RESTRICTED_PROFILE_PARENT_ID, UserInfo.NO_PROFILE_GROUP_ID);
+ String valueString = parser.getAttributeValue(null, ATTR_PARTIAL);
+ if ("true".equals(valueString)) {
+ partial = true;
+ }
+ valueString = parser.getAttributeValue(null, ATTR_GUEST_TO_REMOVE);
+ if ("true".equals(valueString)) {
+ guestToRemove = true;
+ }
- if (type == XmlPullParser.START_TAG && parser.getName().equals(TAG_USER)) {
- int storedId = readIntAttribute(parser, ATTR_ID, -1);
- if (storedId != id) {
- Slog.e(LOG_TAG, "User id does not match the file name");
- return null;
- }
- serialNumber = readIntAttribute(parser, ATTR_SERIAL_NO, id);
- flags = readIntAttribute(parser, ATTR_FLAGS, 0);
- iconPath = parser.getAttributeValue(null, ATTR_ICON_PATH);
- creationTime = readLongAttribute(parser, ATTR_CREATION_TIME, 0);
- lastLoggedInTime = readLongAttribute(parser, ATTR_LAST_LOGGED_IN_TIME, 0);
- lastLoggedInFingerprint = parser.getAttributeValue(null,
- ATTR_LAST_LOGGED_IN_FINGERPRINT);
- profileGroupId = readIntAttribute(parser, ATTR_PROFILE_GROUP_ID,
- UserInfo.NO_PROFILE_GROUP_ID);
- restrictedProfileParentId = readIntAttribute(parser,
- ATTR_RESTRICTED_PROFILE_PARENT_ID, UserInfo.NO_PROFILE_GROUP_ID);
- String valueString = parser.getAttributeValue(null, ATTR_PARTIAL);
- if ("true".equals(valueString)) {
- partial = true;
- }
- valueString = parser.getAttributeValue(null, ATTR_GUEST_TO_REMOVE);
- if ("true".equals(valueString)) {
- guestToRemove = true;
- }
+ seedAccountName = parser.getAttributeValue(null, ATTR_SEED_ACCOUNT_NAME);
+ seedAccountType = parser.getAttributeValue(null, ATTR_SEED_ACCOUNT_TYPE);
+ if (seedAccountName != null || seedAccountType != null) {
+ persistSeedData = true;
+ }
- seedAccountName = parser.getAttributeValue(null, ATTR_SEED_ACCOUNT_NAME);
- seedAccountType = parser.getAttributeValue(null, ATTR_SEED_ACCOUNT_TYPE);
- if (seedAccountName != null || seedAccountType != null) {
+ int outerDepth = parser.getDepth();
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+ String tag = parser.getName();
+ if (TAG_NAME.equals(tag)) {
+ type = parser.next();
+ if (type == XmlPullParser.TEXT) {
+ name = parser.getText();
+ }
+ } else if (TAG_RESTRICTIONS.equals(tag)) {
+ UserRestrictionsUtils.readRestrictions(parser, baseRestrictions);
+ } else if (TAG_DEVICE_POLICY_RESTRICTIONS.equals(tag)) {
+ UserRestrictionsUtils.readRestrictions(parser, localRestrictions);
+ } else if (TAG_ACCOUNT.equals(tag)) {
+ type = parser.next();
+ if (type == XmlPullParser.TEXT) {
+ account = parser.getText();
+ }
+ } else if (TAG_SEED_ACCOUNT_OPTIONS.equals(tag)) {
+ seedAccountOptions = PersistableBundle.restoreFromXml(parser);
persistSeedData = true;
}
-
- int outerDepth = parser.getDepth();
- while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
- && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
- if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
- continue;
- }
- String tag = parser.getName();
- if (TAG_NAME.equals(tag)) {
- type = parser.next();
- if (type == XmlPullParser.TEXT) {
- name = parser.getText();
- }
- } else if (TAG_RESTRICTIONS.equals(tag)) {
- UserRestrictionsUtils.readRestrictions(parser, baseRestrictions);
- } else if (TAG_DEVICE_POLICY_RESTRICTIONS.equals(tag)) {
- UserRestrictionsUtils.readRestrictions(parser, localRestrictions);
- } else if (TAG_ACCOUNT.equals(tag)) {
- type = parser.next();
- if (type == XmlPullParser.TEXT) {
- account = parser.getText();
- }
- } else if (TAG_SEED_ACCOUNT_OPTIONS.equals(tag)) {
- seedAccountOptions = PersistableBundle.restoreFromXml(parser);
- persistSeedData = true;
- }
- }
- }
-
- // Create the UserInfo object that gets passed around
- UserInfo userInfo = new UserInfo(id, name, iconPath, flags);
- userInfo.serialNumber = serialNumber;
- userInfo.creationTime = creationTime;
- userInfo.lastLoggedInTime = lastLoggedInTime;
- userInfo.lastLoggedInFingerprint = lastLoggedInFingerprint;
- userInfo.partial = partial;
- userInfo.guestToRemove = guestToRemove;
- userInfo.profileGroupId = profileGroupId;
- userInfo.restrictedProfileParentId = restrictedProfileParentId;
-
- // Create the UserData object that's internal to this class
- UserData userData = new UserData();
- userData.info = userInfo;
- userData.account = account;
- userData.seedAccountName = seedAccountName;
- userData.seedAccountType = seedAccountType;
- userData.persistSeedData = persistSeedData;
- userData.seedAccountOptions = seedAccountOptions;
-
- synchronized (mRestrictionsLock) {
- mBaseUserRestrictions.put(id, baseRestrictions);
- mDevicePolicyLocalUserRestrictions.put(id, localRestrictions);
- }
- return userData;
- } catch (IOException ioe) {
- } catch (XmlPullParserException pe) {
- } finally {
- if (fis != null) {
- try {
- fis.close();
- } catch (IOException e) {
- }
}
}
- return null;
+
+ // Create the UserInfo object that gets passed around
+ UserInfo userInfo = new UserInfo(id, name, iconPath, flags);
+ userInfo.serialNumber = serialNumber;
+ userInfo.creationTime = creationTime;
+ userInfo.lastLoggedInTime = lastLoggedInTime;
+ userInfo.lastLoggedInFingerprint = lastLoggedInFingerprint;
+ userInfo.partial = partial;
+ userInfo.guestToRemove = guestToRemove;
+ userInfo.profileGroupId = profileGroupId;
+ userInfo.profileBadge = profileBadge;
+ userInfo.restrictedProfileParentId = restrictedProfileParentId;
+
+ // Create the UserData object that's internal to this class
+ UserData userData = new UserData();
+ userData.info = userInfo;
+ userData.account = account;
+ userData.seedAccountName = seedAccountName;
+ userData.seedAccountType = seedAccountType;
+ userData.persistSeedData = persistSeedData;
+ userData.seedAccountOptions = seedAccountOptions;
+
+ synchronized (mRestrictionsLock) {
+ mBaseUserRestrictions.put(id, baseRestrictions);
+ mDevicePolicyLocalUserRestrictions.put(id, localRestrictions);
+ }
+ return userData;
}
private int readIntAttribute(XmlPullParser parser, String attr, int defaultValue) {
@@ -2316,6 +2352,9 @@
userInfo.creationTime = (now > EPOCH_PLUS_30_YEARS) ? now : 0;
userInfo.partial = true;
userInfo.lastLoggedInFingerprint = Build.FINGERPRINT;
+ if (isManagedProfile && parentId != UserHandle.USER_NULL) {
+ userInfo.profileBadge = getFreeProfileBadgeLU(parentId);
+ }
userData = new UserData();
userData.info = userInfo;
mUsers.put(userId, userData);
@@ -3643,4 +3682,39 @@
Log.d(LOG_TAG, message +
(DBG_WITH_STACKTRACE ? " called at\n" + Debug.getCallers(10, " ") : ""));
}
+
+ @VisibleForTesting
+ static int getMaxManagedProfiles() {
+ // Allow overriding max managed profiles on debuggable builds for testing
+ // of multiple profiles.
+ if (!Build.IS_DEBUGGABLE) {
+ return MAX_MANAGED_PROFILES;
+ } else {
+ return SystemProperties.getInt("persist.sys.max_profiles",
+ MAX_MANAGED_PROFILES);
+ }
+ }
+
+ @VisibleForTesting
+ int getFreeProfileBadgeLU(int parentUserId) {
+ int maxManagedProfiles = getMaxManagedProfiles();
+ boolean[] usedBadges = new boolean[maxManagedProfiles];
+ final int userSize = mUsers.size();
+ for (int i = 0; i < userSize; i++) {
+ UserInfo ui = mUsers.valueAt(i).info;
+ // Check which badge indexes are already used by this profile group.
+ if (ui.isManagedProfile()
+ && ui.profileGroupId == parentUserId
+ && !mRemovingUserIds.get(ui.id)
+ && ui.profileBadge < maxManagedProfiles) {
+ usedBadges[ui.profileBadge] = true;
+ }
+ }
+ for (int i = 0; i < maxManagedProfiles; i++) {
+ if (!usedBadges[i]) {
+ return i;
+ }
+ }
+ return 0;
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceCreateProfileTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceCreateProfileTest.java
new file mode 100644
index 0000000..ad514cf
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceCreateProfileTest.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.pm;
+
+import android.app.ApplicationPackageManager;
+import android.content.pm.UserInfo;
+import android.os.Looper;
+import android.os.UserManagerInternal;
+import android.os.UserHandle;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.test.filters.MediumTest;
+
+import com.android.server.LocalServices;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/**
+ * <p>Run with:<pre>
+ * runtest -c com.android.server.pm.UserManagerServiceCreateProfileTest frameworks-services
+ * </pre>
+ */
+@RunWith(AndroidJUnit4.class)
+@MediumTest
+public class UserManagerServiceCreateProfileTest {
+ private UserManagerService mUserManagerService;
+
+ @Before
+ public void setup() {
+ // Currently UserManagerService cannot be instantiated twice inside a VM without a cleanup
+ // TODO: Remove once UMS supports proper dependency injection
+ if (Looper.myLooper() == null) {
+ Looper.prepare();
+ }
+ LocalServices.removeServiceForTest(UserManagerInternal.class);
+ mUserManagerService = new UserManagerService(InstrumentationRegistry.getContext());
+
+ // The tests assume that the device has one user and its the system user.
+ List<UserInfo> users = mUserManagerService.getUsers(/* excludeDying */ false);
+ assertEquals("Multiple users so this test can't run.", 1, users.size());
+ assertEquals("Only user present isn't the system user.",
+ UserHandle.USER_SYSTEM, users.get(0).id);
+ }
+
+ @Test
+ public void testGetProfiles() {
+ try {
+ // Pretend we have a secondary user with a profile.
+ UserInfo secondaryUser = addUser();
+ UserInfo profile = addProfile(secondaryUser);
+
+ // System user should still have no profile so getProfiles should just return 1 user.
+ List<UserInfo> users =
+ mUserManagerService.getProfiles(UserHandle.USER_SYSTEM, /*excludeDying*/ false);
+ assertEquals("Profiles returned where none should exist", 1, users.size());
+ assertEquals("Missing system user from profile list of system user",
+ UserHandle.USER_SYSTEM, users.get(0).id);
+
+ // Secondary user should have 1 profile, so return that and itself.
+ users = mUserManagerService.getProfiles(secondaryUser.id, /*excludeDying*/ false);
+ assertEquals("Profiles returned where none should exist", 2, users.size());
+ assertTrue("Missing secondary user id", users.get(0).id == secondaryUser.id
+ || users.get(1).id == secondaryUser.id);
+ assertTrue("Missing profile user id", users.get(0).id == profile.id
+ || users.get(1).id == profile.id);
+ } finally {
+ removeUsers();
+ }
+ }
+
+ @Test
+ public void testProfileBadge() {
+ try {
+ // First profile for system user should get badge 0
+ assertEquals("First profile isn't given badge index 0", 0,
+ mUserManagerService.getFreeProfileBadgeLU(UserHandle.USER_SYSTEM));
+
+ // Pretend we have a secondary user.
+ UserInfo secondaryUser = addUser();
+
+ // Check first profile badge for secondary user is also 0.
+ assertEquals("First profile for secondary user isn't given badge index 0", 0,
+ mUserManagerService.getFreeProfileBadgeLU(secondaryUser.id));
+
+ // Shouldn't impact the badge for profile in system user
+ assertEquals("First profile isn't given badge index 0 with secondary user", 0,
+ mUserManagerService.getFreeProfileBadgeLU(UserHandle.USER_SYSTEM));
+
+ // Pretend a secondary user has a profile.
+ addProfile(secondaryUser);
+
+ // Shouldn't have impacted the badge for the system user
+ assertEquals("First profile isn't given badge index 0 in secondary user", 0,
+ mUserManagerService.getFreeProfileBadgeLU(UserHandle.USER_SYSTEM));
+ } finally {
+ removeUsers();
+ }
+ }
+
+ @Test
+ public void testProfileBadgeUnique() {
+ try {
+ List<UserInfo> users = mUserManagerService.getUsers(/* excludeDying */ false);
+ UserInfo system = users.get(0);
+ // Badges should get allocated 0 -> max
+ for (int i = 0; i < UserManagerService.getMaxManagedProfiles(); ++i) {
+ int nextBadge = mUserManagerService.getFreeProfileBadgeLU(UserHandle.USER_SYSTEM);
+ assertEquals("Wrong badge allocated", i, nextBadge);
+ UserInfo profile = addProfile(system);
+ profile.profileBadge = nextBadge;
+ }
+ } finally {
+ removeUsers();
+ }
+ }
+
+ @Test
+ public void testProfileBadgeReuse() {
+ try {
+ // Pretend we have a secondary user with a profile.
+ UserInfo secondaryUser = addUser();
+ UserInfo profile = addProfile(secondaryUser);
+ // Add the profile it to the users being removed.
+ mUserManagerService.addRemovingUserIdLocked(profile.id);
+ // We should reuse the badge from the profile being removed.
+ assertEquals("Badge index not reused while removing a user", 0,
+ mUserManagerService.getFreeProfileBadgeLU(secondaryUser.id));
+
+ // Edge case of reuse that only applies if we ever support 3 managed profiles
+ // We should prioritise using lower badge indexes
+ if (UserManagerService.getMaxManagedProfiles() > 2) {
+ UserInfo profileBadgeOne = addProfile(secondaryUser);
+ profileBadgeOne.profileBadge = 1;
+ // 0 and 2 are free, we should reuse 0 rather than 2.
+ assertEquals("Lower index not used", 0,
+ mUserManagerService.getFreeProfileBadgeLU(secondaryUser.id));
+ }
+ } finally {
+ removeUsers();
+ }
+ }
+
+ @Test
+ public void testNumberOfBadges() {
+ assertTrue("Max profiles greater than number of badges",
+ UserManagerService.MAX_MANAGED_PROFILES
+ <= ApplicationPackageManager.CORP_BADGE_COLORS.length);
+ assertEquals("Num colors doesn't match number of badge labels",
+ ApplicationPackageManager.CORP_BADGE_COLORS.length,
+ ApplicationPackageManager.CORP_BADGE_LABEL_RES_ID.length);
+ }
+
+ private void removeUsers() {
+ List<UserInfo> users = mUserManagerService.getUsers(/* excludeDying */ false);
+ for (UserInfo user: users) {
+ if (user.id != UserHandle.USER_SYSTEM) {
+ mUserManagerService.removeUserInfo(user.id);
+ }
+ }
+ }
+
+ private UserInfo addProfile(UserInfo user) {
+ user.profileGroupId = user.id;
+ UserInfo profile = new UserInfo(
+ mUserManagerService.getNextAvailableId(), "profile",
+ UserInfo.FLAG_MANAGED_PROFILE);
+ profile.profileGroupId = user.id;
+ mUserManagerService.putUserInfo(profile);
+ return profile;
+ }
+
+ private UserInfo addUser() {
+ UserInfo secondaryUser = new UserInfo(
+ mUserManagerService.getNextAvailableId(), "secondary", /* flags */ 0);
+ mUserManagerService.putUserInfo(secondaryUser);
+ return secondaryUser;
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java
new file mode 100644
index 0000000..575d7a3
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.pm;
+
+import android.content.pm.UserInfo;
+import android.os.Looper;
+import android.os.Parcel;
+import android.os.UserManagerInternal;
+import android.os.UserHandle;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.test.filters.MediumTest;
+
+import com.android.server.LocalServices;
+import com.android.server.pm.UserManagerService.UserData;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * <p>Run with:<pre>
+ * runtest -c com.android.server.pm.UserManagerServiceUserInfoTest frameworks-services
+ * </pre>
+ */
+@RunWith(AndroidJUnit4.class)
+@MediumTest
+public class UserManagerServiceUserInfoTest {
+ private UserManagerService mUserManagerService;
+
+ @Before
+ public void setup() {
+ // Currently UserManagerService cannot be instantiated twice inside a VM without a cleanup
+ // TODO: Remove once UMS supports proper dependency injection
+ if (Looper.myLooper() == null) {
+ Looper.prepare();
+ }
+ LocalServices.removeServiceForTest(UserManagerInternal.class);
+ mUserManagerService = new UserManagerService(InstrumentationRegistry.getContext());
+
+ // The tests assume that the device has one user and its the system user.
+ List<UserInfo> users = mUserManagerService.getUsers(/* excludeDying */ false);
+ assertEquals("Multiple users so this test can't run.", 1, users.size());
+ assertEquals("Only user present isn't the system user.",
+ UserHandle.USER_SYSTEM, users.get(0).id);
+ }
+
+ @Test
+ public void testWriteReadUserInfo() throws Exception {
+ UserData data = new UserData();
+ data.info = createUser();
+
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ DataOutputStream out = new DataOutputStream(baos);
+ mUserManagerService.writeUserLP(data, out);
+ byte[] bytes = baos.toByteArray();
+
+ UserData read = mUserManagerService.readUserLP(
+ data.info.id, new ByteArrayInputStream(bytes));
+
+ assertUserInfoEquals(data.info, read.info);
+ }
+
+ @Test
+ public void testParcelUnparcelUserInfo() throws Exception {
+ UserInfo info = createUser();
+
+ Parcel out = Parcel.obtain();
+ info.writeToParcel(out, 0);
+ byte[] data = out.marshall();
+ out.recycle();
+
+ Parcel in = Parcel.obtain();
+ in.unmarshall(data, 0, data.length);
+ in.setDataPosition(0);
+ UserInfo read = UserInfo.CREATOR.createFromParcel(in);
+ in.recycle();
+
+ assertUserInfoEquals(info, read);
+ }
+
+ private UserInfo createUser() {
+ UserInfo user = new UserInfo(/*id*/ 21, "A Name", "A path", /*flags*/ 0x0ff0ff);
+ user.serialNumber = 5;
+ user.creationTime = 4L << 32;
+ user.lastLoggedInTime = 5L << 32;
+ user.lastLoggedInFingerprint = "afingerprint";
+ user.profileGroupId = 45;
+ user.restrictedProfileParentId = 4;
+ user.profileBadge = 2;
+ user.partial = true;
+ user.guestToRemove = true;
+ return user;
+ }
+
+ private void assertUserInfoEquals(UserInfo one, UserInfo two) {
+ assertEquals("Id not preserved", one.id, two.id);
+ assertEquals("Name not preserved", one.name, two.name);
+ assertEquals("Icon path not preserved", one.iconPath, two.iconPath);
+ assertEquals("Flags not preserved", one.flags, two.flags);
+ assertEquals("profile group not preserved", one.profileGroupId,
+ two.profileGroupId);
+ assertEquals("restricted profile parent not preseved", one.restrictedProfileParentId,
+ two.restrictedProfileParentId);
+ assertEquals("profile badge not preseved", one.profileBadge, two.profileBadge);
+ assertEquals("partial not preseved", one.partial, two.partial);
+ assertEquals("guestToRemove not preseved", one.guestToRemove, two.guestToRemove);
+ }
+}
diff --git a/test-runner/src/android/test/mock/MockPackageManager.java b/test-runner/src/android/test/mock/MockPackageManager.java
index fee3aa5..1bd5b1d 100644
--- a/test-runner/src/android/test/mock/MockPackageManager.java
+++ b/test-runner/src/android/test/mock/MockPackageManager.java
@@ -524,14 +524,6 @@
throw new UnsupportedOperationException();
}
- /** @hide */
- @Override
- public Drawable getManagedUserBadgedDrawable(Drawable drawable, Rect badgeLocation,
- int badgeDensity) {
- throw new UnsupportedOperationException();
- }
-
-
@Override
public Drawable getUserBadgedIcon(Drawable icon, UserHandle user) {
throw new UnsupportedOperationException();
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePackageManager.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePackageManager.java
index 0b169bd..f47b105 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePackageManager.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePackageManager.java
@@ -495,12 +495,6 @@
}
@Override
- public Drawable getManagedUserBadgedDrawable(Drawable drawable, Rect badgeLocation,
- int badgeDensity) {
- return null;
- }
-
- @Override
public Drawable getUserBadgedIcon(Drawable icon, UserHandle user) {
return null;
}