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