Introduce forwarding intents across profiles.

The package manager service maintains, for some user ids, a list of forwarding intent filters.
A forwarding intent filter is an intent filter with a destination (a user id).
If an intent matches the forwarding intent filter, then activities in the destination can also respond to the intent.

When the package manager service is asked for components that resolve an intent:
If the intent matches the forwarding intent filter, and at least one activity in the destination user can respond to the intent:
The package manager service also returns the IntentForwarderActivity.
This activity will forward the intent to the destination.

Change-Id: Id8957de3e4a4fdbc1e0dea073eadb45e04ef985a
diff --git a/api/current.txt b/api/current.txt
index 54c9d90..605e786 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -4985,8 +4985,10 @@
   public class DevicePolicyManager {
     method public void addPersistentPreferredActivity(android.content.ComponentName, android.content.IntentFilter, android.content.ComponentName);
     method public void addUserRestriction(android.content.ComponentName, java.lang.String);
+    method public void clearForwardingIntentFilters(android.content.ComponentName);
     method public void clearPackagePersistentPreferredActivities(android.content.ComponentName, java.lang.String);
     method public void clearUserRestriction(android.content.ComponentName, java.lang.String);
+    method public void forwardMatchingIntents(android.content.ComponentName, android.content.IntentFilter, int);
     method public java.util.List<android.content.ComponentName> getActiveAdmins();
     method public android.os.Bundle getApplicationRestrictions(android.content.ComponentName, java.lang.String);
     method public boolean getCameraDisabled(android.content.ComponentName);
@@ -5046,6 +5048,8 @@
     field public static final java.lang.String EXTRA_DEVICE_ADMIN = "android.app.extra.DEVICE_ADMIN";
     field public static final java.lang.String EXTRA_PROVISIONING_DEFAULT_MANAGED_PROFILE_NAME = "defaultManagedProfileName";
     field public static final java.lang.String EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME = "deviceAdminPackageName";
+    field public static int FLAG_TO_MANAGED_PROFILE;
+    field public static int FLAG_TO_PRIMARY_USER;
     field public static final int KEYGUARD_DISABLE_FEATURES_ALL = 2147483647; // 0x7fffffff
     field public static final int KEYGUARD_DISABLE_FEATURES_NONE = 0; // 0x0
     field public static final int KEYGUARD_DISABLE_SECURE_CAMERA = 2; // 0x2
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 68ab611..73e5b2a 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -172,6 +172,16 @@
     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
     public static final String ACTION_SET_NEW_PASSWORD
             = "android.app.action.SET_NEW_PASSWORD";
+    /**
+     * Flag for {@link #forwardMatchingIntents}: the intents will forwarded to the primary user.
+     */
+    public static int FLAG_TO_PRIMARY_USER = 0x0001;
+
+    /**
+     * Flag for {@link #forwardMatchingIntents}: the intents will be forwarded to the managed
+     * profile.
+     */
+    public static int FLAG_TO_MANAGED_PROFILE = 0x0002;
 
     /**
      * Return true if the given administrator component is currently
@@ -1953,6 +1963,39 @@
     }
 
     /**
+     * Called by a profile owner to forward intents sent from the managed profile to the owner, or
+     * from the owner to the managed profile.
+     * If an intent matches this intent filter, then activities belonging to the other user can
+     * respond to this intent.
+     * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+     * @param filter if an intent matches this IntentFilter, then it can be forwarded.
+     */
+    public void forwardMatchingIntents(ComponentName admin, IntentFilter filter, int flags) {
+        if (mService != null) {
+            try {
+                mService.forwardMatchingIntents(admin, filter, flags);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed talking with device policy service", e);
+            }
+        }
+    }
+
+    /**
+     * Called by a profile owner to remove all the forwarding intent filters from the current user
+     * and from the owner.
+     * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+     */
+    public void clearForwardingIntentFilters(ComponentName admin) {
+        if (mService != null) {
+            try {
+                mService.clearForwardingIntentFilters(admin);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed talking with device policy service", e);
+            }
+        }
+    }
+
+    /**
      * Called by a profile or device owner to get the application restrictions for a given target
      * application running in the managed profile.
      *
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 72b3c20..eaf4016 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -121,4 +121,6 @@
     Bundle getApplicationRestrictions(in ComponentName who, in String packageName);
 
     void setUserRestriction(in ComponentName who, in String key, boolean enable);
+    void forwardMatchingIntents(in ComponentName admin, in IntentFilter filter, int flags);
+    void clearForwardingIntentFilters(in ComponentName admin);
 }
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 488e25f..cf9a296 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -111,6 +111,8 @@
 
     ResolveInfo resolveIntent(in Intent intent, String resolvedType, int flags, int userId);
 
+    boolean canForwardTo(in Intent intent, String resolvedType, int userIdFrom, int userIdDest);
+
     List<ResolveInfo> queryIntentActivities(in Intent intent, 
             String resolvedType, int flags, int userId);
 
@@ -245,6 +247,10 @@
 
     void clearPackagePersistentPreferredActivities(String packageName, int userId);
 
+    void addForwardingIntentFilter(in IntentFilter filter, int userIdOrig, int userIdDest);
+
+    void clearForwardingIntentFilters(int userIdOrig);
+
     /**
      * Report the set of 'Home' activity candidates, plus (if any) which of them
      * is the current "always use this one" setting.
diff --git a/core/java/com/android/internal/app/IntentForwarderActivity.java b/core/java/com/android/internal/app/IntentForwarderActivity.java
new file mode 100644
index 0000000..2f74372
--- /dev/null
+++ b/core/java/com/android/internal/app/IntentForwarderActivity.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2014 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.app.Activity;
+import android.app.AppGlobals;
+import android.os.Bundle;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.IPackageManager;
+import android.content.pm.UserInfo;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.app.ActivityManagerNative;
+import android.os.RemoteException;
+import android.util.Slog;
+import java.util.List;
+import java.util.Set;
+
+
+
+
+/*
+ * This is used in conjunction with DevicePolicyManager.setForwardingIntents to enable intents to be
+ * passed in and out of a managed profile.
+ */
+
+public class IntentForwarderActivity extends Activity  {
+
+    public static String TAG = "IntentForwarderActivity";
+
+    public static String FORWARD_INTENT_TO_USER_OWNER
+            = "com.android.internal.app.ForwardIntentToUserOwner";
+
+    public static String FORWARD_INTENT_TO_MANAGED_PROFILE
+            = "com.android.internal.app.ForwardIntentToManagedProfile";
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        Intent intentReceived = getIntent();
+
+        String className = intentReceived.getComponent().getClassName();
+        final UserHandle userDest;
+
+        if (className.equals(FORWARD_INTENT_TO_USER_OWNER)) {
+            userDest = UserHandle.OWNER;
+        } else if (className.equals(FORWARD_INTENT_TO_MANAGED_PROFILE)) {
+            userDest = getManagedProfile();
+        } else {
+            Slog.wtf(TAG, IntentForwarderActivity.class.getName() + " cannot be called directly");
+            userDest = null;
+        }
+        if (userDest == null) { // This covers the case where there is no managed profile.
+            finish();
+            return;
+        }
+        Intent newIntent = new Intent(intentReceived);
+        newIntent.setComponent(null);
+        newIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT
+                |Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP);
+        int callingUserId = getUserId();
+        IPackageManager ipm = AppGlobals.getPackageManager();
+        String resolvedType = newIntent.resolveTypeIfNeeded(getContentResolver());
+        boolean canForward = false;
+        try {
+            canForward = ipm.canForwardTo(newIntent, resolvedType, callingUserId,
+                    userDest.getIdentifier());
+        } catch (RemoteException e) {
+            Slog.e(TAG, "PackageManagerService is dead?");
+        }
+        if (canForward) {
+            startActivityAsUser(newIntent, userDest);
+        } else {
+            Slog.wtf(TAG, "the intent: " + newIntent + "cannot be forwarded from user "
+                    + callingUserId + " to user " + userDest.getIdentifier());
+        }
+        finish();
+    }
+
+    /**
+     * Returns the managed profile for this device or null if there is no managed
+     * profile.
+     *
+     * TODO: Remove the assumption that there is only one managed profile
+     * on the device.
+     */
+    private UserHandle getManagedProfile() {
+        UserManager userManager = (UserManager) getSystemService(Context.USER_SERVICE);
+        List<UserInfo> relatedUsers = userManager.getProfiles(UserHandle.USER_OWNER);
+        for (UserInfo userInfo : relatedUsers) {
+            if (userInfo.isManagedProfile()) return new UserHandle(userInfo.id);
+        }
+        Slog.wtf(TAG, FORWARD_INTENT_TO_MANAGED_PROFILE
+                + " has been called, but there is no managed profile");
+        return null;
+    }
+}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 4f093a8..3d3e86f 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2681,6 +2681,26 @@
                 <category android:name="android.intent.category.DEFAULT" />
             </intent-filter>
         </activity>
