Fix cannot share text from twitter to managed profile

Cross profile intent must be implicit, we should also clear the package
and component of the inner intent in chooser intent.

Test: bit FrameworksCoreTests:com.android.internal.app.IntentForwarderActivityTest
Test: can share tweet to work profile
Test: can share web link from personal to work profile and vice versa

Fix: 37767313

Change-Id: I7cbea3525def0ff029d9b5c2953117b4cab6f28b
diff --git a/core/java/com/android/internal/app/IntentForwarderActivity.java b/core/java/com/android/internal/app/IntentForwarderActivity.java
index 0b27c60..398d087 100644
--- a/core/java/com/android/internal/app/IntentForwarderActivity.java
+++ b/core/java/com/android/internal/app/IntentForwarderActivity.java
@@ -16,16 +16,14 @@
 
 package com.android.internal.app;
 
-import static android.content.pm.PackageManager.MATCH_DEFAULT_ONLY;
-
 import android.app.Activity;
 import android.app.ActivityManager;
 import android.app.ActivityThread;
 import android.app.AppGlobals;
 import android.app.admin.DevicePolicyManager;
-import android.content.Context;
 import android.content.Intent;
 import android.content.pm.IPackageManager;
+import android.content.pm.PackageManager;
 import android.content.pm.UserInfo;
 import android.os.Bundle;
 import android.os.RemoteException;
@@ -34,8 +32,12 @@
 import android.util.Slog;
 import android.widget.Toast;
 
+import com.android.internal.annotations.VisibleForTesting;
+
 import java.util.List;
 
