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:
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
30
core/res/res/drawable/ic_corp_badge_case.xml
Normal file
30
core/res/res/drawable/ic_corp_badge_case.xml
Normal 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>
|
||||
24
core/res/res/drawable/ic_corp_badge_color.xml
Normal file
24
core/res/res/drawable/ic_corp_badge_color.xml
Normal 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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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"/>
|
||||
24
core/res/res/drawable/ic_corp_icon_badge_color.xml
Normal file
24
core/res/res/drawable/ic_corp_icon_badge_color.xml
Normal 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>
|
||||
29
core/res/res/drawable/ic_corp_icon_badge_shadow.xml
Normal file
29
core/res/res/drawable/ic_corp_icon_badge_shadow.xml
Normal 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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user