+        <activity android:name="com.android.internal.app.IntentForwarderActivity"
+                android:finishOnCloseSystemDialogs="true"
+                android:theme="@style/Theme.NoDisplay"
+                android:excludeFromRecents="true"
+                android:label="@string/user_owner_label"
+                android:exported="true"
+                >
+        </activity>
+        <activity-alias android:name="com.android.internal.app.ForwardIntentToUserOwner"
+                android:targetActivity="com.android.internal.app.IntentForwarderActivity"
+                android:icon="@drawable/personal_icon"
+                android:exported="true"
+                android:label="@string/user_owner_label">
+        </activity-alias>
+        <activity-alias android:name="com.android.internal.app.ForwardIntentToManagedProfile"
+                android:targetActivity="com.android.internal.app.IntentForwarderActivity"
+                android:icon="@drawable/work_icon"
+                android:exported="true"
+                android:label="@string/managed_profile_label">
+        </activity-alias>
         <activity android:name="com.android.internal.app.HeavyWeightSwitcherActivity"
                 android:theme="@style/Theme.Holo.Dialog"
                 android:label="@string/heavy_weight_switcher_title"
diff --git a/core/res/res/drawable-hdpi/personal_icon.png b/core/res/res/drawable-hdpi/personal_icon.png
new file mode 100644
index 0000000..8d96b5e
--- /dev/null
+++ b/core/res/res/drawable-hdpi/personal_icon.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/work_icon.png b/core/res/res/drawable-hdpi/work_icon.png
new file mode 100644
index 0000000..e90866b
--- /dev/null
+++ b/core/res/res/drawable-hdpi/work_icon.png
Binary files differ
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 57b2c01..97400b2 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -454,6 +454,12 @@
     <!-- Label for the Android system components when they are shown to the user. -->
     <string name="android_system_label">Android System</string>
 
