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
This commit is contained in:
Kenny Guy
2016-11-15 19:36:38 +00:00
parent c66cc5166e
commit 02c8990bd6
19 changed files with 801 additions and 270 deletions

View File

@@ -58,9 +58,11 @@ import android.content.res.Resources;
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 android.view.Display;
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;
@@ -1174,22 +1177,22 @@ public class ApplicationPackageManager extends PackageManager {
return getApplicationLogo(getApplicationInfo(packageName, sDefaultFlags));
}
@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 @@ public class ApplicationPackageManager extends PackageManager {
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 @@ public class ApplicationPackageManager extends PackageManager {
@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 @@ public class ApplicationPackageManager extends PackageManager {
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);
}

View File

@@ -4521,32 +4521,6 @@ public abstract class PackageManager {
public abstract Drawable getApplicationLogo(String packageName)
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

View File

@@ -109,6 +109,8 @@ public class UserInfo implements Parcelable {
*/
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 @@ public class UserInfo implements Parcelable {
profileGroupId = orig.profileGroupId;
restrictedProfileParentId = orig.restrictedProfileParentId;
guestToRemove = orig.guestToRemove;
profileBadge = orig.profileBadge;
}
public UserHandle getUserHandle() {
@@ -261,6 +264,7 @@ public class UserInfo implements Parcelable {
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 @@ public class UserInfo implements Parcelable {
profileGroupId = source.readInt();
guestToRemove = source.readInt() != 0;
restrictedProfileParentId = source.readInt();
profileBadge = source.readInt();
}
}

View File

@@ -86,4 +86,5 @@ interface IUserManager {
UserInfo createProfileForUserEvenWhenDisallowed(in String name, int flags, int userHandle,
in String[] disallowedPackages);
boolean isUserUnlockingOrUnlocked(int userId);
int getManagedProfileBadge(int userId);
}

View File

@@ -947,6 +947,23 @@ public class UserManager {
}
}
/**
* 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.
*

View File

@@ -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>

View File

@@ -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>

View File

@@ -20,11 +20,11 @@ Copyright (C) 2016 The Android Open Source Project
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>
</vector>

View File

@@ -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.
@@ -18,17 +18,6 @@ Copyright (C) 2014 The Android Open Source Project
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"/>
<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"/>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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" />

View File

@@ -63,6 +63,7 @@ import android.os.SELinux;
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.FileDescriptor;
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 @@ public class UserManagerService extends IUserManager.Stub {
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 @@ public class UserManagerService extends IUserManager.Stub {
// 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 @@ public class UserManagerService extends IUserManager.Stub {
* 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
@@ -873,6 +879,22 @@ public class UserManagerService extends IUserManager.Stub {
}
}
@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();
@@ -1464,7 +1486,7 @@ public class UserManagerService extends IUserManager.Stub {
// 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 @@ public class UserManagerService extends IUserManager.Stub {
}
}
/*
* 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 @@ public class UserManagerService extends IUserManager.Stub {
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);
@@ -1954,6 +1898,92 @@ public class UserManagerService extends IUserManager.Stub {
}
}
/*
* 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:
*
@@ -2020,6 +2050,25 @@ public class UserManagerService extends IUserManager.Stub {
}
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 @@ public class UserManagerService extends IUserManager.Stub {
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 @@ public class UserManagerService extends IUserManager.Stub {
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 @@ public class UserManagerService extends IUserManager.Stub {
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 @@ public class UserManagerService extends IUserManager.Stub {
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;
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -524,14 +524,6 @@ public class MockPackageManager extends PackageManager {
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();

View File

@@ -494,12 +494,6 @@ public class BridgePackageManager extends PackageManager {
return null;
}
@Override
public Drawable getManagedUserBadgedDrawable(Drawable drawable, Rect badgeLocation,
int badgeDensity) {
return null;
}
@Override
public Drawable getUserBadgedIcon(Drawable icon, UserHandle user) {
return null;