• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2007 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.net;
18 
19 import android.os.Parcel;
20 import android.os.Parcelable;
21 import android.util.Log;
22 import java.io.File;
23 import java.io.UnsupportedEncodingException;
24 import java.net.URLEncoder;
25 import java.nio.charset.Charsets;
26 import java.util.AbstractList;
27 import java.util.ArrayList;
28 import java.util.Collections;
29 import java.util.LinkedHashSet;
30 import java.util.List;
31 import java.util.Locale;
32 import java.util.RandomAccess;
33 import java.util.Set;
34 import libcore.net.UriCodec;
35 
36 /**
37  * Immutable URI reference. A URI reference includes a URI and a fragment, the
38  * component of the URI following a '#'. Builds and parses URI references
39  * which conform to
40  * <a href="http://www.faqs.org/rfcs/rfc2396.html">RFC 2396</a>.
41  *
42  * <p>In the interest of performance, this class performs little to no
43  * validation. Behavior is undefined for invalid input. This class is very
44  * forgiving--in the face of invalid input, it will return garbage
45  * rather than throw an exception unless otherwise specified.
46  */
47 public abstract class Uri implements Parcelable, Comparable<Uri> {
48 
49     /*
50 
51     This class aims to do as little up front work as possible. To accomplish
52     that, we vary the implementation depending on what the user passes in.
53     For example, we have one implementation if the user passes in a
54     URI string (StringUri) and another if the user passes in the
55     individual components (OpaqueUri).
56 
57     *Concurrency notes*: Like any truly immutable object, this class is safe
58     for concurrent use. This class uses a caching pattern in some places where
59     it doesn't use volatile or synchronized. This is safe to do with ints
60     because getting or setting an int is atomic. It's safe to do with a String
61     because the internal fields are final and the memory model guarantees other
62     threads won't see a partially initialized instance. We are not guaranteed
63     that some threads will immediately see changes from other threads on
64     certain platforms, but we don't mind if those threads reconstruct the
65     cached result. As a result, we get thread safe caching with no concurrency
66     overhead, which means the most common case, access from a single thread,
67     is as fast as possible.
68 
69     From the Java Language spec.:
70 
71     "17.5 Final Field Semantics
72 
73     ... when the object is seen by another thread, that thread will always
74     see the correctly constructed version of that object's final fields.
75     It will also see versions of any object or array referenced by
76     those final fields that are at least as up-to-date as the final fields
77     are."
78 
79     In that same vein, all non-transient fields within Uri
80     implementations should be final and immutable so as to ensure true
81     immutability for clients even when they don't use proper concurrency
82     control.
83 
84     For reference, from RFC 2396:
85 
86     "4.3. Parsing a URI Reference
87 
88        A URI reference is typically parsed according to the four main
89        components and fragment identifier in order to determine what
90        components are present and whether the reference is relative or
91        absolute.  The individual components are then parsed for their
92        subparts and, if not opaque, to verify their validity.
93 
94        Although the BNF defines what is allowed in each component, it is
95        ambiguous in terms of differentiating between an authority component
96        and a path component that begins with two slash characters.  The
97        greedy algorithm is used for disambiguation: the left-most matching
98        rule soaks up as much of the URI reference string as it is capable of
99        matching.  In other words, the authority component wins."
100 
101     The "four main components" of a hierarchical URI consist of
102     <scheme>://<authority><path>?<query>
103 
104     */
105 
106     /** Log tag. */
107     private static final String LOG = Uri.class.getSimpleName();
108 
109     /**
110      * NOTE: EMPTY accesses this field during its own initialization, so this
111      * field *must* be initialized first, or else EMPTY will see a null value!
112      *
113      * Placeholder for strings which haven't been cached. This enables us
114      * to cache null. We intentionally create a new String instance so we can
115      * compare its identity and there is no chance we will confuse it with
116      * user data.
117      */
118     @SuppressWarnings("RedundantStringConstructorCall")
119     private static final String NOT_CACHED = new String("NOT CACHED");
120 
121     /**
122      * The empty URI, equivalent to "".
123      */
124     public static final Uri EMPTY = new HierarchicalUri(null, Part.NULL,
125             PathPart.EMPTY, Part.NULL, Part.NULL);
126 
127     /**
128      * Prevents external subclassing.
129      */
Uri()130     private Uri() {}
131 
132     /**
133      * Returns true if this URI is hierarchical like "http://google.com".
134      * Absolute URIs are hierarchical if the scheme-specific part starts with
135      * a '/'. Relative URIs are always hierarchical.
136      */
isHierarchical()137     public abstract boolean isHierarchical();
138 
139     /**
140      * Returns true if this URI is opaque like "mailto:nobody@google.com". The
141      * scheme-specific part of an opaque URI cannot start with a '/'.
142      */
isOpaque()143     public boolean isOpaque() {
144         return !isHierarchical();
145     }
146 
147     /**
148      * Returns true if this URI is relative, i.e. if it doesn't contain an
149      * explicit scheme.
150      *
151      * @return true if this URI is relative, false if it's absolute
152      */
isRelative()153     public abstract boolean isRelative();
154 
155     /**
156      * Returns true if this URI is absolute, i.e. if it contains an
157      * explicit scheme.
158      *
159      * @return true if this URI is absolute, false if it's relative
160      */
isAbsolute()161     public boolean isAbsolute() {
162         return !isRelative();
163     }
164 
165     /**
166      * Gets the scheme of this URI. Example: "http"
167      *
168      * @return the scheme or null if this is a relative URI
169      */
getScheme()170     public abstract String getScheme();
171 
172     /**
173      * Gets the scheme-specific part of this URI, i.e. everything between the
174      * scheme separator ':' and the fragment separator '#'. If this is a
175      * relative URI, this method returns the entire URI. Decodes escaped octets.
176      *
177      * <p>Example: "//www.google.com/search?q=android"
178      *
179      * @return the decoded scheme-specific-part
180      */
getSchemeSpecificPart()181     public abstract String getSchemeSpecificPart();
182 
183     /**
184      * Gets the scheme-specific part of this URI, i.e. everything between the
185      * scheme separator ':' and the fragment separator '#'. If this is a
186      * relative URI, this method returns the entire URI. Leaves escaped octets
187      * intact.
188      *
189      * <p>Example: "//www.google.com/search?q=android"
190      *
191      * @return the decoded scheme-specific-part
192      */
getEncodedSchemeSpecificPart()193     public abstract String getEncodedSchemeSpecificPart();
194 
195     /**
196      * Gets the decoded authority part of this URI. For
197      * server addresses, the authority is structured as follows:
198      * {@code [ userinfo '@' ] host [ ':' port ]}
199      *
200      * <p>Examples: "google.com", "bob@google.com:80"
201      *
202      * @return the authority for this URI or null if not present
203      */
getAuthority()204     public abstract String getAuthority();
205 
206     /**
207      * Gets the encoded authority part of this URI. For
208      * server addresses, the authority is structured as follows:
209      * {@code [ userinfo '@' ] host [ ':' port ]}
210      *
211      * <p>Examples: "google.com", "bob@google.com:80"
212      *
213      * @return the authority for this URI or null if not present
214      */
getEncodedAuthority()215     public abstract String getEncodedAuthority();
216 
217     /**
218      * Gets the decoded user information from the authority.
219      * For example, if the authority is "nobody@google.com", this method will
220      * return "nobody".
221      *
222      * @return the user info for this URI or null if not present
223      */
getUserInfo()224     public abstract String getUserInfo();
225 
226     /**
227      * Gets the encoded user information from the authority.
228      * For example, if the authority is "nobody@google.com", this method will
229      * return "nobody".
230      *
231      * @return the user info for this URI or null if not present
232      */
getEncodedUserInfo()233     public abstract String getEncodedUserInfo();
234 
235     /**
236      * Gets the encoded host from the authority for this URI. For example,
237      * if the authority is "bob@google.com", this method will return
238      * "google.com".
239      *
240      * @return the host for this URI or null if not present
241      */
getHost()242     public abstract String getHost();
243 
244     /**
245      * Gets the port from the authority for this URI. For example,
246      * if the authority is "google.com:80", this method will return 80.
247      *
248      * @return the port for this URI or -1 if invalid or not present
249      */
getPort()250     public abstract int getPort();
251 
252     /**
253      * Gets the decoded path.
254      *
255      * @return the decoded path, or null if this is not a hierarchical URI
256      * (like "mailto:nobody@google.com") or the URI is invalid
257      */
getPath()258     public abstract String getPath();
259 
260     /**
261      * Gets the encoded path.
262      *
263      * @return the encoded path, or null if this is not a hierarchical URI
264      * (like "mailto:nobody@google.com") or the URI is invalid
265      */
getEncodedPath()266     public abstract String getEncodedPath();
267 
268     /**
269      * Gets the decoded query component from this URI. The query comes after
270      * the query separator ('?') and before the fragment separator ('#'). This
271      * method would return "q=android" for
272      * "http://www.google.com/search?q=android".
273      *
274      * @return the decoded query or null if there isn't one
275      */
getQuery()276     public abstract String getQuery();
277 
278     /**
279      * Gets the encoded query component from this URI. The query comes after
280      * the query separator ('?') and before the fragment separator ('#'). This
281      * method would return "q=android" for
282      * "http://www.google.com/search?q=android".
283      *
284      * @return the encoded query or null if there isn't one
285      */
getEncodedQuery()286     public abstract String getEncodedQuery();
287 
288     /**
289      * Gets the decoded fragment part of this URI, everything after the '#'.
290      *
291      * @return the decoded fragment or null if there isn't one
292      */
getFragment()293     public abstract String getFragment();
294 
295     /**
296      * Gets the encoded fragment part of this URI, everything after the '#'.
297      *
298      * @return the encoded fragment or null if there isn't one
299      */
getEncodedFragment()300     public abstract String getEncodedFragment();
301 
302     /**
303      * Gets the decoded path segments.
304      *
305      * @return decoded path segments, each without a leading or trailing '/'
306      */
getPathSegments()307     public abstract List<String> getPathSegments();
308 
309     /**
310      * Gets the decoded last segment in the path.
311      *
312      * @return the decoded last segment or null if the path is empty
313      */
getLastPathSegment()314     public abstract String getLastPathSegment();
315 
316     /**
317      * Compares this Uri to another object for equality. Returns true if the
318      * encoded string representations of this Uri and the given Uri are
319      * equal. Case counts. Paths are not normalized. If one Uri specifies a
320      * default port explicitly and the other leaves it implicit, they will not
321      * be considered equal.
322      */
equals(Object o)323     public boolean equals(Object o) {
324         if (!(o instanceof Uri)) {
325             return false;
326         }
327 
328         Uri other = (Uri) o;
329 
330         return toString().equals(other.toString());
331     }
332 
333     /**
334      * Hashes the encoded string represention of this Uri consistently with
335      * {@link #equals(Object)}.
336      */
hashCode()337     public int hashCode() {
338         return toString().hashCode();
339     }
340 
341     /**
342      * Compares the string representation of this Uri with that of
343      * another.
344      */
compareTo(Uri other)345     public int compareTo(Uri other) {
346         return toString().compareTo(other.toString());
347     }
348 
349     /**
350      * Returns the encoded string representation of this URI.
351      * Example: "http://google.com/"
352      */
toString()353     public abstract String toString();
354 
355     /**
356      * Return a string representation of the URI that is safe to print
357      * to logs and other places where PII should be avoided.
358      * @hide
359      */
toSafeString()360     public String toSafeString() {
361         String scheme = getScheme();
362         String ssp = getSchemeSpecificPart();
363         if (scheme != null) {
364             if (scheme.equalsIgnoreCase("tel") || scheme.equalsIgnoreCase("sip")
365                     || scheme.equalsIgnoreCase("sms") || scheme.equalsIgnoreCase("smsto")
366                     || scheme.equalsIgnoreCase("mailto")) {
367                 StringBuilder builder = new StringBuilder(64);
368                 builder.append(scheme);
369                 builder.append(':');
370                 if (ssp != null) {
371                     for (int i=0; i<ssp.length(); i++) {
372                         char c = ssp.charAt(i);
373                         if (c == '-' || c == '@' || c == '.') {
374                             builder.append(c);
375                         } else {
376                             builder.append('x');
377                         }
378                     }
379                 }
380                 return builder.toString();
381             }
382         }
383         // Not a sensitive scheme, but let's still be conservative about
384         // the data we include -- only the ssp, not the query params or
385         // fragment, because those can often have sensitive info.
386         StringBuilder builder = new StringBuilder(64);
387         if (scheme != null) {
388             builder.append(scheme);
389             builder.append(':');
390         }
391         if (ssp != null) {
392             builder.append(ssp);
393         }
394         return builder.toString();
395     }
396 
397     /**
398      * Constructs a new builder, copying the attributes from this Uri.
399      */
buildUpon()400     public abstract Builder buildUpon();
401 
402     /** Index of a component which was not found. */
403     private final static int NOT_FOUND = -1;
404 
405     /** Placeholder value for an index which hasn't been calculated yet. */
406     private final static int NOT_CALCULATED = -2;
407 
408     /**
409      * Error message presented when a user tries to treat an opaque URI as
410      * hierarchical.
411      */
412     private static final String NOT_HIERARCHICAL
413             = "This isn't a hierarchical URI.";
414 
415     /** Default encoding. */
416     private static final String DEFAULT_ENCODING = "UTF-8";
417 
418     /**
419      * Creates a Uri which parses the given encoded URI string.
420      *
421      * @param uriString an RFC 2396-compliant, encoded URI
422      * @throws NullPointerException if uriString is null
423      * @return Uri for this given uri string
424      */
parse(String uriString)425     public static Uri parse(String uriString) {
426         return new StringUri(uriString);
427     }
428 
429     /**
430      * Creates a Uri from a file. The URI has the form
431      * "file://<absolute path>". Encodes path characters with the exception of
432      * '/'.
433      *
434      * <p>Example: "file:///tmp/android.txt"
435      *
436      * @throws NullPointerException if file is null
437      * @return a Uri for the given file
438      */
fromFile(File file)439     public static Uri fromFile(File file) {
440         if (file == null) {
441             throw new NullPointerException("file");
442         }
443 
444         PathPart path = PathPart.fromDecoded(file.getAbsolutePath());
445         return new HierarchicalUri(
446                 "file", Part.EMPTY, path, Part.NULL, Part.NULL);
447     }
448 
449     /**
450      * An implementation which wraps a String URI. This URI can be opaque or
451      * hierarchical, but we extend AbstractHierarchicalUri in case we need
452      * the hierarchical functionality.
453      */
454     private static class StringUri extends AbstractHierarchicalUri {
455 
456         /** Used in parcelling. */
457         static final int TYPE_ID = 1;
458 
459         /** URI string representation. */
460         private final String uriString;
461 
StringUri(String uriString)462         private StringUri(String uriString) {
463             if (uriString == null) {
464                 throw new NullPointerException("uriString");
465             }
466 
467             this.uriString = uriString;
468         }
469 
readFrom(Parcel parcel)470         static Uri readFrom(Parcel parcel) {
471             return new StringUri(parcel.readString());
472         }
473 
describeContents()474         public int describeContents() {
475             return 0;
476         }
477 
writeToParcel(Parcel parcel, int flags)478         public void writeToParcel(Parcel parcel, int flags) {
479             parcel.writeInt(TYPE_ID);
480             parcel.writeString(uriString);
481         }
482 
483         /** Cached scheme separator index. */
484         private volatile int cachedSsi = NOT_CALCULATED;
485 
486         /** Finds the first ':'. Returns -1 if none found. */
findSchemeSeparator()487         private int findSchemeSeparator() {
488             return cachedSsi == NOT_CALCULATED
489                     ? cachedSsi = uriString.indexOf(':')
490                     : cachedSsi;
491         }
492 
493         /** Cached fragment separator index. */
494         private volatile int cachedFsi = NOT_CALCULATED;
495 
496         /** Finds the first '#'. Returns -1 if none found. */
findFragmentSeparator()497         private int findFragmentSeparator() {
498             return cachedFsi == NOT_CALCULATED
499                     ? cachedFsi = uriString.indexOf('#', findSchemeSeparator())
500                     : cachedFsi;
501         }
502 
isHierarchical()503         public boolean isHierarchical() {
504             int ssi = findSchemeSeparator();
505 
506             if (ssi == NOT_FOUND) {
507                 // All relative URIs are hierarchical.
508                 return true;
509             }
510 
511             if (uriString.length() == ssi + 1) {
512                 // No ssp.
513                 return false;
514             }
515 
516             // If the ssp starts with a '/', this is hierarchical.
517             return uriString.charAt(ssi + 1) == '/';
518         }
519 
isRelative()520         public boolean isRelative() {
521             // Note: We return true if the index is 0
522             return findSchemeSeparator() == NOT_FOUND;
523         }
524 
525         private volatile String scheme = NOT_CACHED;
526 
getScheme()527         public String getScheme() {
528             @SuppressWarnings("StringEquality")
529             boolean cached = (scheme != NOT_CACHED);
530             return cached ? scheme : (scheme = parseScheme());
531         }
532 
parseScheme()533         private String parseScheme() {
534             int ssi = findSchemeSeparator();
535             return ssi == NOT_FOUND ? null : uriString.substring(0, ssi);
536         }
537 
538         private Part ssp;
539 
getSsp()540         private Part getSsp() {
541             return ssp == null ? ssp = Part.fromEncoded(parseSsp()) : ssp;
542         }
543 
getEncodedSchemeSpecificPart()544         public String getEncodedSchemeSpecificPart() {
545             return getSsp().getEncoded();
546         }
547 
getSchemeSpecificPart()548         public String getSchemeSpecificPart() {
549             return getSsp().getDecoded();
550         }
551 
parseSsp()552         private String parseSsp() {
553             int ssi = findSchemeSeparator();
554             int fsi = findFragmentSeparator();
555 
556             // Return everything between ssi and fsi.
557             return fsi == NOT_FOUND
558                     ? uriString.substring(ssi + 1)
559                     : uriString.substring(ssi + 1, fsi);
560         }
561 
562         private Part authority;
563 
getAuthorityPart()564         private Part getAuthorityPart() {
565             if (authority == null) {
566                 String encodedAuthority
567                         = parseAuthority(this.uriString, findSchemeSeparator());
568                 return authority = Part.fromEncoded(encodedAuthority);
569             }
570 
571             return authority;
572         }
573 
getEncodedAuthority()574         public String getEncodedAuthority() {
575             return getAuthorityPart().getEncoded();
576         }
577 
getAuthority()578         public String getAuthority() {
579             return getAuthorityPart().getDecoded();
580         }
581 
582         private PathPart path;
583 
getPathPart()584         private PathPart getPathPart() {
585             return path == null
586                     ? path = PathPart.fromEncoded(parsePath())
587                     : path;
588         }
589 
getPath()590         public String getPath() {
591             return getPathPart().getDecoded();
592         }
593 
getEncodedPath()594         public String getEncodedPath() {
595             return getPathPart().getEncoded();
596         }
597 
getPathSegments()598         public List<String> getPathSegments() {
599             return getPathPart().getPathSegments();
600         }
601 
parsePath()602         private String parsePath() {
603             String uriString = this.uriString;
604             int ssi = findSchemeSeparator();
605 
606             // If the URI is absolute.
607             if (ssi > -1) {
608                 // Is there anything after the ':'?
609                 boolean schemeOnly = ssi + 1 == uriString.length();
610                 if (schemeOnly) {
611                     // Opaque URI.
612                     return null;
613                 }
614 
615                 // A '/' after the ':' means this is hierarchical.
616                 if (uriString.charAt(ssi + 1) != '/') {
617                     // Opaque URI.
618                     return null;
619                 }
620             } else {
621                 // All relative URIs are hierarchical.
622             }
623 
624             return parsePath(uriString, ssi);
625         }
626 
627         private Part query;
628 
getQueryPart()629         private Part getQueryPart() {
630             return query == null
631                     ? query = Part.fromEncoded(parseQuery()) : query;
632         }
633 
getEncodedQuery()634         public String getEncodedQuery() {
635             return getQueryPart().getEncoded();
636         }
637 
parseQuery()638         private String parseQuery() {
639             // It doesn't make sense to cache this index. We only ever
640             // calculate it once.
641             int qsi = uriString.indexOf('?', findSchemeSeparator());
642             if (qsi == NOT_FOUND) {
643                 return null;
644             }
645 
646             int fsi = findFragmentSeparator();
647 
648             if (fsi == NOT_FOUND) {
649                 return uriString.substring(qsi + 1);
650             }
651 
652             if (fsi < qsi) {
653                 // Invalid.
654                 return null;
655             }
656 
657             return uriString.substring(qsi + 1, fsi);
658         }
659 
getQuery()660         public String getQuery() {
661             return getQueryPart().getDecoded();
662         }
663 
664         private Part fragment;
665 
getFragmentPart()666         private Part getFragmentPart() {
667             return fragment == null
668                     ? fragment = Part.fromEncoded(parseFragment()) : fragment;
669         }
670 
getEncodedFragment()671         public String getEncodedFragment() {
672             return getFragmentPart().getEncoded();
673         }
674 
parseFragment()675         private String parseFragment() {
676             int fsi = findFragmentSeparator();
677             return fsi == NOT_FOUND ? null : uriString.substring(fsi + 1);
678         }
679 
getFragment()680         public String getFragment() {
681             return getFragmentPart().getDecoded();
682         }
683 
toString()684         public String toString() {
685             return uriString;
686         }
687 
688         /**
689          * Parses an authority out of the given URI string.
690          *
691          * @param uriString URI string
692          * @param ssi scheme separator index, -1 for a relative URI
693          *
694          * @return the authority or null if none is found
695          */
parseAuthority(String uriString, int ssi)696         static String parseAuthority(String uriString, int ssi) {
697             int length = uriString.length();
698 
699             // If "//" follows the scheme separator, we have an authority.
700             if (length > ssi + 2
701                     && uriString.charAt(ssi + 1) == '/'
702                     && uriString.charAt(ssi + 2) == '/') {
703                 // We have an authority.
704 
705                 // Look for the start of the path, query, or fragment, or the
706                 // end of the string.
707                 int end = ssi + 3;
708                 LOOP: while (end < length) {
709                     switch (uriString.charAt(end)) {
710                         case '/': // Start of path
711                         case '?': // Start of query
712                         case '#': // Start of fragment
713                             break LOOP;
714                     }
715                     end++;
716                 }
717 
718                 return uriString.substring(ssi + 3, end);
719             } else {
720                 return null;
721             }
722 
723         }
724 
725         /**
726          * Parses a path out of this given URI string.
727          *
728          * @param uriString URI string
729          * @param ssi scheme separator index, -1 for a relative URI
730          *
731          * @return the path
732          */
parsePath(String uriString, int ssi)733         static String parsePath(String uriString, int ssi) {
734             int length = uriString.length();
735 
736             // Find start of path.
737             int pathStart;
738             if (length > ssi + 2
739                     && uriString.charAt(ssi + 1) == '/'
740                     && uriString.charAt(ssi + 2) == '/') {
741                 // Skip over authority to path.
742                 pathStart = ssi + 3;
743                 LOOP: while (pathStart < length) {
744                     switch (uriString.charAt(pathStart)) {
745                         case '?': // Start of query
746                         case '#': // Start of fragment
747                             return ""; // Empty path.
748                         case '/': // Start of path!
749                             break LOOP;
750                     }
751                     pathStart++;
752                 }
753             } else {
754                 // Path starts immediately after scheme separator.
755                 pathStart = ssi + 1;
756             }
757 
758             // Find end of path.
759             int pathEnd = pathStart;
760             LOOP: while (pathEnd < length) {
761                 switch (uriString.charAt(pathEnd)) {
762                     case '?': // Start of query
763                     case '#': // Start of fragment
764                         break LOOP;
765                 }
766                 pathEnd++;
767             }
768 
769             return uriString.substring(pathStart, pathEnd);
770         }
771 
buildUpon()772         public Builder buildUpon() {
773             if (isHierarchical()) {
774                 return new Builder()
775                         .scheme(getScheme())
776                         .authority(getAuthorityPart())
777                         .path(getPathPart())
778                         .query(getQueryPart())
779                         .fragment(getFragmentPart());
780             } else {
781                 return new Builder()
782                         .scheme(getScheme())
783                         .opaquePart(getSsp())
784                         .fragment(getFragmentPart());
785             }
786         }
787     }
788 
789     /**
790      * Creates an opaque Uri from the given components. Encodes the ssp
791      * which means this method cannot be used to create hierarchical URIs.
792      *
793      * @param scheme of the URI
794      * @param ssp scheme-specific-part, everything between the
795      *  scheme separator (':') and the fragment separator ('#'), which will
796      *  get encoded
797      * @param fragment fragment, everything after the '#', null if undefined,
798      *  will get encoded
799      *
800      * @throws NullPointerException if scheme or ssp is null
801      * @return Uri composed of the given scheme, ssp, and fragment
802      *
803      * @see Builder if you don't want the ssp and fragment to be encoded
804      */
fromParts(String scheme, String ssp, String fragment)805     public static Uri fromParts(String scheme, String ssp,
806             String fragment) {
807         if (scheme == null) {
808             throw new NullPointerException("scheme");
809         }
810         if (ssp == null) {
811             throw new NullPointerException("ssp");
812         }
813 
814         return new OpaqueUri(scheme, Part.fromDecoded(ssp),
815                 Part.fromDecoded(fragment));
816     }
817 
818     /**
819      * Opaque URI.
820      */
821     private static class OpaqueUri extends Uri {
822 
823         /** Used in parcelling. */
824         static final int TYPE_ID = 2;
825 
826         private final String scheme;
827         private final Part ssp;
828         private final Part fragment;
829 
OpaqueUri(String scheme, Part ssp, Part fragment)830         private OpaqueUri(String scheme, Part ssp, Part fragment) {
831             this.scheme = scheme;
832             this.ssp = ssp;
833             this.fragment = fragment == null ? Part.NULL : fragment;
834         }
835 
readFrom(Parcel parcel)836         static Uri readFrom(Parcel parcel) {
837             return new OpaqueUri(
838                 parcel.readString(),
839                 Part.readFrom(parcel),
840                 Part.readFrom(parcel)
841             );
842         }
843 
describeContents()844         public int describeContents() {
845             return 0;
846         }
847 
writeToParcel(Parcel parcel, int flags)848         public void writeToParcel(Parcel parcel, int flags) {
849             parcel.writeInt(TYPE_ID);
850             parcel.writeString(scheme);
851             ssp.writeTo(parcel);
852             fragment.writeTo(parcel);
853         }
854 
isHierarchical()855         public boolean isHierarchical() {
856             return false;
857         }
858 
isRelative()859         public boolean isRelative() {
860             return scheme == null;
861         }
862 
getScheme()863         public String getScheme() {
864             return this.scheme;
865         }
866 
getEncodedSchemeSpecificPart()867         public String getEncodedSchemeSpecificPart() {
868             return ssp.getEncoded();
869         }
870 
getSchemeSpecificPart()871         public String getSchemeSpecificPart() {
872             return ssp.getDecoded();
873         }
874 
getAuthority()875         public String getAuthority() {
876             return null;
877         }
878 
getEncodedAuthority()879         public String getEncodedAuthority() {
880             return null;
881         }
882 
getPath()883         public String getPath() {
884             return null;
885         }
886 
getEncodedPath()887         public String getEncodedPath() {
888             return null;
889         }
890 
getQuery()891         public String getQuery() {
892             return null;
893         }
894 
getEncodedQuery()895         public String getEncodedQuery() {
896             return null;
897         }
898 
getFragment()899         public String getFragment() {
900             return fragment.getDecoded();
901         }
902 
getEncodedFragment()903         public String getEncodedFragment() {
904             return fragment.getEncoded();
905         }
906 
getPathSegments()907         public List<String> getPathSegments() {
908             return Collections.emptyList();
909         }
910 
getLastPathSegment()911         public String getLastPathSegment() {
912             return null;
913         }
914 
getUserInfo()915         public String getUserInfo() {
916             return null;
917         }
918 
getEncodedUserInfo()919         public String getEncodedUserInfo() {
920             return null;
921         }
922 
getHost()923         public String getHost() {
924             return null;
925         }
926 
getPort()927         public int getPort() {
928             return -1;
929         }
930 
931         private volatile String cachedString = NOT_CACHED;
932 
toString()933         public String toString() {
934             @SuppressWarnings("StringEquality")
935             boolean cached = cachedString != NOT_CACHED;
936             if (cached) {
937                 return cachedString;
938             }
939 
940             StringBuilder sb = new StringBuilder();
941 
942             sb.append(scheme).append(':');
943             sb.append(getEncodedSchemeSpecificPart());
944 
945             if (!fragment.isEmpty()) {
946                 sb.append('#').append(fragment.getEncoded());
947             }
948 
949             return cachedString = sb.toString();
950         }
951 
buildUpon()952         public Builder buildUpon() {
953             return new Builder()
954                     .scheme(this.scheme)
955                     .opaquePart(this.ssp)
956                     .fragment(this.fragment);
957         }
958     }
959 
960     /**
961      * Wrapper for path segment array.
962      */
963     static class PathSegments extends AbstractList<String>
964             implements RandomAccess {
965 
966         static final PathSegments EMPTY = new PathSegments(null, 0);
967 
968         final String[] segments;
969         final int size;
970 
PathSegments(String[] segments, int size)971         PathSegments(String[] segments, int size) {
972             this.segments = segments;
973             this.size = size;
974         }
975 
get(int index)976         public String get(int index) {
977             if (index >= size) {
978                 throw new IndexOutOfBoundsException();
979             }
980 
981             return segments[index];
982         }
983 
size()984         public int size() {
985             return this.size;
986         }
987     }
988 
989     /**
990      * Builds PathSegments.
991      */
992     static class PathSegmentsBuilder {
993 
994         String[] segments;
995         int size = 0;
996 
add(String segment)997         void add(String segment) {
998             if (segments == null) {
999                 segments = new String[4];
1000             } else if (size + 1 == segments.length) {
1001                 String[] expanded = new String[segments.length * 2];
1002                 System.arraycopy(segments, 0, expanded, 0, segments.length);
1003                 segments = expanded;
1004             }
1005 
1006             segments[size++] = segment;
1007         }
1008 
build()1009         PathSegments build() {
1010             if (segments == null) {
1011                 return PathSegments.EMPTY;
1012             }
1013 
1014             try {
1015                 return new PathSegments(segments, size);
1016             } finally {
1017                 // Makes sure this doesn't get reused.
1018                 segments = null;
1019             }
1020         }
1021     }
1022 
1023     /**
1024      * Support for hierarchical URIs.
1025      */
1026     private abstract static class AbstractHierarchicalUri extends Uri {
1027 
getLastPathSegment()1028         public String getLastPathSegment() {
1029             // TODO: If we haven't parsed all of the segments already, just
1030             // grab the last one directly so we only allocate one string.
1031 
1032             List<String> segments = getPathSegments();
1033             int size = segments.size();
1034             if (size == 0) {
1035                 return null;
1036             }
1037             return segments.get(size - 1);
1038         }
1039 
1040         private Part userInfo;
1041 
getUserInfoPart()1042         private Part getUserInfoPart() {
1043             return userInfo == null
1044                     ? userInfo = Part.fromEncoded(parseUserInfo()) : userInfo;
1045         }
1046 
getEncodedUserInfo()1047         public final String getEncodedUserInfo() {
1048             return getUserInfoPart().getEncoded();
1049         }
1050 
parseUserInfo()1051         private String parseUserInfo() {
1052             String authority = getEncodedAuthority();
1053             if (authority == null) {
1054                 return null;
1055             }
1056 
1057             int end = authority.indexOf('@');
1058             return end == NOT_FOUND ? null : authority.substring(0, end);
1059         }
1060 
getUserInfo()1061         public String getUserInfo() {
1062             return getUserInfoPart().getDecoded();
1063         }
1064 
1065         private volatile String host = NOT_CACHED;
1066 
getHost()1067         public String getHost() {
1068             @SuppressWarnings("StringEquality")
1069             boolean cached = (host != NOT_CACHED);
1070             return cached ? host
1071                     : (host = parseHost());
1072         }
1073 
parseHost()1074         private String parseHost() {
1075             String authority = getEncodedAuthority();
1076             if (authority == null) {
1077                 return null;
1078             }
1079 
1080             // Parse out user info and then port.
1081             int userInfoSeparator = authority.indexOf('@');
1082             int portSeparator = authority.indexOf(':', userInfoSeparator);
1083 
1084             String encodedHost = portSeparator == NOT_FOUND
1085                     ? authority.substring(userInfoSeparator + 1)
1086                     : authority.substring(userInfoSeparator + 1, portSeparator);
1087 
1088             return decode(encodedHost);
1089         }
1090 
1091         private volatile int port = NOT_CALCULATED;
1092 
getPort()1093         public int getPort() {
1094             return port == NOT_CALCULATED
1095                     ? port = parsePort()
1096                     : port;
1097         }
1098 
parsePort()1099         private int parsePort() {
1100             String authority = getEncodedAuthority();
1101             if (authority == null) {
1102                 return -1;
1103             }
1104 
1105             // Make sure we look for the port separtor *after* the user info
1106             // separator. We have URLs with a ':' in the user info.
1107             int userInfoSeparator = authority.indexOf('@');
1108             int portSeparator = authority.indexOf(':', userInfoSeparator);
1109 
1110             if (portSeparator == NOT_FOUND) {
1111                 return -1;
1112             }
1113 
1114             String portString = decode(authority.substring(portSeparator + 1));
1115             try {
1116                 return Integer.parseInt(portString);
1117             } catch (NumberFormatException e) {
1118                 Log.w(LOG, "Error parsing port string.", e);
1119                 return -1;
1120             }
1121         }
1122     }
1123 
1124     /**
1125      * Hierarchical Uri.
1126      */
1127     private static class HierarchicalUri extends AbstractHierarchicalUri {
1128 
1129         /** Used in parcelling. */
1130         static final int TYPE_ID = 3;
1131 
1132         private final String scheme; // can be null
1133         private final Part authority;
1134         private final PathPart path;
1135         private final Part query;
1136         private final Part fragment;
1137 
HierarchicalUri(String scheme, Part authority, PathPart path, Part query, Part fragment)1138         private HierarchicalUri(String scheme, Part authority, PathPart path,
1139                 Part query, Part fragment) {
1140             this.scheme = scheme;
1141             this.authority = Part.nonNull(authority);
1142             this.path = path == null ? PathPart.NULL : path;
1143             this.query = Part.nonNull(query);
1144             this.fragment = Part.nonNull(fragment);
1145         }
1146 
readFrom(Parcel parcel)1147         static Uri readFrom(Parcel parcel) {
1148             return new HierarchicalUri(
1149                 parcel.readString(),
1150                 Part.readFrom(parcel),
1151                 PathPart.readFrom(parcel),
1152                 Part.readFrom(parcel),
1153                 Part.readFrom(parcel)
1154             );
1155         }
1156 
describeContents()1157         public int describeContents() {
1158             return 0;
1159         }
1160 
writeToParcel(Parcel parcel, int flags)1161         public void writeToParcel(Parcel parcel, int flags) {
1162             parcel.writeInt(TYPE_ID);
1163             parcel.writeString(scheme);
1164             authority.writeTo(parcel);
1165             path.writeTo(parcel);
1166             query.writeTo(parcel);
1167             fragment.writeTo(parcel);
1168         }
1169 
isHierarchical()1170         public boolean isHierarchical() {
1171             return true;
1172         }
1173 
isRelative()1174         public boolean isRelative() {
1175             return scheme == null;
1176         }
1177 
getScheme()1178         public String getScheme() {
1179             return scheme;
1180         }
1181 
1182         private Part ssp;
1183 
getSsp()1184         private Part getSsp() {
1185             return ssp == null
1186                     ? ssp = Part.fromEncoded(makeSchemeSpecificPart()) : ssp;
1187         }
1188 
getEncodedSchemeSpecificPart()1189         public String getEncodedSchemeSpecificPart() {
1190             return getSsp().getEncoded();
1191         }
1192 
getSchemeSpecificPart()1193         public String getSchemeSpecificPart() {
1194             return getSsp().getDecoded();
1195         }
1196 
1197         /**
1198          * Creates the encoded scheme-specific part from its sub parts.
1199          */
makeSchemeSpecificPart()1200         private String makeSchemeSpecificPart() {
1201             StringBuilder builder = new StringBuilder();
1202             appendSspTo(builder);
1203             return builder.toString();
1204         }
1205 
appendSspTo(StringBuilder builder)1206         private void appendSspTo(StringBuilder builder) {
1207             String encodedAuthority = authority.getEncoded();
1208             if (encodedAuthority != null) {
1209                 // Even if the authority is "", we still want to append "//".
1210                 builder.append("//").append(encodedAuthority);
1211             }
1212 
1213             String encodedPath = path.getEncoded();
1214             if (encodedPath != null) {
1215                 builder.append(encodedPath);
1216             }
1217 
1218             if (!query.isEmpty()) {
1219                 builder.append('?').append(query.getEncoded());
1220             }
1221         }
1222 
getAuthority()1223         public String getAuthority() {
1224             return this.authority.getDecoded();
1225         }
1226 
getEncodedAuthority()1227         public String getEncodedAuthority() {
1228             return this.authority.getEncoded();
1229         }
1230 
getEncodedPath()1231         public String getEncodedPath() {
1232             return this.path.getEncoded();
1233         }
1234 
getPath()1235         public String getPath() {
1236             return this.path.getDecoded();
1237         }
1238 
getQuery()1239         public String getQuery() {
1240             return this.query.getDecoded();
1241         }
1242 
getEncodedQuery()1243         public String getEncodedQuery() {
1244             return this.query.getEncoded();
1245         }
1246 
getFragment()1247         public String getFragment() {
1248             return this.fragment.getDecoded();
1249         }
1250 
getEncodedFragment()1251         public String getEncodedFragment() {
1252             return this.fragment.getEncoded();
1253         }
1254 
getPathSegments()1255         public List<String> getPathSegments() {
1256             return this.path.getPathSegments();
1257         }
1258 
1259         private volatile String uriString = NOT_CACHED;
1260 
1261         @Override
toString()1262         public String toString() {
1263             @SuppressWarnings("StringEquality")
1264             boolean cached = (uriString != NOT_CACHED);
1265             return cached ? uriString
1266                     : (uriString = makeUriString());
1267         }
1268 
makeUriString()1269         private String makeUriString() {
1270             StringBuilder builder = new StringBuilder();
1271 
1272             if (scheme != null) {
1273                 builder.append(scheme).append(':');
1274             }
1275 
1276             appendSspTo(builder);
1277 
1278             if (!fragment.isEmpty()) {
1279                 builder.append('#').append(fragment.getEncoded());
1280             }
1281 
1282             return builder.toString();
1283         }
1284 
buildUpon()1285         public Builder buildUpon() {
1286             return new Builder()
1287                     .scheme(scheme)
1288                     .authority(authority)
1289                     .path(path)
1290                     .query(query)
1291                     .fragment(fragment);
1292         }
1293     }
1294 
1295     /**
1296      * Helper class for building or manipulating URI references. Not safe for
1297      * concurrent use.
1298      *
1299      * <p>An absolute hierarchical URI reference follows the pattern:
1300      * {@code <scheme>://<authority><absolute path>?<query>#<fragment>}
1301      *
1302      * <p>Relative URI references (which are always hierarchical) follow one
1303      * of two patterns: {@code <relative or absolute path>?<query>#<fragment>}
1304      * or {@code //<authority><absolute path>?<query>#<fragment>}
1305      *
1306      * <p>An opaque URI follows this pattern:
1307      * {@code <scheme>:<opaque part>#<fragment>}
1308      *
1309      * <p>Use {@link Uri#buildUpon()} to obtain a builder representing an existing URI.
1310      */
1311     public static final class Builder {
1312 
1313         private String scheme;
1314         private Part opaquePart;
1315         private Part authority;
1316         private PathPart path;
1317         private Part query;
1318         private Part fragment;
1319 
1320         /**
1321          * Constructs a new Builder.
1322          */
Builder()1323         public Builder() {}
1324 
1325         /**
1326          * Sets the scheme.
1327          *
1328          * @param scheme name or {@code null} if this is a relative Uri
1329          */
scheme(String scheme)1330         public Builder scheme(String scheme) {
1331             this.scheme = scheme;
1332             return this;
1333         }
1334 
opaquePart(Part opaquePart)1335         Builder opaquePart(Part opaquePart) {
1336             this.opaquePart = opaquePart;
1337             return this;
1338         }
1339 
1340         /**
1341          * Encodes and sets the given opaque scheme-specific-part.
1342          *
1343          * @param opaquePart decoded opaque part
1344          */
opaquePart(String opaquePart)1345         public Builder opaquePart(String opaquePart) {
1346             return opaquePart(Part.fromDecoded(opaquePart));
1347         }
1348 
1349         /**
1350          * Sets the previously encoded opaque scheme-specific-part.
1351          *
1352          * @param opaquePart encoded opaque part
1353          */
encodedOpaquePart(String opaquePart)1354         public Builder encodedOpaquePart(String opaquePart) {
1355             return opaquePart(Part.fromEncoded(opaquePart));
1356         }
1357 
authority(Part authority)1358         Builder authority(Part authority) {
1359             // This URI will be hierarchical.
1360             this.opaquePart = null;
1361 
1362             this.authority = authority;
1363             return this;
1364         }
1365 
1366         /**
1367          * Encodes and sets the authority.
1368          */
authority(String authority)1369         public Builder authority(String authority) {
1370             return authority(Part.fromDecoded(authority));
1371         }
1372 
1373         /**
1374          * Sets the previously encoded authority.
1375          */
encodedAuthority(String authority)1376         public Builder encodedAuthority(String authority) {
1377             return authority(Part.fromEncoded(authority));
1378         }
1379 
path(PathPart path)1380         Builder path(PathPart path) {
1381             // This URI will be hierarchical.
1382             this.opaquePart = null;
1383 
1384             this.path = path;
1385             return this;
1386         }
1387 
1388         /**
1389          * Sets the path. Leaves '/' characters intact but encodes others as
1390          * necessary.
1391          *
1392          * <p>If the path is not null and doesn't start with a '/', and if
1393          * you specify a scheme and/or authority, the builder will prepend the
1394          * given path with a '/'.
1395          */
path(String path)1396         public Builder path(String path) {
1397             return path(PathPart.fromDecoded(path));
1398         }
1399 
1400         /**
1401          * Sets the previously encoded path.
1402          *
1403          * <p>If the path is not null and doesn't start with a '/', and if
1404          * you specify a scheme and/or authority, the builder will prepend the
1405          * given path with a '/'.
1406          */
encodedPath(String path)1407         public Builder encodedPath(String path) {
1408             return path(PathPart.fromEncoded(path));
1409         }
1410 
1411         /**
1412          * Encodes the given segment and appends it to the path.
1413          */
appendPath(String newSegment)1414         public Builder appendPath(String newSegment) {
1415             return path(PathPart.appendDecodedSegment(path, newSegment));
1416         }
1417 
1418         /**
1419          * Appends the given segment to the path.
1420          */
appendEncodedPath(String newSegment)1421         public Builder appendEncodedPath(String newSegment) {
1422             return path(PathPart.appendEncodedSegment(path, newSegment));
1423         }
1424 
query(Part query)1425         Builder query(Part query) {
1426             // This URI will be hierarchical.
1427             this.opaquePart = null;
1428 
1429             this.query = query;
1430             return this;
1431         }
1432 
1433         /**
1434          * Encodes and sets the query.
1435          */
query(String query)1436         public Builder query(String query) {
1437             return query(Part.fromDecoded(query));
1438         }
1439 
1440         /**
1441          * Sets the previously encoded query.
1442          */
encodedQuery(String query)1443         public Builder encodedQuery(String query) {
1444             return query(Part.fromEncoded(query));
1445         }
1446 
fragment(Part fragment)1447         Builder fragment(Part fragment) {
1448             this.fragment = fragment;
1449             return this;
1450         }
1451 
1452         /**
1453          * Encodes and sets the fragment.
1454          */
fragment(String fragment)1455         public Builder fragment(String fragment) {
1456             return fragment(Part.fromDecoded(fragment));
1457         }
1458 
1459         /**
1460          * Sets the previously encoded fragment.
1461          */
encodedFragment(String fragment)1462         public Builder encodedFragment(String fragment) {
1463             return fragment(Part.fromEncoded(fragment));
1464         }
1465 
1466         /**
1467          * Encodes the key and value and then appends the parameter to the
1468          * query string.
1469          *
1470          * @param key which will be encoded
1471          * @param value which will be encoded
1472          */
appendQueryParameter(String key, String value)1473         public Builder appendQueryParameter(String key, String value) {
1474             // This URI will be hierarchical.
1475             this.opaquePart = null;
1476 
1477             String encodedParameter = encode(key, null) + "="
1478                     + encode(value, null);
1479 
1480             if (query == null) {
1481                 query = Part.fromEncoded(encodedParameter);
1482                 return this;
1483             }
1484 
1485             String oldQuery = query.getEncoded();
1486             if (oldQuery == null || oldQuery.length() == 0) {
1487                 query = Part.fromEncoded(encodedParameter);
1488             } else {
1489                 query = Part.fromEncoded(oldQuery + "&" + encodedParameter);
1490             }
1491 
1492             return this;
1493         }
1494 
1495         /**
1496          * Clears the the previously set query.
1497          */
clearQuery()1498         public Builder clearQuery() {
1499           return query((Part) null);
1500         }
1501 
1502         /**
1503          * Constructs a Uri with the current attributes.
1504          *
1505          * @throws UnsupportedOperationException if the URI is opaque and the
1506          *  scheme is null
1507          */
build()1508         public Uri build() {
1509             if (opaquePart != null) {
1510                 if (this.scheme == null) {
1511                     throw new UnsupportedOperationException(
1512                             "An opaque URI must have a scheme.");
1513                 }
1514 
1515                 return new OpaqueUri(scheme, opaquePart, fragment);
1516             } else {
1517                 // Hierarchical URIs should not return null for getPath().
1518                 PathPart path = this.path;
1519                 if (path == null || path == PathPart.NULL) {
1520                     path = PathPart.EMPTY;
1521                 } else {
1522                     // If we have a scheme and/or authority, the path must
1523                     // be absolute. Prepend it with a '/' if necessary.
1524                     if (hasSchemeOrAuthority()) {
1525                         path = PathPart.makeAbsolute(path);
1526                     }
1527                 }
1528 
1529                 return new HierarchicalUri(
1530                         scheme, authority, path, query, fragment);
1531             }
1532         }
1533 
hasSchemeOrAuthority()1534         private boolean hasSchemeOrAuthority() {
1535             return scheme != null
1536                     || (authority != null && authority != Part.NULL);
1537 
1538         }
1539 
1540         @Override
toString()1541         public String toString() {
1542             return build().toString();
1543         }
1544     }
1545 
1546     /**
1547      * Returns a set of the unique names of all query parameters. Iterating
1548      * over the set will return the names in order of their first occurrence.
1549      *
1550      * @throws UnsupportedOperationException if this isn't a hierarchical URI
1551      *
1552      * @return a set of decoded names
1553      */
getQueryParameterNames()1554     public Set<String> getQueryParameterNames() {
1555         if (isOpaque()) {
1556             throw new UnsupportedOperationException(NOT_HIERARCHICAL);
1557         }
1558 
1559         String query = getEncodedQuery();
1560         if (query == null) {
1561             return Collections.emptySet();
1562         }
1563 
1564         Set<String> names = new LinkedHashSet<String>();
1565         int start = 0;
1566         do {
1567             int next = query.indexOf('&', start);
1568             int end = (next == -1) ? query.length() : next;
1569 
1570             int separator = query.indexOf('=', start);
1571             if (separator > end || separator == -1) {
1572                 separator = end;
1573             }
1574 
1575             String name = query.substring(start, separator);
1576             names.add(decode(name));
1577 
1578             // Move start to end of name.
1579             start = end + 1;
1580         } while (start < query.length());
1581 
1582         return Collections.unmodifiableSet(names);
1583     }
1584 
1585     /**
1586      * Searches the query string for parameter values with the given key.
1587      *
1588      * @param key which will be encoded
1589      *
1590      * @throws UnsupportedOperationException if this isn't a hierarchical URI
1591      * @throws NullPointerException if key is null
1592      * @return a list of decoded values
1593      */
getQueryParameters(String key)1594     public List<String> getQueryParameters(String key) {
1595         if (isOpaque()) {
1596             throw new UnsupportedOperationException(NOT_HIERARCHICAL);
1597         }
1598         if (key == null) {
1599           throw new NullPointerException("key");
1600         }
1601 
1602         String query = getEncodedQuery();
1603         if (query == null) {
1604             return Collections.emptyList();
1605         }
1606 
1607         String encodedKey;
1608         try {
1609             encodedKey = URLEncoder.encode(key, DEFAULT_ENCODING);
1610         } catch (UnsupportedEncodingException e) {
1611             throw new AssertionError(e);
1612         }
1613 
1614         ArrayList<String> values = new ArrayList<String>();
1615 
1616         int start = 0;
1617         do {
1618             int nextAmpersand = query.indexOf('&', start);
1619             int end = nextAmpersand != -1 ? nextAmpersand : query.length();
1620 
1621             int separator = query.indexOf('=', start);
1622             if (separator > end || separator == -1) {
1623                 separator = end;
1624             }
1625 
1626             if (separator - start == encodedKey.length()
1627                     && query.regionMatches(start, encodedKey, 0, encodedKey.length())) {
1628                 if (separator == end) {
1629                   values.add("");
1630                 } else {
1631                   values.add(decode(query.substring(separator + 1, end)));
1632                 }
1633             }
1634 
1635             // Move start to end of name.
1636             if (nextAmpersand != -1) {
1637                 start = nextAmpersand + 1;
1638             } else {
1639                 break;
1640             }
1641         } while (true);
1642 
1643         return Collections.unmodifiableList(values);
1644     }
1645 
1646     /**
1647      * Searches the query string for the first value with the given key.
1648      *
1649      * <p><strong>Warning:</strong> Prior to Ice Cream Sandwich, this decoded
1650      * the '+' character as '+' rather than ' '.
1651      *
1652      * @param key which will be encoded
1653      * @throws UnsupportedOperationException if this isn't a hierarchical URI
1654      * @throws NullPointerException if key is null
1655      * @return the decoded value or null if no parameter is found
1656      */
getQueryParameter(String key)1657     public String getQueryParameter(String key) {
1658         if (isOpaque()) {
1659             throw new UnsupportedOperationException(NOT_HIERARCHICAL);
1660         }
1661         if (key == null) {
1662             throw new NullPointerException("key");
1663         }
1664 
1665         final String query = getEncodedQuery();
1666         if (query == null) {
1667             return null;
1668         }
1669 
1670         final String encodedKey = encode(key, null);
1671         final int length = query.length();
1672         int start = 0;
1673         do {
1674             int nextAmpersand = query.indexOf('&', start);
1675             int end = nextAmpersand != -1 ? nextAmpersand : length;
1676 
1677             int separator = query.indexOf('=', start);
1678             if (separator > end || separator == -1) {
1679                 separator = end;
1680             }
1681 
1682             if (separator - start == encodedKey.length()
1683                     && query.regionMatches(start, encodedKey, 0, encodedKey.length())) {
1684                 if (separator == end) {
1685                     return "";
1686                 } else {
1687                     String encodedValue = query.substring(separator + 1, end);
1688                     return UriCodec.decode(encodedValue, true, Charsets.UTF_8, false);
1689                 }
1690             }
1691 
1692             // Move start to end of name.
1693             if (nextAmpersand != -1) {
1694                 start = nextAmpersand + 1;
1695             } else {
1696                 break;
1697             }
1698         } while (true);
1699         return null;
1700     }
1701 
1702     /**
1703      * Searches the query string for the first value with the given key and interprets it
1704      * as a boolean value. "false" and "0" are interpreted as <code>false</code>, everything
1705      * else is interpreted as <code>true</code>.
1706      *
1707      * @param key which will be decoded
1708      * @param defaultValue the default value to return if there is no query parameter for key
1709      * @return the boolean interpretation of the query parameter key
1710      */
getBooleanQueryParameter(String key, boolean defaultValue)1711     public boolean getBooleanQueryParameter(String key, boolean defaultValue) {
1712         String flag = getQueryParameter(key);
1713         if (flag == null) {
1714             return defaultValue;
1715         }
1716         flag = flag.toLowerCase();
1717         return (!"false".equals(flag) && !"0".equals(flag));
1718     }
1719 
1720     /**
1721      * Return an equivalent URI with a lowercase scheme component.
1722      * This aligns the Uri with Android best practices for
1723      * intent filtering.
1724      *
1725      * <p>For example, "HTTP://www.android.com" becomes
1726      * "http://www.android.com"
1727      *
1728      * <p>All URIs received from outside Android (such as user input,
1729      * or external sources like Bluetooth, NFC, or the Internet) should
1730      * be normalized before they are used to create an Intent.
1731      *
1732      * <p class="note">This method does <em>not</em> validate bad URI's,
1733      * or 'fix' poorly formatted URI's - so do not use it for input validation.
1734      * A Uri will always be returned, even if the Uri is badly formatted to
1735      * begin with and a scheme component cannot be found.
1736      *
1737      * @return normalized Uri (never null)
1738      * @see {@link android.content.Intent#setData}
1739      * @see {@link #setNormalizedData}
1740      */
normalizeScheme()1741     public Uri normalizeScheme() {
1742         String scheme = getScheme();
1743         if (scheme == null) return this;  // give up
1744         String lowerScheme = scheme.toLowerCase(Locale.US);
1745         if (scheme.equals(lowerScheme)) return this;  // no change
1746 
1747         return buildUpon().scheme(lowerScheme).build();
1748     }
1749 
1750     /** Identifies a null parcelled Uri. */
1751     private static final int NULL_TYPE_ID = 0;
1752 
1753     /**
1754      * Reads Uris from Parcels.
1755      */
1756     public static final Parcelable.Creator<Uri> CREATOR
1757             = new Parcelable.Creator<Uri>() {
1758         public Uri createFromParcel(Parcel in) {
1759             int type = in.readInt();
1760             switch (type) {
1761                 case NULL_TYPE_ID: return null;
1762                 case StringUri.TYPE_ID: return StringUri.readFrom(in);
1763                 case OpaqueUri.TYPE_ID: return OpaqueUri.readFrom(in);
1764                 case HierarchicalUri.TYPE_ID:
1765                     return HierarchicalUri.readFrom(in);
1766             }
1767 
1768             throw new IllegalArgumentException("Unknown URI type: " + type);
1769         }
1770 
1771         public Uri[] newArray(int size) {
1772             return new Uri[size];
1773         }
1774     };
1775 
1776     /**
1777      * Writes a Uri to a Parcel.
1778      *
1779      * @param out parcel to write to
1780      * @param uri to write, can be null
1781      */
writeToParcel(Parcel out, Uri uri)1782     public static void writeToParcel(Parcel out, Uri uri) {
1783         if (uri == null) {
1784             out.writeInt(NULL_TYPE_ID);
1785         } else {
1786             uri.writeToParcel(out, 0);
1787         }
1788     }
1789 
1790     private static final char[] HEX_DIGITS = "0123456789ABCDEF".toCharArray();
1791 
1792     /**
1793      * Encodes characters in the given string as '%'-escaped octets
1794      * using the UTF-8 scheme. Leaves letters ("A-Z", "a-z"), numbers
1795      * ("0-9"), and unreserved characters ("_-!.~'()*") intact. Encodes
1796      * all other characters.
1797      *
1798      * @param s string to encode
1799      * @return an encoded version of s suitable for use as a URI component,
1800      *  or null if s is null
1801      */
encode(String s)1802     public static String encode(String s) {
1803         return encode(s, null);
1804     }
1805 
1806     /**
1807      * Encodes characters in the given string as '%'-escaped octets
1808      * using the UTF-8 scheme. Leaves letters ("A-Z", "a-z"), numbers
1809      * ("0-9"), and unreserved characters ("_-!.~'()*") intact. Encodes
1810      * all other characters with the exception of those specified in the
1811      * allow argument.
1812      *
1813      * @param s string to encode
1814      * @param allow set of additional characters to allow in the encoded form,
1815      *  null if no characters should be skipped
1816      * @return an encoded version of s suitable for use as a URI component,
1817      *  or null if s is null
1818      */
encode(String s, String allow)1819     public static String encode(String s, String allow) {
1820         if (s == null) {
1821             return null;
1822         }
1823 
1824         // Lazily-initialized buffers.
1825         StringBuilder encoded = null;
1826 
1827         int oldLength = s.length();
1828 
1829         // This loop alternates between copying over allowed characters and
1830         // encoding in chunks. This results in fewer method calls and
1831         // allocations than encoding one character at a time.
1832         int current = 0;
1833         while (current < oldLength) {
1834             // Start in "copying" mode where we copy over allowed chars.
1835 
1836             // Find the next character which needs to be encoded.
1837             int nextToEncode = current;
1838             while (nextToEncode < oldLength
1839                     && isAllowed(s.charAt(nextToEncode), allow)) {
1840                 nextToEncode++;
1841             }
1842 
1843             // If there's nothing more to encode...
1844             if (nextToEncode == oldLength) {
1845                 if (current == 0) {
1846                     // We didn't need to encode anything!
1847                     return s;
1848                 } else {
1849                     // Presumably, we've already done some encoding.
1850                     encoded.append(s, current, oldLength);
1851                     return encoded.toString();
1852                 }
1853             }
1854 
1855             if (encoded == null) {
1856                 encoded = new StringBuilder();
1857             }
1858 
1859             if (nextToEncode > current) {
1860                 // Append allowed characters leading up to this point.
1861                 encoded.append(s, current, nextToEncode);
1862             } else {
1863                 // assert nextToEncode == current
1864             }
1865 
1866             // Switch to "encoding" mode.
1867 
1868             // Find the next allowed character.
1869             current = nextToEncode;
1870             int nextAllowed = current + 1;
1871             while (nextAllowed < oldLength
1872                     && !isAllowed(s.charAt(nextAllowed), allow)) {
1873                 nextAllowed++;
1874             }
1875 
1876             // Convert the substring to bytes and encode the bytes as
1877             // '%'-escaped octets.
1878             String toEncode = s.substring(current, nextAllowed);
1879             try {
1880                 byte[] bytes = toEncode.getBytes(DEFAULT_ENCODING);
1881                 int bytesLength = bytes.length;
1882                 for (int i = 0; i < bytesLength; i++) {
1883                     encoded.append('%');
1884                     encoded.append(HEX_DIGITS[(bytes[i] & 0xf0) >> 4]);
1885                     encoded.append(HEX_DIGITS[bytes[i] & 0xf]);
1886                 }
1887             } catch (UnsupportedEncodingException e) {
1888                 throw new AssertionError(e);
1889             }
1890 
1891             current = nextAllowed;
1892         }
1893 
1894         // Encoded could still be null at this point if s is empty.
1895         return encoded == null ? s : encoded.toString();
1896     }
1897 
1898     /**
1899      * Returns true if the given character is allowed.
1900      *
1901      * @param c character to check
1902      * @param allow characters to allow
1903      * @return true if the character is allowed or false if it should be
1904      *  encoded
1905      */
isAllowed(char c, String allow)1906     private static boolean isAllowed(char c, String allow) {
1907         return (c >= 'A' && c <= 'Z')
1908                 || (c >= 'a' && c <= 'z')
1909                 || (c >= '0' && c <= '9')
1910                 || "_-!.~'()*".indexOf(c) != NOT_FOUND
1911                 || (allow != null && allow.indexOf(c) != NOT_FOUND);
1912     }
1913 
1914     /**
1915      * Decodes '%'-escaped octets in the given string using the UTF-8 scheme.
1916      * Replaces invalid octets with the unicode replacement character
1917      * ("\\uFFFD").
1918      *
1919      * @param s encoded string to decode
1920      * @return the given string with escaped octets decoded, or null if
1921      *  s is null
1922      */
decode(String s)1923     public static String decode(String s) {
1924         if (s == null) {
1925             return null;
1926         }
1927         return UriCodec.decode(s, false, Charsets.UTF_8, false);
1928     }
1929 
1930     /**
1931      * Support for part implementations.
1932      */
1933     static abstract class AbstractPart {
1934 
1935         /**
1936          * Enum which indicates which representation of a given part we have.
1937          */
1938         static class Representation {
1939             static final int BOTH = 0;
1940             static final int ENCODED = 1;
1941             static final int DECODED = 2;
1942         }
1943 
1944         volatile String encoded;
1945         volatile String decoded;
1946 
AbstractPart(String encoded, String decoded)1947         AbstractPart(String encoded, String decoded) {
1948             this.encoded = encoded;
1949             this.decoded = decoded;
1950         }
1951 
getEncoded()1952         abstract String getEncoded();
1953 
getDecoded()1954         final String getDecoded() {
1955             @SuppressWarnings("StringEquality")
1956             boolean hasDecoded = decoded != NOT_CACHED;
1957             return hasDecoded ? decoded : (decoded = decode(encoded));
1958         }
1959 
writeTo(Parcel parcel)1960         final void writeTo(Parcel parcel) {
1961             @SuppressWarnings("StringEquality")
1962             boolean hasEncoded = encoded != NOT_CACHED;
1963 
1964             @SuppressWarnings("StringEquality")
1965             boolean hasDecoded = decoded != NOT_CACHED;
1966 
1967             if (hasEncoded && hasDecoded) {
1968                 parcel.writeInt(Representation.BOTH);
1969                 parcel.writeString(encoded);
1970                 parcel.writeString(decoded);
1971             } else if (hasEncoded) {
1972                 parcel.writeInt(Representation.ENCODED);
1973                 parcel.writeString(encoded);
1974             } else if (hasDecoded) {
1975                 parcel.writeInt(Representation.DECODED);
1976                 parcel.writeString(decoded);
1977             } else {
1978                 throw new IllegalArgumentException("Neither encoded nor decoded");
1979             }
1980         }
1981     }
1982 
1983     /**
1984      * Immutable wrapper of encoded and decoded versions of a URI part. Lazily
1985      * creates the encoded or decoded version from the other.
1986      */
1987     static class Part extends AbstractPart {
1988 
1989         /** A part with null values. */
1990         static final Part NULL = new EmptyPart(null);
1991 
1992         /** A part with empty strings for values. */
1993         static final Part EMPTY = new EmptyPart("");
1994 
Part(String encoded, String decoded)1995         private Part(String encoded, String decoded) {
1996             super(encoded, decoded);
1997         }
1998 
isEmpty()1999         boolean isEmpty() {
2000             return false;
2001         }
2002 
getEncoded()2003         String getEncoded() {
2004             @SuppressWarnings("StringEquality")
2005             boolean hasEncoded = encoded != NOT_CACHED;
2006             return hasEncoded ? encoded : (encoded = encode(decoded));
2007         }
2008 
readFrom(Parcel parcel)2009         static Part readFrom(Parcel parcel) {
2010             int representation = parcel.readInt();
2011             switch (representation) {
2012                 case Representation.BOTH:
2013                     return from(parcel.readString(), parcel.readString());
2014                 case Representation.ENCODED:
2015                     return fromEncoded(parcel.readString());
2016                 case Representation.DECODED:
2017                     return fromDecoded(parcel.readString());
2018                 default:
2019                     throw new IllegalArgumentException("Unknown representation: "
2020                             + representation);
2021             }
2022         }
2023 
2024         /**
2025          * Returns given part or {@link #NULL} if the given part is null.
2026          */
nonNull(Part part)2027         static Part nonNull(Part part) {
2028             return part == null ? NULL : part;
2029         }
2030 
2031         /**
2032          * Creates a part from the encoded string.
2033          *
2034          * @param encoded part string
2035          */
fromEncoded(String encoded)2036         static Part fromEncoded(String encoded) {
2037             return from(encoded, NOT_CACHED);
2038         }
2039 
2040         /**
2041          * Creates a part from the decoded string.
2042          *
2043          * @param decoded part string
2044          */
fromDecoded(String decoded)2045         static Part fromDecoded(String decoded) {
2046             return from(NOT_CACHED, decoded);
2047         }
2048 
2049         /**
2050          * Creates a part from the encoded and decoded strings.
2051          *
2052          * @param encoded part string
2053          * @param decoded part string
2054          */
from(String encoded, String decoded)2055         static Part from(String encoded, String decoded) {
2056             // We have to check both encoded and decoded in case one is
2057             // NOT_CACHED.
2058 
2059             if (encoded == null) {
2060                 return NULL;
2061             }
2062             if (encoded.length() == 0) {
2063                 return EMPTY;
2064             }
2065 
2066             if (decoded == null) {
2067                 return NULL;
2068             }
2069             if (decoded .length() == 0) {
2070                 return EMPTY;
2071             }
2072 
2073             return new Part(encoded, decoded);
2074         }
2075 
2076         private static class EmptyPart extends Part {
EmptyPart(String value)2077             public EmptyPart(String value) {
2078                 super(value, value);
2079             }
2080 
2081             @Override
isEmpty()2082             boolean isEmpty() {
2083                 return true;
2084             }
2085         }
2086     }
2087 
2088     /**
2089      * Immutable wrapper of encoded and decoded versions of a path part. Lazily
2090      * creates the encoded or decoded version from the other.
2091      */
2092     static class PathPart extends AbstractPart {
2093 
2094         /** A part with null values. */
2095         static final PathPart NULL = new PathPart(null, null);
2096 
2097         /** A part with empty strings for values. */
2098         static final PathPart EMPTY = new PathPart("", "");
2099 
PathPart(String encoded, String decoded)2100         private PathPart(String encoded, String decoded) {
2101             super(encoded, decoded);
2102         }
2103 
getEncoded()2104         String getEncoded() {
2105             @SuppressWarnings("StringEquality")
2106             boolean hasEncoded = encoded != NOT_CACHED;
2107 
2108             // Don't encode '/'.
2109             return hasEncoded ? encoded : (encoded = encode(decoded, "/"));
2110         }
2111 
2112         /**
2113          * Cached path segments. This doesn't need to be volatile--we don't
2114          * care if other threads see the result.
2115          */
2116         private PathSegments pathSegments;
2117 
2118         /**
2119          * Gets the individual path segments. Parses them if necessary.
2120          *
2121          * @return parsed path segments or null if this isn't a hierarchical
2122          *  URI
2123          */
getPathSegments()2124         PathSegments getPathSegments() {
2125             if (pathSegments != null) {
2126                 return pathSegments;
2127             }
2128 
2129             String path = getEncoded();
2130             if (path == null) {
2131                 return pathSegments = PathSegments.EMPTY;
2132             }
2133 
2134             PathSegmentsBuilder segmentBuilder = new PathSegmentsBuilder();
2135 
2136             int previous = 0;
2137             int current;
2138             while ((current = path.indexOf('/', previous)) > -1) {
2139                 // This check keeps us from adding a segment if the path starts
2140                 // '/' and an empty segment for "//".
2141                 if (previous < current) {
2142                     String decodedSegment
2143                             = decode(path.substring(previous, current));
2144                     segmentBuilder.add(decodedSegment);
2145                 }
2146                 previous = current + 1;
2147             }
2148 
2149             // Add in the final path segment.
2150             if (previous < path.length()) {
2151                 segmentBuilder.add(decode(path.substring(previous)));
2152             }
2153 
2154             return pathSegments = segmentBuilder.build();
2155         }
2156 
appendEncodedSegment(PathPart oldPart, String newSegment)2157         static PathPart appendEncodedSegment(PathPart oldPart,
2158                 String newSegment) {
2159             // If there is no old path, should we make the new path relative
2160             // or absolute? I pick absolute.
2161 
2162             if (oldPart == null) {
2163                 // No old path.
2164                 return fromEncoded("/" + newSegment);
2165             }
2166 
2167             String oldPath = oldPart.getEncoded();
2168 
2169             if (oldPath == null) {
2170                 oldPath = "";
2171             }
2172 
2173             int oldPathLength = oldPath.length();
2174             String newPath;
2175             if (oldPathLength == 0) {
2176                 // No old path.
2177                 newPath = "/" + newSegment;
2178             } else if (oldPath.charAt(oldPathLength - 1) == '/') {
2179                 newPath = oldPath + newSegment;
2180             } else {
2181                 newPath = oldPath + "/" + newSegment;
2182             }
2183 
2184             return fromEncoded(newPath);
2185         }
2186 
appendDecodedSegment(PathPart oldPart, String decoded)2187         static PathPart appendDecodedSegment(PathPart oldPart, String decoded) {
2188             String encoded = encode(decoded);
2189 
2190             // TODO: Should we reuse old PathSegments? Probably not.
2191             return appendEncodedSegment(oldPart, encoded);
2192         }
2193 
readFrom(Parcel parcel)2194         static PathPart readFrom(Parcel parcel) {
2195             int representation = parcel.readInt();
2196             switch (representation) {
2197                 case Representation.BOTH:
2198                     return from(parcel.readString(), parcel.readString());
2199                 case Representation.ENCODED:
2200                     return fromEncoded(parcel.readString());
2201                 case Representation.DECODED:
2202                     return fromDecoded(parcel.readString());
2203                 default:
2204                     throw new IllegalArgumentException("Bad representation: " + representation);
2205             }
2206         }
2207 
2208         /**
2209          * Creates a path from the encoded string.
2210          *
2211          * @param encoded part string
2212          */
fromEncoded(String encoded)2213         static PathPart fromEncoded(String encoded) {
2214             return from(encoded, NOT_CACHED);
2215         }
2216 
2217         /**
2218          * Creates a path from the decoded string.
2219          *
2220          * @param decoded part string
2221          */
fromDecoded(String decoded)2222         static PathPart fromDecoded(String decoded) {
2223             return from(NOT_CACHED, decoded);
2224         }
2225 
2226         /**
2227          * Creates a path from the encoded and decoded strings.
2228          *
2229          * @param encoded part string
2230          * @param decoded part string
2231          */
from(String encoded, String decoded)2232         static PathPart from(String encoded, String decoded) {
2233             if (encoded == null) {
2234                 return NULL;
2235             }
2236 
2237             if (encoded.length() == 0) {
2238                 return EMPTY;
2239             }
2240 
2241             return new PathPart(encoded, decoded);
2242         }
2243 
2244         /**
2245          * Prepends path values with "/" if they're present, not empty, and
2246          * they don't already start with "/".
2247          */
makeAbsolute(PathPart oldPart)2248         static PathPart makeAbsolute(PathPart oldPart) {
2249             @SuppressWarnings("StringEquality")
2250             boolean encodedCached = oldPart.encoded != NOT_CACHED;
2251 
2252             // We don't care which version we use, and we don't want to force
2253             // unneccessary encoding/decoding.
2254             String oldPath = encodedCached ? oldPart.encoded : oldPart.decoded;
2255 
2256             if (oldPath == null || oldPath.length() == 0
2257                     || oldPath.startsWith("/")) {
2258                 return oldPart;
2259             }
2260 
2261             // Prepend encoded string if present.
2262             String newEncoded = encodedCached
2263                     ? "/" + oldPart.encoded : NOT_CACHED;
2264 
2265             // Prepend decoded string if present.
2266             @SuppressWarnings("StringEquality")
2267             boolean decodedCached = oldPart.decoded != NOT_CACHED;
2268             String newDecoded = decodedCached
2269                     ? "/" + oldPart.decoded
2270                     : NOT_CACHED;
2271 
2272             return new PathPart(newEncoded, newDecoded);
2273         }
2274     }
2275 
2276     /**
2277      * Creates a new Uri by appending an already-encoded path segment to a
2278      * base Uri.
2279      *
2280      * @param baseUri Uri to append path segment to
2281      * @param pathSegment encoded path segment to append
2282      * @return a new Uri based on baseUri with the given segment appended to
2283      *  the path
2284      * @throws NullPointerException if baseUri is null
2285      */
withAppendedPath(Uri baseUri, String pathSegment)2286     public static Uri withAppendedPath(Uri baseUri, String pathSegment) {
2287         Builder builder = baseUri.buildUpon();
2288         builder = builder.appendEncodedPath(pathSegment);
2289         return builder.build();
2290     }
2291 }
2292