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