Extend Intent/Uri conversion for use by Browser

This introduces a new Uri form of Intent with an "intent:" scheme, and a
corresponding update to the parser to handle these, so that the browser
can use this generic facility for starting activities based on the links
that are clicked and allow for web pages to link to arbitrary intents.

There is also a new "package" field on Intent which allows you to limit
the components it finds to a given package.  This replaces the new method
that was added to PackageManger for doing this when resolving activities,
and implements it for all Intent queries against the package manager.
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index d4a7815..17fcb91 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -2040,10 +2040,25 @@
     public static final int FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT = 0x20000000;
 
     // ---------------------------------------------------------------------
+    // ---------------------------------------------------------------------
+    // toUri() and parseUri() options.
+
+    /**
+     * Flag for use with {@link #toUri} and {@link #parseUri}: the URI string
+     * always has the "intent:" scheme.  This syntax can be used when you want
+     * to later disambiguate between URIs that are intended to describe an
+     * Intent vs. all others that should be treated as raw URIs.  When used
+     * with {@link #parseUri}, any other scheme will result in a generic
+     * VIEW action for that raw URI.
+     */
+    public static final int URI_INTENT_SCHEME = 1<<0;
+    
+    // ---------------------------------------------------------------------
 
     private String mAction;
     private Uri mData;
     private String mType;
+    private String mPackage;
     private ComponentName mComponent;
     private int mFlags;
     private HashSet<String> mCategories;
@@ -2064,6 +2079,7 @@
         this.mAction = o.mAction;
         this.mData = o.mData;
         this.mType = o.mType;
