Fix issue #5714517: App shortcuts can result in bad task intents

New API to let you build an Intent whose base configuration is correct,
but has an additional "selector" to pick out the specific app that you
would like launched.

Change-Id: Ide9db6dc60e2844b7696cfe09b28337fe7dd63db
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 9948985..4e5598b 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -2315,6 +2315,11 @@
     /**
      * Used with {@link #ACTION_MAIN} to launch the browser application.
      * The activity should be able to browse the Internet.
+     * <p>NOTE: This should not be used as the primary key of an Intent,
+     * since it will not result in the app launching with the correct
+     * action and category.  Instead, use this with
+     * {@link #makeMainSelectorActivity(String, String) to generate a main
+     * Intent with this category in the selector.</p>
      */
     @SdkConstant(SdkConstantType.INTENT_CATEGORY)
     public static final String CATEGORY_APP_BROWSER = "android.intent.category.APP_BROWSER";
@@ -2322,6 +2327,11 @@
     /**
      * Used with {@link #ACTION_MAIN} to launch the calculator application.
      * The activity should be able to perform standard arithmetic operations.
+     * <p>NOTE: This should not be used as the primary key of an Intent,
+     * since it will not result in the app launching with the correct
+     * action and category.  Instead, use this with
+     * {@link #makeMainSelectorActivity(String, String) to generate a main
+     * Intent with this category in the selector.</p>
      */
     @SdkConstant(SdkConstantType.INTENT_CATEGORY)
     public static final String CATEGORY_APP_CALCULATOR = "android.intent.category.APP_CALCULATOR";
@@ -2329,6 +2339,11 @@
     /**
      * Used with {@link #ACTION_MAIN} to launch the calendar application.
      * The activity should be able to view and manipulate calendar entries.
+     * <p>NOTE: This should not be used as the primary key of an Intent,
+     * since it will not result in the app launching with the correct
+     * action and category.  Instead, use this with
+     * {@link #makeMainSelectorActivity(String, String) to generate a main
+     * Intent with this category in the selector.</p>
      */
     @SdkConstant(SdkConstantType.INTENT_CATEGORY)
     public static final String CATEGORY_APP_CALENDAR = "android.intent.category.APP_CALENDAR";
@@ -2336,6 +2351,11 @@
     /**
      * Used with {@link #ACTION_MAIN} to launch the contacts application.
      * The activity should be able to view and manipulate address book entries.
+     * <p>NOTE: This should not be used as the primary key of an Intent,
+     * since it will not result in the app launching with the correct
+     * action and category.  Instead, use this with
+     * {@link #makeMainSelectorActivity(String, String) to generate a main
+     * Intent with this category in the selector.</p>
      */
     @SdkConstant(SdkConstantType.INTENT_CATEGORY)
     public static final String CATEGORY_APP_CONTACTS = "android.intent.category.APP_CONTACTS";
@@ -2343,6 +2363,11 @@
     /**
      * Used with {@link #ACTION_MAIN} to launch the email application.
      * The activity should be able to send and receive email.
+     * <p>NOTE: This should not be used as the primary key of an Intent,
+     * since it will not result in the app launching with the correct
+     * action and category.  Instead, use this with
+     * {@link #makeMainSelectorActivity(String, String) to generate a main
+     * Intent with this category in the selector.</p>
      */
     @SdkConstant(SdkConstantType.INTENT_CATEGORY)
     public static final String CATEGORY_APP_EMAIL = "android.intent.category.APP_EMAIL";