+import static android.content.pm.PackageManager.MATCH_DEFAULT_ONLY;
+
 /**
  * This is used in conjunction with
  * {@link DevicePolicyManager#addCrossProfileIntentFilter} to enable intents to
@@ -51,15 +53,17 @@
     public static String FORWARD_INTENT_TO_MANAGED_PROFILE
             = "com.android.internal.app.ForwardIntentToManagedProfile";
 
+    private Injector mInjector;
+
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
-        Intent intentReceived = getIntent();
+        mInjector = createInjector();
 
+        Intent intentReceived = getIntent();
         String className = intentReceived.getComponent().getClassName();
         final int targetUserId;
         final int userMessageId;
-
         if (className.equals(FORWARD_INTENT_TO_PARENT)) {
             userMessageId = com.android.internal.R.string.forward_intent_to_owner;
             targetUserId = getProfileParent();
@@ -76,17 +80,12 @@
             finish();
             return;
         }
-        Intent newIntent = new Intent(intentReceived);
-        newIntent.setComponent(null);
-        // Apps should not be allowed to target a specific package in the target user.
-        newIntent.setPackage(null);
-        newIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT
-                |Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP);
-        int callingUserId = getUserId();
 
-        if (canForward(newIntent, targetUserId)) {
+        final int callingUserId = getUserId();
+        final Intent newIntent = canForward(intentReceived, targetUserId);
+        if (newIntent != null) {
             if (Intent.ACTION_CHOOSER.equals(newIntent.getAction())) {
-                Intent innerIntent = (Intent) newIntent.getParcelableExtra(Intent.EXTRA_INTENT);
+                Intent innerIntent = newIntent.getParcelableExtra(Intent.EXTRA_INTENT);
                 // At this point, innerIntent is not null. Otherwise, canForward would have returned
                 // false.
                 innerIntent.prepareToLeaveUser(callingUserId);
@@ -94,15 +93,18 @@
                 newIntent.prepareToLeaveUser(callingUserId);
             }
 
-            final android.content.pm.ResolveInfo ri = getPackageManager().resolveActivityAsUser(
-                        newIntent, MATCH_DEFAULT_ONLY, targetUserId);
+            final android.content.pm.ResolveInfo ri =
+                    mInjector.getPackageManager().resolveActivityAsUser(
+                            newIntent,
+                            MATCH_DEFAULT_ONLY,
+                            targetUserId);
 
             // Don't show the disclosure if next activity is ResolverActivity or ChooserActivity
             // as those will already have shown work / personal as neccesary etc.
             final boolean shouldShowDisclosure = ri == null || ri.activityInfo == null ||
                     !"android".equals(ri.activityInfo.packageName) ||
                     !(ResolverActivity.class.getName().equals(ri.activityInfo.name)
-                    || ChooserActivity.class.getName().equals(ri.activityInfo.name));
+                            || ChooserActivity.class.getName().equals(ri.activityInfo.name));
 
             try {
                 startActivityAsCaller(newIntent, null, false, targetUserId);
@@ -126,44 +128,56 @@
                 Toast.makeText(this, getString(userMessageId), Toast.LENGTH_LONG).show();
             }
         } else {
-            Slog.wtf(TAG, "the intent: " + newIntent + " cannot be forwarded from user "
+            Slog.wtf(TAG, "the intent: " + intentReceived + " cannot be forwarded from user "
                     + callingUserId + " to user " + targetUserId);
         }
         finish();
     }
 
-    boolean canForward(Intent intent, int targetUserId)  {
-        IPackageManager ipm = AppGlobals.getPackageManager();
-        if (Intent.ACTION_CHOOSER.equals(intent.getAction())) {
+    /**
+     * Check whether the intent can be forwarded to target user. Return the intent used for
+     * forwarding if it can be forwarded, {@code null} otherwise.
+     */
+    Intent canForward(Intent incomingIntent, int targetUserId)  {
+        Intent forwardIntent = new Intent(incomingIntent);
+        forwardIntent.addFlags(
+                Intent.FLAG_ACTIVITY_FORWARD_RESULT | Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP);
+        sanitizeIntent(forwardIntent);
+
+        Intent intentToCheck = forwardIntent;
+        if (Intent.ACTION_CHOOSER.equals(forwardIntent.getAction())) {
             // The EXTRA_INITIAL_INTENTS may not be allowed to be forwarded.
-            if (intent.hasExtra(Intent.EXTRA_INITIAL_INTENTS)) {
+            if (forwardIntent.hasExtra(Intent.EXTRA_INITIAL_INTENTS)) {
                 Slog.wtf(TAG, "An chooser intent with extra initial intents cannot be forwarded to"
                         + " a different user");
-                return false;
+                return null;
             }
-            if (intent.hasExtra(Intent.EXTRA_REPLACEMENT_EXTRAS)) {
+            if (forwardIntent.hasExtra(Intent.EXTRA_REPLACEMENT_EXTRAS)) {
                 Slog.wtf(TAG, "A chooser intent with replacement extras cannot be forwarded to a"
                         + " different user");
-                return false;
+                return null;
             }
-            intent = (Intent) intent.getParcelableExtra(Intent.EXTRA_INTENT);
-            if (intent == null) {
+            intentToCheck = forwardIntent.getParcelableExtra(Intent.EXTRA_INTENT);
+            if (intentToCheck == null) {
                 Slog.wtf(TAG, "Cannot forward a chooser intent with no extra "
                         + Intent.EXTRA_INTENT);
-                return false;
+                return null;
             }
         }
-        String resolvedType = intent.resolveTypeIfNeeded(getContentResolver());
-        if (intent.getSelector() != null) {
-            intent = intent.getSelector();
+        if (forwardIntent.getSelector() != null) {
+            intentToCheck = forwardIntent.getSelector();
         }
+        String resolvedType = intentToCheck.resolveTypeIfNeeded(getContentResolver());
+        sanitizeIntent(intentToCheck);
         try {
-            return ipm.canForwardTo(intent, resolvedType, getUserId(),
-                    targetUserId);
+            if (mInjector.getIPackageManager().
+                    canForwardTo(intentToCheck, resolvedType, getUserId(), targetUserId)) {
+                return forwardIntent;
+            }
         } catch (RemoteException e) {
             Slog.e(TAG, "PackageManagerService is dead?");
-            return false;
         }
+        return null;
     }
 
     /**
@@ -174,8 +188,7 @@
      * on the device.
      */
     private int getManagedProfile() {
-        UserManager userManager = (UserManager) getSystemService(Context.USER_SERVICE);
-        List<UserInfo> relatedUsers = userManager.getProfiles(UserHandle.myUserId());
+        List<UserInfo> relatedUsers = mInjector.getUserManager().getProfiles(UserHandle.myUserId());
         for (UserInfo userInfo : relatedUsers) {
             if (userInfo.isManagedProfile()) return userInfo.id;
         }
@@ -189,8 +202,7 @@
      * no parent.
      */
     private int getProfileParent() {
-        UserManager userManager = (UserManager) getSystemService(Context.USER_SERVICE);
-        UserInfo parent = userManager.getProfileParent(UserHandle.myUserId());
+        UserInfo parent = mInjector.getUserManager().getProfileParent(UserHandle.myUserId());
         if (parent == null) {
             Slog.wtf(TAG, FORWARD_INTENT_TO_PARENT
                     + " has been called, but there is no parent");
@@ -198,4 +210,44 @@
         }
         return parent.id;
     }
+
+    /**
+     * Sanitize the intent in place.
+     */
+    private void sanitizeIntent(Intent intent) {
+        // Apps should not be allowed to target a specific package/ component in the target user.
+        intent.setPackage(null);
+        intent.setComponent(null);
+    }
+
+    @VisibleForTesting
+    protected Injector createInjector() {
+        return new InjectorImpl();
+    }
+
+    private class InjectorImpl implements Injector {
+
+        @Override
+        public IPackageManager getIPackageManager() {
+            return AppGlobals.getPackageManager();
+        }
+
+        @Override
+        public UserManager getUserManager() {
+            return getSystemService(UserManager.class);
+        }
+
+        @Override
+        public PackageManager getPackageManager() {
+            return IntentForwarderActivity.this.getPackageManager();
+        }
+    }
+
+    public interface Injector {
+        IPackageManager getIPackageManager();
+
+        UserManager getUserManager();
+
+        PackageManager getPackageManager();
+    }
 }
diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml
index 94a515b..63adeb7 100644
--- a/core/tests/coretests/AndroidManifest.xml
+++ b/core/tests/coretests/AndroidManifest.xml
@@ -1172,6 +1172,7 @@
         </activity>
         <activity android:name="com.android.internal.app.ChooserWrapperActivity"/>
         <activity android:name="com.android.internal.app.ResolverWrapperActivity"/>
+        <activity android:name="com.android.internal.app.IntentForwarderActivityTest$IntentForwarderWrapperActivity"/>
 
         <receiver android:name="android.app.activity.AbortReceiver">
             <intent-filter android:priority="1">
diff --git a/core/tests/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java b/core/tests/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java
new file mode 100644
index 0000000..b18fa74
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java
@@ -0,0 +1,296 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.app;
+
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageManager;
+import android.content.pm.UserInfo;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.Log;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@RunWith(AndroidJUnit4.class)
+public class IntentForwarderActivityTest {
+    private static final ComponentName FORWARD_TO_MANAGED_PROFILE_COMPONENT_NAME =
+            new ComponentName(
+                    "android",
+                    IntentForwarderActivity.FORWARD_INTENT_TO_MANAGED_PROFILE
+            );
+    private static final String TYPE_PLAIN_TEXT = "text/plain";
+
+    private static UserInfo MANAGED_PROFILE_INFO = new UserInfo();
+    static {
+        MANAGED_PROFILE_INFO.id = 10;
+        MANAGED_PROFILE_INFO.flags = UserInfo.FLAG_MANAGED_PROFILE;
+    }
+
+    private static UserInfo CURRENT_USER_INFO = new UserInfo();
+    static {
+        CURRENT_USER_INFO.id = UserHandle.myUserId();
+        CURRENT_USER_INFO.flags = 0;
+    }
+
+    private static IntentForwarderActivity.Injector sInjector;
+    private static ComponentName sComponentName;
+
+    @Mock private IPackageManager mIPm;
+    @Mock private PackageManager mPm;
+    @Mock private UserManager mUserManager;
+
+    @Rule
+    public ActivityTestRule<IntentForwarderWrapperActivity> mActivityRule =
+            new ActivityTestRule<>(IntentForwarderWrapperActivity.class, true, false);
+
+    private Context mContext;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        mContext = InstrumentationRegistry.getTargetContext();
+        sInjector = new TestInjector();
+    }
+
+    @Test
+    public void forwardToManagedProfile_canForward_sendIntent() throws Exception {
+        sComponentName = FORWARD_TO_MANAGED_PROFILE_COMPONENT_NAME;
+
+        // Intent can be forwarded.
+        when(mIPm.canForwardTo(
+                any(Intent.class), nullable(String.class), anyInt(), anyInt())).thenReturn(true);
+
+        // Managed profile exists.
+        List<UserInfo> profiles = new ArrayList<>();
+        profiles.add(CURRENT_USER_INFO);
+        profiles.add(MANAGED_PROFILE_INFO);
+        when(mUserManager.getProfiles(anyInt())).thenReturn(profiles);
+
+        Intent intent = new Intent(mContext, IntentForwarderWrapperActivity.class);
+        intent.setAction(Intent.ACTION_SEND);
+        intent.setType(TYPE_PLAIN_TEXT);
+        IntentForwarderWrapperActivity activity = mActivityRule.launchActivity(intent);
+
+        ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+        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());
+        assertNotNull(activity.mStartActivityIntent);
+        assertEquals(Intent.ACTION_SEND, activity.mStartActivityIntent.getAction());
+        assertNull(activity.mStartActivityIntent.getPackage());
+        assertNull(activity.mStartActivityIntent.getComponent());
+        assertEquals(CURRENT_USER_INFO.id, activity.mStartActivityIntent.getContentUserHint());
+
+        assertEquals(MANAGED_PROFILE_INFO.id, activity.mUserIdActivityLaunchedIn);
+    }
+
+    @Test
+    public void forwardToManagedProfile_cannotForward_sendIntent() throws Exception {
+        sComponentName = FORWARD_TO_MANAGED_PROFILE_COMPONENT_NAME;
+
+        // Intent cannot be forwarded.
+        when(mIPm.canForwardTo(
+                any(Intent.class), nullable(String.class), anyInt(), anyInt())).thenReturn(false);
+
+        // Managed profile exists.
+        List<UserInfo> profiles = new ArrayList<>();
+        profiles.add(CURRENT_USER_INFO);
+        profiles.add(MANAGED_PROFILE_INFO);
+        when(mUserManager.getProfiles(anyInt())).thenReturn(profiles);
+
+        // Create ACTION_SEND intent.
+        Intent intent = new Intent(mContext, IntentForwarderWrapperActivity.class);
+        intent.setAction(Intent.ACTION_SEND);
+        IntentForwarderWrapperActivity activity = mActivityRule.launchActivity(intent);
+
+        assertNull(activity.mStartActivityIntent);
+    }
+
+    @Test
+    public void forwardToManagedProfile_noManagedProfile_sendIntent() throws Exception {
+        sComponentName = FORWARD_TO_MANAGED_PROFILE_COMPONENT_NAME;
+
+        // Intent can be forwarded.
+        when(mIPm.canForwardTo(
+                any(Intent.class), anyString(), anyInt(), anyInt())).thenReturn(true);
+
+        // Managed profile does not exist.
+        List<UserInfo> profiles = new ArrayList<>();
+        profiles.add(CURRENT_USER_INFO);
+        when(mUserManager.getProfiles(anyInt())).thenReturn(profiles);
+
+        // Create ACTION_SEND intent.
+        Intent intent = new Intent(mContext, IntentForwarderWrapperActivity.class);
+        intent.setAction(Intent.ACTION_SEND);
+        IntentForwarderWrapperActivity activity = mActivityRule.launchActivity(intent);
+
+        assertNull(activity.mStartActivityIntent);
+    }
+
+    @Test
+    public void forwardToManagedProfile_canForward_chooserIntent() throws Exception {
+        sComponentName = FORWARD_TO_MANAGED_PROFILE_COMPONENT_NAME;
+
+        // Intent can be forwarded.
+        when(mIPm.canForwardTo(
+                any(Intent.class), nullable(String.class), anyInt(), anyInt())).thenReturn(true);
+
+        // Manage profile exists.
+        List<UserInfo> profiles = new ArrayList<>();
+        profiles.add(CURRENT_USER_INFO);
+        profiles.add(MANAGED_PROFILE_INFO);
+        when(mUserManager.getProfiles(anyInt())).thenReturn(profiles);
+
+        // Create chooser Intent
+        Intent intent = new Intent(mContext, IntentForwarderWrapperActivity.class);
+        intent.setAction(Intent.ACTION_CHOOSER);
+        Intent sendIntent = new Intent(Intent.ACTION_SEND);
+        sendIntent.setComponent(new ComponentName("xx", "yyy"));
+        sendIntent.setType(TYPE_PLAIN_TEXT);
+        intent.putExtra(Intent.EXTRA_INTENT, sendIntent);
+        IntentForwarderWrapperActivity activity = mActivityRule.launchActivity(intent);
+
+        ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+        verify(mIPm).canForwardTo(intentCaptor.capture(), eq(TYPE_PLAIN_TEXT), anyInt(), anyInt());
+        assertEquals(Intent.ACTION_SEND, intentCaptor.getValue().getAction());
+
+        assertNotNull(activity.mStartActivityIntent);
+        assertEquals(Intent.ACTION_CHOOSER, activity.mStartActivityIntent.getAction());
+        assertNull(activity.mStartActivityIntent.getPackage());
+        assertNull(activity.mStartActivityIntent.getComponent());
+
+        Intent innerIntent = activity.mStartActivityIntent.getParcelableExtra(Intent.EXTRA_INTENT);
+        assertNotNull(innerIntent);
+        assertEquals(Intent.ACTION_SEND, innerIntent.getAction());
+        assertNull(innerIntent.getComponent());
+        assertNull(innerIntent.getPackage());
+        assertEquals(CURRENT_USER_INFO.id, innerIntent.getContentUserHint());
+
+        assertEquals(MANAGED_PROFILE_INFO.id, activity.mUserIdActivityLaunchedIn);
+    }
+
+    @Test
+    public void forwardToManagedProfile_canForward_selectorIntent() throws Exception {
+        sComponentName = FORWARD_TO_MANAGED_PROFILE_COMPONENT_NAME;
+
+        // Intent can be forwarded.
+        when(mIPm.canForwardTo(
+                any(Intent.class), nullable(String.class), anyInt(), anyInt())).thenReturn(true);
+
+        // Manage profile exists.
+        List<UserInfo> profiles = new ArrayList<>();
+        profiles.add(CURRENT_USER_INFO);
+        profiles.add(MANAGED_PROFILE_INFO);
+        when(mUserManager.getProfiles(anyInt())).thenReturn(profiles);
+
+        // 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);
+        verify(mIPm).canForwardTo(
+                intentCaptor.capture(), nullable(String.class), anyInt(), anyInt());
+        assertEquals(Intent.ACTION_VIEW, intentCaptor.getValue().getAction());
+
+        assertNotNull(activity.mStartActivityIntent);
+        assertEquals(Intent.ACTION_MAIN, activity.mStartActivityIntent.getAction());
+        assertNull(activity.mStartActivityIntent.getPackage());
+        assertNull(activity.mStartActivityIntent.getComponent());
+        assertEquals(CURRENT_USER_INFO.id, activity.mStartActivityIntent.getContentUserHint());
+
+        Intent innerIntent = activity.mStartActivityIntent.getSelector();
+        assertNotNull(innerIntent);
+        assertEquals(Intent.ACTION_VIEW, innerIntent.getAction());
+        assertNull(innerIntent.getComponent());
+        assertNull(innerIntent.getPackage());
+
+        assertEquals(MANAGED_PROFILE_INFO.id, activity.mUserIdActivityLaunchedIn);
+    }
+
+
+    public static class IntentForwarderWrapperActivity extends IntentForwarderActivity {
+        private Intent mStartActivityIntent;
+        private int mUserIdActivityLaunchedIn;
+
+        @Override
+        public void onCreate(@Nullable Bundle savedInstanceState) {
+            getIntent().setComponent(sComponentName);
+            super.onCreate(savedInstanceState);
+        }
+
+        @Override
+        protected Injector createInjector() {
+            return sInjector;
+        }
+
+        @Override
+        public void startActivityAsCaller(Intent intent, @Nullable Bundle options, boolean
+                ignoreTargetSecurity, int userId) {
+            mStartActivityIntent = intent;
+            mUserIdActivityLaunchedIn = userId;
+        }
+    }
+
+    class TestInjector implements IntentForwarderActivity.Injector {
+
+        @Override
+        public IPackageManager getIPackageManager() {
+            return mIPm;
+        }
+
+        @Override
+        public UserManager getUserManager() {
+            return mUserManager;
+        }
+
+        @Override
+        public PackageManager getPackageManager() {
+            return mPm;
+        }
+    }
+}
\ No newline at end of file