Loading core/java/com/android/internal/app/IntentForwarderActivity.java +52 −4 Original line number Diff line number Diff line Loading @@ -45,8 +45,13 @@ import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; import android.util.Slog; import android.view.View; import android.widget.Button; import android.widget.ImageView; import android.widget.TextView; import android.widget.Toast; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; Loading Loading @@ -151,17 +156,60 @@ public class IntentForwarderActivity extends Activity { if (isResolverActivityResolveInfo(targetResolveInfo)) { launchResolverActivityWithCorrectTab(intentReceived, className, newIntent, callingUserId, targetUserId); return targetResolveInfo; } // When switching to the personal profile, automatically start the activity } else if (className.equals(FORWARD_INTENT_TO_PARENT)) { startActivityAsCaller(newIntent, targetUserId); } return targetResolveInfo; }, mExecutorService) .thenAcceptAsync(result -> { // When switching to the personal profile, inform user after starting activity if (className.equals(FORWARD_INTENT_TO_PARENT)) { maybeShowDisclosure(intentReceived, result, userMessage); finish(); // When switching to the work profile, ask the user for consent before launching } else if (className.equals(FORWARD_INTENT_TO_MANAGED_PROFILE)) { maybeShowUserConsentMiniResolver(result, newIntent, targetUserId); } }, getApplicationContext().getMainExecutor()); } private void maybeShowUserConsentMiniResolver( ResolveInfo target, Intent launchIntent, int targetUserId) { if (target == null || isIntentForwarderResolveInfo(target) || !isDeviceProvisioned()) { finish(); return; } int layoutId = R.layout.miniresolver; setContentView(layoutId); findViewById(R.id.title_container).setElevation(0); ImageView icon = findViewById(R.id.icon); PackageManager packageManagerForTargetUser = createContextAsUser(UserHandle.of(targetUserId), /* flags= */ 0) .getPackageManager(); icon.setImageDrawable(target.loadIcon(packageManagerForTargetUser)); View buttonContainer = findViewById(R.id.button_bar_container); buttonContainer.setPadding(0, 0, 0, buttonContainer.getPaddingBottom()); ((TextView) findViewById(R.id.open_cross_profile)).setText( getResources().getString( R.string.miniresolver_open_in_work, target.loadLabel(packageManagerForTargetUser))); // 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); findViewById(R.id.use_same_profile_browser).setOnClickListener(v -> finish()); findViewById(R.id.button_open).setOnClickListener(v -> { startActivityAsCaller(launchIntent, targetUserId); finish(); }); } private String getForwardToPersonalMessage() { return getSystemService(DevicePolicyManager.class).getResources().getString( FORWARD_INTENT_TO_PERSONAL, Loading core/res/AndroidManifest.xml +4 −1 Original line number Diff line number Diff line Loading @@ -7695,8 +7695,11 @@ </activity> <activity android:name="com.android.internal.app.IntentForwarderActivity" android:finishOnCloseSystemDialogs="true" android:theme="@style/Theme.Translucent.NoTitleBar" android:theme="@style/Theme.DeviceDefault.Resolver" android:excludeFromRecents="true" android:documentLaunchMode="never" android:relinquishTaskIdentity="true" android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|keyboard|keyboardHidden" android:label="@string/user_owner_label" android:exported="true" android:visibleToInstantApps="true" Loading core/res/res/layout/miniresolver.xml +6 −0 Original line number Diff line number Diff line Loading @@ -14,6 +14,11 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> <!-- Layout used to decide whether to launch a single target in another profile. When this layout is used in ResolverActivity, the user can choose between a verified app in the other profile and the default browser in the current profile. In IntentForwarderActivity, they choose whether to launch in the other profile or cancel. --> <com.android.internal.widget.ResolverDrawerLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" Loading @@ -24,6 +29,7 @@ android:id="@id/contentPanel"> <RelativeLayout android:id="@+id/title_container" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alwaysShow="true" Loading core/tests/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java +33 −2 Original line number Diff line number Diff line Loading @@ -16,6 +16,12 @@ package com.android.internal.app; import static androidx.test.espresso.Espresso.onView; import static androidx.test.espresso.action.ViewActions.click; import static androidx.test.espresso.assertion.ViewAssertions.matches; import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed; import static androidx.test.espresso.matcher.ViewMatchers.withId; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertNotNull; import static junit.framework.Assert.assertNull; Loading Loading @@ -53,6 +59,7 @@ import androidx.test.InstrumentationRegistry; import androidx.test.rule.ActivityTestRule; import androidx.test.runner.AndroidJUnit4; import com.android.internal.R; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; Loading Loading @@ -140,6 +147,8 @@ public class IntentForwarderActivityTest { @Test public void forwardToManagedProfile_canForward_sendIntent() throws Exception { sComponentName = FORWARD_TO_MANAGED_PROFILE_COMPONENT_NAME; sActivityName = "MyTestActivity"; sPackageName = "test.package.name"; // Intent can be forwarded. when(mIPm.canForwardTo( Loading @@ -160,7 +169,13 @@ public class IntentForwarderActivityTest { verify(mIPm).canForwardTo(intentCaptor.capture(), eq(TYPE_PLAIN_TEXT), anyInt(), anyInt()); assertEquals(Intent.ACTION_SEND, intentCaptor.getValue().getAction()); assertEquals(Intent.ACTION_SEND, intentCaptor.getValue().getAction()); InstrumentationRegistry.getInstrumentation().waitForIdleSync(); onView(withId(R.id.icon)).check(matches(isDisplayed())); onView(withId(R.id.open_cross_profile)).check(matches(isDisplayed())); onView(withId(R.id.use_same_profile_browser)).check(matches(isDisplayed())); onView(withId(R.id.button_open)).perform(click()); InstrumentationRegistry.getInstrumentation().waitForIdleSync(); assertNotNull(activity.mStartActivityIntent); assertEquals(Intent.ACTION_SEND, activity.mStartActivityIntent.getAction()); assertNull(activity.mStartActivityIntent.getPackage()); Loading Loading @@ -250,6 +265,8 @@ public class IntentForwarderActivityTest { @Test public void forwardToManagedProfile_canForward_selectorIntent() throws Exception { sComponentName = FORWARD_TO_MANAGED_PROFILE_COMPONENT_NAME; sActivityName = "MyTestActivity"; sPackageName = "test.package.name"; // Intent can be forwarded. when(mIPm.canForwardTo( Loading @@ -264,6 +281,7 @@ public class IntentForwarderActivityTest { // Create selector intent. Intent intent = Intent.makeMainSelectorActivity( Intent.ACTION_VIEW, Intent.CATEGORY_BROWSABLE); IntentForwarderWrapperActivity activity = mActivityRule.launchActivity(intent); ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class); Loading @@ -271,6 +289,13 @@ public class IntentForwarderActivityTest { intentCaptor.capture(), nullable(String.class), anyInt(), anyInt()); assertEquals(Intent.ACTION_VIEW, intentCaptor.getValue().getAction()); InstrumentationRegistry.getInstrumentation().waitForIdleSync(); onView(withId(R.id.icon)).check(matches(isDisplayed())); onView(withId(R.id.open_cross_profile)).check(matches(isDisplayed())); onView(withId(R.id.use_same_profile_browser)).check(matches(isDisplayed())); onView(withId(R.id.button_open)).perform(click()); InstrumentationRegistry.getInstrumentation().waitForIdleSync(); assertNotNull(activity.mStartActivityIntent); assertEquals(Intent.ACTION_MAIN, activity.mStartActivityIntent.getAction()); assertNull(activity.mStartActivityIntent.getPackage()); Loading Loading @@ -608,7 +633,7 @@ public class IntentForwarderActivityTest { } private void setupShouldSkipDisclosureTest() throws RemoteException { sComponentName = FORWARD_TO_MANAGED_PROFILE_COMPONENT_NAME; sComponentName = FORWARD_TO_PARENT_COMPONENT_NAME; sActivityName = "MyTestActivity"; sPackageName = "test.package.name"; Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.DEVICE_PROVISIONED, Loading @@ -619,6 +644,7 @@ public class IntentForwarderActivityTest { profiles.add(CURRENT_USER_INFO); profiles.add(MANAGED_PROFILE_INFO); when(mUserManager.getProfiles(anyInt())).thenReturn(profiles); when(mUserManager.getProfileParent(anyInt())).thenReturn(CURRENT_USER_INFO); // Intent can be forwarded. when(mIPm.canForwardTo( any(Intent.class), nullable(String.class), anyInt(), anyInt())).thenReturn(true); Loading Loading @@ -653,6 +679,11 @@ public class IntentForwarderActivityTest { mUserIdActivityLaunchedIn = userId; } @Override public Context createContextAsUser(UserHandle user, int flags) { return this; } @Override protected MetricsLogger getMetricsLogger() { return mMetricsLogger; Loading Loading
core/java/com/android/internal/app/IntentForwarderActivity.java +52 −4 Original line number Diff line number Diff line Loading @@ -45,8 +45,13 @@ import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; import android.util.Slog; import android.view.View; import android.widget.Button; import android.widget.ImageView; import android.widget.TextView; import android.widget.Toast; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; Loading Loading @@ -151,17 +156,60 @@ public class IntentForwarderActivity extends Activity { if (isResolverActivityResolveInfo(targetResolveInfo)) { launchResolverActivityWithCorrectTab(intentReceived, className, newIntent, callingUserId, targetUserId); return targetResolveInfo; } // When switching to the personal profile, automatically start the activity } else if (className.equals(FORWARD_INTENT_TO_PARENT)) { startActivityAsCaller(newIntent, targetUserId); } return targetResolveInfo; }, mExecutorService) .thenAcceptAsync(result -> { // When switching to the personal profile, inform user after starting activity if (className.equals(FORWARD_INTENT_TO_PARENT)) { maybeShowDisclosure(intentReceived, result, userMessage); finish(); // When switching to the work profile, ask the user for consent before launching } else if (className.equals(FORWARD_INTENT_TO_MANAGED_PROFILE)) { maybeShowUserConsentMiniResolver(result, newIntent, targetUserId); } }, getApplicationContext().getMainExecutor()); } private void maybeShowUserConsentMiniResolver( ResolveInfo target, Intent launchIntent, int targetUserId) { if (target == null || isIntentForwarderResolveInfo(target) || !isDeviceProvisioned()) { finish(); return; } int layoutId = R.layout.miniresolver; setContentView(layoutId); findViewById(R.id.title_container).setElevation(0); ImageView icon = findViewById(R.id.icon); PackageManager packageManagerForTargetUser = createContextAsUser(UserHandle.of(targetUserId), /* flags= */ 0) .getPackageManager(); icon.setImageDrawable(target.loadIcon(packageManagerForTargetUser)); View buttonContainer = findViewById(R.id.button_bar_container); buttonContainer.setPadding(0, 0, 0, buttonContainer.getPaddingBottom()); ((TextView) findViewById(R.id.open_cross_profile)).setText( getResources().getString( R.string.miniresolver_open_in_work, target.loadLabel(packageManagerForTargetUser))); // 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); findViewById(R.id.use_same_profile_browser).setOnClickListener(v -> finish()); findViewById(R.id.button_open).setOnClickListener(v -> { startActivityAsCaller(launchIntent, targetUserId); finish(); }); } private String getForwardToPersonalMessage() { return getSystemService(DevicePolicyManager.class).getResources().getString( FORWARD_INTENT_TO_PERSONAL, Loading
core/res/AndroidManifest.xml +4 −1 Original line number Diff line number Diff line Loading @@ -7695,8 +7695,11 @@ </activity> <activity android:name="com.android.internal.app.IntentForwarderActivity" android:finishOnCloseSystemDialogs="true" android:theme="@style/Theme.Translucent.NoTitleBar" android:theme="@style/Theme.DeviceDefault.Resolver" android:excludeFromRecents="true" android:documentLaunchMode="never" android:relinquishTaskIdentity="true" android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|keyboard|keyboardHidden" android:label="@string/user_owner_label" android:exported="true" android:visibleToInstantApps="true" Loading
core/res/res/layout/miniresolver.xml +6 −0 Original line number Diff line number Diff line Loading @@ -14,6 +14,11 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> <!-- Layout used to decide whether to launch a single target in another profile. When this layout is used in ResolverActivity, the user can choose between a verified app in the other profile and the default browser in the current profile. In IntentForwarderActivity, they choose whether to launch in the other profile or cancel. --> <com.android.internal.widget.ResolverDrawerLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" Loading @@ -24,6 +29,7 @@ android:id="@id/contentPanel"> <RelativeLayout android:id="@+id/title_container" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alwaysShow="true" Loading
core/tests/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java +33 −2 Original line number Diff line number Diff line Loading @@ -16,6 +16,12 @@ package com.android.internal.app; import static androidx.test.espresso.Espresso.onView; import static androidx.test.espresso.action.ViewActions.click; import static androidx.test.espresso.assertion.ViewAssertions.matches; import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed; import static androidx.test.espresso.matcher.ViewMatchers.withId; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertNotNull; import static junit.framework.Assert.assertNull; Loading Loading @@ -53,6 +59,7 @@ import androidx.test.InstrumentationRegistry; import androidx.test.rule.ActivityTestRule; import androidx.test.runner.AndroidJUnit4; import com.android.internal.R; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; Loading Loading @@ -140,6 +147,8 @@ public class IntentForwarderActivityTest { @Test public void forwardToManagedProfile_canForward_sendIntent() throws Exception { sComponentName = FORWARD_TO_MANAGED_PROFILE_COMPONENT_NAME; sActivityName = "MyTestActivity"; sPackageName = "test.package.name"; // Intent can be forwarded. when(mIPm.canForwardTo( Loading @@ -160,7 +169,13 @@ public class IntentForwarderActivityTest { verify(mIPm).canForwardTo(intentCaptor.capture(), eq(TYPE_PLAIN_TEXT), anyInt(), anyInt()); assertEquals(Intent.ACTION_SEND, intentCaptor.getValue().getAction()); assertEquals(Intent.ACTION_SEND, intentCaptor.getValue().getAction()); InstrumentationRegistry.getInstrumentation().waitForIdleSync(); onView(withId(R.id.icon)).check(matches(isDisplayed())); onView(withId(R.id.open_cross_profile)).check(matches(isDisplayed())); onView(withId(R.id.use_same_profile_browser)).check(matches(isDisplayed())); onView(withId(R.id.button_open)).perform(click()); InstrumentationRegistry.getInstrumentation().waitForIdleSync(); assertNotNull(activity.mStartActivityIntent); assertEquals(Intent.ACTION_SEND, activity.mStartActivityIntent.getAction()); assertNull(activity.mStartActivityIntent.getPackage()); Loading Loading @@ -250,6 +265,8 @@ public class IntentForwarderActivityTest { @Test public void forwardToManagedProfile_canForward_selectorIntent() throws Exception { sComponentName = FORWARD_TO_MANAGED_PROFILE_COMPONENT_NAME; sActivityName = "MyTestActivity"; sPackageName = "test.package.name"; // Intent can be forwarded. when(mIPm.canForwardTo( Loading @@ -264,6 +281,7 @@ public class IntentForwarderActivityTest { // Create selector intent. Intent intent = Intent.makeMainSelectorActivity( Intent.ACTION_VIEW, Intent.CATEGORY_BROWSABLE); IntentForwarderWrapperActivity activity = mActivityRule.launchActivity(intent); ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class); Loading @@ -271,6 +289,13 @@ public class IntentForwarderActivityTest { intentCaptor.capture(), nullable(String.class), anyInt(), anyInt()); assertEquals(Intent.ACTION_VIEW, intentCaptor.getValue().getAction()); InstrumentationRegistry.getInstrumentation().waitForIdleSync(); onView(withId(R.id.icon)).check(matches(isDisplayed())); onView(withId(R.id.open_cross_profile)).check(matches(isDisplayed())); onView(withId(R.id.use_same_profile_browser)).check(matches(isDisplayed())); onView(withId(R.id.button_open)).perform(click()); InstrumentationRegistry.getInstrumentation().waitForIdleSync(); assertNotNull(activity.mStartActivityIntent); assertEquals(Intent.ACTION_MAIN, activity.mStartActivityIntent.getAction()); assertNull(activity.mStartActivityIntent.getPackage()); Loading Loading @@ -608,7 +633,7 @@ public class IntentForwarderActivityTest { } private void setupShouldSkipDisclosureTest() throws RemoteException { sComponentName = FORWARD_TO_MANAGED_PROFILE_COMPONENT_NAME; sComponentName = FORWARD_TO_PARENT_COMPONENT_NAME; sActivityName = "MyTestActivity"; sPackageName = "test.package.name"; Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.DEVICE_PROVISIONED, Loading @@ -619,6 +644,7 @@ public class IntentForwarderActivityTest { profiles.add(CURRENT_USER_INFO); profiles.add(MANAGED_PROFILE_INFO); when(mUserManager.getProfiles(anyInt())).thenReturn(profiles); when(mUserManager.getProfileParent(anyInt())).thenReturn(CURRENT_USER_INFO); // Intent can be forwarded. when(mIPm.canForwardTo( any(Intent.class), nullable(String.class), anyInt(), anyInt())).thenReturn(true); Loading Loading @@ -653,6 +679,11 @@ public class IntentForwarderActivityTest { mUserIdActivityLaunchedIn = userId; } @Override public Context createContextAsUser(UserHandle user, int flags) { return this; } @Override protected MetricsLogger getMetricsLogger() { return mMetricsLogger; Loading