+    <!-- Label for the user owner in the intent forwarding app. -->
+    <string name="user_owner_label">Personal</string>
+
+    <!-- Label for a corporate profile in the intent forwarding app. -->
+    <string name="managed_profile_label">Work</string>
+
     <!-- Title of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permgrouplab_costMoney">Services that cost you money</string>
     <!-- Description of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. -->
diff --git a/services/core/java/com/android/server/pm/ForwardingIntentFilter.java b/services/core/java/com/android/server/pm/ForwardingIntentFilter.java
new file mode 100644
index 0000000..aba796b
--- /dev/null
+++ b/services/core/java/com/android/server/pm/ForwardingIntentFilter.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2014, 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.server.pm;
+
+import com.android.internal.util.XmlUtils;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+import android.content.IntentFilter;
+import android.util.Log;
+import java.io.IOException;
+import android.os.UserHandle;
+
+/**
+ * The {@link PackageManagerService} maintains some {@link ForwardingIntentFilter}s for every user.
+ * If an {@link Intent} matches the {@link ForwardingIntentFilter}, then it can be forwarded to the
+ * {@link #mUserIdDest}.
+ */
+class ForwardingIntentFilter extends IntentFilter {
+    private static final String ATTR_USER_ID_DEST = "userIdDest";
+    private static final String ATTR_FILTER = "filter";
+
+    private static final String TAG = "ForwardingIntentFilter";
+
+    // If the intent matches the IntentFilter, then it can be forwarded to this userId.
+    final int mUserIdDest;
+
+    ForwardingIntentFilter(IntentFilter filter, int userIdDest) {
+        super(filter);
+        mUserIdDest = userIdDest;
+    }
+
+    public int getUserIdDest() {
+        return mUserIdDest;
+    }
+
+    ForwardingIntentFilter(XmlPullParser parser) throws XmlPullParserException, IOException {
+        String userIdDestString = parser.getAttributeValue(null, ATTR_USER_ID_DEST);
+        if (userIdDestString == null) {
+            String msg = "Missing element under " + TAG +": " + ATTR_USER_ID_DEST + " at " +
+                    parser.getPositionDescription();
+            PackageManagerService.reportSettingsProblem(Log.WARN, msg);
+            mUserIdDest = UserHandle.USER_NULL;
+        } else {
+            mUserIdDest = Integer.parseInt(userIdDestString);
+        }
+        int outerDepth = parser.getDepth();
+        String tagName = parser.getName();
+        int type;
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+            tagName = parser.getName();
+            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                continue;
+            } else if (type == XmlPullParser.START_TAG) {
+                if (tagName.equals(ATTR_FILTER)) {
+                    break;
+                } else {
+                    String msg = "Unknown element under " + Settings.TAG_FORWARDING_INTENT_FILTERS
+                            + ": " + tagName + " at " + parser.getPositionDescription();
+                    PackageManagerService.reportSettingsProblem(Log.WARN, msg);
+                    XmlUtils.skipCurrentTag(parser);
+                }
+            }
+        }
+        if (tagName.equals(ATTR_FILTER)) {
+            readFromXml(parser);
+        } else {
+            String msg = "Missing element under " + TAG + ": " + ATTR_FILTER +
+                    " at " + parser.getPositionDescription();
+            PackageManagerService.reportSettingsProblem(Log.WARN, msg);
+            XmlUtils.skipCurrentTag(parser);
+        }
+    }
+
+    public void writeToXml(XmlSerializer serializer) throws IOException {
+        serializer.attribute(null, ATTR_USER_ID_DEST, Integer.toString(mUserIdDest));
+        serializer.startTag(null, ATTR_FILTER);
+            super.writeToXml(serializer);
+        serializer.endTag(null, ATTR_FILTER);
+    }
+
+    @Override
+    public String toString() {
+        return "ForwardingIntentFilter{0x" + Integer.toHexString(System.identityHashCode(this))
+                + " " + Integer.toString(mUserIdDest) + "}";
+    }
+}
diff --git a/services/core/java/com/android/server/pm/ForwardingIntentResolver.java b/services/core/java/com/android/server/pm/ForwardingIntentResolver.java
new file mode 100644
index 0000000..1616395
--- /dev/null
+++ b/services/core/java/com/android/server/pm/ForwardingIntentResolver.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2014, 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.server.pm;
+
+
+import java.io.PrintWriter;
+import com.android.server.IntentResolver;
+import java.util.List;
+
+/**
+ * Used to find a list of {@link ForwardingIntentFilter}s that match an intent.
+ */
+class ForwardingIntentResolver
+        extends IntentResolver<ForwardingIntentFilter, ForwardingIntentFilter> {
+    @Override
+    protected ForwardingIntentFilter[] newArray(int size) {
+        return new ForwardingIntentFilter[size];
+    }
+
+    @Override
+    protected boolean isPackageForFilter(String packageName, ForwardingIntentFilter filter) {
+        return false;
+    }
+
+    @Override
+    protected void sortResults(List<ForwardingIntentFilter> results) {
+        //We don't sort the results
+    }
+}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index d0412ef..87c0935 100755
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -28,6 +28,8 @@
 import static android.system.OsConstants.S_IXGRP;
 import static android.system.OsConstants.S_IROTH;
 import static android.system.OsConstants.S_IXOTH;
