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