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