+import static com.android.internal.app.IntentForwarderActivity.FORWARD_INTENT_TO_USER_OWNER;
+import static com.android.internal.app.IntentForwarderActivity.FORWARD_INTENT_TO_MANAGED_PROFILE;
 import static com.android.internal.util.ArrayUtils.appendInt;
 import static com.android.internal.util.ArrayUtils.removeInt;
 
@@ -3124,6 +3126,33 @@
         return null;
     }
 
+    /*
+     * Returns if intent can be forwarded from the userId from to dest
+     */
+    @Override
+    public boolean canForwardTo(Intent intent, String resolvedType, int userIdFrom, int userIdDest) {
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
+        List<ForwardingIntentFilter> matches =
+                getMatchingForwardingIntentFilters(intent, resolvedType, userIdFrom);
+        if (matches != null) {
+            int size = matches.size();
+            for (int i = 0; i < size; i++) {
+                if (matches.get(i).getUserIdDest() == userIdDest) return true;
+            }
+        }
+        return false;
+    }
+
+    private List<ForwardingIntentFilter> getMatchingForwardingIntentFilters(Intent intent,
+            String resolvedType, int userId) {
+        ForwardingIntentResolver fir = mSettings.mForwardingIntentResolvers.get(userId);
+        if (fir != null) {
+            return fir.queryIntent(intent, resolvedType, false, userId);
+        }
+        return null;
+    }
+
     @Override
     public List<ResolveInfo> queryIntentActivities(Intent intent,
             String resolvedType, int flags, int userId) {
@@ -3152,7 +3181,38 @@
         synchronized (mPackages) {
             final String pkgName = intent.getPackage();
             if (pkgName == null) {
-                return mActivities.queryIntent(intent, resolvedType, flags, userId);
+                List<ResolveInfo> result =
+                        mActivities.queryIntent(intent, resolvedType, flags, userId);
+                // Checking if we can forward the intent to another user
+                List<ForwardingIntentFilter> fifs =
+                        getMatchingForwardingIntentFilters(intent, resolvedType, userId);
+                if (fifs != null) {
+                    ForwardingIntentFilter forwardingIntentFilterWithResult = null;
+                    HashSet<Integer> alreadyTriedUserIds = new HashSet<Integer>();
+                    for (ForwardingIntentFilter fif : fifs) {
+                        int userIdDest = fif.getUserIdDest();
+                        // Two {@link ForwardingIntentFilter}s can have the same userIdDest and
+                        // match the same an intent. For performance reasons, it is better not to
+                        // run queryIntent twice for the same userId
+                        if (!alreadyTriedUserIds.contains(userIdDest)) {
+                            List<ResolveInfo> resultUser = mActivities.queryIntent(intent,
+                                    resolvedType, flags, userIdDest);
+                            if (resultUser != null) {
+                                forwardingIntentFilterWithResult = fif;
+                                // As soon as there is a match in another user, we add the
+                                // intentForwarderActivity to the list of ResolveInfo.
+                                break;
+                            }
+                            alreadyTriedUserIds.add(userIdDest);
+                        }
+                    }
+                    if (forwardingIntentFilterWithResult != null) {
+                        ResolveInfo forwardingResolveInfo = createForwardingResolveInfo(
+                                forwardingIntentFilterWithResult, userId);
+                        result.add(forwardingResolveInfo);
+                    }
+                }
+                return result;
             }
             final PackageParser.Package pkg = mPackages.get(pkgName);
             if (pkg != null) {
@@ -3163,6 +3223,28 @@
         }
     }
 
+    private ResolveInfo createForwardingResolveInfo(ForwardingIntentFilter fif, int userIdFrom) {
+        String className;
+        int userIdDest = fif.getUserIdDest();
+        if (userIdDest == UserHandle.USER_OWNER) {
+            className = FORWARD_INTENT_TO_USER_OWNER;
+        } else {
+            className = FORWARD_INTENT_TO_MANAGED_PROFILE;
+        }
+        ComponentName forwardingActivityComponentName = new ComponentName(
+                mAndroidApplication.packageName, className);
+        ActivityInfo forwardingActivityInfo = getActivityInfo(forwardingActivityComponentName, 0,
+                userIdFrom);
+        ResolveInfo forwardingResolveInfo = new ResolveInfo();
+        forwardingResolveInfo.activityInfo = forwardingActivityInfo;
+        forwardingResolveInfo.priority = 0;
+        forwardingResolveInfo.preferredOrder = 0;
+        forwardingResolveInfo.match = 0;
+        forwardingResolveInfo.isDefault = true;
+        forwardingResolveInfo.filter = fif;
+        return forwardingResolveInfo;
+    }
+
     @Override
     public List<ResolveInfo> queryIntentActivityOptions(ComponentName caller,
             Intent[] specifics, String[] specificTypes, Intent intent,
@@ -10817,6 +10899,47 @@
         }
     }
 