@@ -2351,6 +2376,11 @@
      * Used with {@link #ACTION_MAIN} to launch the gallery application.
      * The activity should be able to view and manipulate image and video files
      * stored on the device.
+     * <p>NOTE: This should not be used as the primary key of an Intent,
+     * since it will not result in the app launching with the correct
+     * action and category.  Instead, use this with
+     * {@link #makeMainSelectorActivity(String, String) to generate a main
+     * Intent with this category in the selector.</p>
      */
     @SdkConstant(SdkConstantType.INTENT_CATEGORY)
     public static final String CATEGORY_APP_GALLERY = "android.intent.category.APP_GALLERY";
@@ -2358,6 +2388,11 @@
     /**
      * Used with {@link #ACTION_MAIN} to launch the maps application.
      * The activity should be able to show the user's current location and surroundings.
+     * <p>NOTE: This should not be used as the primary key of an Intent,
+     * since it will not result in the app launching with the correct
+     * action and category.  Instead, use this with
+     * {@link #makeMainSelectorActivity(String, String) to generate a main
+     * Intent with this category in the selector.</p>
      */
     @SdkConstant(SdkConstantType.INTENT_CATEGORY)
     public static final String CATEGORY_APP_MAPS = "android.intent.category.APP_MAPS";
@@ -2365,13 +2400,24 @@
     /**
      * Used with {@link #ACTION_MAIN} to launch the messaging application.
      * The activity should be able to send and receive text messages.
+     * <p>NOTE: This should not be used as the primary key of an Intent,
+     * since it will not result in the app launching with the correct
+     * action and category.  Instead, use this with
+     * {@link #makeMainSelectorActivity(String, String) to generate a main
+     * Intent with this category in the selector.</p>
      */
     @SdkConstant(SdkConstantType.INTENT_CATEGORY)
     public static final String CATEGORY_APP_MESSAGING = "android.intent.category.APP_MESSAGING";
 
     /**
      * Used with {@link #ACTION_MAIN} to launch the music application.
-     * The activity should be able to play, browse, or manipulate music files stored on the device.
+     * The activity should be able to play, browse, or manipulate music files
+     * stored on the device.
+     * <p>NOTE: This should not be used as the primary key of an Intent,
+     * since it will not result in the app launching with the correct
+     * action and category.  Instead, use this with
+     * {@link #makeMainSelectorActivity(String, String) to generate a main
+     * Intent with this category in the selector.</p>
      */
     @SdkConstant(SdkConstantType.INTENT_CATEGORY)
     public static final String CATEGORY_APP_MUSIC = "android.intent.category.APP_MUSIC";
@@ -2963,6 +3009,7 @@
     private HashSet<String> mCategories;
     private Bundle mExtras;
     private Rect mSourceBounds;
+    private Intent mSelector;
 
     // ---------------------------------------------------------------------
 
@@ -2991,6 +3038,9 @@
         if (o.mSourceBounds != null) {
             this.mSourceBounds = new Rect(o.mSourceBounds);
         }
+        if (o.mSelector != null) {
+            this.mSelector = new Intent(o.mSelector);
+        }
     }
 
     @Override
@@ -3131,6 +3181,39 @@
     }
 
     /**
+     * Make an Intent for the main activity of an application, without
+     * specifying a specific activity to run but giving a selector to find
+     * the activity.  This results in a final Intent that is structured
+     * the same as when the application is launched from
+     * Home.  For anything else that wants to launch an application in the
+     * same way, it is important that they use an Intent structured the same
+     * way, and can use this function to ensure this is the case.
+     *
+     * <p>The returned Intent has {@link #ACTION_MAIN} as its action, and includes the
+     * category {@link #CATEGORY_LAUNCHER}.  This does <em>not</em> have
+     * {@link #FLAG_ACTIVITY_NEW_TASK} set, though typically you will want
+     * to do that through {@link #addFlags(int)} on the returned Intent.
+     *
+     * @param selectorAction The action name of the Intent's selector.
+     * @param selectorCategory The name of a category to add to the Intent's
+     * selector.
+     * @return Returns a newly created Intent that can be used to launch the
+     * activity as a main application entry.
+     *
+     * @see #setSelector(Intent)
+     */
+    public static Intent makeMainSelectorActivity(String selectorAction,
+            String selectorCategory) {
+        Intent intent = new Intent(ACTION_MAIN);
+        intent.addCategory(CATEGORY_LAUNCHER);
+        Intent selector = new Intent();
+        selector.setAction(selectorAction);
+        selector.addCategory(selectorCategory);
+        intent.setSelector(selector);
+        return intent;
+    }
+
+    /**
      * Make an Intent that can be used to re-launch an application's task
      * in its base state.  This is like {@link #makeMainActivity(ComponentName)},
      * but also sets the flags {@link #FLAG_ACTIVITY_NEW_TASK} and
@@ -3205,6 +3288,7 @@
 
             // new format
             Intent intent = new Intent(ACTION_VIEW);
+            Intent baseIntent = intent;
 
             // fetch data part, if present
             String data = i >= 0 ? uri.substring(0, i) : null;
@@ -3214,8 +3298,9 @@
             // loop over contents of Intent, all name=value;
             while (!uri.startsWith("end", i)) {
                 int eq = uri.indexOf('=', i);
-                int semi = uri.indexOf(';', eq);
-                String value = Uri.decode(uri.substring(eq + 1, semi));
+                if (eq < 0) eq = i-1;
+                int semi = uri.indexOf(';', i);
+                String value = eq < semi ? Uri.decode(uri.substring(eq + 1, semi)) : "";
 
                 // action
                 if (uri.startsWith("action=", i)) {
@@ -3257,6 +3342,11 @@
                     intent.mSourceBounds = Rect.unflattenFromString(value);
                 }
 
+                // selector
+                else if (semi == (i+3) && uri.startsWith("SEL", i)) {
+                    intent = new Intent();
+                }
+
                 // extra
                 else {
                     String key = Uri.decode(uri.substring(i + 2, eq));
@@ -3280,6 +3370,12 @@
                 i = semi + 1;
             }
 
+            if (intent != baseIntent) {
+                // The Intent had a selector; fix it up.
+                baseIntent.setSelector(intent);
+                intent = baseIntent;
+            }
+
             if (data != null) {
                 if (data.startsWith("intent:")) {
                     data = data.substring(7);
@@ -3605,7 +3701,7 @@
      * Return the set of all categories in the intent.  If there are no categories,
      * returns NULL.
      *
-     * @return Set The set of categories you can examine.  Do not modify!
+     * @return The set of categories you can examine.  Do not modify!
      *
      * @see #hasCategory
      * @see #addCategory
@@ -3615,6 +3711,16 @@
     }
 
     /**
+     * Return the specific selector associated with this Intent.  If there is
+     * none, returns null.  See {@link #setSelector} for more information.
+     *
+     * @see #setSelector
+     */
+    public Intent getSelector() {
+        return mSelector;
+    }
+
+    /**
      * Sets the ClassLoader that will be used when unmarshalling
      * any Parcelable values from the extras of this Intent.
      *
@@ -4433,6 +4539,49 @@
     }
 
     /**
+     * Set a selector for this Intent.  This is a modification to the kinds of
+     * things the Intent will match.  If the selector is set, it will be used
+     * when trying to find entities that can handle the Intent, instead of the
+     * main contents of the Intent.  This allows you build an Intent containing
+     * a generic protocol while targeting it more specifically.
+     *
+     * <p>An example of where this may be used is with things like
+     * {@link #CATEGORY_APP_BROWSER}.  This category allows you to build an
+     * Intent that will launch the Browser application.  However, the correct
+     * main entry point of an application is actually {@link #ACTION_MAIN}
+     * {@link #CATEGORY_LAUNCHER} with {@link #setComponent(ComponentName)}
+     * used to specify the actual Activity to launch.  If you launch the browser
+     * with something different, undesired behavior may happen if the user has
+     * previously or later launches it the normal way, since they do not match.
+     * Instead, you can build an Intent with the MAIN action (but no ComponentName
+     * yet specified) and set a selector with {@link #ACTION_MAIN} and
+     * {@link #CATEGORY_APP_BROWSER} to point it specifically to the browser activity.
+     *
+     * <p>Setting a selector does not impact the behavior of
+     * {@link #filterEquals(Intent)} and {@link #filterHashCode()}.  This is part of the
+     * desired behavior of a selector -- it does not impact the base meaning
+     * of the Intent, just what kinds of things will be matched against it
+     * when determining who can handle it.</p>
+     *
+     * <p>You can not use both a selector and {@link #setPackage(String)} on
+     * the same base Intent.</p>
+     *
+     * @param selector The desired selector Intent; set to null to not use
+     * a special selector.
+     */
+    public void setSelector(Intent selector) {
+        if (selector == this) {
+            throw new IllegalArgumentException(
+                    "Intent being set as a selector of itself");
+        }
+        if (selector != null && mPackage != null) {
+            throw new IllegalArgumentException(
+                    "Can't set selector when package name is already set");
+        }
+        mSelector = selector;
+    }
+
+    /**
      * Add extended data to the intent.  The name must include a package
      * prefix, for example the app com.android.contacts would use names
      * like "com.android.contacts.ShowAll".
@@ -5259,6 +5408,10 @@
      * @see #resolveActivity
      */
     public Intent setPackage(String packageName) {
+        if (packageName != null && mSelector != null) {
+            throw new IllegalArgumentException(
+                    "Can't set package name when selector is already set");
+        }
         mPackage = packageName;
         return this;
     }