+        this.mPackage = o.mPackage;
         this.mComponent = o.mComponent;
         this.mFlags = o.mFlags;
         if (o.mCategories != null) {
@@ -2083,6 +2099,7 @@
         this.mAction = o.mAction;
         this.mData = o.mData;
         this.mType = o.mType;
+        this.mPackage = o.mPackage;
         this.mComponent = o.mComponent;
         if (o.mCategories != null) {
             this.mCategories = new HashSet<String>(o.mCategories);
@@ -2183,23 +2200,50 @@
     }
 
     /**
+     * Call {@link #parseUri} with 0 flags.
+     * @deprecated Use {@link #parseUri} instead.
+     */
+    @Deprecated
+    public static Intent getIntent(String uri) throws URISyntaxException {
+        return parseUri(uri, 0);
+    }
+    
+    /**
      * Create an intent from a URI.  This URI may encode the action,
-     * category, and other intent fields, if it was returned by toURI().  If
-     * the Intent was not generate by toURI(), its data will be the entire URI
-     * and its action will be ACTION_VIEW.
+     * category, and other intent fields, if it was returned by
+     * {@link #toUri}..  If the Intent was not generate by toUri(), its data
+     * will be the entire URI and its action will be ACTION_VIEW.
      *
      * <p>The URI given here must not be relative -- that is, it must include
      * the scheme and full path.
      *
      * @param uri The URI to turn into an Intent.
+     * @param flags Additional processing flags.  Either 0 or
      *
      * @return Intent The newly created Intent object.
      *
-     * @see #toURI
+     * @throws URISyntaxException Throws URISyntaxError if the basic URI syntax
+     * it bad (as parsed by the Uri class) or the Intent data within the
+     * URI is invalid.
+     * 
+     * @see #toUri
      */
-    public static Intent getIntent(String uri) throws URISyntaxException {
+    public static Intent parseUri(String uri, int flags) throws URISyntaxException {
         int i = 0;
         try {
+            // Validate intent scheme for if requested.
+            if ((flags&URI_INTENT_SCHEME) != 0) {
+                if (!uri.startsWith("intent:")) {
+                    Intent intent = new Intent(ACTION_VIEW);
+                    try {
+                        intent.setData(Uri.parse(uri));
+                    } catch (IllegalArgumentException e) {
+                        throw new URISyntaxException(uri, e.getMessage());
+                    }
+                    return intent;
+                }
+            }
+            
             // simple case
             i = uri.lastIndexOf("#");
             if (i == -1) return new Intent(ACTION_VIEW, Uri.parse(uri));
@@ -2211,16 +2255,15 @@
             Intent intent = new Intent(ACTION_VIEW);
 
             // fetch data part, if present
-            if (i > 0) {
-                intent.mData = Uri.parse(uri.substring(0, i));
-            }
+            String data = i >= 0 ? uri.substring(0, i) : null;
+            String scheme = null;
             i += "#Intent;".length();
 
             // 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.substring(eq + 1, semi);
+                String value = Uri.decode(uri.substring(eq + 1, semi));
 
                 // action
                 if (uri.startsWith("action=", i)) {
@@ -2242,15 +2285,24 @@
                     intent.mFlags = Integer.decode(value).intValue();
                 }
 
+                // package
+                else if (uri.startsWith("package=", i)) {
+                    intent.mPackage = value;
+                }
+
                 // component
                 else if (uri.startsWith("component=", i)) {
                     intent.mComponent = ComponentName.unflattenFromString(value);
                 }
 
+                // scheme
+                else if (uri.startsWith("scheme=", i)) {
+                    scheme = value;
+                }
+
                 // extra
                 else {
                     String key = Uri.decode(uri.substring(i + 2, eq));
-                    value = Uri.decode(value);
                     // create Bundle if it doesn't already exist
                     if (intent.mExtras == null) intent.mExtras = new Bundle();
                     Bundle b = intent.mExtras;
@@ -2271,6 +2323,23 @@
                 i = semi + 1;
             }
 
+            if (data != null) {
+                if (data.startsWith("intent:")) {
+                    data = data.substring(7);
+                    if (scheme != null) {
+                        data = scheme + ':' + data;
+                    }
+                }
+                
+                if (data.length() > 0) {
+                    try {
+                        intent.mData = Uri.parse(data);
+                    } catch (IllegalArgumentException e) {
+                        throw new URISyntaxException(uri, e.getMessage());
+                    }
+                }
+            }
+            
             return intent;
 
         } catch (IndexOutOfBoundsException e) {
@@ -3084,6 +3153,20 @@
     }
 
     /**
+     * Retrieve the application package name this Intent is limited to.  When
+     * resolving an Intent, if non-null this limits the resolution to only
+     * components in the given application package.
+     *
+     * @return The name of the application package for the Intent.
+     *
+     * @see #resolveActivity
+     * @see #setPackage
+     */
+    public String getPackage() {
+        return mPackage;
+    }
+
+    /**
      * Retrieve the concrete component associated with the intent.  When receiving
      * an intent, this is the component that was found to best handle it (that is,
      * yourself) and will always be non-null; in all other cases it will be
@@ -3118,6 +3201,9 @@
      * <p>If {@link #addCategory} has added any categories, the activity must
      * handle ALL of the categories specified.
      *
+     * <p>If {@link #getPackage} is non-NULL, only activity components in
+     * that application package will be considered.
+     *
      * <p>If there are no activities that satisfy all of these conditions, a
      * null string is returned.
      *
@@ -4089,6 +4175,27 @@
     }
 
     /**
+     * (Usually optional) Set an explicit application package name that limits
+     * the components this Intent will resolve to.  If left to the default
+     * value of null, all components in all applications will considered.
+     * If non-null, the Intent can only match the components in the given
+     * application package.
+     *
+     * @param packageName The name of the application package to handle the
+     * intent, or null to allow any application package.
+     *
+     * @return Returns the same Intent object, for chaining multiple calls
+     * into a single statement.
+     *
+     * @see #getPackage
+     * @see #resolveActivity
+     */
+    public Intent setPackage(String packageName) {
+        mPackage = packageName;
+        return this;
+    }
+
+    /**
      * (Usually optional) Explicitly set the component to handle the intent.
      * If left with the default value of null, the system will determine the
      * appropriate class to use based on the other fields (action, data,
@@ -4200,6 +4307,12 @@
     public static final int FILL_IN_COMPONENT = 1<<3;
 
     /**
+     * Use with {@link #fillIn} to allow the current package value to be
+     * overwritten, even if it is already set.
+     */
+    public static final int FILL_IN_PACKAGE = 1<<4;
+
+    /**
      * 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
@@ -4210,14 +4323,15 @@
      * <li> data URI and MIME type, as set by {@link #setData(Uri)},
      * {@link #setType(String)}, or {@link #setDataAndType(Uri, String)}.
      * <li> categories, as set by {@link #addCategory}.
+     * <li> package, as set by {@link #setPackage}.
      * <li> component, as set by {@link #setComponent(ComponentName)} or
      * related methods.
      * <li> each top-level name in the associated extras.
      * </ul>
      *
      * <p>In addition, you can use the {@link #FILL_IN_ACTION},
-     * {@link #FILL_IN_DATA}, {@link #FILL_IN_CATEGORIES}, and
-     * {@link #FILL_IN_COMPONENT} to override the restriction where the
+     * {@link #FILL_IN_DATA}, {@link #FILL_IN_CATEGORIES}, {@link #FILL_IN_PACKAGE},
+     * and {@link #FILL_IN_COMPONENT} to override the restriction where the
      * corresponding field will not be replaced if it is already set.
      *
      * <p>For example, consider Intent A with {data="foo", categories="bar"}
@@ -4233,32 +4347,39 @@
      * @param flags Options to control which fields can be filled in.
      *
      * @return Returns a bit mask of {@link #FILL_IN_ACTION},
-     * {@link #FILL_IN_DATA}, {@link #FILL_IN_CATEGORIES}, and
-     * {@link #FILL_IN_COMPONENT} indicating which fields were changed.
+     * {@link #FILL_IN_DATA}, {@link #FILL_IN_CATEGORIES}, {@link #FILL_IN_PACKAGE},
+     * and {@link #FILL_IN_COMPONENT} indicating which fields were changed.
      */
     public int fillIn(Intent other, int flags) {
         int changes = 0;
-        if ((mAction == null && other.mAction == null)
-                || (flags&FILL_IN_ACTION) != 0) {
+        if (other.mAction != null
+                && (mAction == null || (flags&FILL_IN_ACTION) != 0)) {
             mAction = other.mAction;
             changes |= FILL_IN_ACTION;
         }
-        if ((mData == null && mType == null &&
-                (other.mData != null || other.mType != null))
-                || (flags&FILL_IN_DATA) != 0) {
+        if ((other.mData != null || other.mType != null)
+                && ((mData == null && mType == null)
+                        || (flags&FILL_IN_DATA) != 0)) {
             mData = other.mData;
             mType = other.mType;
             changes |= FILL_IN_DATA;
         }
-        if ((mCategories == null && other.mCategories == null)
-                || (flags&FILL_IN_CATEGORIES) != 0) {
+        if (other.mCategories != null
+                && (mCategories == null || (flags&FILL_IN_CATEGORIES) != 0)) {
             if (other.mCategories != null) {
                 mCategories = new HashSet<String>(other.mCategories);
             }
             changes |= FILL_IN_CATEGORIES;
         }
-        if ((mComponent == null && other.mComponent == null)
-                || (flags&FILL_IN_COMPONENT) != 0) {
+        if (other.mPackage != null
+                && (mPackage == null || (flags&FILL_IN_PACKAGE) != 0)) {
+            mPackage = other.mPackage;
+            changes |= FILL_IN_PACKAGE;
+        }
+        // Component is special: it can -only- be set if explicitly allowed,
+        // since otherwise the sender could force the intent somewhere the
+        // originator didn't intend.
+        if (other.mComponent != null && (flags&FILL_IN_COMPONENT) != 0) {
             mComponent = other.mComponent;
             changes |= FILL_IN_COMPONENT;
         }
@@ -4373,6 +4494,17 @@
                 }
             }
         }
+        if (mPackage != other.mPackage) {
+            if (mPackage != null) {
+                if (!mPackage.equals(other.mPackage)) {
+                    return false;
+                }
+            } else {
+                if (!other.mPackage.equals(mPackage)) {
+                    return false;
+                }
+            }
+        }
         if (mComponent != other.mComponent) {
             if (mComponent != null) {
                 if (!mComponent.equals(other.mComponent)) {
@@ -4418,6 +4550,9 @@
         if (mType != null) {
             code += mType.hashCode();
         }
+        if (mPackage != null) {
+            code += mPackage.hashCode();
+        }
         if (mComponent != null) {
             code += mComponent.hashCode();
         }
@@ -4488,6 +4623,13 @@
             first = false;
             b.append("flg=0x").append(Integer.toHexString(mFlags));
         }
+        if (mPackage != null) {
+            if (!first) {
+                b.append(' ');
+            }
+            first = false;
+            b.append("pkg=").append(mPackage);
+        }
         if (comp && mComponent != null) {
             if (!first) {
                 b.append(' ');
@@ -4504,28 +4646,87 @@
         }
     }
 
+    /**
+     * Call {@link #toUri} with 0 flags.
+     * @deprecated Use {@link #toUri} instead.
+     */
+    @Deprecated
     public String toURI() {
+        return toUri(0);
+    }
+
+    /**
+     * Convert this Intent into a String holding a URI representation of it.
+     * The returned URI string has been properly URI encoded, so it can be
+     * used with {@link Uri#parse Uri.parse(String)}.  The URI contains the
+     * Intent's data as the base URI, with an additional fragment describing
+     * the action, categories, type, flags, package, component, and extras.
+     * 
+     * <p>You can convert the returned string back to an Intent with
+     * {@link #getIntent}.
+     * 
+     * @param flags Additional operating flags.  Either 0 or
+     * {@link #URI_INTENT_SCHEME}.
+     * 
+     * @return Returns a URI encoding URI string describing the entire contents
+     * of the Intent.
+     */
+    public String toUri(int flags) {
         StringBuilder uri = new StringBuilder(128);
-        if (mData != null) uri.append(mData.toString());
+        String scheme = null;
+        if (mData != null) {
+            String data = mData.toString();
+            if ((flags&URI_INTENT_SCHEME) != 0) {
+                final int N = data.length();
+                for (int i=0; i<N; i++) {
+                    char c = data.charAt(i);
+                    if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
+                            || c == '.' || c == '-') {
+                        continue;
+                    }
+                    if (c == ':' && i > 0) {
+                        // Valid scheme.
+                        scheme = data.substring(0, i);
+                        uri.append("intent:");
+                        data = data.substring(i+1);
+                        break;
+                    }
+                    
+                    // No scheme.
+                    break;
+                }
+            }
+            uri.append(data);
+            
+        } else if ((flags&URI_INTENT_SCHEME) != 0) {
+            uri.append("intent:");
+        }
 
         uri.append("#Intent;");
 
+        if (scheme != null) {
+            uri.append("scheme=").append(scheme).append(';');
+        }
         if (mAction != null) {
-            uri.append("action=").append(mAction).append(';');
+            uri.append("action=").append(Uri.encode(mAction)).append(';');
         }
         if (mCategories != null) {
             for (String category : mCategories) {
-                uri.append("category=").append(category).append(';');
+                uri.append("category=").append(Uri.encode(category)).append(';');
             }
         }
         if (mType != null) {
-            uri.append("type=").append(mType).append(';');
+            uri.append("type=").append(Uri.encode(mType, "/")).append(';');
         }
         if (mFlags != 0) {
             uri.append("launchFlags=0x").append(Integer.toHexString(mFlags)).append(';');
         }
+        if (mPackage != null) {
+            uri.append("package=").append(Uri.encode(mPackage)).append(';');
+        }
         if (mComponent != null) {
-            uri.append("component=").append(mComponent.flattenToShortString()).append(';');
+            uri.append("component=").append(Uri.encode(
+                    mComponent.flattenToShortString(), "/")).append(';');
         }
         if (mExtras != null) {
             for (String key : mExtras.keySet()) {
@@ -4567,6 +4768,7 @@
         Uri.writeToParcel(out, mData);
         out.writeString(mType);
         out.writeInt(mFlags);
+        out.writeString(mPackage);
         ComponentName.writeToParcel(mComponent, out);
 
         if (mCategories != null) {
@@ -4600,6 +4802,7 @@
         mData = Uri.CREATOR.createFromParcel(in);
         mType = in.readString();
         mFlags = in.readInt();
+        mPackage = in.readString();
         mComponent = ComponentName.readFromParcel(in);
 
         int N = in.readInt();