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