@@ -5394,12 +5547,18 @@
     public static final int FILL_IN_PACKAGE = 1<<4;
 
     /**
-     * Use with {@link #fillIn} to allow the current package value to be
+     * Use with {@link #fillIn} to allow the current bounds rectangle to be
      * overwritten, even if it is already set.
      */
     public static final int FILL_IN_SOURCE_BOUNDS = 1<<5;
 
     /**
+     * Use with {@link #fillIn} to allow the current selector to be
+     * overwritten, even if it is already set.
+     */
+    public static final int FILL_IN_SELECTOR = 1<<6;
+
+    /**
      * Copy the contents of <var>other</var> in to this object, but only
      * where fields are not defined by this object.  For purposes of a field
      * being defined, the following pieces of data in the Intent are
@@ -5419,11 +5578,13 @@
      *
      * <p>In addition, you can use the {@link #FILL_IN_ACTION},
      * {@link #FILL_IN_DATA}, {@link #FILL_IN_CATEGORIES}, {@link #FILL_IN_PACKAGE},
-     * and {@link #FILL_IN_COMPONENT} to override the restriction where the
+     * {@link #FILL_IN_COMPONENT}, {@link #FILL_IN_SOURCE_BOUNDS}, and
+     * {@link #FILL_IN_SELECTOR} to override the restriction where the
      * corresponding field will not be replaced if it is already set.
      *
      * <p>Note: The component field will only be copied if {@link #FILL_IN_COMPONENT} is explicitly
-     * specified.
+     * specified.  The selector will only be copied if {@link #FILL_IN_SELECTOR} is
+     * explicitly specified.
      *
      * <p>For example, consider Intent A with {data="foo", categories="bar"}
      * and Intent B with {action="gotit", data-type="some/thing",
@@ -5439,7 +5600,8 @@
      *
      * @return Returns a bit mask of {@link #FILL_IN_ACTION},
      * {@link #FILL_IN_DATA}, {@link #FILL_IN_CATEGORIES}, {@link #FILL_IN_PACKAGE},
-     * and {@link #FILL_IN_COMPONENT} indicating which fields were changed.
+     * {@link #FILL_IN_COMPONENT}, {@link #FILL_IN_SOURCE_BOUNDS}, and
+     * {@link #FILL_IN_SELECTOR} indicating which fields were changed.
      */
     public int fillIn(Intent other, int flags) {
         int changes = 0;
@@ -5464,8 +5626,20 @@
         }
         if (other.mPackage != null
                 && (mPackage == null || (flags&FILL_IN_PACKAGE) != 0)) {
-            mPackage = other.mPackage;
-            changes |= FILL_IN_PACKAGE;
+            // Only do this if mSelector is not set.
+            if (mSelector == null) {
+                mPackage = other.mPackage;
+                changes |= FILL_IN_PACKAGE;
+            }
+        }
+        // Selector is special: it can only be set if explicitly allowed,
+        // for the same reason as the component name.
+        if (other.mSelector != null && (flags&FILL_IN_SELECTOR) != 0) {
+            if (mPackage == null) {
+                mSelector = new Intent(other.mSelector);
+                mPackage = null;
+                changes |= FILL_IN_SELECTOR;
+            }
         }
         // Component is special: it can -only- be set if explicitly allowed,
         // since otherwise the sender could force the intent somewhere the
