• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2007 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.net;
18 
19 import android.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 path
724                           // Per http://url.spec.whatwg.org/#host-state, the \ character
725                           // is treated as if it were a / character when encountered in a
726                           // host
727                         case '?': // Start of query
728                         case '#': // Start of fragment
729                             break LOOP;
730                     }
731                     end++;
732                 }
733 
734                 return uriString.substring(ssi + 3, end);
735             } else {
736                 return null;
737             }
738 
739         }
740 
741         /**
742          * Parses a path out of this given URI string.
743          *
744          * @param uriString URI string
745          * @param ssi scheme separator index, -1 for a relative URI
746          *
747          * @return the path
748          */
parsePath(String uriString, int ssi)749         static String parsePath(String uriString, int ssi) {
750             int length = uriString.length();
751 
752             // Find start of path.
753             int pathStart;
754             if (length > ssi + 2
755                     && uriString.charAt(ssi + 1) == '/'
756                     && uriString.charAt(ssi + 2) == '/') {
757                 // Skip over authority to path.
758                 pathStart = ssi + 3;
759                 LOOP: while (pathStart < length) {
760                     switch (uriString.charAt(pathStart)) {
761                         case '?': // Start of query
762                         case '#': // Start of fragment
763                             return ""; // Empty path.
764                         case '/': // Start of path!
765                         case '\\':// Start of path!
766                           // Per http://url.spec.whatwg.org/#host-state, the \ character
767                           // is treated as if it were a / character when encountered in a
768                           // host
769                             break LOOP;
770                     }
771                     pathStart++;
772                 }
773             } else {
774                 // Path starts immediately after scheme separator.
775                 pathStart = ssi + 1;
776             }
777 
778             // Find end of path.
779             int pathEnd = pathStart;
780             LOOP: while (pathEnd < length) {
781                 switch (uriString.charAt(pathEnd)) {
782                     case '?': // Start of query
783                     case '#': // Start of fragment
784                         break LOOP;
785                 }
786                 pathEnd++;
787             }
788 
789             return uriString.substring(pathStart, pathEnd);
790         }
791 
buildUpon()792         public Builder buildUpon() {
793             if (isHierarchical()) {
794                 return new Builder()
795                         .scheme(getScheme())
796                         .authority(getAuthorityPart())
797                         .path(getPathPart())
798                         .query(getQueryPart())
799                         .fragment(getFragmentPart());
800             } else {
801                 return new Builder()
802                         .scheme(getScheme())
803                         .opaquePart(getSsp())
804                         .fragment(getFragmentPart());
805             }
806         }
807     }
808 
809     /**
810      * Creates an opaque Uri from the given components. Encodes the ssp
811      * which means this method cannot be used to create hierarchical URIs.
812      *
813      * @param scheme of the URI
814      * @param ssp scheme-specific-part, everything between the
815      *  scheme separator (':') and the fragment separator ('#'), which will
816      *  get encoded
817      * @param fragment fragment, everything after the '#', null if undefined,
818      *  will get encoded
819      *
820      * @throws NullPointerException if scheme or ssp is null
821      * @return Uri composed of the given scheme, ssp, and fragment
822      *
823      * @see Builder if you don't want the ssp and fragment to be encoded
824      */
fromParts(String scheme, String ssp, String fragment)825     public static Uri fromParts(String scheme, String ssp,
826             String fragment) {
827         if (scheme == null) {
828             throw new NullPointerException("scheme");
829         }
830         if (ssp == null) {
831             throw new NullPointerException("ssp");
832         }
833 
834         return new OpaqueUri(scheme, Part.fromDecoded(ssp),
835                 Part.fromDecoded(fragment));
836     }
837 
838     /**
839      * Opaque URI.
840      */
841     private static class OpaqueUri extends Uri {
842 
843         /** Used in parcelling. */
844         static final int TYPE_ID = 2;
845 
846         private final String scheme;
847         private final Part ssp;
848         private final Part fragment;
849 
OpaqueUri(String scheme, Part ssp, Part fragment)850         private OpaqueUri(String scheme, Part ssp, Part fragment) {
851             this.scheme = scheme;
852             this.ssp = ssp;
853             this.fragment = fragment == null ? Part.NULL : fragment;
854         }
855 
readFrom(Parcel parcel)856         static Uri readFrom(Parcel parcel) {
857             return new OpaqueUri(
858                 parcel.readString(),
859                 Part.readFrom(parcel),
860                 Part.readFrom(parcel)
861             );
862         }
863 
describeContents()864         public int describeContents() {
865             return 0;
866         }
867 
writeToParcel(Parcel parcel, int flags)868         public void writeToParcel(Parcel parcel, int flags) {
869             parcel.writeInt(TYPE_ID);
870             parcel.writeString(scheme);
871             ssp.writeTo(parcel);
872             fragment.writeTo(parcel);
873         }
874 
isHierarchical()875         public boolean isHierarchical() {
876             return false;
877         }
878 
isRelative()879         public boolean isRelative() {
880             return scheme == null;
881         }
882 
getScheme()883         public String getScheme() {
884             return this.scheme;
885         }
886 
getEncodedSchemeSpecificPart()887         public String getEncodedSchemeSpecificPart() {
888             return ssp.getEncoded();
889         }
890 
getSchemeSpecificPart()891         public String getSchemeSpecificPart() {
892             return ssp.getDecoded();
893         }
894 
getAuthority()895         public String getAuthority() {
896             return null;
897         }
898 
getEncodedAuthority()899         public String getEncodedAuthority() {
900             return null;
901         }
902 
getPath()903         public String getPath() {
904             return null;
905         }
906 
getEncodedPath()907         public String getEncodedPath() {
908             return null;
909         }
910 
getQuery()911         public String getQuery() {
912             return null;
913         }
914 
getEncodedQuery()915         public String getEncodedQuery() {
916             return null;
917         }
918 
getFragment()919         public String getFragment() {
920             return fragment.getDecoded();
921         }
922 
getEncodedFragment()923         public String getEncodedFragment() {
924             return fragment.getEncoded();
925         }
926 
getPathSegments()927         public List<String> getPathSegments() {
928             return Collections.emptyList();
929         }
930 
getLastPathSegment()931         public String getLastPathSegment() {
932             return null;
933         }
934 
getUserInfo()935         public String getUserInfo() {
936             return null;
937         }
938 
getEncodedUserInfo()939         public String getEncodedUserInfo() {
940             return null;
941         }
942 
getHost()943         public String getHost() {
944             return null;
945         }
946 
getPort()947         public int getPort() {
948             return -1;
949         }
950 
951         private volatile String cachedString = NOT_CACHED;
952 
toString()953         public String toString() {
954             @SuppressWarnings("StringEquality")
955             boolean cached = cachedString != NOT_CACHED;
956             if (cached) {
957                 return cachedString;
958             }
959 
960             StringBuilder sb = new StringBuilder();
961 
962             sb.append(scheme).append(':');
963             sb.append(getEncodedSchemeSpecificPart());
964 
965             if (!fragment.isEmpty()) {
966                 sb.append('#').append(fragment.getEncoded());
967             }
968 
969             return cachedString = sb.toString();
970         }
971 
buildUpon()972         public Builder buildUpon() {
973             return new Builder()
974                     .scheme(this.scheme)
975                     .opaquePart(this.ssp)
976                     .fragment(this.fragment);
977         }
978     }
979 
980     /**
981      * Wrapper for path segment array.
982      */
983     static class PathSegments extends AbstractList<String>
984             implements RandomAccess {
985 
986         static final PathSegments EMPTY = new PathSegments(null, 0);
987 
988         final String[] segments;
989         final int size;
990 
PathSegments(String[] segments, int size)991         PathSegments(String[] segments, int size) {
992             this.segments = segments;
993             this.size = size;
994         }
995 
get(int index)996         public String get(int index) {
997             if (index >= size) {
998                 throw new IndexOutOfBoundsException();
999             }
1000 
1001             return segments[index];
1002         }
1003 
size()1004         public int size() {
1005             return this.size;
1006         }
1007     }
1008 
1009     /**
1010      * Builds PathSegments.
1011      */
1012     static class PathSegmentsBuilder {
1013 
1014         String[] segments;
1015         int size = 0;
1016 
add(String segment)1017         void add(String segment) {
1018             if (segments == null) {
1019                 segments = new String[4];
1020             } else if (size + 1 == segments.length) {
1021                 String[] expanded = new String[segments.length * 2];
1022                 System.arraycopy(segments, 0, expanded, 0, segments.length);
1023                 segments = expanded;
1024             }
1025 
1026             segments[size++] = segment;
1027         }
1028 
build()1029         PathSegments build() {
1030             if (segments == null) {
1031                 return PathSegments.EMPTY;
1032             }
1033 
1034             try {
1035                 return new PathSegments(segments, size);
1036             } finally {
1037                 // Makes sure this doesn't get reused.
1038                 segments = null;
1039             }
1040         }
1041     }
1042 
1043     /**
1044      * Support for hierarchical URIs.
1045      */
1046     private abstract static class AbstractHierarchicalUri extends Uri {
1047 
getLastPathSegment()1048         public String getLastPathSegment() {
1049             // TODO: If we haven't parsed all of the segments already, just
1050             // grab the last one directly so we only allocate one string.
1051 
1052             List<String> segments = getPathSegments();
1053             int size = segments.size();
1054             if (size == 0) {
1055                 return null;
1056             }
1057             return segments.get(size - 1);
1058         }
1059 
1060         private Part userInfo;
1061 
getUserInfoPart()1062         private Part getUserInfoPart() {
1063             return userInfo == null
1064                     ? userInfo = Part.fromEncoded(parseUserInfo()) : userInfo;
1065         }
1066 
getEncodedUserInfo()1067         public final String getEncodedUserInfo() {
1068             return getUserInfoPart().getEncoded();
1069         }
1070 
parseUserInfo()1071         private String parseUserInfo() {
1072             String authority = getEncodedAuthority();
1073             if (authority == null) {
1074                 return null;
1075             }
1076 
1077             int end = authority.lastIndexOf('@');
1078             return end == NOT_FOUND ? null : authority.substring(0, end);
1079         }
1080 
getUserInfo()1081         public String getUserInfo() {
1082             return getUserInfoPart().getDecoded();
1083         }
1084 
1085         private volatile String host = NOT_CACHED;
1086 
getHost()1087         public String getHost() {
1088             @SuppressWarnings("StringEquality")
1089             boolean cached = (host != NOT_CACHED);
1090             return cached ? host
1091                     : (host = parseHost());
1092         }
1093 
parseHost()1094         private String parseHost() {
1095             String authority = getEncodedAuthority();
1096             if (authority == null) {
1097                 return null;
1098             }
1099 
1100             // Parse out user info and then port.
1101             int userInfoSeparator = authority.lastIndexOf('@');
1102             int portSeparator = authority.indexOf(':', userInfoSeparator);
1103 
1104             String encodedHost = portSeparator == NOT_FOUND
1105                     ? authority.substring(userInfoSeparator + 1)
1106                     : authority.substring(userInfoSeparator + 1, portSeparator);
1107 
1108             return decode(encodedHost);
1109         }
1110 
1111         private volatile int port = NOT_CALCULATED;
1112 
getPort()1113         public int getPort() {
1114             return port == NOT_CALCULATED
1115                     ? port = parsePort()
1116                     : port;
1117         }
1118 
parsePort()1119         private int parsePort() {
1120             String authority = getEncodedAuthority();
1121             if (authority == null) {
1122                 return -1;
1123             }
1124 
1125             // Make sure we look for the port separtor *after* the user info
1126             // separator. We have URLs with a ':' in the user info.
1127             int userInfoSeparator = authority.lastIndexOf('@');
1128             int portSeparator = authority.indexOf(':', userInfoSeparator);
1129 
1130             if (portSeparator == NOT_FOUND) {
1131                 return -1;
1132             }
1133 
1134             String portString = decode(authority.substring(portSeparator + 1));
1135             try {
1136                 return Integer.parseInt(portString);
1137             } catch (NumberFormatException e) {
1138                 Log.w(LOG, "Error parsing port string.", e);
1139                 return -1;
1140             }
1141         }
1142     }
1143 
1144     /**
1145      * Hierarchical Uri.
1146      */
1147     private static class HierarchicalUri extends AbstractHierarchicalUri {
1148 
1149         /** Used in parcelling. */
1150         static final int TYPE_ID = 3;
1151 
1152         private final String scheme; // can be null
1153         private final Part authority;
1154         private final PathPart path;
1155         private final Part query;
1156         private final Part fragment;
1157 
HierarchicalUri(String scheme, Part authority, PathPart path, Part query, Part fragment)1158         private HierarchicalUri(String scheme, Part authority, PathPart path,
1159                 Part query, Part fragment) {
1160             this.scheme = scheme;
1161             this.authority = Part.nonNull(authority);
1162             this.path = path == null ? PathPart.NULL : path;
1163             this.query = Part.nonNull(query);
1164             this.fragment = Part.nonNull(fragment);
1165         }
1166 
readFrom(Parcel parcel)1167         static Uri readFrom(Parcel parcel) {
1168             return new HierarchicalUri(
1169                 parcel.readString(),
1170                 Part.readFrom(parcel),
1171                 PathPart.readFrom(parcel),
1172                 Part.readFrom(parcel),
1173                 Part.readFrom(parcel)
1174             );
1175         }
1176 
describeContents()1177         public int describeContents() {
1178             return 0;
1179         }
1180 
writeToParcel(Parcel parcel, int flags)1181         public void writeToParcel(Parcel parcel, int flags) {
1182             parcel.writeInt(TYPE_ID);
1183             parcel.writeString(scheme);
1184             authority.writeTo(parcel);
1185             path.writeTo(parcel);
1186             query.writeTo(parcel);
1187             fragment.writeTo(parcel);
1188         }
1189 
isHierarchical()1190         public boolean isHierarchical() {
1191             return true;
1192         }
1193 
isRelative()1194         public boolean isRelative() {
1195             return scheme == null;
1196         }
1197 
getScheme()1198         public String getScheme() {
1199             return scheme;
1200         }
1201 
1202         private Part ssp;
1203 
getSsp()1204         private Part getSsp() {
1205             return ssp == null
1206                     ? ssp = Part.fromEncoded(makeSchemeSpecificPart()) : ssp;
1207         }
1208 
getEncodedSchemeSpecificPart()1209         public String getEncodedSchemeSpecificPart() {
1210             return getSsp().getEncoded();
1211         }
1212 
getSchemeSpecificPart()1213         public String getSchemeSpecificPart() {
1214             return getSsp().getDecoded();
1215         }
1216 
1217         /**
1218          * Creates the encoded scheme-specific part from its sub parts.
1219          */
makeSchemeSpecificPart()1220         private String makeSchemeSpecificPart() {
1221             StringBuilder builder = new StringBuilder();
1222             appendSspTo(builder);
1223             return builder.toString();
1224         }
1225 
appendSspTo(StringBuilder builder)1226         private void appendSspTo(StringBuilder builder) {
1227             String encodedAuthority = authority.getEncoded();
1228             if (encodedAuthority != null) {
1229                 // Even if the authority is "", we still want to append "//".
1230                 builder.append("//").append(encodedAuthority);
1231             }
1232 
1233             String encodedPath = path.getEncoded();
1234             if (encodedPath != null) {
1235                 builder.append(encodedPath);
1236             }
1237 
1238             if (!query.isEmpty()) {
1239                 builder.append('?').append(query.getEncoded());
1240             }
1241         }
1242 
getAuthority()1243         public String getAuthority() {
1244             return this.authority.getDecoded();
1245         }
1246 
getEncodedAuthority()1247         public String getEncodedAuthority() {
1248             return this.authority.getEncoded();
1249         }
1250 
getEncodedPath()1251         public String getEncodedPath() {
1252             return this.path.getEncoded();
1253         }
1254 
getPath()1255         public String getPath() {
1256             return this.path.getDecoded();
1257         }
1258 
getQuery()1259         public String getQuery() {
1260             return this.query.getDecoded();
1261         }
1262 
getEncodedQuery()1263         public String getEncodedQuery() {
1264             return this.query.getEncoded();
1265         }
1266 
getFragment()1267         public String getFragment() {
1268             return this.fragment.getDecoded();
1269         }
1270 
getEncodedFragment()1271         public String getEncodedFragment() {
1272             return this.fragment.getEncoded();
1273         }
1274 
getPathSegments()1275         public List<String> getPathSegments() {
1276             return this.path.getPathSegments();
1277         }
1278 
1279         private volatile String uriString = NOT_CACHED;
1280 
1281         @Override
toString()1282         public String toString() {
1283             @SuppressWarnings("StringEquality")
1284             boolean cached = (uriString != NOT_CACHED);
1285             return cached ? uriString
1286                     : (uriString = makeUriString());
1287         }
1288 
makeUriString()1289         private String makeUriString() {
1290             StringBuilder builder = new StringBuilder();
1291 
1292             if (scheme != null) {
1293                 builder.append(scheme).append(':');
1294             }
1295 
1296             appendSspTo(builder);
1297 
1298             if (!fragment.isEmpty()) {
1299                 builder.append('#').append(fragment.getEncoded());
1300             }
1301 
1302             return builder.toString();
1303         }
1304 
buildUpon()1305         public Builder buildUpon() {
1306             return new Builder()
1307                     .scheme(scheme)
1308                     .authority(authority)
1309                     .path(path)
1310                     .query(query)
1311                     .fragment(fragment);
1312         }
1313     }
1314 
1315     /**
1316      * Helper class for building or manipulating URI references. Not safe for
1317      * concurrent use.
1318      *
1319      * <p>An absolute hierarchical URI reference follows the pattern:
1320      * {@code <scheme>://<authority><absolute path>?<query>#<fragment>}
1321      *
1322      * <p>Relative URI references (which are always hierarchical) follow one
1323      * of two patterns: {@code <relative or absolute path>?<query>#<fragment>}
1324      * or {@code //<authority><absolute path>?<query>#<fragment>}
1325      *
1326      * <p>An opaque URI follows this pattern:
1327      * {@code <scheme>:<opaque part>#<fragment>}
1328      *
1329      * <p>Use {@link Uri#buildUpon()} to obtain a builder representing an existing URI.
1330      */
1331     public static final class Builder {
1332 
1333         private String scheme;
1334         private Part opaquePart;
1335         private Part authority;
1336         private PathPart path;
1337         private Part query;
1338         private Part fragment;
1339 
1340         /**
1341          * Constructs a new Builder.
1342          */
Builder()1343         public Builder() {}
1344 
1345         /**
1346          * Sets the scheme.
1347          *
1348          * @param scheme name or {@code null} if this is a relative Uri
1349          */
scheme(String scheme)1350         public Builder scheme(String scheme) {
1351             this.scheme = scheme;
1352             return this;
1353         }
1354 
opaquePart(Part opaquePart)1355         Builder opaquePart(Part opaquePart) {
1356             this.opaquePart = opaquePart;
1357             return this;
1358         }
1359 
1360         /**
1361          * Encodes and sets the given opaque scheme-specific-part.
1362          *
1363          * @param opaquePart decoded opaque part
1364          */
opaquePart(String opaquePart)1365         public Builder opaquePart(String opaquePart) {
1366             return opaquePart(Part.fromDecoded(opaquePart));
1367         }
1368 
1369         /**
1370          * Sets the previously encoded opaque scheme-specific-part.
1371          *
1372          * @param opaquePart encoded opaque part
1373          */
encodedOpaquePart(String opaquePart)1374         public Builder encodedOpaquePart(String opaquePart) {
1375             return opaquePart(Part.fromEncoded(opaquePart));
1376         }
1377 
authority(Part authority)1378         Builder authority(Part authority) {
1379             // This URI will be hierarchical.
1380             this.opaquePart = null;
1381 
1382             this.authority = authority;
1383             return this;
1384         }
1385 
1386         /**
1387          * Encodes and sets the authority.
1388          */
authority(String authority)1389         public Builder authority(String authority) {
1390             return authority(Part.fromDecoded(authority));
1391         }
1392 
1393         /**
1394          * Sets the previously encoded authority.
1395          */
encodedAuthority(String authority)1396         public Builder encodedAuthority(String authority) {
1397             return authority(Part.fromEncoded(authority));
1398         }
1399 
path(PathPart path)1400         Builder path(PathPart path) {
1401             // This URI will be hierarchical.
1402             this.opaquePart = null;
1403 
1404             this.path = path;
1405             return this;
1406         }
1407 
1408         /**
1409          * Sets the path. Leaves '/' characters intact but encodes others as
1410          * necessary.
1411          *
1412          * <p>If the path is not null and doesn't start with a '/', and if
1413          * you specify a scheme and/or authority, the builder will prepend the
1414          * given path with a '/'.
1415          */
path(String path)1416         public Builder path(String path) {
1417             return path(PathPart.fromDecoded(path));
1418         }
1419 
1420         /**
1421          * Sets the previously encoded path.
1422          *
1423          * <p>If the path is not null and doesn't start with a '/', and if
1424          * you specify a scheme and/or authority, the builder will prepend the
1425          * given path with a '/'.
1426          */
encodedPath(String path)1427         public Builder encodedPath(String path) {
1428             return path(PathPart.fromEncoded(path));
1429         }
1430 
1431         /**
1432          * Encodes the given segment and appends it to the path.
1433          */
appendPath(String newSegment)1434         public Builder appendPath(String newSegment) {
1435             return path(PathPart.appendDecodedSegment(path, newSegment));
1436         }
1437 
1438         /**
1439          * Appends the given segment to the path.
1440          */
appendEncodedPath(String newSegment)1441         public Builder appendEncodedPath(String newSegment) {
1442             return path(PathPart.appendEncodedSegment(path, newSegment));
1443         }
1444 
query(Part query)1445         Builder query(Part query) {
1446             // This URI will be hierarchical.
1447             this.opaquePart = null;
1448 
1449             this.query = query;
1450             return this;
1451         }
1452 
1453         /**
1454          * Encodes and sets the query.
1455          */
query(String query)1456         public Builder query(String query) {
1457             return query(Part.fromDecoded(query));
1458         }
1459 
1460         /**
1461          * Sets the previously encoded query.
1462          */
encodedQuery(String query)1463         public Builder encodedQuery(String query) {
1464             return query(Part.fromEncoded(query));
1465         }
1466 
fragment(Part fragment)1467         Builder fragment(Part fragment) {
1468             this.fragment = fragment;
1469             return this;
1470         }
1471 
1472         /**
1473          * Encodes and sets the fragment.
1474          */
fragment(String fragment)1475         public Builder fragment(String fragment) {
1476             return fragment(Part.fromDecoded(fragment));
1477         }
1478 
1479         /**
1480          * Sets the previously encoded fragment.
1481          */
encodedFragment(String fragment)1482         public Builder encodedFragment(String fragment) {
1483             return fragment(Part.fromEncoded(fragment));
1484         }
1485 
1486         /**
1487          * Encodes the key and value and then appends the parameter to the
1488          * query string.
1489          *
1490          * @param key which will be encoded
1491          * @param value which will be encoded
1492          */
appendQueryParameter(String key, String value)1493         public Builder appendQueryParameter(String key, String value) {
1494             // This URI will be hierarchical.
1495             this.opaquePart = null;
1496 
1497             String encodedParameter = encode(key, null) + "="
1498                     + encode(value, null);
1499 
1500             if (query == null) {
1501                 query = Part.fromEncoded(encodedParameter);
1502                 return this;
1503             }
1504 
1505             String oldQuery = query.getEncoded();
1506             if (oldQuery == null || oldQuery.length() == 0) {
1507                 query = Part.fromEncoded(encodedParameter);
1508             } else {
1509                 query = Part.fromEncoded(oldQuery + "&" + encodedParameter);
1510             }
1511 
1512             return this;
1513         }
1514 
1515         /**
1516          * Clears the the previously set query.
1517          */
clearQuery()1518         public Builder clearQuery() {
1519           return query((Part) null);
1520         }
1521 
1522         /**
1523          * Constructs a Uri with the current attributes.
1524          *
1525          * @throws UnsupportedOperationException if the URI is opaque and the
1526          *  scheme is null
1527          */
build()1528         public Uri build() {
1529             if (opaquePart != null) {
1530                 if (this.scheme == null) {
1531                     throw new UnsupportedOperationException(
1532                             "An opaque URI must have a scheme.");
1533                 }
1534 
1535                 return new OpaqueUri(scheme, opaquePart, fragment);
1536             } else {
1537                 // Hierarchical URIs should not return null for getPath().
1538                 PathPart path = this.path;
1539                 if (path == null || path == PathPart.NULL) {
1540                     path = PathPart.EMPTY;
1541                 } else {
1542                     // If we have a scheme and/or authority, the path must
1543                     // be absolute. Prepend it with a '/' if necessary.
1544                     if (hasSchemeOrAuthority()) {
1545                         path = PathPart.makeAbsolute(path);
1546                     }
1547                 }
1548 
1549                 return new HierarchicalUri(
1550                         scheme, authority, path, query, fragment);
1551             }
1552         }
1553 
hasSchemeOrAuthority()1554         private boolean hasSchemeOrAuthority() {
1555             return scheme != null
1556                     || (authority != null && authority != Part.NULL);
1557 
1558         }
1559 
1560         @Override
toString()1561         public String toString() {
1562             return build().toString();
1563         }
1564     }
1565 
1566     /**
1567      * Returns a set of the unique names of all query parameters. Iterating
1568      * over the set will return the names in order of their first occurrence.
1569      *
1570      * @throws UnsupportedOperationException if this isn't a hierarchical URI
1571      *
1572      * @return a set of decoded names
1573      */
getQueryParameterNames()1574     public Set<String> getQueryParameterNames() {
1575         if (isOpaque()) {
1576             throw new UnsupportedOperationException(NOT_HIERARCHICAL);
1577         }
1578 
1579         String query = getEncodedQuery();
1580         if (query == null) {
1581             return Collections.emptySet();
1582         }
1583 
1584         Set<String> names = new LinkedHashSet<String>();
1585         int start = 0;
1586         do {
1587             int next = query.indexOf('&', start);
1588             int end = (next == -1) ? query.length() : next;
1589 
1590             int separator = query.indexOf('=', start);
1591             if (separator > end || separator == -1) {
1592                 separator = end;
1593             }
1594 
1595             String name = query.substring(start, separator);
1596             names.add(decode(name));
1597 
1598             // Move start to end of name.
1599             start = end + 1;
1600         } while (start < query.length());
1601 
1602         return Collections.unmodifiableSet(names);
1603     }
1604 
1605     /**
1606      * Searches the query string for parameter values with the given key.
1607      *
1608      * @param key which will be encoded
1609      *
1610      * @throws UnsupportedOperationException if this isn't a hierarchical URI
1611      * @throws NullPointerException if key is null
1612      * @return a list of decoded values
1613      */
getQueryParameters(String key)1614     public List<String> getQueryParameters(String key) {
1615         if (isOpaque()) {
1616             throw new UnsupportedOperationException(NOT_HIERARCHICAL);
1617         }
1618         if (key == null) {
1619           throw new NullPointerException("key");
1620         }
1621 
1622         String query = getEncodedQuery();
1623         if (query == null) {
1624             return Collections.emptyList();
1625         }
1626 
1627         String encodedKey;
1628         try {
1629             encodedKey = URLEncoder.encode(key, DEFAULT_ENCODING);
1630         } catch (UnsupportedEncodingException e) {
1631             throw new AssertionError(e);
1632         }
1633 
1634         ArrayList<String> values = new ArrayList<String>();
1635 
1636         int start = 0;
1637         do {
1638             int nextAmpersand = query.indexOf('&', start);
1639             int end = nextAmpersand != -1 ? nextAmpersand : query.length();
1640 
1641             int separator = query.indexOf('=', start);
1642             if (separator > end || separator == -1) {
1643                 separator = end;
1644             }
1645 
1646             if (separator - start == encodedKey.length()
1647                     && query.regionMatches(start, encodedKey, 0, encodedKey.length())) {
1648                 if (separator == end) {
1649                   values.add("");
1650                 } else {
1651                   values.add(decode(query.substring(separator + 1, end)));
1652                 }
1653             }
1654 
1655             // Move start to end of name.
1656             if (nextAmpersand != -1) {
1657                 start = nextAmpersand + 1;
1658             } else {
1659                 break;
1660             }
1661         } while (true);
1662 
1663         return Collections.unmodifiableList(values);
1664     }
1665 
1666     /**
1667      * Searches the query string for the first value with the given key.
1668      *
1669      * <p><strong>Warning:</strong> Prior to Jelly Bean, this decoded
1670      * the '+' character as '+' rather than ' '.
1671      *
1672      * @param key which will be encoded
1673      * @throws UnsupportedOperationException if this isn't a hierarchical URI
1674      * @throws NullPointerException if key is null
1675      * @return the decoded value or null if no parameter is found
1676      */
getQueryParameter(String key)1677     public String getQueryParameter(String key) {
1678         if (isOpaque()) {
1679             throw new UnsupportedOperationException(NOT_HIERARCHICAL);
1680         }
1681         if (key == null) {
1682             throw new NullPointerException("key");
1683         }
1684 
1685         final String query = getEncodedQuery();
1686         if (query == null) {
1687             return null;
1688         }
1689 
1690         final String encodedKey = encode(key, null);
1691         final int length = query.length();
1692         int start = 0;
1693         do {
1694             int nextAmpersand = query.indexOf('&', start);
1695             int end = nextAmpersand != -1 ? nextAmpersand : length;
1696 
1697             int separator = query.indexOf('=', start);
1698             if (separator > end || separator == -1) {
1699                 separator = end;
1700             }
1701 
1702             if (separator - start == encodedKey.length()
1703                     && query.regionMatches(start, encodedKey, 0, encodedKey.length())) {
1704                 if (separator == end) {
1705                     return "";
1706                 } else {
1707                     String encodedValue = query.substring(separator + 1, end);
1708                     return UriCodec.decode(encodedValue, true, StandardCharsets.UTF_8, false);
1709                 }
1710             }
1711 
1712             // Move start to end of name.
1713             if (nextAmpersand != -1) {
1714                 start = nextAmpersand + 1;
1715             } else {
1716                 break;
1717             }
1718         } while (true);
1719         return null;
1720     }
1721 
1722     /**
1723      * Searches the query string for the first value with the given key and interprets it
1724      * as a boolean value. "false" and "0" are interpreted as <code>false</code>, everything
1725      * else is interpreted as <code>true</code>.
1726      *
1727      * @param key which will be decoded
1728      * @param defaultValue the default value to return if there is no query parameter for key
1729      * @return the boolean interpretation of the query parameter key
1730      */
getBooleanQueryParameter(String key, boolean defaultValue)1731     public boolean getBooleanQueryParameter(String key, boolean defaultValue) {
1732         String flag = getQueryParameter(key);
1733         if (flag == null) {
1734             return defaultValue;
1735         }
1736         flag = flag.toLowerCase(Locale.ROOT);
1737         return (!"false".equals(flag) && !"0".equals(flag));
1738     }
1739 
1740     /**
1741      * Return an equivalent URI with a lowercase scheme component.
1742      * This aligns the Uri with Android best practices for
1743      * intent filtering.
1744      *
1745      * <p>For example, "HTTP://www.android.com" becomes
1746      * "http://www.android.com"
1747      *
1748      * <p>All URIs received from outside Android (such as user input,
1749      * or external sources like Bluetooth, NFC, or the Internet) should
1750      * be normalized before they are used to create an Intent.
1751      *
1752      * <p class="note">This method does <em>not</em> validate bad URI's,
1753      * or 'fix' poorly formatted URI's - so do not use it for input validation.
1754      * A Uri will always be returned, even if the Uri is badly formatted to
1755      * begin with and a scheme component cannot be found.
1756      *
1757      * @return normalized Uri (never null)
1758      * @see android.content.Intent#setData
1759      * @see android.content.Intent#setDataAndNormalize
1760      */
normalizeScheme()1761     public Uri normalizeScheme() {
1762         String scheme = getScheme();
1763         if (scheme == null) return this;  // give up
1764         String lowerScheme = scheme.toLowerCase(Locale.ROOT);
1765         if (scheme.equals(lowerScheme)) return this;  // no change
1766 
1767         return buildUpon().scheme(lowerScheme).build();
1768     }
1769 
1770     /** Identifies a null parcelled Uri. */
1771     private static final int NULL_TYPE_ID = 0;
1772 
1773     /**
1774      * Reads Uris from Parcels.
1775      */
1776     public static final Parcelable.Creator<Uri> CREATOR
1777             = new Parcelable.Creator<Uri>() {
1778         public Uri createFromParcel(Parcel in) {
1779             int type = in.readInt();
1780             switch (type) {
1781                 case NULL_TYPE_ID: return null;
1782                 case StringUri.TYPE_ID: return StringUri.readFrom(in);
1783                 case OpaqueUri.TYPE_ID: return OpaqueUri.readFrom(in);
1784                 case HierarchicalUri.TYPE_ID:
1785                     return HierarchicalUri.readFrom(in);
1786             }
1787 
1788             throw new IllegalArgumentException("Unknown URI type: " + type);
1789         }
1790 
1791         public Uri[] newArray(int size) {
1792             return new Uri[size];
1793         }
1794     };
1795 
1796     /**
1797      * Writes a Uri to a Parcel.
1798      *
1799      * @param out parcel to write to
1800      * @param uri to write, can be null
1801      */
writeToParcel(Parcel out, Uri uri)1802     public static void writeToParcel(Parcel out, Uri uri) {
1803         if (uri == null) {
1804             out.writeInt(NULL_TYPE_ID);
1805         } else {
1806             uri.writeToParcel(out, 0);
1807         }
1808     }
1809 
1810     private static final char[] HEX_DIGITS = "0123456789ABCDEF".toCharArray();
1811 
1812     /**
1813      * Encodes characters in the given string as '%'-escaped octets
1814      * using the UTF-8 scheme. Leaves letters ("A-Z", "a-z"), numbers
1815      * ("0-9"), and unreserved characters ("_-!.~'()*") intact. Encodes
1816      * all other characters.
1817      *
1818      * @param s string to encode
1819      * @return an encoded version of s suitable for use as a URI component,
1820      *  or null if s is null
1821      */
encode(String s)1822     public static String encode(String s) {
1823         return encode(s, null);
1824     }
1825 
1826     /**
1827      * Encodes characters in the given string as '%'-escaped octets
1828      * using the UTF-8 scheme. Leaves letters ("A-Z", "a-z"), numbers
1829      * ("0-9"), and unreserved characters ("_-!.~'()*") intact. Encodes
1830      * all other characters with the exception of those specified in the
1831      * allow argument.
1832      *
1833      * @param s string to encode
1834      * @param allow set of additional characters to allow in the encoded form,
1835      *  null if no characters should be skipped
1836      * @return an encoded version of s suitable for use as a URI component,
1837      *  or null if s is null
1838      */
encode(String s, String allow)1839     public static String encode(String s, String allow) {
1840         if (s == null) {
1841             return null;
1842         }
1843 
1844         // Lazily-initialized buffers.
1845         StringBuilder encoded = null;
1846 
1847         int oldLength = s.length();
1848 
1849         // This loop alternates between copying over allowed characters and
1850         // encoding in chunks. This results in fewer method calls and
1851         // allocations than encoding one character at a time.
1852         int current = 0;
1853         while (current < oldLength) {
1854             // Start in "copying" mode where we copy over allowed chars.
1855 
1856             // Find the next character which needs to be encoded.
1857             int nextToEncode = current;
1858             while (nextToEncode < oldLength
1859                     && isAllowed(s.charAt(nextToEncode), allow)) {
1860                 nextToEncode++;
1861             }
1862 
1863             // If there's nothing more to encode...
1864             if (nextToEncode == oldLength) {
1865                 if (current == 0) {
1866                     // We didn't need to encode anything!
1867                     return s;
1868                 } else {
1869                     // Presumably, we've already done some encoding.
1870                     encoded.append(s, current, oldLength);
1871                     return encoded.toString();
1872                 }
1873             }
1874 
1875             if (encoded == null) {
1876                 encoded = new StringBuilder();
1877             }
1878 
1879             if (nextToEncode > current) {
1880                 // Append allowed characters leading up to this point.
1881                 encoded.append(s, current, nextToEncode);
1882             } else {
1883                 // assert nextToEncode == current
1884             }
1885 
1886             // Switch to "encoding" mode.
1887 
1888             // Find the next allowed character.
1889             current = nextToEncode;
1890             int nextAllowed = current + 1;
1891             while (nextAllowed < oldLength
1892                     && !isAllowed(s.charAt(nextAllowed), allow)) {
1893                 nextAllowed++;
1894             }
1895 
1896             // Convert the substring to bytes and encode the bytes as
1897             // '%'-escaped octets.
1898             String toEncode = s.substring(current, nextAllowed);
1899             try {
1900                 byte[] bytes = toEncode.getBytes(DEFAULT_ENCODING);
1901                 int bytesLength = bytes.length;
1902                 for (int i = 0; i < bytesLength; i++) {
1903                     encoded.append('%');
1904                     encoded.append(HEX_DIGITS[(bytes[i] & 0xf0) >> 4]);
1905                     encoded.append(HEX_DIGITS[bytes[i] & 0xf]);
1906                 }
1907             } catch (UnsupportedEncodingException e) {
1908                 throw new AssertionError(e);
1909             }
1910 
1911             current = nextAllowed;
1912         }
1913 
1914         // Encoded could still be null at this point if s is empty.
1915         return encoded == null ? s : encoded.toString();
1916     }
1917 
1918     /**
1919      * Returns true if the given character is allowed.
1920      *
1921      * @param c character to check
1922      * @param allow characters to allow
1923      * @return true if the character is allowed or false if it should be
1924      *  encoded
1925      */
isAllowed(char c, String allow)1926     private static boolean isAllowed(char c, String allow) {
1927         return (c >= 'A' && c <= 'Z')
1928                 || (c >= 'a' && c <= 'z')
1929                 || (c >= '0' && c <= '9')
1930                 || "_-!.~'()*".indexOf(c) != NOT_FOUND
1931                 || (allow != null && allow.indexOf(c) != NOT_FOUND);
1932     }
1933 
1934     /**
1935      * Decodes '%'-escaped octets in the given string using the UTF-8 scheme.
1936      * Replaces invalid octets with the unicode replacement character
1937      * ("\\uFFFD").
1938      *
1939      * @param s encoded string to decode
1940      * @return the given string with escaped octets decoded, or null if
1941      *  s is null
1942      */
decode(String s)1943     public static String decode(String s) {
1944         if (s == null) {
1945             return null;
1946         }
1947         return UriCodec.decode(s, false, StandardCharsets.UTF_8, false);
1948     }
1949 
1950     /**
1951      * Support for part implementations.
1952      */
1953     static abstract class AbstractPart {
1954 
1955         /**
1956          * Enum which indicates which representation of a given part we have.
1957          */
1958         static class Representation {
1959             static final int BOTH = 0;
1960             static final int ENCODED = 1;
1961             static final int DECODED = 2;
1962         }
1963 
1964         volatile String encoded;
1965         volatile String decoded;
1966 
AbstractPart(String encoded, String decoded)1967         AbstractPart(String encoded, String decoded) {
1968             this.encoded = encoded;
1969             this.decoded = decoded;
1970         }
1971 
getEncoded()1972         abstract String getEncoded();
1973 
getDecoded()1974         final String getDecoded() {
1975             @SuppressWarnings("StringEquality")
1976             boolean hasDecoded = decoded != NOT_CACHED;
1977             return hasDecoded ? decoded : (decoded = decode(encoded));
1978         }
1979 
writeTo(Parcel parcel)1980         final void writeTo(Parcel parcel) {
1981             @SuppressWarnings("StringEquality")
1982             boolean hasEncoded = encoded != NOT_CACHED;
1983 
1984             @SuppressWarnings("StringEquality")
1985             boolean hasDecoded = decoded != NOT_CACHED;
1986 
1987             if (hasEncoded && hasDecoded) {
1988                 parcel.writeInt(Representation.BOTH);
1989                 parcel.writeString(encoded);
1990                 parcel.writeString(decoded);
1991             } else if (hasEncoded) {
1992                 parcel.writeInt(Representation.ENCODED);
1993                 parcel.writeString(encoded);
1994             } else if (hasDecoded) {
1995                 parcel.writeInt(Representation.DECODED);
1996                 parcel.writeString(decoded);
1997             } else {
1998                 throw new IllegalArgumentException("Neither encoded nor decoded");
1999             }
2000         }
2001     }
2002 
2003     /**
2004      * Immutable wrapper of encoded and decoded versions of a URI part. Lazily
2005      * creates the encoded or decoded version from the other.
2006      */
2007     static class Part extends AbstractPart {
2008 
2009         /** A part with null values. */
2010         static final Part NULL = new EmptyPart(null);
2011 
2012         /** A part with empty strings for values. */
2013         static final Part EMPTY = new EmptyPart("");
2014 
Part(String encoded, String decoded)2015         private Part(String encoded, String decoded) {
2016             super(encoded, decoded);
2017         }
2018 
isEmpty()2019         boolean isEmpty() {
2020             return false;
2021         }
2022 
getEncoded()2023         String getEncoded() {
2024             @SuppressWarnings("StringEquality")
2025             boolean hasEncoded = encoded != NOT_CACHED;
2026             return hasEncoded ? encoded : (encoded = encode(decoded));
2027         }
2028 
readFrom(Parcel parcel)2029         static Part readFrom(Parcel parcel) {
2030             int representation = parcel.readInt();
2031             switch (representation) {
2032                 case Representation.BOTH:
2033                     return from(parcel.readString(), parcel.readString());
2034                 case Representation.ENCODED:
2035                     return fromEncoded(parcel.readString());
2036                 case Representation.DECODED:
2037                     return fromDecoded(parcel.readString());
2038                 default:
2039                     throw new IllegalArgumentException("Unknown representation: "
2040                             + representation);
2041             }
2042         }
2043 
2044         /**
2045          * Returns given part or {@link #NULL} if the given part is null.
2046          */
nonNull(Part part)2047         static Part nonNull(Part part) {
2048             return part == null ? NULL : part;
2049         }
2050 
2051         /**
2052          * Creates a part from the encoded string.
2053          *
2054          * @param encoded part string
2055          */
fromEncoded(String encoded)2056         static Part fromEncoded(String encoded) {
2057             return from(encoded, NOT_CACHED);
2058         }
2059 
2060         /**
2061          * Creates a part from the decoded string.
2062          *
2063          * @param decoded part string
2064          */
fromDecoded(String decoded)2065         static Part fromDecoded(String decoded) {
2066             return from(NOT_CACHED, decoded);
2067         }
2068 
2069         /**
2070          * Creates a part from the encoded and decoded strings.
2071          *
2072          * @param encoded part string
2073          * @param decoded part string
2074          */
from(String encoded, String decoded)2075         static Part from(String encoded, String decoded) {
2076             // We have to check both encoded and decoded in case one is
2077             // NOT_CACHED.
2078 
2079             if (encoded == null) {
2080                 return NULL;
2081             }
2082             if (encoded.length() == 0) {
2083                 return EMPTY;
2084             }
2085 
2086             if (decoded == null) {
2087                 return NULL;
2088             }
2089             if (decoded .length() == 0) {
2090                 return EMPTY;
2091             }
2092 
2093             return new Part(encoded, decoded);
2094         }
2095 
2096         private static class EmptyPart extends Part {
EmptyPart(String value)2097             public EmptyPart(String value) {
2098                 super(value, value);
2099             }
2100 
2101             @Override
isEmpty()2102             boolean isEmpty() {
2103                 return true;
2104             }
2105         }
2106     }
2107 
2108     /**
2109      * Immutable wrapper of encoded and decoded versions of a path part. Lazily
2110      * creates the encoded or decoded version from the other.
2111      */
2112     static class PathPart extends AbstractPart {
2113 
2114         /** A part with null values. */
2115         static final PathPart NULL = new PathPart(null, null);
2116 
2117         /** A part with empty strings for values. */
2118         static final PathPart EMPTY = new PathPart("", "");
2119 
PathPart(String encoded, String decoded)2120         private PathPart(String encoded, String decoded) {
2121             super(encoded, decoded);
2122         }
2123 
getEncoded()2124         String getEncoded() {
2125             @SuppressWarnings("StringEquality")
2126             boolean hasEncoded = encoded != NOT_CACHED;
2127 
2128             // Don't encode '/'.
2129             return hasEncoded ? encoded : (encoded = encode(decoded, "/"));
2130         }
2131 
2132         /**
2133          * Cached path segments. This doesn't need to be volatile--we don't
2134          * care if other threads see the result.
2135          */
2136         private PathSegments pathSegments;
2137 
2138         /**
2139          * Gets the individual path segments. Parses them if necessary.
2140          *
2141          * @return parsed path segments or null if this isn't a hierarchical
2142          *  URI
2143          */
getPathSegments()2144         PathSegments getPathSegments() {
2145             if (pathSegments != null) {
2146                 return pathSegments;
2147             }
2148 
2149             String path = getEncoded();
2150             if (path == null) {
2151                 return pathSegments = PathSegments.EMPTY;
2152             }
2153 
2154             PathSegmentsBuilder segmentBuilder = new PathSegmentsBuilder();
2155 
2156             int previous = 0;
2157             int current;
2158             while ((current = path.indexOf('/', previous)) > -1) {
2159                 // This check keeps us from adding a segment if the path starts
2160                 // '/' and an empty segment for "//".
2161                 if (previous < current) {
2162                     String decodedSegment
2163                             = decode(path.substring(previous, current));
2164                     segmentBuilder.add(decodedSegment);
2165                 }
2166                 previous = current + 1;
2167             }
2168 
2169             // Add in the final path segment.
2170             if (previous < path.length()) {
2171                 segmentBuilder.add(decode(path.substring(previous)));
2172             }
2173 
2174             return pathSegments = segmentBuilder.build();
2175         }
2176 
appendEncodedSegment(PathPart oldPart, String newSegment)2177         static PathPart appendEncodedSegment(PathPart oldPart,
2178                 String newSegment) {
2179             // If there is no old path, should we make the new path relative
2180             // or absolute? I pick absolute.
2181 
2182             if (oldPart == null) {
2183                 // No old path.
2184                 return fromEncoded("/" + newSegment);
2185             }
2186 
2187             String oldPath = oldPart.getEncoded();
2188 
2189             if (oldPath == null) {
2190                 oldPath = "";
2191             }
2192 
2193             int oldPathLength = oldPath.length();
2194             String newPath;
2195             if (oldPathLength == 0) {
2196                 // No old path.
2197                 newPath = "/" + newSegment;
2198             } else if (oldPath.charAt(oldPathLength - 1) == '/') {
2199                 newPath = oldPath + newSegment;
2200             } else {
2201                 newPath = oldPath + "/" + newSegment;
2202             }
2203 
2204             return fromEncoded(newPath);
2205         }
2206 
appendDecodedSegment(PathPart oldPart, String decoded)2207         static PathPart appendDecodedSegment(PathPart oldPart, String decoded) {
2208             String encoded = encode(decoded);
2209 
2210             // TODO: Should we reuse old PathSegments? Probably not.
2211             return appendEncodedSegment(oldPart, encoded);
2212         }
2213 
readFrom(Parcel parcel)2214         static PathPart readFrom(Parcel parcel) {
2215             int representation = parcel.readInt();
2216             switch (representation) {
2217                 case Representation.BOTH:
2218                     return from(parcel.readString(), parcel.readString());
2219                 case Representation.ENCODED:
2220                     return fromEncoded(parcel.readString());
2221                 case Representation.DECODED:
2222                     return fromDecoded(parcel.readString());
2223                 default:
2224                     throw new IllegalArgumentException("Bad representation: " + representation);
2225             }
2226         }
2227 
2228         /**
2229          * Creates a path from the encoded string.
2230          *
2231          * @param encoded part string
2232          */
fromEncoded(String encoded)2233         static PathPart fromEncoded(String encoded) {
2234             return from(encoded, NOT_CACHED);
2235         }
2236 
2237         /**
2238          * Creates a path from the decoded string.
2239          *
2240          * @param decoded part string
2241          */
fromDecoded(String decoded)2242         static PathPart fromDecoded(String decoded) {
2243             return from(NOT_CACHED, decoded);
2244         }
2245 
2246         /**
2247          * Creates a path from the encoded and decoded strings.
2248          *
2249          * @param encoded part string
2250          * @param decoded part string
2251          */
from(String encoded, String decoded)2252         static PathPart from(String encoded, String decoded) {
2253             if (encoded == null) {
2254                 return NULL;
2255             }
2256 
2257             if (encoded.length() == 0) {
2258                 return EMPTY;
2259             }
2260 
2261             return new PathPart(encoded, decoded);
2262         }
2263 
2264         /**
2265          * Prepends path values with "/" if they're present, not empty, and
2266          * they don't already start with "/".
2267          */
makeAbsolute(PathPart oldPart)2268         static PathPart makeAbsolute(PathPart oldPart) {
2269             @SuppressWarnings("StringEquality")
2270             boolean encodedCached = oldPart.encoded != NOT_CACHED;
2271 
2272             // We don't care which version we use, and we don't want to force
2273             // unneccessary encoding/decoding.
2274             String oldPath = encodedCached ? oldPart.encoded : oldPart.decoded;
2275 
2276             if (oldPath == null || oldPath.length() == 0
2277                     || oldPath.startsWith("/")) {
2278                 return oldPart;
2279             }
2280 
2281             // Prepend encoded string if present.
2282             String newEncoded = encodedCached
2283                     ? "/" + oldPart.encoded : NOT_CACHED;
2284 
2285             // Prepend decoded string if present.
2286             @SuppressWarnings("StringEquality")
2287             boolean decodedCached = oldPart.decoded != NOT_CACHED;
2288             String newDecoded = decodedCached
2289                     ? "/" + oldPart.decoded
2290                     : NOT_CACHED;
2291 
2292             return new PathPart(newEncoded, newDecoded);
2293         }
2294     }
2295 
2296     /**
2297      * Creates a new Uri by appending an already-encoded path segment to a
2298      * base Uri.
2299      *
2300      * @param baseUri Uri to append path segment to
2301      * @param pathSegment encoded path segment to append
2302      * @return a new Uri based on baseUri with the given segment appended to
2303      *  the path
2304      * @throws NullPointerException if baseUri is null
2305      */
withAppendedPath(Uri baseUri, String pathSegment)2306     public static Uri withAppendedPath(Uri baseUri, String pathSegment) {
2307         Builder builder = baseUri.buildUpon();
2308         builder = builder.appendEncodedPath(pathSegment);
2309         return builder.build();
2310     }
2311 
2312     /**
2313      * If this {@link Uri} is {@code file://}, then resolve and return its
2314      * canonical path. Also fixes legacy emulated storage paths so they are
2315      * usable across user boundaries. Should always be called from the app
2316      * process before sending elsewhere.
2317      *
2318      * @hide
2319      */
getCanonicalUri()2320     public Uri getCanonicalUri() {
2321         if ("file".equals(getScheme())) {
2322             final String canonicalPath;
2323             try {
2324                 canonicalPath = new File(getPath()).getCanonicalPath();
2325             } catch (IOException e) {
2326                 return this;
2327             }
2328 
2329             if (Environment.isExternalStorageEmulated()) {
2330                 final String legacyPath = Environment.getLegacyExternalStorageDirectory()
2331                         .toString();
2332 
2333                 // Splice in user-specific path when legacy path is found
2334                 if (canonicalPath.startsWith(legacyPath)) {
2335                     return Uri.fromFile(new File(
2336                             Environment.getExternalStorageDirectory().toString(),
2337                             canonicalPath.substring(legacyPath.length() + 1)));
2338                 }
2339             }
2340 
2341             return Uri.fromFile(new File(canonicalPath));
2342         } else {
2343             return this;
2344         }
2345     }
2346 
2347     /**
2348      * If this is a {@code file://} Uri, it will be reported to
2349      * {@link StrictMode}.
2350      *
2351      * @hide
2352      */
checkFileUriExposed(String location)2353     public void checkFileUriExposed(String location) {
2354         if ("file".equals(getScheme())
2355                 && (getPath() != null) && !getPath().startsWith("/system/")) {
2356             StrictMode.onFileUriExposed(this, location);
2357         }
2358     }
2359 
2360     /**
2361      * If this is a {@code content://} Uri without access flags, it will be
2362      * reported to {@link StrictMode}.
2363      *
2364      * @hide
2365      */
checkContentUriWithoutPermission(String location, int flags)2366     public void checkContentUriWithoutPermission(String location, int flags) {
2367         if ("content".equals(getScheme()) && !Intent.isAccessUriMode(flags)) {
2368             StrictMode.onContentUriWithoutPermission(this, location);
2369         }
2370     }
2371 
2372     /**
2373      * Test if this is a path prefix match against the given Uri. Verifies that
2374      * scheme, authority, and atomic path segments match.
2375      *
2376      * @hide
2377      */
isPathPrefixMatch(Uri prefix)2378     public boolean isPathPrefixMatch(Uri prefix) {
2379         if (!Objects.equals(getScheme(), prefix.getScheme())) return false;
2380         if (!Objects.equals(getAuthority(), prefix.getAuthority())) return false;
2381 
2382         List<String> seg = getPathSegments();
2383         List<String> prefixSeg = prefix.getPathSegments();
2384 
2385         final int prefixSize = prefixSeg.size();
2386         if (seg.size() < prefixSize) return false;
2387 
2388         for (int i = 0; i < prefixSize; i++) {
2389             if (!Objects.equals(seg.get(i), prefixSeg.get(i))) {
2390                 return false;
2391             }
2392         }
2393 
2394         return true;
2395     }
2396 }
2397