Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit c07cb1a9 authored by Liahav Eitan's avatar Liahav Eitan
Browse files

Add APIs in CrossProfileApps to obtain the profile type of target profiles

Bug: 323001115
Test: atest android.devicepolicy.cts.CrossProfileAppsTest
Change-Id: I38e7c021c4a9fe3be2098958c2d5378373697569
parent eb649022
Loading
Loading
Loading
Loading
+2 −0
Original line number Original line Diff line number Diff line
@@ -12243,6 +12243,8 @@ package android.content.pm {
    method @NonNull public android.graphics.drawable.Drawable getProfileSwitchingIconDrawable(@NonNull android.os.UserHandle);
    method @NonNull public android.graphics.drawable.Drawable getProfileSwitchingIconDrawable(@NonNull android.os.UserHandle);
    method @NonNull public CharSequence getProfileSwitchingLabel(@NonNull android.os.UserHandle);
    method @NonNull public CharSequence getProfileSwitchingLabel(@NonNull android.os.UserHandle);
    method @NonNull public java.util.List<android.os.UserHandle> getTargetUserProfiles();
    method @NonNull public java.util.List<android.os.UserHandle> getTargetUserProfiles();
    method @FlaggedApi("android.app.admin.flags.allow_querying_profile_type") public boolean isManagedProfile(@NonNull android.os.UserHandle);
    method @FlaggedApi("android.app.admin.flags.allow_querying_profile_type") public boolean isProfile(@NonNull android.os.UserHandle);
    method @RequiresPermission(anyOf={android.Manifest.permission.INTERACT_ACROSS_PROFILES, "android.permission.INTERACT_ACROSS_USERS"}) public void startActivity(@NonNull android.content.Intent, @NonNull android.os.UserHandle, @Nullable android.app.Activity);
    method @RequiresPermission(anyOf={android.Manifest.permission.INTERACT_ACROSS_PROFILES, "android.permission.INTERACT_ACROSS_USERS"}) public void startActivity(@NonNull android.content.Intent, @NonNull android.os.UserHandle, @Nullable android.app.Activity);
    method @RequiresPermission(anyOf={android.Manifest.permission.INTERACT_ACROSS_PROFILES, "android.permission.INTERACT_ACROSS_USERS"}) public void startActivity(@NonNull android.content.Intent, @NonNull android.os.UserHandle, @Nullable android.app.Activity, @Nullable android.os.Bundle);
    method @RequiresPermission(anyOf={android.Manifest.permission.INTERACT_ACROSS_PROFILES, "android.permission.INTERACT_ACROSS_USERS"}) public void startActivity(@NonNull android.content.Intent, @NonNull android.os.UserHandle, @Nullable android.app.Activity, @Nullable android.os.Bundle);
    method public void startMainActivity(@NonNull android.content.ComponentName, @NonNull android.os.UserHandle);
    method public void startMainActivity(@NonNull android.content.ComponentName, @NonNull android.os.UserHandle);
+7 −0
Original line number Original line Diff line number Diff line
@@ -49,6 +49,13 @@ flag {
  bug: "304999634"
  bug: "304999634"
}
}


flag {
    name: "allow_querying_profile_type"
    namespace: "enterprise"
    description: "Public APIs to query if a user is a profile and what kind of profile type it is."
    bug: "323001115"
}

