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