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

Commit daad86ba authored by Olivier Nshimiye's avatar Olivier Nshimiye
Browse files

Add an intent redirection consent dialog for Private Space

Sample dialog: http://shortn/_fdJgCRWyc9

Bug: 325576602
Test: atest IntentForwarderActivityTest
Test: Manual
Change-Id: Iaaab493a5da43a0ad75d330409e2e23df3abd13c
parent b30ba815
Loading
Loading
Loading
Loading
+105 −20
Original line number Diff line number Diff line
@@ -172,6 +172,20 @@ public class IntentForwarderActivity extends Activity {
        newIntent.prepareToLeaveUser(callingUserId);
        final CompletableFuture<ResolveInfo> targetResolveInfoFuture =
                mInjector.resolveActivityAsUser(newIntent, MATCH_DEFAULT_ONLY, targetUserId);

        if (isPrivateProfile(callingUserId)) {
            buildAndExecuteForPrivateProfile(intentReceived, className, newIntent, callingUserId,
                    targetUserId);
        } else {
            buildAndExecute(targetResolveInfoFuture, intentReceived, className, newIntent,
                    callingUserId,
                    targetUserId, userMessage, managedProfile);
        }
    }

    private void buildAndExecute(CompletableFuture<ResolveInfo> targetResolveInfoFuture,
            Intent intentReceived, String className, Intent newIntent, int callingUserId,
            int targetUserId, String userMessage, UserInfo managedProfile) {
        targetResolveInfoFuture
                .thenApplyAsync(targetResolveInfo -> {
                    if (isResolverActivityResolveInfo(targetResolveInfo)) {
@@ -195,6 +209,23 @@ public class IntentForwarderActivity extends Activity {
                }, getApplicationContext().getMainExecutor());
    }

    private void buildAndExecuteForPrivateProfile(
            Intent intentReceived, String className, Intent newIntent, int callingUserId,
            int targetUserId) {
        final CompletableFuture<ResolveInfo> targetResolveInfoFuture =
                mInjector.resolveActivityAsUser(newIntent, MATCH_DEFAULT_ONLY, targetUserId);
        targetResolveInfoFuture
                .thenAcceptAsync(targetResolveInfo -> {
                    if (isResolverActivityResolveInfo(targetResolveInfo)) {
                        launchResolverActivityWithCorrectTab(intentReceived, className, newIntent,
                                callingUserId, targetUserId);
                    } else {
                        maybeShowUserConsentMiniResolverPrivate(targetResolveInfo, newIntent,
                                targetUserId);
                    }
                }, getApplicationContext().getMainExecutor());
    }

    private void maybeShowUserConsentMiniResolver(
            ResolveInfo target, Intent launchIntent, UserInfo managedProfile) {
        if (target == null || isIntentForwarderResolveInfo(target) || !isDeviceProvisioned()) {
@@ -233,24 +264,70 @@ public class IntentForwarderActivity extends Activity {
                "Showing user consent for redirection into the managed profile for intent [%s] and "
                        + " calling package [%s]",
                launchIntent, callingPackage));
        int layoutId = R.layout.miniresolver;
        setContentView(layoutId);
        PackageManager packageManagerForTargetUser =
                createContextAsUser(UserHandle.of(targetUserId), /* flags= */ 0)
                        .getPackageManager();
        buildMiniResolver(target, launchIntent, targetUserId,
                getOpenInWorkMessage(launchIntent, target.loadLabel(packageManagerForTargetUser)),
                packageManagerForTargetUser);

        findViewById(R.id.title_container).setElevation(0);

        View telephonyInfo = findViewById(R.id.miniresolver_info_section);

        // Additional information section is work telephony specific. Therefore, it is only shown
        // for telephony related intents, when all sim subscriptions are in the work profile.
        if ((isDialerIntent(launchIntent) || isTextMessageIntent(launchIntent))
                && devicePolicyManager.getManagedSubscriptionsPolicy().getPolicyType()
                == ManagedSubscriptionsPolicy.TYPE_ALL_MANAGED_SUBSCRIPTIONS) {
            telephonyInfo.setVisibility(View.VISIBLE);
            ((TextView) findViewById(R.id.miniresolver_info_section_text))
                    .setText(getWorkTelephonyInfoSectionMessage(launchIntent));
        } else {
            telephonyInfo.setVisibility(View.GONE);
        }
    }

    private void maybeShowUserConsentMiniResolverPrivate(
            ResolveInfo target, Intent launchIntent, int targetUserId) {
        if (target == null || isIntentForwarderResolveInfo(target)) {
            finish();
            return;
        }

        String callingPackage = getCallingPackage();

        Log.i("IntentForwarderActivity", String.format(
                "Showing user consent for redirection into the main profile for intent [%s] and "
                        + " calling package [%s]",
                launchIntent, callingPackage));
        PackageManager packageManagerForTargetUser =
                createContextAsUser(UserHandle.of(targetUserId), /* flags= */ 0)
                        .getPackageManager();
        buildMiniResolver(target, launchIntent, targetUserId,
                getString(R.string.miniresolver_open_in_personal,
                        target.loadLabel(packageManagerForTargetUser)),
                packageManagerForTargetUser);

        View telephonyInfo = findViewById(R.id.miniresolver_info_section);
        telephonyInfo.setVisibility(View.GONE);
    }

    private void buildMiniResolver(ResolveInfo target, Intent launchIntent, int targetUserId,
            String resolverTitle, PackageManager pmForTargetUser) {
        int layoutId = R.layout.miniresolver;
        setContentView(layoutId);

        findViewById(R.id.title_container).setElevation(0);

        ImageView icon = findViewById(R.id.icon);
        icon.setImageDrawable(
                getAppIcon(target, launchIntent, targetUserId, packageManagerForTargetUser));
                getAppIcon(target, launchIntent, targetUserId, pmForTargetUser));

        View buttonContainer = findViewById(R.id.button_bar_container);
        buttonContainer.setPadding(0, 0, 0, buttonContainer.getPaddingBottom());

        ((TextView) findViewById(R.id.open_cross_profile)).setText(
                getOpenInWorkMessage(launchIntent, target.loadLabel(packageManagerForTargetUser)));
                resolverTitle);

        // The mini-resolver's negative button is reused in this flow to cancel the intent
        ((Button) findViewById(R.id.use_same_profile_browser)).setText(R.string.cancel);
@@ -269,21 +346,6 @@ public class IntentForwarderActivity extends Activity {
                    targetUserId);
            finish();
        });


        View telephonyInfo = findViewById(R.id.miniresolver_info_section);

        // Additional information section is work telephony specific. Therefore, it is only shown
        // for telephony related intents, when all sim subscriptions are in the work profile.
        if ((isDialerIntent(launchIntent) || isTextMessageIntent(launchIntent))
                && devicePolicyManager.getManagedSubscriptionsPolicy().getPolicyType()
                    == ManagedSubscriptionsPolicy.TYPE_ALL_MANAGED_SUBSCRIPTIONS) {
            telephonyInfo.setVisibility(View.VISIBLE);
            ((TextView) findViewById(R.id.miniresolver_info_section_text))
                    .setText(getWorkTelephonyInfoSectionMessage(launchIntent));
        } else {
            telephonyInfo.setVisibility(View.GONE);
        }
    }

    private Drawable getAppIcon(
@@ -547,6 +609,18 @@ public class IntentForwarderActivity extends Activity {
        return null;
    }

    /**
     * Returns the private profile for this device or null if there is no private profile.
     */
    @Nullable
    private UserInfo getPrivateProfile() {
        List<UserInfo> relatedUsers = mInjector.getUserManager().getProfiles(UserHandle.myUserId());
        for (UserInfo userInfo : relatedUsers) {
            if (userInfo.isPrivateProfile()) return userInfo;
        }
        return null;
    }

    /**
     * Returns the userId of the profile parent or UserHandle.USER_NULL if there is
     * no parent.
@@ -577,6 +651,17 @@ public class IntentForwarderActivity extends Activity {
        return mMetricsLogger;
    }

    private boolean isPrivateProfile(int userId) {
        UserInfo privateProfile = getPrivateProfile();
        return privateSpaceFlagsEnabled() && privateProfile != null
                && privateProfile.id == userId;
    }

    private boolean privateSpaceFlagsEnabled() {
        return android.os.Flags.allowPrivateProfile()
                && android.multiuser.Flags.enablePrivateSpaceIntentRedirection();
    }

    @VisibleForTesting
    protected Injector createInjector() {
        return new InjectorImpl();
+61 −0
Original line number Diff line number Diff line
@@ -53,6 +53,7 @@ import android.os.Bundle;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings;

import androidx.test.InstrumentationRegistry;
@@ -93,6 +94,9 @@ public class IntentForwarderActivityTest {
    private static final String TYPE_PLAIN_TEXT = "text/plain";

    private static UserInfo MANAGED_PROFILE_INFO = new UserInfo();
    private static UserInfo PRIVATE_PROFILE_INFO = new UserInfo(12, "Private", null,
            UserInfo.FLAG_PROFILE, UserManager.USER_TYPE_PROFILE_PRIVATE);
    @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();

    static {
        MANAGED_PROFILE_INFO.id = 10;
@@ -131,6 +135,7 @@ public class IntentForwarderActivityTest {

    @Before
    public void setup() {

        MockitoAnnotations.initMocks(this);
        mContext = InstrumentationRegistry.getTargetContext();
        sInjector = spy(new TestInjector());
@@ -632,6 +637,54 @@ public class IntentForwarderActivityTest {
                logMakerCaptor.getValue().getSubtype());
    }

    @Test
    public void shouldForwardToParent_telephony_privateProfile() throws Exception {
        mSetFlagsRule.enableFlags(
                android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
                android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_INTENT_REDIRECTION);

        sComponentName = FORWARD_TO_PARENT_COMPONENT_NAME;
        when(mIPm.canForwardTo(
                any(Intent.class), nullable(String.class), anyInt(), anyInt())).thenReturn(true);

        List<UserInfo> profiles = new ArrayList<>();
        profiles.add(CURRENT_USER_INFO);
        profiles.add(PRIVATE_PROFILE_INFO);
        when(mUserManager.getProfiles(anyInt())).thenReturn(profiles);
        when(mUserManager.getProfileParent(anyInt())).thenReturn(CURRENT_USER_INFO);
        Intent intent = new Intent(mContext, IntentForwarderWrapperActivity.class);
        intent.setAction(Intent.ACTION_DIAL);
        IntentForwarderWrapperActivity activity = mActivityRule.launchActivity(intent);
        verify(mIPm).canForwardTo(any(), any(), anyInt(), anyInt());
        assertEquals(activity.getStartActivityIntent().getAction(), intent.getAction());
        assertEquals(activity.getUserIdActivityLaunchedIn(), CURRENT_USER_INFO.id);
    }

    @Test
    public void shouldForwardToParent_mms_privateProfile() throws Exception {
        mSetFlagsRule.enableFlags(
                android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
                android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_INTENT_REDIRECTION);

        sComponentName = FORWARD_TO_PARENT_COMPONENT_NAME;
        when(mIPm.canForwardTo(
                any(Intent.class), nullable(String.class), anyInt(), anyInt())).thenReturn(true);

        List<UserInfo> profiles = new ArrayList<>();
        profiles.add(CURRENT_USER_INFO);
        profiles.add(PRIVATE_PROFILE_INFO);
        when(mUserManager.getProfiles(anyInt())).thenReturn(profiles);
        when(mUserManager.getProfileParent(anyInt())).thenReturn(CURRENT_USER_INFO);
        Intent intent = new Intent(mContext, IntentForwarderWrapperActivity.class);
        intent.setAction(Intent.ACTION_SEND);
        intent.setType(TYPE_PLAIN_TEXT);
        IntentForwarderWrapperActivity activity = mActivityRule.launchActivity(intent);
        verify(mIPm).canForwardTo(any(), any(), anyInt(), anyInt());
        assertEquals(activity.getStartActivityIntent().getAction(), intent.getAction());
        assertEquals(activity.getStartActivityIntent().getType(), intent.getType());
        assertEquals(activity.getUserIdActivityLaunchedIn(), CURRENT_USER_INFO.id);
    }

    private void setupShouldSkipDisclosureTest() throws RemoteException {
        sComponentName = FORWARD_TO_PARENT_COMPONENT_NAME;
        sActivityName = "MyTestActivity";
@@ -688,6 +741,14 @@ public class IntentForwarderActivityTest {
        protected MetricsLogger getMetricsLogger() {
            return mMetricsLogger;
        }

        Intent getStartActivityIntent() {
            return mStartActivityIntent;
        }

        int getUserIdActivityLaunchedIn() {
            return mUserIdActivityLaunchedIn;
        }
    }

    public class TestInjector implements IntentForwarderActivity.Injector {