2 * Copyright (C) 2007 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
19 import android.os.Environment;
20 import android.os.Parcel;
21 import android.os.Parcelable;
22 import android.os.StrictMode;
23 import android.util.Log;
26 import java.io.IOException;
27 import java.io.UnsupportedEncodingException;
28 import java.net.URLEncoder;
29 import java.nio.charset.StandardCharsets;
30 import java.util.AbstractList;
31 import java.util.ArrayList;
32 import java.util.Collections;
33 import java.util.LinkedHashSet;
34 import java.util.List;
35 import java.util.Locale;
36 import java.util.Objects;
37 import java.util.RandomAccess;
40 import libcore.net.UriCodec;
43 * Immutable URI reference. A URI reference includes a URI and a fragment, the
44 * component of the URI following a '#'. Builds and parses URI references
46 * <a href="http://www.faqs.org/rfcs/rfc2396.html">RFC 2396</a>.
48 * <p>In the interest of performance, this class performs little to no
49 * validation. Behavior is undefined for invalid input. This class is very
50 * forgiving--in the face of invalid input, it will return garbage
51 * rather than throw an exception unless otherwise specified.
53 public abstract class Uri implements Parcelable, Comparable<Uri> {
57 This class aims to do as little up front work as possible. To accomplish
58 that, we vary the implementation depending on what the user passes in.
59 For example, we have one implementation if the user passes in a
60 URI string (StringUri) and another if the user passes in the
61 individual components (OpaqueUri).
63 *Concurrency notes*: Like any truly immutable object, this class is safe
64 for concurrent use. This class uses a caching pattern in some places where
65 it doesn't use volatile or synchronized. This is safe to do with ints
66 because getting or setting an int is atomic. It's safe to do with a String
67 because the internal fields are final and the memory model guarantees other
68 threads won't see a partially initialized instance. We are not guaranteed
69 that some threads will immediately see changes from other threads on
70 certain platforms, but we don't mind if those threads reconstruct the
71 cached result. As a result, we get thread safe caching with no concurrency
72 overhead, which means the most common case, access from a single thread,
73 is as fast as possible.
75 From the Java Language spec.:
77 "17.5 Final Field Semantics
79 ... when the object is seen by another thread, that thread will always
80 see the correctly constructed version of that object's final fields.
81 It will also see versions of any object or array referenced by
82 those final fields that are at least as up-to-date as the final fields
85 In that same vein, all non-transient fields within Uri
86 implementations should be final and immutable so as to ensure true
87 immutability for clients even when they don't use proper concurrency
90 For reference, from RFC 2396:
92 "4.3. Parsing a URI Reference
94 A URI reference is typically parsed according to the four main
95 components and fragment identifier in order to determine what
96 components are present and whether the reference is relative or
97 absolute. The individual components are then parsed for their
98 subparts and, if not opaque, to verify their validity.
100 Although the BNF defines what is allowed in each component, it is
101 ambiguous in terms of differentiating between an authority component
102 and a path component that begins with two slash characters. The
103 greedy algorithm is used for disambiguation: the left-most matching
104 rule soaks up as much of the URI reference string as it is capable of
105 matching. In other words, the authority component wins."
107 The "four main components" of a hierarchical URI consist of
108 <scheme>://<authority><path>?<query>
113 private static final String LOG = Uri.class.getSimpleName();
116 * NOTE: EMPTY accesses this field during its own initialization, so this
117 * field *must* be initialized first, or else EMPTY will see a null value!
119 * Placeholder for strings which haven't been cached. This enables us
120 * to cache null. We intentionally create a new String instance so we can
121 * compare its identity and there is no chance we will confuse it with
124 @SuppressWarnings("RedundantStringConstructorCall")
125 private static final String NOT_CACHED = new String("NOT CACHED");
128 * The empty URI, equivalent to "".
130 public static final Uri EMPTY = new HierarchicalUri(null, Part.NULL,
131 PathPart.EMPTY, Part.NULL, Part.NULL);
134 * Prevents external subclassing.
139 * Returns true if this URI is hierarchical like "http://google.com".
140 * Absolute URIs are hierarchical if the scheme-specific part starts with
141 * a '/'. Relative URIs are always hierarchical.
143 public abstract boolean isHierarchical();
146 * Returns true if this URI is opaque like "mailto:nobody@google.com". The
147 * scheme-specific part of an opaque URI cannot start with a '/'.
149 public boolean isOpaque() {
150 return !isHierarchical();
154 * Returns true if this URI is relative, i.e. if it doesn't contain an
157 * @return true if this URI is relative, false if it's absolute
159 public abstract boolean isRelative();
162 * Returns true if this URI is absolute, i.e. if it contains an
165 * @return true if this URI is absolute, false if it's relative
167 public boolean isAbsolute() {
168 return !isRelative();
172 * Gets the scheme of this URI. Example: "http"
174 * @return the scheme or null if this is a relative URI
176 public abstract String getScheme();
179 * Gets the scheme-specific part of this URI, i.e. everything between
180 * the scheme separator ':' and the fragment separator '#'. If this is a
181 * relative URI, this method returns the entire URI. Decodes escaped octets.
183 * <p>Example: "//www.google.com/search?q=android"
185 * @return the decoded scheme-specific-part
187 public abstract String getSchemeSpecificPart();
190 * Gets the scheme-specific part of this URI, i.e. everything between
191 * the scheme separator ':' and the fragment separator '#'. If this is a
192 * relative URI, this method returns the entire URI. Leaves escaped octets
195 * <p>Example: "//www.google.com/search?q=android"
197 * @return the decoded scheme-specific-part
199 public abstract String getEncodedSchemeSpecificPart();
202 * Gets the decoded authority part of this URI. For
203 * server addresses, the authority is structured as follows:
204 * {@code [ userinfo '@' ] host [ ':' port ]}
206 * <p>Examples: "google.com", "bob@google.com:80"
208 * @return the authority for this URI or null if not present
210 public abstract String getAuthority();
213 * Gets the encoded authority part of this URI. For
214 * server addresses, the authority is structured as follows:
215 * {@code [ userinfo '@' ] host [ ':' port ]}
217 * <p>Examples: "google.com", "bob@google.com:80"
219 * @return the authority for this URI or null if not present
221 public abstract String getEncodedAuthority();
224 * Gets the decoded user information from the authority.
225 * For example, if the authority is "nobody@google.com", this method will
228 * @return the user info for this URI or null if not present
230 public abstract String getUserInfo();
233 * Gets the encoded user information from the authority.
234 * For example, if the authority is "nobody@google.com", this method will
237 * @return the user info for this URI or null if not present
239 public abstract String getEncodedUserInfo();
242 * Gets the encoded host from the authority for this URI. For example,
243 * if the authority is "bob@google.com", this method will return
246 * @return the host for this URI or null if not present
248 public abstract String getHost();
251 * Gets the port from the authority for this URI. For example,
252 * if the authority is "google.com:80", this method will return 80.
254 * @return the port for this URI or -1 if invalid or not present
256 public abstract int getPort();
259 * Gets the decoded path.
261 * @return the decoded path, or null if this is not a hierarchical URI
262 * (like "mailto:nobody@google.com") or the URI is invalid
264 public abstract String getPath();
267 * Gets the encoded path.
269 * @return the encoded path, or null if this is not a hierarchical URI
270 * (like "mailto:nobody@google.com") or the URI is invalid
272 public abstract String getEncodedPath();
275 * Gets the decoded query component from this URI. The query comes after
276 * the query separator ('?') and before the fragment separator ('#'). This
277 * method would return "q=android" for
278 * "http://www.google.com/search?q=android".
280 * @return the decoded query or null if there isn't one
282 public abstract String getQuery();
285 * Gets the encoded query component from this URI. The query comes after
286 * the query separator ('?') and before the fragment separator ('#'). This
287 * method would return "q=android" for
288 * "http://www.google.com/search?q=android".
290 * @return the encoded query or null if there isn't one
292 public abstract String getEncodedQuery();
295 * Gets the decoded fragment part of this URI, everything after the '#'.
297 * @return the decoded fragment or null if there isn't one
299 public abstract String getFragment();
302 * Gets the encoded fragment part of this URI, everything after the '#'.
304 * @return the encoded fragment or null if there isn't one
306 public abstract String getEncodedFragment();
309 * Gets the decoded path segments.
311 * @return decoded path segments, each without a leading or trailing '/'
313 public abstract List<String> getPathSegments();
316 * Gets the decoded last segment in the path.
318 * @return the decoded last segment or null if the path is empty
320 public abstract String getLastPathSegment();
323 * Compares this Uri to another object for equality. Returns true if the
324 * encoded string representations of this Uri and the given Uri are
325 * equal. Case counts. Paths are not normalized. If one Uri specifies a
326 * default port explicitly and the other leaves it implicit, they will not
327 * be considered equal.
329 public boolean equals(Object o) {
330 if (!(o instanceof Uri)) {
336 return toString().equals(other.toString());
340 * Hashes the encoded string represention of this Uri consistently with
341 * {@link #equals(Object)}.
343 public int hashCode() {
344 return toString().hashCode();
348 * Compares the string representation of this Uri with that of
351 public int compareTo(Uri other) {
352 return toString().compareTo(other.toString());
356 * Returns the encoded string representation of this URI.
357 * Example: "http://google.com/"
359 public abstract String toString();
362 * Return a string representation of the URI that is safe to print
363 * to logs and other places where PII should be avoided.
366 public String toSafeString() {
367 String scheme = getScheme();
368 String ssp = getSchemeSpecificPart();
369 if (scheme != null) {
370 if (scheme.equalsIgnoreCase("tel") || scheme.equalsIgnoreCase("sip")
371 || scheme.equalsIgnoreCase("sms") || scheme.equalsIgnoreCase("smsto")
372 || scheme.equalsIgnoreCase("mailto")) {
373 StringBuilder builder = new StringBuilder(64);
374 builder.append(scheme);
377 for (int i=0; i<ssp.length(); i++) {
378 char c = ssp.charAt(i);
379 if (c == '-' || c == '@' || c == '.') {
386 return builder.toString();
387 } else if (scheme.equalsIgnoreCase("http") || scheme.equalsIgnoreCase("https")
388 || scheme.equalsIgnoreCase("ftp")) {
389 ssp = "//" + ((getHost() != null) ? getHost() : "")
390 + ((getPort() != -1) ? (":" + getPort()) : "")
394 // Not a sensitive scheme, but let's still be conservative about
395 // the data we include -- only the ssp, not the query params or
396 // fragment, because those can often have sensitive info.
397 StringBuilder builder = new StringBuilder(64);
398 if (scheme != null) {
399 builder.append(scheme);
405 return builder.toString();
409 * Constructs a new builder, copying the attributes from this Uri.
411 public abstract Builder buildUpon();
413 /** Index of a component which was not found. */
414 private final static int NOT_FOUND = -1;
416 /** Placeholder value for an index which hasn't been calculated yet. */
417 private final static int NOT_CALCULATED = -2;
420 * Error message presented when a user tries to treat an opaque URI as
423 private static final String NOT_HIERARCHICAL
424 = "This isn't a hierarchical URI.";
426 /** Default encoding. */
427 private static final String DEFAULT_ENCODING = "UTF-8";
430 * Creates a Uri which parses the given encoded URI string.
432 * @param uriString an RFC 2396-compliant, encoded URI
433 * @throws NullPointerException if uriString is null
434 * @return Uri for this given uri string
436 public static Uri parse(String uriString) {
437 return new StringUri(uriString);
441 * Creates a Uri from a file. The URI has the form
442 * "file://<absolute path>". Encodes path characters with the exception of
445 * <p>Example: "file:///tmp/android.txt"
447 * @throws NullPointerException if file is null
448 * @return a Uri for the given file
450 public static Uri fromFile(File file) {
452 throw new NullPointerException("file");
455 PathPart path = PathPart.fromDecoded(file.getAbsolutePath());
456 return new HierarchicalUri(
457 "file", Part.EMPTY, path, Part.NULL, Part.NULL);
461 * An implementation which wraps a String URI. This URI can be opaque or
462 * hierarchical, but we extend AbstractHierarchicalUri in case we need
463 * the hierarchical functionality.
465 private static class StringUri extends AbstractHierarchicalUri {
467 /** Used in parcelling. */
468 static final int TYPE_ID = 1;
470 /** URI string representation. */
471 private final String uriString;
473 private StringUri(String uriString) {
474 if (uriString == null) {
475 throw new NullPointerException("uriString");
478 this.uriString = uriString;
481 static Uri readFrom(Parcel parcel) {
482 return new StringUri(parcel.readString());
485 public int describeContents() {
489 public void writeToParcel(Parcel parcel, int flags) {
490 parcel.writeInt(TYPE_ID);
491 parcel.writeString(uriString);
494 /** Cached scheme separator index. */
495 private volatile int cachedSsi = NOT_CALCULATED;
497 /** Finds the first ':'. Returns -1 if none found. */
498 private int findSchemeSeparator() {
499 return cachedSsi == NOT_CALCULATED
500 ? cachedSsi = uriString.indexOf(':')
504 /** Cached fragment separator index. */
505 private volatile int cachedFsi = NOT_CALCULATED;
507 /** Finds the first '#'. Returns -1 if none found. */
508 private int findFragmentSeparator() {
509 return cachedFsi == NOT_CALCULATED
510 ? cachedFsi = uriString.indexOf('#', findSchemeSeparator())
514 public boolean isHierarchical() {
515 int ssi = findSchemeSeparator();
517 if (ssi == NOT_FOUND) {
518 // All relative URIs are hierarchical.
522 if (uriString.length() == ssi + 1) {
527 // If the ssp starts with a '/', this is hierarchical.
528 return uriString.charAt(ssi + 1) == '/';
531 public boolean isRelative() {
532 // Note: We return true if the index is 0
533 return findSchemeSeparator() == NOT_FOUND;
536 private volatile String scheme = NOT_CACHED;
538 public String getScheme() {
539 @SuppressWarnings("StringEquality")
540 boolean cached = (scheme != NOT_CACHED);
541 return cached ? scheme : (scheme = parseScheme());
544 private String parseScheme() {
545 int ssi = findSchemeSeparator();
546 return ssi == NOT_FOUND ? null : uriString.substring(0, ssi);
551 private Part getSsp() {
552 return ssp == null ? ssp = Part.fromEncoded(parseSsp()) : ssp;
555 public String getEncodedSchemeSpecificPart() {
556 return getSsp().getEncoded();
559 public String getSchemeSpecificPart() {
560 return getSsp().getDecoded();
563 private String parseSsp() {
564 int ssi = findSchemeSeparator();
565 int fsi = findFragmentSeparator();
567 // Return everything between ssi and fsi.
568 return fsi == NOT_FOUND
569 ? uriString.substring(ssi + 1)
570 : uriString.substring(ssi + 1, fsi);
573 private Part authority;
575 private Part getAuthorityPart() {
576 if (authority == null) {
577 String encodedAuthority
578 = parseAuthority(this.uriString, findSchemeSeparator());
579 return authority = Part.fromEncoded(encodedAuthority);
585 public String getEncodedAuthority() {
586 return getAuthorityPart().getEncoded();
589 public String getAuthority() {
590 return getAuthorityPart().getDecoded();
593 private PathPart path;
595 private PathPart getPathPart() {
597 ? path = PathPart.fromEncoded(parsePath())
601 public String getPath() {
602 return getPathPart().getDecoded();
605 public String getEncodedPath() {
606 return getPathPart().getEncoded();
609 public List<String> getPathSegments() {
610 return getPathPart().getPathSegments();
613 private String parsePath() {
614 String uriString = this.uriString;
615 int ssi = findSchemeSeparator();
617 // If the URI is absolute.
619 // Is there anything after the ':'?
620 boolean schemeOnly = ssi + 1 == uriString.length();
626 // A '/' after the ':' means this is hierarchical.
627 if (uriString.charAt(ssi + 1) != '/') {
632 // All relative URIs are hierarchical.
635 return parsePath(uriString, ssi);
640 private Part getQueryPart() {
642 ? query = Part.fromEncoded(parseQuery()) : query;
645 public String getEncodedQuery() {
646 return getQueryPart().getEncoded();
649 private String parseQuery() {
650 // It doesn't make sense to cache this index. We only ever
651 // calculate it once.
652 int qsi = uriString.indexOf('?', findSchemeSeparator());
653 if (qsi == NOT_FOUND) {
657 int fsi = findFragmentSeparator();
659 if (fsi == NOT_FOUND) {
660 return uriString.substring(qsi + 1);
668 return uriString.substring(qsi + 1, fsi);
671 public String getQuery() {
672 return getQueryPart().getDecoded();
675 private Part fragment;
677 private Part getFragmentPart() {
678 return fragment == null
679 ? fragment = Part.fromEncoded(parseFragment()) : fragment;
682 public String getEncodedFragment() {
683 return getFragmentPart().getEncoded();
686 private String parseFragment() {
687 int fsi = findFragmentSeparator();
688 return fsi == NOT_FOUND ? null : uriString.substring(fsi + 1);
691 public String getFragment() {
692 return getFragmentPart().getDecoded();
695 public String toString() {
700 * Parses an authority out of the given URI string.
702 * @param uriString URI string
703 * @param ssi scheme separator index, -1 for a relative URI
705 * @return the authority or null if none is found
707 static String parseAuthority(String uriString, int ssi) {
708 int length = uriString.length();
710 // If "//" follows the scheme separator, we have an authority.
712 && uriString.charAt(ssi + 1) == '/'
713 && uriString.charAt(ssi + 2) == '/') {
714 // We have an authority.
716 // Look for the start of the path, query, or fragment, or the
717 // end of the string.
719 LOOP: while (end < length) {
720 switch (uriString.charAt(end)) {
721 case '/': // Start of path
722 case '?': // Start of query
723 case '#': // Start of fragment
729 return uriString.substring(ssi + 3, end);
737 * Parses a path out of this given URI string.
739 * @param uriString URI string
740 * @param ssi scheme separator index, -1 for a relative URI
744 static String parsePath(String uriString, int ssi) {
745 int length = uriString.length();
747 // Find start of path.
750 && uriString.charAt(ssi + 1) == '/'
751 && uriString.charAt(ssi + 2) == '/') {
752 // Skip over authority to path.
754 LOOP: while (pathStart < length) {
755 switch (uriString.charAt(pathStart)) {
756 case '?': // Start of query
757 case '#': // Start of fragment
758 return ""; // Empty path.
759 case '/': // Start of path!
765 // Path starts immediately after scheme separator.
770 int pathEnd = pathStart;
771 LOOP: while (pathEnd < length) {
772 switch (uriString.charAt(pathEnd)) {
773 case '?': // Start of query
774 case '#': // Start of fragment
780 return uriString.substring(pathStart, pathEnd);
783 public Builder buildUpon() {
784 if (isHierarchical()) {
787 .authority(getAuthorityPart())
789 .query(getQueryPart())
790 .fragment(getFragmentPart());
794 .opaquePart(getSsp())
795 .fragment(getFragmentPart());
801 * Creates an opaque Uri from the given components. Encodes the ssp
802 * which means this method cannot be used to create hierarchical URIs.
804 * @param scheme of the URI
805 * @param ssp scheme-specific-part, everything between the
806 * scheme separator (':') and the fragment separator ('#'), which will
808 * @param fragment fragment, everything after the '#', null if undefined,
811 * @throws NullPointerException if scheme or ssp is null
812 * @return Uri composed of the given scheme, ssp, and fragment
814 * @see Builder if you don't want the ssp and fragment to be encoded
816 public static Uri fromParts(String scheme, String ssp,
818 if (scheme == null) {
819 throw new NullPointerException("scheme");
822 throw new NullPointerException("ssp");
825 return new OpaqueUri(scheme, Part.fromDecoded(ssp),
826 Part.fromDecoded(fragment));
832 private static class OpaqueUri extends Uri {
834 /** Used in parcelling. */
835 static final int TYPE_ID = 2;
837 private final String scheme;
838 private final Part ssp;
839 private final Part fragment;
841 private OpaqueUri(String scheme, Part ssp, Part fragment) {
842 this.scheme = scheme;
844 this.fragment = fragment == null ? Part.NULL : fragment;
847 static Uri readFrom(Parcel parcel) {
848 return new OpaqueUri(
850 Part.readFrom(parcel),
851 Part.readFrom(parcel)
855 public int describeContents() {
859 public void writeToParcel(Parcel parcel, int flags) {
860 parcel.writeInt(TYPE_ID);
861 parcel.writeString(scheme);
863 fragment.writeTo(parcel);
866 public boolean isHierarchical() {
870 public boolean isRelative() {
871 return scheme == null;
874 public String getScheme() {
878 public String getEncodedSchemeSpecificPart() {
879 return ssp.getEncoded();
882 public String getSchemeSpecificPart() {
883 return ssp.getDecoded();
886 public String getAuthority() {
890 public String getEncodedAuthority() {
894 public String getPath() {
898 public String getEncodedPath() {
902 public String getQuery() {
906 public String getEncodedQuery() {
910 public String getFragment() {
911 return fragment.getDecoded();
914 public String getEncodedFragment() {
915 return fragment.getEncoded();
918 public List<String> getPathSegments() {
919 return Collections.emptyList();
922 public String getLastPathSegment() {
926 public String getUserInfo() {
930 public String getEncodedUserInfo() {
934 public String getHost() {
938 public int getPort() {
942 private volatile String cachedString = NOT_CACHED;
944 public String toString() {
945 @SuppressWarnings("StringEquality")
946 boolean cached = cachedString != NOT_CACHED;
951 StringBuilder sb = new StringBuilder();
953 sb.append(scheme).append(':');
954 sb.append(getEncodedSchemeSpecificPart());
956 if (!fragment.isEmpty()) {
957 sb.append('#').append(fragment.getEncoded());
960 return cachedString = sb.toString();
963 public Builder buildUpon() {
966 .opaquePart(this.ssp)
967 .fragment(this.fragment);
972 * Wrapper for path segment array.
974 static class PathSegments extends AbstractList<String>
975 implements RandomAccess {
977 static final PathSegments EMPTY = new PathSegments(null, 0);
979 final String[] segments;
982 PathSegments(String[] segments, int size) {
983 this.segments = segments;
987 public String get(int index) {
989 throw new IndexOutOfBoundsException();
992 return segments[index];
1001 * Builds PathSegments.
1003 static class PathSegmentsBuilder {
1008 void add(String segment) {
1009 if (segments == null) {
1010 segments = new String[4];
1011 } else if (size + 1 == segments.length) {
1012 String[] expanded = new String[segments.length * 2];
1013 System.arraycopy(segments, 0, expanded, 0, segments.length);
1014 segments = expanded;
1017 segments[size++] = segment;
1020 PathSegments build() {
1021 if (segments == null) {
1022 return PathSegments.EMPTY;
1026 return new PathSegments(segments, size);
1028 // Makes sure this doesn't get reused.
1035 * Support for hierarchical URIs.
1037 private abstract static class AbstractHierarchicalUri extends Uri {
1039 public String getLastPathSegment() {
1040 // TODO: If we haven't parsed all of the segments already, just
1041 // grab the last one directly so we only allocate one string.
1043 List<String> segments = getPathSegments();
1044 int size = segments.size();
1048 return segments.get(size - 1);
1051 private Part userInfo;
1053 private Part getUserInfoPart() {
1054 return userInfo == null
1055 ? userInfo = Part.fromEncoded(parseUserInfo()) : userInfo;
1058 public final String getEncodedUserInfo() {
1059 return getUserInfoPart().getEncoded();
1062 private String parseUserInfo() {
1063 String authority = getEncodedAuthority();
1064 if (authority == null) {
1068 int end = authority.lastIndexOf('@');
1069 return end == NOT_FOUND ? null : authority.substring(0, end);
1072 public String getUserInfo() {
1073 return getUserInfoPart().getDecoded();
1076 private volatile String host = NOT_CACHED;
1078 public String getHost() {
1079 @SuppressWarnings("StringEquality")
1080 boolean cached = (host != NOT_CACHED);
1081 return cached ? host
1082 : (host = parseHost());
1085 private String parseHost() {
1086 String authority = getEncodedAuthority();
1087 if (authority == null) {
1091 // Parse out user info and then port.
1092 int userInfoSeparator = authority.lastIndexOf('@');
1093 int portSeparator = authority.indexOf(':', userInfoSeparator);
1095 String encodedHost = portSeparator == NOT_FOUND
1096 ? authority.substring(userInfoSeparator + 1)
1097 : authority.substring(userInfoSeparator + 1, portSeparator);
1099 return decode(encodedHost);
1102 private volatile int port = NOT_CALCULATED;
1104 public int getPort() {
1105 return port == NOT_CALCULATED
1106 ? port = parsePort()
1110 private int parsePort() {
1111 String authority = getEncodedAuthority();
1112 if (authority == null) {
1116 // Make sure we look for the port separtor *after* the user info
1117 // separator. We have URLs with a ':' in the user info.
1118 int userInfoSeparator = authority.lastIndexOf('@');
1119 int portSeparator = authority.indexOf(':', userInfoSeparator);
1121 if (portSeparator == NOT_FOUND) {
1125 String portString = decode(authority.substring(portSeparator + 1));
1127 return Integer.parseInt(portString);
1128 } catch (NumberFormatException e) {
1129 Log.w(LOG, "Error parsing port string.", e);
1138 private static class HierarchicalUri extends AbstractHierarchicalUri {
1140 /** Used in parcelling. */
1141 static final int TYPE_ID = 3;
1143 private final String scheme; // can be null
1144 private final Part authority;
1145 private final PathPart path;
1146 private final Part query;
1147 private final Part fragment;
1149 private HierarchicalUri(String scheme, Part authority, PathPart path,
1150 Part query, Part fragment) {
1151 this.scheme = scheme;
1152 this.authority = Part.nonNull(authority);
1153 this.path = path == null ? PathPart.NULL : path;
1154 this.query = Part.nonNull(query);
1155 this.fragment = Part.nonNull(fragment);
1158 static Uri readFrom(Parcel parcel) {
1159 return new HierarchicalUri(
1160 parcel.readString(),
1161 Part.readFrom(parcel),
1162 PathPart.readFrom(parcel),
1163 Part.readFrom(parcel),
1164 Part.readFrom(parcel)
1168 public int describeContents() {
1172 public void writeToParcel(Parcel parcel, int flags) {
1173 parcel.writeInt(TYPE_ID);
1174 parcel.writeString(scheme);
1175 authority.writeTo(parcel);
1176 path.writeTo(parcel);
1177 query.writeTo(parcel);
1178 fragment.writeTo(parcel);
1181 public boolean isHierarchical() {
1185 public boolean isRelative() {
1186 return scheme == null;
1189 public String getScheme() {
1195 private Part getSsp() {
1197 ? ssp = Part.fromEncoded(makeSchemeSpecificPart()) : ssp;
1200 public String getEncodedSchemeSpecificPart() {
1201 return getSsp().getEncoded();
1204 public String getSchemeSpecificPart() {
1205 return getSsp().getDecoded();
1209 * Creates the encoded scheme-specific part from its sub parts.
1211 private String makeSchemeSpecificPart() {
1212 StringBuilder builder = new StringBuilder();
1213 appendSspTo(builder);
1214 return builder.toString();
1217 private void appendSspTo(StringBuilder builder) {
1218 String encodedAuthority = authority.getEncoded();
1219 if (encodedAuthority != null) {
1220 // Even if the authority is "", we still want to append "//".
1221 builder.append("//").append(encodedAuthority);
1224 String encodedPath = path.getEncoded();
1225 if (encodedPath != null) {
1226 builder.append(encodedPath);
1229 if (!query.isEmpty()) {
1230 builder.append('?').append(query.getEncoded());
1234 public String getAuthority() {
1235 return this.authority.getDecoded();
1238 public String getEncodedAuthority() {
1239 return this.authority.getEncoded();
1242 public String getEncodedPath() {
1243 return this.path.getEncoded();
1246 public String getPath() {
1247 return this.path.getDecoded();
1250 public String getQuery() {
1251 return this.query.getDecoded();
1254 public String getEncodedQuery() {
1255 return this.query.getEncoded();
1258 public String getFragment() {
1259 return this.fragment.getDecoded();
1262 public String getEncodedFragment() {
1263 return this.fragment.getEncoded();
1266 public List<String> getPathSegments() {
1267 return this.path.getPathSegments();
1270 private volatile String uriString = NOT_CACHED;
1273 public String toString() {
1274 @SuppressWarnings("StringEquality")
1275 boolean cached = (uriString != NOT_CACHED);
1276 return cached ? uriString
1277 : (uriString = makeUriString());
1280 private String makeUriString() {
1281 StringBuilder builder = new StringBuilder();
1283 if (scheme != null) {
1284 builder.append(scheme).append(':');
1287 appendSspTo(builder);
1289 if (!fragment.isEmpty()) {
1290 builder.append('#').append(fragment.getEncoded());
1293 return builder.toString();
1296 public Builder buildUpon() {
1297 return new Builder()
1299 .authority(authority)
1302 .fragment(fragment);
1307 * Helper class for building or manipulating URI references. Not safe for
1310 * <p>An absolute hierarchical URI reference follows the pattern:
1311 * {@code <scheme>://<authority><absolute path>?<query>#<fragment>}
1313 * <p>Relative URI references (which are always hierarchical) follow one
1314 * of two patterns: {@code <relative or absolute path>?<query>#<fragment>}
1315 * or {@code //<authority><absolute path>?<query>#<fragment>}
1317 * <p>An opaque URI follows this pattern:
1318 * {@code <scheme>:<opaque part>#<fragment>}
1320 * <p>Use {@link Uri#buildUpon()} to obtain a builder representing an existing URI.
1322 public static final class Builder {
1324 private String scheme;
1325 private Part opaquePart;
1326 private Part authority;
1327 private PathPart path;
1329 private Part fragment;
1332 * Constructs a new Builder.
1339 * @param scheme name or {@code null} if this is a relative Uri
1341 public Builder scheme(String scheme) {
1342 this.scheme = scheme;
1346 Builder opaquePart(Part opaquePart) {
1347 this.opaquePart = opaquePart;
1352 * Encodes and sets the given opaque scheme-specific-part.
1354 * @param opaquePart decoded opaque part
1356 public Builder opaquePart(String opaquePart) {
1357 return opaquePart(Part.fromDecoded(opaquePart));
1361 * Sets the previously encoded opaque scheme-specific-part.
1363 * @param opaquePart encoded opaque part
1365 public Builder encodedOpaquePart(String opaquePart) {
1366 return opaquePart(Part.fromEncoded(opaquePart));
1369 Builder authority(Part authority) {
1370 // This URI will be hierarchical.
1371 this.opaquePart = null;
1373 this.authority = authority;
1378 * Encodes and sets the authority.
1380 public Builder authority(String authority) {
1381 return authority(Part.fromDecoded(authority));
1385 * Sets the previously encoded authority.
1387 public Builder encodedAuthority(String authority) {
1388 return authority(Part.fromEncoded(authority));
1391 Builder path(PathPart path) {
1392 // This URI will be hierarchical.
1393 this.opaquePart = null;
1400 * Sets the path. Leaves '/' characters intact but encodes others as
1403 * <p>If the path is not null and doesn't start with a '/', and if
1404 * you specify a scheme and/or authority, the builder will prepend the
1405 * given path with a '/'.
1407 public Builder path(String path) {
1408 return path(PathPart.fromDecoded(path));
1412 * Sets the previously encoded path.
1414 * <p>If the path is not null and doesn't start with a '/', and if
1415 * you specify a scheme and/or authority, the builder will prepend the
1416 * given path with a '/'.
1418 public Builder encodedPath(String path) {
1419 return path(PathPart.fromEncoded(path));
1423 * Encodes the given segment and appends it to the path.
1425 public Builder appendPath(String newSegment) {
1426 return path(PathPart.appendDecodedSegment(path, newSegment));
1430 * Appends the given segment to the path.
1432 public Builder appendEncodedPath(String newSegment) {
1433 return path(PathPart.appendEncodedSegment(path, newSegment));
1436 Builder query(Part query) {
1437 // This URI will be hierarchical.
1438 this.opaquePart = null;
1445 * Encodes and sets the query.
1447 public Builder query(String query) {
1448 return query(Part.fromDecoded(query));
1452 * Sets the previously encoded query.
1454 public Builder encodedQuery(String query) {
1455 return query(Part.fromEncoded(query));
1458 Builder fragment(Part fragment) {
1459 this.fragment = fragment;
1464 * Encodes and sets the fragment.
1466 public Builder fragment(String fragment) {
1467 return fragment(Part.fromDecoded(fragment));
1471 * Sets the previously encoded fragment.
1473 public Builder encodedFragment(String fragment) {
1474 return fragment(Part.fromEncoded(fragment));
1478 * Encodes the key and value and then appends the parameter to the
1481 * @param key which will be encoded
1482 * @param value which will be encoded
1484 public Builder appendQueryParameter(String key, String value) {
1485 // This URI will be hierarchical.
1486 this.opaquePart = null;
1488 String encodedParameter = encode(key, null) + "="
1489 + encode(value, null);
1491 if (query == null) {
1492 query = Part.fromEncoded(encodedParameter);
1496 String oldQuery = query.getEncoded();
1497 if (oldQuery == null || oldQuery.length() == 0) {
1498 query = Part.fromEncoded(encodedParameter);
1500 query = Part.fromEncoded(oldQuery + "&" + encodedParameter);
1507 * Clears the the previously set query.
1509 public Builder clearQuery() {
1510 return query((Part) null);
1514 * Constructs a Uri with the current attributes.
1516 * @throws UnsupportedOperationException if the URI is opaque and the
1519 public Uri build() {
1520 if (opaquePart != null) {
1521 if (this.scheme == null) {
1522 throw new UnsupportedOperationException(
1523 "An opaque URI must have a scheme.");
1526 return new OpaqueUri(scheme, opaquePart, fragment);
1528 // Hierarchical URIs should not return null for getPath().
1529 PathPart path = this.path;
1530 if (path == null || path == PathPart.NULL) {
1531 path = PathPart.EMPTY;
1533 // If we have a scheme and/or authority, the path must
1534 // be absolute. Prepend it with a '/' if necessary.
1535 if (hasSchemeOrAuthority()) {
1536 path = PathPart.makeAbsolute(path);
1540 return new HierarchicalUri(
1541 scheme, authority, path, query, fragment);
1545 private boolean hasSchemeOrAuthority() {
1546 return scheme != null
1547 || (authority != null && authority != Part.NULL);
1552 public String toString() {
1553 return build().toString();
1558 * Returns a set of the unique names of all query parameters. Iterating
1559 * over the set will return the names in order of their first occurrence.
1561 * @throws UnsupportedOperationException if this isn't a hierarchical URI
1563 * @return a set of decoded names
1565 public Set<String> getQueryParameterNames() {
1567 throw new UnsupportedOperationException(NOT_HIERARCHICAL);
1570 String query = getEncodedQuery();
1571 if (query == null) {
1572 return Collections.emptySet();
1575 Set<String> names = new LinkedHashSet<String>();
1578 int next = query.indexOf('&', start);
1579 int end = (next == -1) ? query.length() : next;
1581 int separator = query.indexOf('=', start);
1582 if (separator > end || separator == -1) {
1586 String name = query.substring(start, separator);
1587 names.add(decode(name));
1589 // Move start to end of name.
1591 } while (start < query.length());
1593 return Collections.unmodifiableSet(names);
1597 * Searches the query string for parameter values with the given key.
1599 * @param key which will be encoded
1601 * @throws UnsupportedOperationException if this isn't a hierarchical URI
1602 * @throws NullPointerException if key is null
1603 * @return a list of decoded values
1605 public List<String> getQueryParameters(String key) {
1607 throw new UnsupportedOperationException(NOT_HIERARCHICAL);
1610 throw new NullPointerException("key");
1613 String query = getEncodedQuery();
1614 if (query == null) {
1615 return Collections.emptyList();
1620 encodedKey = URLEncoder.encode(key, DEFAULT_ENCODING);
1621 } catch (UnsupportedEncodingException e) {
1622 throw new AssertionError(e);
1625 ArrayList<String> values = new ArrayList<String>();
1629 int nextAmpersand = query.indexOf('&', start);
1630 int end = nextAmpersand != -1 ? nextAmpersand : query.length();
1632 int separator = query.indexOf('=', start);
1633 if (separator > end || separator == -1) {
1637 if (separator - start == encodedKey.length()
1638 && query.regionMatches(start, encodedKey, 0, encodedKey.length())) {
1639 if (separator == end) {
1642 values.add(decode(query.substring(separator + 1, end)));
1646 // Move start to end of name.
1647 if (nextAmpersand != -1) {
1648 start = nextAmpersand + 1;
1654 return Collections.unmodifiableList(values);
1658 * Searches the query string for the first value with the given key.
1660 * <p><strong>Warning:</strong> Prior to Jelly Bean, this decoded
1661 * the '+' character as '+' rather than ' '.
1663 * @param key which will be encoded
1664 * @throws UnsupportedOperationException if this isn't a hierarchical URI
1665 * @throws NullPointerException if key is null
1666 * @return the decoded value or null if no parameter is found
1668 public String getQueryParameter(String key) {
1670 throw new UnsupportedOperationException(NOT_HIERARCHICAL);
1673 throw new NullPointerException("key");
1676 final String query = getEncodedQuery();
1677 if (query == null) {
1681 final String encodedKey = encode(key, null);
1682 final int length = query.length();
1685 int nextAmpersand = query.indexOf('&', start);
1686 int end = nextAmpersand != -1 ? nextAmpersand : length;
1688 int separator = query.indexOf('=', start);
1689 if (separator > end || separator == -1) {
1693 if (separator - start == encodedKey.length()
1694 && query.regionMatches(start, encodedKey, 0, encodedKey.length())) {
1695 if (separator == end) {
1698 String encodedValue = query.substring(separator + 1, end);
1699 return UriCodec.decode(encodedValue, true, StandardCharsets.UTF_8, false);
1703 // Move start to end of name.
1704 if (nextAmpersand != -1) {
1705 start = nextAmpersand + 1;
1714 * Searches the query string for the first value with the given key and interprets it
1715 * as a boolean value. "false" and "0" are interpreted as <code>false</code>, everything
1716 * else is interpreted as <code>true</code>.
1718 * @param key which will be decoded
1719 * @param defaultValue the default value to return if there is no query parameter for key
1720 * @return the boolean interpretation of the query parameter key
1722 public boolean getBooleanQueryParameter(String key, boolean defaultValue) {
1723 String flag = getQueryParameter(key);
1725 return defaultValue;
1727 flag = flag.toLowerCase(Locale.ROOT);
1728 return (!"false".equals(flag) && !"0".equals(flag));
1732 * Return an equivalent URI with a lowercase scheme component.
1733 * This aligns the Uri with Android best practices for
1736 * <p>For example, "HTTP://www.android.com" becomes
1737 * "http://www.android.com"
1739 * <p>All URIs received from outside Android (such as user input,
1740 * or external sources like Bluetooth, NFC, or the Internet) should
1741 * be normalized before they are used to create an Intent.
1743 * <p class="note">This method does <em>not</em> validate bad URI's,
1744 * or 'fix' poorly formatted URI's - so do not use it for input validation.
1745 * A Uri will always be returned, even if the Uri is badly formatted to
1746 * begin with and a scheme component cannot be found.
1748 * @return normalized Uri (never null)
1749 * @see android.content.Intent#setData
1750 * @see android.content.Intent#setDataAndNormalize
1752 public Uri normalizeScheme() {
1753 String scheme = getScheme();
1754 if (scheme == null) return this; // give up
1755 String lowerScheme = scheme.toLowerCase(Locale.ROOT);
1756 if (scheme.equals(lowerScheme)) return this; // no change
1758 return buildUpon().scheme(lowerScheme).build();
1761 /** Identifies a null parcelled Uri. */
1762 private static final int NULL_TYPE_ID = 0;
1765 * Reads Uris from Parcels.
1767 public static final Parcelable.Creator<Uri> CREATOR
1768 = new Parcelable.Creator<Uri>() {
1769 public Uri createFromParcel(Parcel in) {
1770 int type = in.readInt();
1772 case NULL_TYPE_ID: return null;
1773 case StringUri.TYPE_ID: return StringUri.readFrom(in);
1774 case OpaqueUri.TYPE_ID: return OpaqueUri.readFrom(in);
1775 case HierarchicalUri.TYPE_ID:
1776 return HierarchicalUri.readFrom(in);
1779 throw new IllegalArgumentException("Unknown URI type: " + type);
1782 public Uri[] newArray(int size) {
1783 return new Uri[size];
1788 * Writes a Uri to a Parcel.
1790 * @param out parcel to write to
1791 * @param uri to write, can be null
1793 public static void writeToParcel(Parcel out, Uri uri) {
1795 out.writeInt(NULL_TYPE_ID);
1797 uri.writeToParcel(out, 0);
1801 private static final char[] HEX_DIGITS = "0123456789ABCDEF".toCharArray();
1804 * Encodes characters in the given string as '%'-escaped octets
1805 * using the UTF-8 scheme. Leaves letters ("A-Z", "a-z"), numbers
1806 * ("0-9"), and unreserved characters ("_-!.~'()*") intact. Encodes
1807 * all other characters.
1809 * @param s string to encode
1810 * @return an encoded version of s suitable for use as a URI component,
1811 * or null if s is null
1813 public static String encode(String s) {
1814 return encode(s, null);
1818 * Encodes characters in the given string as '%'-escaped octets
1819 * using the UTF-8 scheme. Leaves letters ("A-Z", "a-z"), numbers
1820 * ("0-9"), and unreserved characters ("_-!.~'()*") intact. Encodes
1821 * all other characters with the exception of those specified in the
1824 * @param s string to encode
1825 * @param allow set of additional characters to allow in the encoded form,
1826 * null if no characters should be skipped
1827 * @return an encoded version of s suitable for use as a URI component,
1828 * or null if s is null
1830 public static String encode(String s, String allow) {
1835 // Lazily-initialized buffers.
1836 StringBuilder encoded = null;
1838 int oldLength = s.length();
1840 // This loop alternates between copying over allowed characters and
1841 // encoding in chunks. This results in fewer method calls and
1842 // allocations than encoding one character at a time.
1844 while (current < oldLength) {
1845 // Start in "copying" mode where we copy over allowed chars.
1847 // Find the next character which needs to be encoded.
1848 int nextToEncode = current;
1849 while (nextToEncode < oldLength
1850 && isAllowed(s.charAt(nextToEncode), allow)) {
1854 // If there's nothing more to encode...
1855 if (nextToEncode == oldLength) {
1857 // We didn't need to encode anything!
1860 // Presumably, we've already done some encoding.
1861 encoded.append(s, current, oldLength);
1862 return encoded.toString();
1866 if (encoded == null) {
1867 encoded = new StringBuilder();
1870 if (nextToEncode > current) {
1871 // Append allowed characters leading up to this point.
1872 encoded.append(s, current, nextToEncode);
1874 // assert nextToEncode == current
1877 // Switch to "encoding" mode.
1879 // Find the next allowed character.
1880 current = nextToEncode;
1881 int nextAllowed = current + 1;
1882 while (nextAllowed < oldLength
1883 && !isAllowed(s.charAt(nextAllowed), allow)) {
1887 // Convert the substring to bytes and encode the bytes as
1888 // '%'-escaped octets.
1889 String toEncode = s.substring(current, nextAllowed);
1891 byte[] bytes = toEncode.getBytes(DEFAULT_ENCODING);
1892 int bytesLength = bytes.length;
1893 for (int i = 0; i < bytesLength; i++) {
1894 encoded.append('%');
1895 encoded.append(HEX_DIGITS[(bytes[i] & 0xf0) >> 4]);
1896 encoded.append(HEX_DIGITS[bytes[i] & 0xf]);
1898 } catch (UnsupportedEncodingException e) {
1899 throw new AssertionError(e);
1902 current = nextAllowed;
1905 // Encoded could still be null at this point if s is empty.
1906 return encoded == null ? s : encoded.toString();
1910 * Returns true if the given character is allowed.
1912 * @param c character to check
1913 * @param allow characters to allow
1914 * @return true if the character is allowed or false if it should be
1917 private static boolean isAllowed(char c, String allow) {
1918 return (c >= 'A' && c <= 'Z')
1919 || (c >= 'a' && c <= 'z')
1920 || (c >= '0' && c <= '9')
1921 || "_-!.~'()*".indexOf(c) != NOT_FOUND
1922 || (allow != null && allow.indexOf(c) != NOT_FOUND);
1926 * Decodes '%'-escaped octets in the given string using the UTF-8 scheme.
1927 * Replaces invalid octets with the unicode replacement character
1930 * @param s encoded string to decode
1931 * @return the given string with escaped octets decoded, or null if
1934 public static String decode(String s) {
1938 return UriCodec.decode(s, false, StandardCharsets.UTF_8, false);
1942 * Support for part implementations.
1944 static abstract class AbstractPart {
1947 * Enum which indicates which representation of a given part we have.
1949 static class Representation {
1950 static final int BOTH = 0;
1951 static final int ENCODED = 1;
1952 static final int DECODED = 2;
1955 volatile String encoded;
1956 volatile String decoded;
1958 AbstractPart(String encoded, String decoded) {
1959 this.encoded = encoded;
1960 this.decoded = decoded;
1963 abstract String getEncoded();
1965 final String getDecoded() {
1966 @SuppressWarnings("StringEquality")
1967 boolean hasDecoded = decoded != NOT_CACHED;
1968 return hasDecoded ? decoded : (decoded = decode(encoded));
1971 final void writeTo(Parcel parcel) {
1972 @SuppressWarnings("StringEquality")
1973 boolean hasEncoded = encoded != NOT_CACHED;
1975 @SuppressWarnings("StringEquality")
1976 boolean hasDecoded = decoded != NOT_CACHED;
1978 if (hasEncoded && hasDecoded) {
1979 parcel.writeInt(Representation.BOTH);
1980 parcel.writeString(encoded);
1981 parcel.writeString(decoded);
1982 } else if (hasEncoded) {
1983 parcel.writeInt(Representation.ENCODED);
1984 parcel.writeString(encoded);
1985 } else if (hasDecoded) {
1986 parcel.writeInt(Representation.DECODED);
1987 parcel.writeString(decoded);
1989 throw new IllegalArgumentException("Neither encoded nor decoded");
1995 * Immutable wrapper of encoded and decoded versions of a URI part. Lazily
1996 * creates the encoded or decoded version from the other.
1998 static class Part extends AbstractPart {
2000 /** A part with null values. */
2001 static final Part NULL = new EmptyPart(null);
2003 /** A part with empty strings for values. */
2004 static final Part EMPTY = new EmptyPart("");
2006 private Part(String encoded, String decoded) {
2007 super(encoded, decoded);
2014 String getEncoded() {
2015 @SuppressWarnings("StringEquality")
2016 boolean hasEncoded = encoded != NOT_CACHED;
2017 return hasEncoded ? encoded : (encoded = encode(decoded));
2020 static Part readFrom(Parcel parcel) {
2021 int representation = parcel.readInt();
2022 switch (representation) {
2023 case Representation.BOTH:
2024 return from(parcel.readString(), parcel.readString());
2025 case Representation.ENCODED:
2026 return fromEncoded(parcel.readString());
2027 case Representation.DECODED:
2028 return fromDecoded(parcel.readString());
2030 throw new IllegalArgumentException("Unknown representation: "
2036 * Returns given part or {@link #NULL} if the given part is null.
2038 static Part nonNull(Part part) {
2039 return part == null ? NULL : part;
2043 * Creates a part from the encoded string.
2045 * @param encoded part string
2047 static Part fromEncoded(String encoded) {
2048 return from(encoded, NOT_CACHED);
2052 * Creates a part from the decoded string.
2054 * @param decoded part string
2056 static Part fromDecoded(String decoded) {
2057 return from(NOT_CACHED, decoded);
2061 * Creates a part from the encoded and decoded strings.
2063 * @param encoded part string
2064 * @param decoded part string
2066 static Part from(String encoded, String decoded) {
2067 // We have to check both encoded and decoded in case one is
2070 if (encoded == null) {
2073 if (encoded.length() == 0) {
2077 if (decoded == null) {
2080 if (decoded .length() == 0) {
2084 return new Part(encoded, decoded);
2087 private static class EmptyPart extends Part {
2088 public EmptyPart(String value) {
2089 super(value, value);
2100 * Immutable wrapper of encoded and decoded versions of a path part. Lazily
2101 * creates the encoded or decoded version from the other.
2103 static class PathPart extends AbstractPart {
2105 /** A part with null values. */
2106 static final PathPart NULL = new PathPart(null, null);
2108 /** A part with empty strings for values. */
2109 static final PathPart EMPTY = new PathPart("", "");
2111 private PathPart(String encoded, String decoded) {
2112 super(encoded, decoded);
2115 String getEncoded() {
2116 @SuppressWarnings("StringEquality")
2117 boolean hasEncoded = encoded != NOT_CACHED;
2119 // Don't encode '/'.
2120 return hasEncoded ? encoded : (encoded = encode(decoded, "/"));
2124 * Cached path segments. This doesn't need to be volatile--we don't
2125 * care if other threads see the result.
2127 private PathSegments pathSegments;
2130 * Gets the individual path segments. Parses them if necessary.
2132 * @return parsed path segments or null if this isn't a hierarchical
2135 PathSegments getPathSegments() {
2136 if (pathSegments != null) {
2137 return pathSegments;
2140 String path = getEncoded();
2142 return pathSegments = PathSegments.EMPTY;
2145 PathSegmentsBuilder segmentBuilder = new PathSegmentsBuilder();
2149 while ((current = path.indexOf('/', previous)) > -1) {
2150 // This check keeps us from adding a segment if the path starts
2151 // '/' and an empty segment for "//".
2152 if (previous < current) {
2153 String decodedSegment
2154 = decode(path.substring(previous, current));
2155 segmentBuilder.add(decodedSegment);
2157 previous = current + 1;
2160 // Add in the final path segment.
2161 if (previous < path.length()) {
2162 segmentBuilder.add(decode(path.substring(previous)));
2165 return pathSegments = segmentBuilder.build();
2168 static PathPart appendEncodedSegment(PathPart oldPart,
2169 String newSegment) {
2170 // If there is no old path, should we make the new path relative
2171 // or absolute? I pick absolute.
2173 if (oldPart == null) {
2175 return fromEncoded("/" + newSegment);
2178 String oldPath = oldPart.getEncoded();
2180 if (oldPath == null) {
2184 int oldPathLength = oldPath.length();
2186 if (oldPathLength == 0) {
2188 newPath = "/" + newSegment;
2189 } else if (oldPath.charAt(oldPathLength - 1) == '/') {
2190 newPath = oldPath + newSegment;
2192 newPath = oldPath + "/" + newSegment;
2195 return fromEncoded(newPath);
2198 static PathPart appendDecodedSegment(PathPart oldPart, String decoded) {
2199 String encoded = encode(decoded);
2201 // TODO: Should we reuse old PathSegments? Probably not.
2202 return appendEncodedSegment(oldPart, encoded);
2205 static PathPart readFrom(Parcel parcel) {
2206 int representation = parcel.readInt();
2207 switch (representation) {
2208 case Representation.BOTH:
2209 return from(parcel.readString(), parcel.readString());
2210 case Representation.ENCODED:
2211 return fromEncoded(parcel.readString());
2212 case Representation.DECODED:
2213 return fromDecoded(parcel.readString());
2215 throw new IllegalArgumentException("Bad representation: " + representation);
2220 * Creates a path from the encoded string.
2222 * @param encoded part string
2224 static PathPart fromEncoded(String encoded) {
2225 return from(encoded, NOT_CACHED);
2229 * Creates a path from the decoded string.
2231 * @param decoded part string
2233 static PathPart fromDecoded(String decoded) {
2234 return from(NOT_CACHED, decoded);
2238 * Creates a path from the encoded and decoded strings.
2240 * @param encoded part string
2241 * @param decoded part string
2243 static PathPart from(String encoded, String decoded) {
2244 if (encoded == null) {
2248 if (encoded.length() == 0) {
2252 return new PathPart(encoded, decoded);
2256 * Prepends path values with "/" if they're present, not empty, and
2257 * they don't already start with "/".
2259 static PathPart makeAbsolute(PathPart oldPart) {
2260 @SuppressWarnings("StringEquality")
2261 boolean encodedCached = oldPart.encoded != NOT_CACHED;
2263 // We don't care which version we use, and we don't want to force
2264 // unneccessary encoding/decoding.
2265 String oldPath = encodedCached ? oldPart.encoded : oldPart.decoded;
2267 if (oldPath == null || oldPath.length() == 0
2268 || oldPath.startsWith("/")) {
2272 // Prepend encoded string if present.
2273 String newEncoded = encodedCached
2274 ? "/" + oldPart.encoded : NOT_CACHED;
2276 // Prepend decoded string if present.
2277 @SuppressWarnings("StringEquality")
2278 boolean decodedCached = oldPart.decoded != NOT_CACHED;
2279 String newDecoded = decodedCached
2280 ? "/" + oldPart.decoded
2283 return new PathPart(newEncoded, newDecoded);
2288 * Creates a new Uri by appending an already-encoded path segment to a
2291 * @param baseUri Uri to append path segment to
2292 * @param pathSegment encoded path segment to append
2293 * @return a new Uri based on baseUri with the given segment appended to
2295 * @throws NullPointerException if baseUri is null
2297 public static Uri withAppendedPath(Uri baseUri, String pathSegment) {
2298 Builder builder = baseUri.buildUpon();
2299 builder = builder.appendEncodedPath(pathSegment);
2300 return builder.build();
2304 * If this {@link Uri} is {@code file://}, then resolve and return its
2305 * canonical path. Also fixes legacy emulated storage paths so they are
2306 * usable across user boundaries. Should always be called from the app
2307 * process before sending elsewhere.
2311 public Uri getCanonicalUri() {
2312 if ("file".equals(getScheme())) {
2313 final String canonicalPath;
2315 canonicalPath = new File(getPath()).getCanonicalPath();
2316 } catch (IOException e) {
2320 if (Environment.isExternalStorageEmulated()) {
2321 final String legacyPath = Environment.getLegacyExternalStorageDirectory()
2324 // Splice in user-specific path when legacy path is found
2325 if (canonicalPath.startsWith(legacyPath)) {
2326 return Uri.fromFile(new File(
2327 Environment.getExternalStorageDirectory().toString(),
2328 canonicalPath.substring(legacyPath.length() + 1)));
2332 return Uri.fromFile(new File(canonicalPath));
2339 * If this is a {@code file://} Uri, it will be reported to
2340 * {@link StrictMode}.
2344 public void checkFileUriExposed(String location) {
2345 if ("file".equals(getScheme()) && !getPath().startsWith("/system/")) {
2346 StrictMode.onFileUriExposed(this, location);
2351 * Test if this is a path prefix match against the given Uri. Verifies that
2352 * scheme, authority, and atomic path segments match.
2356 public boolean isPathPrefixMatch(Uri prefix) {
2357 if (!Objects.equals(getScheme(), prefix.getScheme())) return false;
2358 if (!Objects.equals(getAuthority(), prefix.getAuthority())) return false;
2360 List<String> seg = getPathSegments();
2361 List<String> prefixSeg = prefix.getPathSegments();
2363 final int prefixSize = prefixSeg.size();
2364 if (seg.size() < prefixSize) return false;
2366 for (int i = 0; i < prefixSize; i++) {
2367 if (!Objects.equals(seg.get(i), prefixSeg.get(i))) {