@@ -5763,6 +5937,11 @@
             first = false;
             b.append("(has extras)");
         }
+        if (mSelector != null) {
+            b.append(" sel={");
+            mSelector.toShortString(b, secure, comp, extras);
+            b.append("}");
+        }
     }
 
     /**
@@ -5823,6 +6002,21 @@
 
         uri.append("#Intent;");
 
+        toUriInner(uri, scheme, flags);
+        if (mSelector != null) {
+            uri.append("SEL;");
+            // Note that for now we are not going to try to handle the
+            // data part; not clear how to represent this as a URI, and
+            // not much utility in it.
+            mSelector.toUriInner(uri, null, flags);
+        }
+
+        uri.append("end");
+
+        return uri.toString();
+    }
+
+    private void toUriInner(StringBuilder uri, String scheme, int flags) {
         if (scheme != null) {
             uri.append("scheme=").append(scheme).append(';');
         }
@@ -5877,10 +6071,6 @@
                 }
             }
         }
-
-        uri.append("end");
-
-        return uri.toString();
     }
 
     public int describeContents() {
@@ -5911,6 +6101,13 @@
             out.writeInt(0);
         }
 
+        if (mSelector != null) {
+            out.writeInt(1);
+            mSelector.writeToParcel(out, flags);
+        } else {
+            out.writeInt(0);
+        }
+
         out.writeBundle(mExtras);
     }
 
@@ -5952,6 +6149,10 @@
             mCategories = null;
         }
 
+        if (in.readInt() != 0) {
+            mSelector = new Intent(in);
+        }
+
         mExtras = in.readBundle();
     }