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