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