+    /*
+     * For filters that are added with this method:
+     * if an intent for the user whose id is userIdOrig matches the filter, then this intent can
+     * also be resolved in the user whose id is userIdDest.
+     */
+    @Override
+    public void addForwardingIntentFilter(IntentFilter filter, int userIdOrig, int userIdDest) {
+        int callingUid = Binder.getCallingUid();
+        if (callingUid != Process.SYSTEM_UID) {
+            throw new SecurityException(
+                    "addForwardingIntentFilter can only be run by the system");
+        }
+        if (filter.countActions() == 0) {
+            Slog.w(TAG, "Cannot set a forwarding intent filter with no filter actions");
+            return;
+        }
+        synchronized (mPackages) {
+            mSettings.editForwardingIntentResolverLPw(userIdOrig).addFilter(
+                    new ForwardingIntentFilter(filter, userIdDest));
+            mSettings.writePackageRestrictionsLPr(userIdOrig);
+        }
+    }
+
+    @Override
+    public void clearForwardingIntentFilters(int userIdOrig) {
+        int callingUid = Binder.getCallingUid();
+        if (callingUid != Process.SYSTEM_UID) {
+            throw new SecurityException(
+                    "clearForwardingIntentFilter can only be run by the system");
+        }
+        synchronized (mPackages) {
+            ForwardingIntentResolver fir = mSettings.editForwardingIntentResolverLPw(userIdOrig);
+            HashSet<ForwardingIntentFilter> set =
+                    new HashSet<ForwardingIntentFilter>(fir.filterSet());
+            for (ForwardingIntentFilter fif : set) {
+                fir.removeFilter(fif);
+            }
+            mSettings.writePackageRestrictionsLPr(userIdOrig);
+        }
+    }
+
     @Override
     public ComponentName getHomeActivities(List<ResolveInfo> allHomeCandidates) {
         Intent intent = new Intent(Intent.ACTION_MAIN);
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index e4dd2d4..fca3933 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -130,6 +130,8 @@
     private static final String TAG_PACKAGE = "pkg";
     private static final String TAG_PERSISTENT_PREFERRED_ACTIVITIES =
             "persistent-preferred-activities";
+    static final String TAG_FORWARDING_INTENT_FILTERS =
+            "forwarding-intent-filters";
 
     private static final String ATTR_NAME = "name";
     private static final String ATTR_USER = "user";
@@ -184,6 +186,10 @@
     final SparseArray<PersistentPreferredIntentResolver> mPersistentPreferredActivities =
             new SparseArray<PersistentPreferredIntentResolver>();
 
+    // For every user, it is used to find to which other users the intent can be forwarded.
+    final SparseArray<ForwardingIntentResolver> mForwardingIntentResolvers =
+            new SparseArray<ForwardingIntentResolver>();
+
     final HashMap<String, SharedUserSetting> mSharedUsers =
             new HashMap<String, SharedUserSetting>();
     private final ArrayList<Object> mUserIds = new ArrayList<Object>();
@@ -831,6 +837,15 @@
         return ppir;
     }
 
