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