Merge "Add app clone profile and APIs." into sc-dev

This commit is contained in:
Saumya Pathak
2021-03-23 11:15:59 +00:00
committed by Android (Google) Code Review
9 changed files with 179 additions and 3 deletions

View File

@@ -8500,6 +8500,7 @@ package android.os {
method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean hasRestrictedProfiles();
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional=true) public boolean hasUserRestrictionForUser(@NonNull String, @NonNull android.os.UserHandle);
method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isAdminUser();
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional=true) public boolean isCloneProfile();
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean isGuestUser();
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional=true) public boolean isManagedProfile(int);
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean isPrimaryUser();
@@ -8513,6 +8514,7 @@ package android.os {
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean removeUser(@NonNull android.os.UserHandle);
method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public void setUserIcon(@NonNull android.graphics.Bitmap) throws android.os.UserManager.UserOperationException;
method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public void setUserName(@Nullable String);
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional=true) public boolean sharesMediaWithParent();
field public static final String ACTION_USER_RESTRICTIONS_CHANGED = "android.os.action.USER_RESTRICTIONS_CHANGED";
field @Deprecated public static final String DISALLOW_OEM_UNLOCK = "no_oem_unlock";
field public static final String DISALLOW_RUN_IN_BACKGROUND = "no_run_in_background";
@@ -8526,6 +8528,7 @@ package android.os {
field public static final int SWITCHABILITY_STATUS_USER_SWITCH_DISALLOWED = 2; // 0x2
field public static final String USER_TYPE_FULL_SECONDARY = "android.os.usertype.full.SECONDARY";
field public static final String USER_TYPE_FULL_SYSTEM = "android.os.usertype.full.SYSTEM";
field public static final String USER_TYPE_PROFILE_CLONE = "android.os.usertype.profile.CLONE";
field public static final String USER_TYPE_PROFILE_MANAGED = "android.os.usertype.profile.MANAGED";
field public static final String USER_TYPE_SYSTEM_HEADLESS = "android.os.usertype.system.HEADLESS";
}

View File

