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

Commit 876c7b88 authored by Olivier Nshimiye's avatar Olivier Nshimiye Committed by Android (Google) Code Review
Browse files

Merge "Add an intent redirection consent dialog for Private Space" into main

parents 320b1db4 daad86ba
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 {