diff options
4 files changed, 95 insertions, 7 deletions
diff --git a/core/java/com/android/internal/app/IntentForwarderActivity.java b/core/java/com/android/internal/app/IntentForwarderActivity.java index 47b83be35f87..ae192a4effc7 100644 --- a/core/java/com/android/internal/app/IntentForwarderActivity.java +++ b/core/java/com/android/internal/app/IntentForwarderActivity.java @@ -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; @@ -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); } - startActivityAsCaller(newIntent, targetUserId); return targetResolveInfo; }, mExecutorService) .thenAcceptAsync(result -> { - maybeShowDisclosure(intentReceived, result, userMessage); - finish(); + // 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, diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index e2431221d995..c8335535cc39 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -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" diff --git a/core/res/res/layout/miniresolver.xml b/core/res/res/layout/miniresolver.xml index 38a71f0e17f6..d07ad8986dec 100644 --- a/core/res/res/layout/miniresolver.xml +++ b/core/res/res/layout/miniresolver.xml @@ -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" @@ -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" diff --git a/core/tests/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java b/core/tests/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java index a663095aa893..58cfc6625051 100644 --- a/core/tests/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java +++ b/core/tests/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java @@ -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; @@ -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; @@ -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( @@ -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()); @@ -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( @@ -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); @@ -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()); @@ -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, @@ -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); @@ -654,6 +680,11 @@ public class IntentForwarderActivityTest { } @Override + public Context createContextAsUser(UserHandle user, int flags) { + return this; + } + + @Override protected MetricsLogger getMetricsLogger() { return mMetricsLogger; } |