Ask for user consent before resolving personal intent in work profile
Bug: 262700480
Test: manual, see screencasts on bug
Change-Id: I32a8960d0a59f26752ad838ba6c3aee3c4d64be0
(cherry picked from commit 13b8f773507cdbe7d70eccf25bd51ee275cb13bd)
Merged-In: I32a8960d0a59f26752ad838ba6c3aee3c4d64be0
diff --git a/core/java/com/android/internal/app/IntentForwarderActivity.java b/core/java/com/android/internal/app/IntentForwarderActivity.java
index 47b83be..ae192a4 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.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 @@
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 e243122..c833553 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 38a71f0..d07ad89 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 a663095..58cfc66 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.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 @@
@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 @@
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 @@
@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 @@
// 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 @@
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 @@
}
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 @@
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 @@
}
@Override
+ public Context createContextAsUser(UserHandle user, int flags) {
+ return this;
+ }
+
+ @Override
protected MetricsLogger getMetricsLogger() {
return mMetricsLogger;
}