Merge "Ask for user consent before resolving personal intent in work profile" into udc-dev
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;
         }