@@ -817,6 +817,7 @@ package android.content.pm {
method public int describeContents();
method public android.os.UserHandle getUserHandle();
method public boolean isAdmin();
method public boolean isCloneProfile();
method public boolean isDemo();
method public boolean isEnabled();
method public boolean isEphemeral();

View File

@@ -321,6 +321,10 @@ public class UserInfo implements Parcelable {
return UserManager.isUserTypeManagedProfile(userType);
}
public boolean isCloneProfile() {
return UserManager.isUserTypeCloneProfile(userType);
}
@UnsupportedAppUsage
public boolean isEnabled() {
return (flags & FLAG_DISABLED) != FLAG_DISABLED;

View File

@@ -99,6 +99,8 @@ interface IUserManager {
boolean someUserHasSeedAccount(in String accountName, in String accountType);
boolean isProfile(int userId);
boolean isManagedProfile(int userId);
boolean isCloneProfile(int userId);
boolean sharesMediaWithParent(int userId);
boolean isDemoUser(int userId);
boolean isPreCreated(int userId);
UserInfo createProfileForUserEvenWhenDisallowedWithThrow(in String name, in String userType, int flags,

View File

@@ -25,6 +25,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.StringDef;
import android.annotation.SuppressAutoDoc;
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.annotation.TestApi;
@@ -135,6 +136,16 @@ public class UserManager {
@SystemApi
public static final String USER_TYPE_PROFILE_MANAGED = "android.os.usertype.profile.MANAGED";
/**
* User type representing a clone profile. Clone profile is a user profile type used to run
* second instance of an otherwise single user App (eg, messengers). Only the primary user
* is allowed to have a clone profile.
*
* @hide
*/
@SystemApi
public static final String USER_TYPE_PROFILE_CLONE = "android.os.usertype.profile.CLONE";
/**
* User type representing a generic profile for testing purposes. Only on debuggable builds.
* @hide
@@ -1983,6 +1994,14 @@ public class UserManager {
return USER_TYPE_FULL_DEMO.equals(userType);
}
/**
* Returns whether the user type is a {@link UserManager#USER_TYPE_PROFILE_CLONE clone user}.
* @hide
*/
public static boolean isUserTypeCloneProfile(String userType) {
return USER_TYPE_PROFILE_CLONE.equals(userType);
}
/**
* Returns the enum defined in the statsd UserLifecycleJourneyReported atom corresponding to the
* user type.
@@ -2232,6 +2251,31 @@ public class UserManager {
}
}
/**
* Checks if the context user is a clone profile.
*
* <p>Requires {@link android.Manifest.permission#MANAGE_USERS} or
* {@link android.Manifest.permission#INTERACT_ACROSS_USERS} permission, otherwise the caller
* must be in the same profile group of the user.
*
* @return whether the context user is a clone profile.
*
* @see android.os.UserManager#USER_TYPE_PROFILE_CLONE
* @hide
*/
@SystemApi
@RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS,
Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true)
@UserHandleAware
@SuppressAutoDoc
public boolean isCloneProfile() {
try {
return mService.isCloneProfile(mUserId);
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
}
/**
* Checks if the calling app is running as an ephemeral user.
*
@@ -4063,6 +4107,31 @@ public class UserManager {
}
}
/**
* If the user is a {@link UserManager#isProfile profile}, checks if the user
* shares media with its parent user (the user that created this profile).
* Returns false for any other type of user.
*
* <p>Requires {@link android.Manifest.permission#MANAGE_USERS} or
* {@link android.Manifest.permission#INTERACT_ACROSS_USERS} permission, otherwise the
* caller must be in the same profile group as the user.
*
* @return true if the user shares media with its parent user, false otherwise.
* @hide
*/
@SystemApi
@RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS,
Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true)
@UserHandleAware
@SuppressAutoDoc
public boolean sharesMediaWithParent() {
try {
return mService.sharesMediaWithParent(mUserId);
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
}
/**
* Removes a user and all associated data.
* @param userId the integer handle of the user.

View File

@@ -1506,6 +1506,26 @@ public class UserManagerService extends IUserManager.Stub {
}
}
@Override
public boolean isCloneProfile(@UserIdInt int userId) {
checkManageOrInteractPermissionIfCallerInOtherProfileGroup(userId, "isCloneProfile");
synchronized (mUsersLock) {
UserInfo userInfo = getUserInfoLU(userId);
return userInfo != null && userInfo.isCloneProfile();
}
}
@Override
public boolean sharesMediaWithParent(@UserIdInt int userId) {
checkManageOrInteractPermissionIfCallerInOtherProfileGroup(userId,
"sharesMediaWithParent");
synchronized (mUsersLock) {
UserTypeDetails userTypeDetails = getUserTypeDetailsNoChecks(userId);
return userTypeDetails != null ? userTypeDetails.isProfile()
&& userTypeDetails.sharesMediaWithParent() : false;
}
}
@Override
public boolean isUserUnlockingOrUnlocked(@UserIdInt int userId) {
checkManageOrInteractPermissionIfCallerInOtherProfileGroup(userId,

View File

@@ -149,6 +149,13 @@ public final class UserTypeDetails {
*/
private final @Nullable int[] mDarkThemeBadgeColors;
/**
* Denotes if the user shares media with its parent user.
*
* <p> Default value is false
*/
private final boolean mSharesMediaWithParent;
private UserTypeDetails(@NonNull String name, boolean enabled, int maxAllowed,
@UserInfoFlag int baseType, @UserInfoFlag int defaultUserInfoPropertyFlags, int label,
int maxAllowedPerParent,
@@ -158,7 +165,8 @@ public final class UserTypeDetails {
@Nullable Bundle defaultRestrictions,
@Nullable Bundle defaultSystemSettings,
@Nullable Bundle defaultSecureSettings,
@Nullable List<DefaultCrossProfileIntentFilter> defaultCrossProfileIntentFilters) {
@Nullable List<DefaultCrossProfileIntentFilter> defaultCrossProfileIntentFilters,
boolean sharesMediaWithParent) {
this.mName = name;
this.mEnabled = enabled;
this.mMaxAllowed = maxAllowed;
@@ -177,6 +185,7 @@ public final class UserTypeDetails {
this.mBadgeLabels = badgeLabels;
this.mBadgeColors = badgeColors;
this.mDarkThemeBadgeColors = darkThemeBadgeColors;
this.mSharesMediaWithParent = sharesMediaWithParent;
}
/**
@@ -291,6 +300,13 @@ public final class UserTypeDetails {
return (mBaseType & UserInfo.FLAG_SYSTEM) != 0;
}
/**
* Returns true if the user has shared media with parent user or false otherwise.
*/
public boolean sharesMediaWithParent() {
return mSharesMediaWithParent;
}
/** Returns a {@link Bundle} representing the default user restrictions. */
@NonNull Bundle getDefaultRestrictions() {
return BundleUtils.clone(mDefaultRestrictions);
@@ -318,7 +334,6 @@ public final class UserTypeDetails {
: Collections.emptyList();
}
/** Dumps details of the UserTypeDetails. Do not parse this. */
public void dump(PrintWriter pw, String prefix) {
pw.print(prefix); pw.print("mName: "); pw.println(mName);
@@ -383,6 +398,7 @@ public final class UserTypeDetails {
private @DrawableRes int mIconBadge = Resources.ID_NULL;
private @DrawableRes int mBadgePlain = Resources.ID_NULL;
private @DrawableRes int mBadgeNoBackground = Resources.ID_NULL;
private boolean mSharesMediaWithParent = false;
public Builder setName(String name) {
mName = name;
@@ -473,6 +489,15 @@ public final class UserTypeDetails {
return this;
}
/**
* Sets shared media property for the user.
* @param sharesMediaWithParent the value to be set, true or false
*/
public Builder setSharesMediaWithParent(boolean sharesMediaWithParent) {
mSharesMediaWithParent = sharesMediaWithParent;
return this;
}
@UserInfoFlag int getBaseType() {
return mBaseType;
}
@@ -502,7 +527,7 @@ public final class UserTypeDetails {
mIconBadge, mBadgePlain, mBadgeNoBackground, mBadgeLabels, mBadgeColors,
mDarkThemeBadgeColors == null ? mBadgeColors : mDarkThemeBadgeColors,
mDefaultRestrictions, mDefaultSystemSettings, mDefaultSecureSettings,
mDefaultCrossProfileIntentFilters);
mDefaultCrossProfileIntentFilters, mSharesMediaWithParent);
}
private boolean hasBadge() {

View File

@@ -29,6 +29,7 @@ import static android.os.UserManager.USER_TYPE_FULL_GUEST;
import static android.os.UserManager.USER_TYPE_FULL_RESTRICTED;
import static android.os.UserManager.USER_TYPE_FULL_SECONDARY;
import static android.os.UserManager.USER_TYPE_FULL_SYSTEM;
import static android.os.UserManager.USER_TYPE_PROFILE_CLONE;
import static android.os.UserManager.USER_TYPE_PROFILE_MANAGED;
import static android.os.UserManager.USER_TYPE_PROFILE_TEST;
import static android.os.UserManager.USER_TYPE_SYSTEM_HEADLESS;
@@ -100,6 +101,7 @@ public final class UserTypeFactory {
builders.put(USER_TYPE_FULL_DEMO, getDefaultTypeFullDemo());
builders.put(USER_TYPE_FULL_RESTRICTED, getDefaultTypeFullRestricted());
builders.put(USER_TYPE_SYSTEM_HEADLESS, getDefaultTypeSystemHeadless());
builders.put(USER_TYPE_PROFILE_CLONE, getDefaultTypeProfileClone());
if (Build.IS_DEBUGGABLE) {
builders.put(USER_TYPE_PROFILE_TEST, getDefaultTypeProfileTest());
}
@@ -107,6 +109,21 @@ public final class UserTypeFactory {
return builders;
}
/**
* Returns the Builder for the default {@link UserManager#USER_TYPE_PROFILE_CLONE}
* configuration.
*/
// TODO(b/182396009): Add default restrictions, if needed for clone user type.
private static UserTypeDetails.Builder getDefaultTypeProfileClone() {
return new UserTypeDetails.Builder()
.setName(USER_TYPE_PROFILE_CLONE)
.setBaseType(FLAG_PROFILE)
.setMaxAllowedPerParent(1)
.setLabel(0)
.setDefaultRestrictions(null)
.setSharesMediaWithParent(true);
}
/**
* Returns the Builder for the default {@link UserManager#USER_TYPE_PROFILE_MANAGED}
* configuration.

View File

@@ -58,6 +58,7 @@ import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import javax.annotation.concurrent.GuardedBy;
@@ -158,6 +159,40 @@ public final class UserManagerTest {
fail("Didn't find a guest: " + list);
}
@Test
public void testCloneUser() throws Exception {
// Test that only one clone user can be created
final int primaryUserId = mUserManager.getPrimaryUser().id;
UserInfo userInfo = createProfileForUser("Clone user1",
UserManager.USER_TYPE_PROFILE_CLONE,
primaryUserId);
assertThat(userInfo).isNotNull();
UserInfo userInfo2 = createProfileForUser("Clone user2",
UserManager.USER_TYPE_PROFILE_CLONE,
primaryUserId);
assertThat(userInfo2).isNull();
final Context userContext = mContext.createPackageContextAsUser("system", 0,
UserHandle.of(userInfo.id));
assertThat(userContext.getSystemService(
UserManager.class).sharesMediaWithParent()).isTrue();
List<UserInfo> list = mUserManager.getUsers();
List<UserInfo> cloneUsers = list.stream().filter(
user -> (user.id == userInfo.id && user.name.equals("Clone user1")
&& user.isCloneProfile()))
.collect(Collectors.toList());
assertThat(cloneUsers.size()).isEqualTo(1);
// Verify clone user parent
assertThat(mUserManager.getProfileParent(primaryUserId)).isNull();
UserInfo parentProfileInfo = mUserManager.getProfileParent(userInfo.id);
assertThat(parentProfileInfo).isNotNull();
assertThat(primaryUserId).isEqualTo(parentProfileInfo.id);
removeUser(userInfo.id);
assertThat(mUserManager.getProfileParent(primaryUserId)).isNull();
}
@MediumTest
@Test
public void testAdd2Users() throws Exception {