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