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