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