flag {
flag {
  name: "quiet_mode_credential_bug_fix"
  name: "quiet_mode_credential_bug_fix"
  namespace: "enterprise"
  namespace: "enterprise"
+42 −0
Original line number Original line Diff line number Diff line
@@ -19,7 +19,9 @@ import static android.Manifest.permission.INTERACT_ACROSS_USERS;
import static android.app.admin.DevicePolicyResources.Strings.Core.SWITCH_TO_PERSONAL_LABEL;
import static android.app.admin.DevicePolicyResources.Strings.Core.SWITCH_TO_PERSONAL_LABEL;
import static android.app.admin.DevicePolicyResources.Strings.Core.SWITCH_TO_WORK_LABEL;
import static android.app.admin.DevicePolicyResources.Strings.Core.SWITCH_TO_WORK_LABEL;
import static android.content.pm.PackageManager.MATCH_DEFAULT_ONLY;
import static android.content.pm.PackageManager.MATCH_DEFAULT_ONLY;
import static android.app.admin.flags.Flags.FLAG_ALLOW_QUERYING_PROFILE_TYPE;


import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.RequiresPermission;
@@ -314,6 +316,41 @@ public class CrossProfileApps {
        }
        }
    }
    }



    /**
     * Checks if the specified user is a profile, i.e. not the parent user.
     *
     * @param userHandle The UserHandle of the target profile, must be one of the users returned by
     *        {@link #getTargetUserProfiles()}, otherwise a {@link SecurityException} will
     *        be thrown.
     * @return whether the specified user is a profile.
     */
    @FlaggedApi(FLAG_ALLOW_QUERYING_PROFILE_TYPE)
    public boolean isProfile(@NonNull UserHandle userHandle) {
        // Note that this is not a security check, but rather a check for correct use.
        // The actual security check is performed by UserManager.
        verifyCanAccessUser(userHandle);

        return mUserManager.isProfile(userHandle.getIdentifier());
    }

    /**
     * Checks if the specified user is a managed profile.
     *
     * @param userHandle The UserHandle of the target profile, must be one of the users returned by
     *        {@link #getTargetUserProfiles()}, otherwise a {@link SecurityException} will
     *        be thrown.
     * @return whether the specified user is a managed profile.
     */
    @FlaggedApi(FLAG_ALLOW_QUERYING_PROFILE_TYPE)
    public boolean isManagedProfile(@NonNull UserHandle userHandle) {
        // Note that this is not a security check, but rather a check for correct use.
        // The actual security check is performed by UserManager.
        verifyCanAccessUser(userHandle);

        return mUserManager.isManagedProfile(userHandle.getIdentifier());
    }

    /**
    /**
     * Return a label that calling app can show to user for the semantic of profile switching --
     * Return a label that calling app can show to user for the semantic of profile switching --
     * launching its own activity in specified user profile. For example, it may return
     * launching its own activity in specified user profile. For example, it may return
@@ -677,6 +714,11 @@ public class CrossProfileApps {
        }
        }
    }
    }


    /**
     * A validation method to check that the methods in this class are only being applied to user
     * handles returned by {@link #getTargetUserProfiles()}. As this is run client-side for
     * input validation purposes, this should never replace a real security check service-side.
     */
    private void verifyCanAccessUser(UserHandle userHandle) {
    private void verifyCanAccessUser(UserHandle userHandle) {
        if (!getTargetUserProfiles().contains(userHandle)) {
        if (!getTargetUserProfiles().contains(userHandle)) {
            throw new SecurityException("Not allowed to access " + userHandle);
            throw new SecurityException("Not allowed to access " + userHandle);
+5 −1
Original line number Original line Diff line number Diff line
@@ -3204,7 +3204,11 @@ public class UserManager {
        return isProfile(mUserId);
        return isProfile(mUserId);
    }
    }


    private boolean isProfile(@UserIdInt int userId) {
    /**
     * Returns whether the specified user is a profile.
     * @hide
     */
    public boolean isProfile(@UserIdInt int userId) {
        final String profileType = getProfileType(userId);
        final String profileType = getProfileType(userId);
        return profileType != null && !profileType.equals("");
        return profileType != null && !profileType.equals("");
    }
    }
+45 −0
Original line number Original line Diff line number Diff line
@@ -27,6 +27,8 @@ import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.when;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;


import android.app.admin.DevicePolicyManager;
import android.app.admin.DevicePolicyManager;
import android.app.admin.DevicePolicyResourcesManager;
import android.app.admin.DevicePolicyResourcesManager;
@@ -118,11 +120,54 @@ public class CrossProfileAppsTest {
    public void initUsers() throws Exception {
    public void initUsers() throws Exception {
        when(mUserManager.isManagedProfile(PERSONAL_PROFILE.getIdentifier())).thenReturn(false);
        when(mUserManager.isManagedProfile(PERSONAL_PROFILE.getIdentifier())).thenReturn(false);
        when(mUserManager.isManagedProfile(MANAGED_PROFILE.getIdentifier())).thenReturn(true);
        when(mUserManager.isManagedProfile(MANAGED_PROFILE.getIdentifier())).thenReturn(true);
        when(mUserManager.isProfile(PERSONAL_PROFILE.getIdentifier())).thenReturn(false);
        when(mUserManager.isProfile(MANAGED_PROFILE.getIdentifier())).thenReturn(true);


        mTargetProfiles = new ArrayList<>();
        mTargetProfiles = new ArrayList<>();
        when(mService.getTargetUserProfiles(MY_PACKAGE)).thenReturn(mTargetProfiles);
        when(mService.getTargetUserProfiles(MY_PACKAGE)).thenReturn(mTargetProfiles);
    }
    }


    @Test
    public void isProfile_managedProfile_returnsTrue() {
        setValidTargetProfile(MANAGED_PROFILE);

        boolean result = mCrossProfileApps.isProfile(MANAGED_PROFILE);

        assertTrue(result);
    }

    @Test
    public void isProfile_personalProfile_returnsFalse() {
        setValidTargetProfile(PERSONAL_PROFILE);

        boolean result = mCrossProfileApps.isProfile(PERSONAL_PROFILE);

        assertFalse(result);
    }

    @Test
    public void isManagedProfile_managedProfile_returnsTrue() {
        setValidTargetProfile(MANAGED_PROFILE);

        boolean result = mCrossProfileApps.isManagedProfile(MANAGED_PROFILE);

        assertTrue(result);
    }

    @Test
    public void isManagedProfile_personalProfile_returnsFalse() {
        setValidTargetProfile(PERSONAL_PROFILE);

        boolean result = mCrossProfileApps.isManagedProfile(PERSONAL_PROFILE);

        assertFalse(result);
    }

    @Test(expected = SecurityException.class)
    public void isManagedProfile_notValidTarget_throwsSecurityException() {
        mCrossProfileApps.isManagedProfile(PERSONAL_PROFILE);
    }

    @Test
    @Test
    public void getProfileSwitchingLabel_managedProfile() {
    public void getProfileSwitchingLabel_managedProfile() {
        setValidTargetProfile(MANAGED_PROFILE);
        setValidTargetProfile(MANAGED_PROFILE);