Loading core/java/com/android/internal/app/IntentForwarderActivity.java +105 −20 Original line number Diff line number Diff line Loading @@ -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)) { Loading @@ -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()) { Loading Loading @@ -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); Loading @@ -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( Loading Loading @@ -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. Loading Loading @@ -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(); Loading core/tests/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java +61 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -131,6 +135,7 @@ public class IntentForwarderActivityTest { @Before public void setup() { MockitoAnnotations.initMocks(this); mContext = InstrumentationRegistry.getTargetContext(); sInjector = spy(new TestInjector()); Loading Loading @@ -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"; Loading Loading @@ -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 { Loading Loading
core/java/com/android/internal/app/IntentForwarderActivity.java +105 −20 Original line number Diff line number Diff line Loading @@ -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)) { Loading @@ -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()) { Loading Loading @@ -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); Loading @@ -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( Loading Loading @@ -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. Loading Loading @@ -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(); Loading
core/tests/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java +61 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -131,6 +135,7 @@ public class IntentForwarderActivityTest { @Before public void setup() { MockitoAnnotations.initMocks(this); mContext = InstrumentationRegistry.getTargetContext(); sInjector = spy(new TestInjector()); Loading Loading @@ -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"; Loading Loading @@ -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 { Loading