+    ForwardingIntentResolver editForwardingIntentResolverLPw(int userId) {
+        ForwardingIntentResolver fir = mForwardingIntentResolvers.get(userId);
+        if (fir == null) {
+            fir = new ForwardingIntentResolver();
+            mForwardingIntentResolvers.put(userId, fir);
+        }
+        return fir;
+    }
+
     private File getUserPackagesStateFile(int userId) {
         return new File(Environment.getUserSystemDirectory(userId), "package-restrictions.xml");
     }
@@ -946,6 +961,28 @@
         }
     }
 
+    private void readForwardingIntentFiltersLPw(XmlPullParser parser, int userId)
+            throws XmlPullParserException, IOException {
+        int outerDepth = parser.getDepth();
+        int type;
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                continue;
+            }
+            String tagName = parser.getName();
+            if (tagName.equals(TAG_ITEM)) {
+                ForwardingIntentFilter fif = new ForwardingIntentFilter(parser);
+                editForwardingIntentResolverLPw(userId).addFilter(fif);
+            } else {
+                String msg = "Unknown element under " +  TAG_FORWARDING_INTENT_FILTERS + ": " +
+                        parser.getName();
+                PackageManagerService.reportSettingsProblem(Log.WARN, msg);
+                XmlUtils.skipCurrentTag(parser);
+            }
+        }
+    }
+
     void readPackageRestrictionsLPr(int userId) {
         if (DEBUG_MU) {
             Log.i(TAG, "Reading package restrictions for user=" + userId);
@@ -1074,6 +1111,8 @@
                     readPreferredActivitiesLPw(parser, userId);
                 } else if (tagName.equals(TAG_PERSISTENT_PREFERRED_ACTIVITIES)) {
                     readPersistentPreferredActivitiesLPw(parser, userId);
+                } else if (tagName.equals(TAG_FORWARDING_INTENT_FILTERS)) {
+                    readForwardingIntentFiltersLPw(parser, userId);
                 } else {
                     Slog.w(PackageManagerService.TAG, "Unknown element under <stopped-packages>: "
                           + parser.getName());
@@ -1151,6 +1190,20 @@
         serializer.endTag(null, TAG_PERSISTENT_PREFERRED_ACTIVITIES);
     }
 
+    void writeForwardingIntentFiltersLPr(XmlSerializer serializer, int userId)
+            throws IllegalArgumentException, IllegalStateException, IOException {
+        serializer.startTag(null, TAG_FORWARDING_INTENT_FILTERS);
+        ForwardingIntentResolver fir = mForwardingIntentResolvers.get(userId);
+        if (fir != null) {
+            for (final ForwardingIntentFilter fif : fir.filterSet()) {
+                serializer.startTag(null, TAG_ITEM);
+                fif.writeToXml(serializer);
+                serializer.endTag(null, TAG_ITEM);
+            }
+        }
+        serializer.endTag(null, TAG_FORWARDING_INTENT_FILTERS);
+    }
+
     void writePackageRestrictionsLPr(int userId) {
         if (DEBUG_MU) {
             Log.i(TAG, "Writing package restrictions for user=" + userId);
@@ -1249,6 +1302,8 @@
 
             writePersistentPreferredActivitiesLPr(serializer, userId);
 
+            writeForwardingIntentFiltersLPr(serializer, userId);
+
             serializer.endTag(null, TAG_PACKAGE_RESTRICTIONS);
 
             serializer.endDocument();
@@ -1866,6 +1921,10 @@
                     // TODO: check whether this is okay! as it is very
                     // similar to how preferred-activities are treated
                     readPersistentPreferredActivitiesLPw(parser, 0);
+                } else if (tagName.equals(TAG_FORWARDING_INTENT_FILTERS)) {
+                    // TODO: check whether this is okay! as it is very
+                    // similar to how preferred-activities are treated
+                    readForwardingIntentFiltersLPw(parser, 0);
                 } else if (tagName.equals("updated-package")) {
                     readDisabledSysPackageLPw(parser);
                 } else if (tagName.equals("cleaning-package")) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index f1ee280..8e1f82a 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -3099,6 +3099,51 @@
         }
     }
 
