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