Added methods for managing uri query parameters

Change-Id: Ic98c1bd159740dd4d895889079f9f2abae4fc2b9
diff --git a/api/current.xml b/api/current.xml
index c26f589..d261a97 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -105431,6 +105431,17 @@
 <parameter name="key" type="java.lang.String">
 </parameter>
 </method>
+<method name="getQueryParameterNames"
+ return="java.util.Set&lt;java.lang.String&gt;"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
 <method name="getQueryParameters"
  return="java.util.List&lt;java.lang.String&gt;"
  abstract="false"
@@ -105677,6 +105688,17 @@
  visibility="public"
 >
 </method>
+<method name="clearQuery"
+ return="android.net.Uri.Builder"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
 <method name="encodedAuthority"
  return="android.net.Uri.Builder"
  abstract="false"
diff --git a/core/java/android/net/Uri.java b/core/java/android/net/Uri.java
index 6f4144b..3b21590 100644
--- a/core/java/android/net/Uri.java
+++ b/core/java/android/net/Uri.java
@@ -28,8 +28,10 @@
 import java.util.AbstractList;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.RandomAccess;
+import java.util.Set;
 
 /**
  * Immutable URI reference. A URI reference includes a URI and a fragment, the
@@ -47,7 +49,7 @@
     /*
 
     This class aims to do as little up front work as possible. To accomplish
-    that, we vary the implementation dependending on what the user passes in.
+    that, we vary the implementation depending on what the user passes in.
     For example, we have one implementation if the user passes in a
     URI string (StringUri) and another if the user passes in the
     individual components (OpaqueUri).
@@ -1261,6 +1263,8 @@
      *
      * <p>An opaque URI follows this pattern:
      * {@code <scheme>:<opaque part>#<fragment>}
+     * 
+     * <p>Use {@link Uri#buildUpon()} to obtain a builder representing an existing URI.
      */
     public static final class Builder {
 
@@ -1447,6 +1451,13 @@
         }
 
         /**
+         * Clears the the previously set query.
+         */
+        public Builder clearQuery() {
+          return query((Part) null);
+        }
+
+        /**
          * Constructs a Uri with the current attributes.
          *
          * @throws UnsupportedOperationException if the URI is opaque and the
@@ -1491,19 +1502,60 @@
     }
 
     /**
+     * Returns a set of the unique names of all query parameters. Iterating
+     * over the set will return the names in order of their first occurrence.
+     *
+     * @throws UnsupportedOperationException if this isn't a hierarchical URI
+     *
+     * @return a set of decoded names
+     */
+    public Set<String> getQueryParameterNames() {
+        if (isOpaque()) {
+            throw new UnsupportedOperationException(NOT_HIERARCHICAL);
+        }
+
+        String query = getEncodedQuery();
+        if (query == null) {
+            return Collections.emptySet();
+        }
+
+        Set<String> names = new LinkedHashSet<String>();
+        int start = 0;
+        do {
+            int next = query.indexOf('&', start);
+            int end = (next == -1) ? query.length() : next;
+
+            int separator = query.indexOf('=', start);
+            if (separator > end || separator == -1) {
+                separator = end;
+            }
+
+            String name = query.substring(start, separator);
+            names.add(decode(name));
+
+            // Move start to end of name.
+            start = end + 1;
+        } while (start < query.length());
+
+        return Collections.unmodifiableSet(names);
+    }
+
+    /**
      * Searches the query string for parameter values with the given key.
      *
      * @param key which will be encoded
      *
      * @throws UnsupportedOperationException if this isn't a hierarchical URI
      * @throws NullPointerException if key is null
-     *
      * @return a list of decoded values
      */
     public List<String> getQueryParameters(String key) {
         if (isOpaque()) {
             throw new UnsupportedOperationException(NOT_HIERARCHICAL);
         }
+        if (key == null) {
+          throw new NullPointerException("key");
+        }
 
         String query = getEncodedQuery();
         if (query == null) {
@@ -1517,39 +1569,34 @@
             throw new AssertionError(e);
         }
 
-        // Prepend query with "&" making the first parameter the same as the
-        // rest.
-        query = "&" + query;
-
-        // Parameter prefix.
-        String prefix = "&" + encodedKey + "=";
-
         ArrayList<String> values = new ArrayList<String>();
 
         int start = 0;
-        int length = query.length();
-        while (start < length) {
-            start = query.indexOf(prefix, start);
+        do {
+            int nextAmpersand = query.indexOf('&', start);
+            int end = nextAmpersand != -1 ? nextAmpersand : query.length();
 
-            if (start == -1) {
-                // No more values.
+            int separator = query.indexOf('=', start);
+            if (separator > end || separator == -1) {
+                separator = end;
+            }
+
+            if (separator - start == encodedKey.length()
+                    && query.regionMatches(start, encodedKey, 0, encodedKey.length())) {
+                if (separator == end) {
+                  values.add("");
+                } else {
+                  values.add(decode(query.substring(separator + 1, end)));
+                }
+            }
+
+            // Move start to end of name.
+            if (nextAmpersand != -1) {
+                start = nextAmpersand + 1;
+            } else {
                 break;
             }
-
-            // Move start to start of value.
-            start += prefix.length();
-
-            // Find end of value.
-            int end = query.indexOf('&', start);
-            if (end == -1) {
-                end = query.length();
-            }
-
-            String value = query.substring(start, end);
-            values.add(decode(value));
-
-            start = end;
-        }
+        } while (true);
 
         return Collections.unmodifiableList(values);
     }
@@ -1560,7 +1607,6 @@
      * @param key which will be encoded
      * @throws UnsupportedOperationException if this isn't a hierarchical URI
      * @throws NullPointerException if key is null
-     *
      * @return the decoded value or null if no parameter is found
      */
     public String getQueryParameter(String key) {
@@ -1577,34 +1623,33 @@
         }
 
         final String encodedKey = encode(key, null);
-        final int encodedKeyLength = encodedKey.length();
+        final int length = query.length();
+        int start = 0;
+        do {
+            int nextAmpersand = query.indexOf('&', start);
+            int end = nextAmpersand != -1 ? nextAmpersand : length;
 
-        int encodedKeySearchIndex = 0;
-        final int encodedKeySearchEnd = query.length() - (encodedKeyLength + 1);
+            int separator = query.indexOf('=', start);
+            if (separator > end || separator == -1) {
+                separator = end;
+            }
 
-        while (encodedKeySearchIndex <= encodedKeySearchEnd) {
-            int keyIndex = query.indexOf(encodedKey, encodedKeySearchIndex);
-            if (keyIndex == -1) {
-                break;
-            }
-            final int equalsIndex = keyIndex + encodedKeyLength;
-            if (equalsIndex >= query.length()) {
-                break;
-            }
-            if (query.charAt(equalsIndex) != '=') {
-                encodedKeySearchIndex = equalsIndex + 1;
-                continue;
-            }
-            if (keyIndex == 0 || query.charAt(keyIndex - 1) == '&') {
-                int end = query.indexOf('&', equalsIndex);
-                if (end == -1) {
-                    end = query.length();
+            if (separator - start == encodedKey.length()
+                    && query.regionMatches(start, encodedKey, 0, encodedKey.length())) {
+                if (separator == end) {
+                  return "";
+                } else {
+                  return decode(query.substring(separator + 1, end));
                 }
-                return decode(query.substring(equalsIndex + 1, end));
-            } else {
-                encodedKeySearchIndex = equalsIndex + 1;
             }
-        }
+
+            // Move start to end of name.
+            if (nextAmpersand != -1) {
+                start = nextAmpersand + 1;
+            } else {
+                break;
+            }
+        } while (true);
         return null;
     }
 
diff --git a/core/tests/coretests/src/android/net/UriTest.java b/core/tests/coretests/src/android/net/UriTest.java
index a5fda20..fe608b5 100644
--- a/core/tests/coretests/src/android/net/UriTest.java
+++ b/core/tests/coretests/src/android/net/UriTest.java
@@ -24,6 +24,9 @@
 
 import java.io.File;
 import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
 
 public class UriTest extends TestCase {
 
@@ -52,20 +55,20 @@
 
     private void parcelAndUnparcel(Uri u) {
         Parcel p = Parcel.obtain();
-	try {
-		Uri.writeToParcel(p, u);
-		p.setDataPosition(0);
-		assertEquals(u, Uri.CREATOR.createFromParcel(p));
+        try {
+            Uri.writeToParcel(p, u);
+            p.setDataPosition(0);
+            assertEquals(u, Uri.CREATOR.createFromParcel(p));
 
-		p.setDataPosition(0);
-		u = u.buildUpon().build();        
-		Uri.writeToParcel(p, u);
-		p.setDataPosition(0);
-		assertEquals(u, Uri.CREATOR.createFromParcel(p));
-	}
-	finally {
-		p.recycle();
-	}
+            p.setDataPosition(0);
+            u = u.buildUpon().build();
+            Uri.writeToParcel(p, u);
+            p.setDataPosition(0);
+            assertEquals(u, Uri.CREATOR.createFromParcel(p));
+        }
+        finally {
+            p.recycle();
+        }
     }
 
     @SmallTest
@@ -603,4 +606,122 @@
         assertEquals("", uri.getQueryParameter("b"));
         assertEquals("", uri.getQueryParameter("c"));
     }
+
+    public void testGetQueryParameterEmptyKey() {
+        Uri uri = Uri.parse("http://www.google.com/?=b");
+        assertEquals("b", uri.getQueryParameter(""));
+    }
+
+    public void testGetQueryParameterEmptyKey2() {
+      Uri uri = Uri.parse("http://www.google.com/?a=b&&c=d");
+      assertEquals("", uri.getQueryParameter(""));
+    }
+
+    public void testGetQueryParameterEmptyKey3() {
+      Uri uri = Uri.parse("http://www.google.com?");
+      assertEquals("", uri.getQueryParameter(""));
+    }
+
+    public void testGetQueryParameterEmptyKey4() {
+      Uri uri = Uri.parse("http://www.google.com?a=b&");
+      assertEquals("", uri.getQueryParameter(""));
+    }
+
+    public void testGetQueryParametersEmptyKey() {
+        Uri uri = Uri.parse("http://www.google.com/?=b&");
+        List<String> values = uri.getQueryParameters("");
+        assertEquals(2, values.size());
+        assertEquals("b", values.get(0));
+        assertEquals("", values.get(1));
+    }
+
+    public void testGetQueryParametersEmptyKey2() {
+        Uri uri = Uri.parse("http://www.google.com?");
+        List<String> values = uri.getQueryParameters("");
+        assertEquals(1, values.size());
+        assertEquals("", values.get(0));
+    }
+
+    public void testGetQueryParametersEmptyKey3() {
+      Uri uri = Uri.parse("http://www.google.com/?a=b&&c=d");
+      List<String> values = uri.getQueryParameters("");
+      assertEquals(1, values.size());
+      assertEquals("", values.get(0));
+    }
+
+    public void testGetQueryParameterNames() {
+        Uri uri = Uri.parse("http://test?a=1");
+        Set<String> names = uri.getQueryParameterNames();
+        assertEquals(1, names.size());
+        assertEquals("a", names.iterator().next());
+    }
+
+    public void testGetQueryParameterNamesEmptyKey() {
+        Uri uri = Uri.parse("http://www.google.com/?a=x&&c=z");
+        Set<String> names = uri.getQueryParameterNames();
+        Iterator<String> iter = names.iterator();
+        assertEquals(3, names.size());
+        assertEquals("a", iter.next());
+        assertEquals("", iter.next());
+        assertEquals("c", iter.next());
+    }
+
+    public void testGetQueryParameterNamesEmptyKey2() {
+        Uri uri = Uri.parse("http://www.google.com/?a=x&=d&c=z");
+        Set<String> names = uri.getQueryParameterNames();
+        Iterator<String> iter = names.iterator();
+        assertEquals(3, names.size());
+        assertEquals("a", iter.next());
+        assertEquals("", iter.next());
+        assertEquals("c", iter.next());
+    }
+
+    public void testGetQueryParameterNamesEmptyValues() {
+        Uri uri = Uri.parse("http://www.google.com/?a=foo&b=&c=");
+        Set<String> names = uri.getQueryParameterNames();
+        Iterator<String> iter = names.iterator();
+        assertEquals(3, names.size());
+        assertEquals("a", iter.next());
+        assertEquals("b", iter.next());
+        assertEquals("c", iter.next());
+    }
+
+    public void testGetQueryParameterNamesEdgeCases() {
+        Uri uri = Uri.parse("http://foo?a=bar&b=bar&c=&&d=baz&e&f&g=buzz&&&a&b=bar&h");
+        Set<String> names = uri.getQueryParameterNames();
+        Iterator<String> iter = names.iterator();
+        assertEquals(9, names.size());
+        assertEquals("a", iter.next());
+        assertEquals("b", iter.next());
+        assertEquals("c", iter.next());
+        assertEquals("", iter.next());
+        assertEquals("d", iter.next());
+        assertEquals("e", iter.next());
+        assertEquals("f", iter.next());
+        assertEquals("g", iter.next());
+        assertEquals("h", iter.next());
+    }
+
+    public void testGetQueryParameterNamesEscapedKeys() {
+        Uri uri = Uri.parse("http://www.google.com/?a%20b=foo&c%20d=");
+        Set<String> names = uri.getQueryParameterNames();
+        assertEquals(2, names.size());
+        Iterator<String> iter = names.iterator();
+        assertEquals("a b", iter.next());
+        assertEquals("c d", iter.next());
+    }
+
+    public void testGetQueryParameterEscapedKeys() {
+        Uri uri = Uri.parse("http://www.google.com/?a%20b=foo&c%20d=");
+        String value = uri.getQueryParameter("a b");
+        assertEquals("foo", value);
+    }
+    
+    public void testClearQueryParameters() {
+        Uri uri = Uri.parse("http://www.google.com/?a=x&b=y&c=z").buildUpon()
+            .clearQuery().appendQueryParameter("foo", "bar").build();
+        Set<String> names = uri.getQueryParameterNames();
+        assertEquals(1, names.size());
+        assertEquals("foo", names.iterator().next());
+    }
 }