+    public void forwardMatchingIntents(ComponentName who, IntentFilter filter, int flags) {
+        int callingUserId = UserHandle.getCallingUserId();
+        synchronized (this) {
+            if (who == null) {
+                throw new NullPointerException("ComponentName is null");
+            }
+            getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+
+            IPackageManager pm = AppGlobals.getPackageManager();
+            long id = Binder.clearCallingIdentity();
+            try {
+                if ((flags & DevicePolicyManager.FLAG_TO_PRIMARY_USER) != 0) {
+                    pm.addForwardingIntentFilter(filter, callingUserId, UserHandle.USER_OWNER);
+                }
+                if ((flags & DevicePolicyManager.FLAG_TO_MANAGED_PROFILE) != 0) {
+                    pm.addForwardingIntentFilter(filter, UserHandle.USER_OWNER, callingUserId);
+                }
+            } catch (RemoteException re) {
+                // Shouldn't happen
+            } finally {
+                restoreCallingIdentity(id);
+            }
+        }
+    }
+
+    public void clearForwardingIntentFilters(ComponentName who) {
+        int callingUserId = UserHandle.getCallingUserId();
+        synchronized (this) {
+            if (who == null) {
+                throw new NullPointerException("ComponentName is null");
+            }
+            getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+            IPackageManager pm = AppGlobals.getPackageManager();
+            long id = Binder.clearCallingIdentity();
+            try {
+                pm.clearForwardingIntentFilters(callingUserId);
+                pm.clearForwardingIntentFilters(UserHandle.USER_OWNER);
+            } catch (RemoteException re) {
+                // Shouldn't happen
+            } finally {
+                restoreCallingIdentity(id);
+            }
+        }
+    }
+
     @Override
     public Bundle getApplicationRestrictions(ComponentName who, String packageName) {
         final UserHandle userHandle = new UserHandle(UserHandle.getCallingUserId());