Home | History | Annotate | Download | only in provider
      1 /*
      2  * Copyright (C) 2017 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.inputmethodservice.cts.provider;
     18 
     19 import android.content.UriMatcher;
     20 import android.net.Uri;
     21 import android.provider.BaseColumns;
     22 import androidx.annotation.NonNull;
     23 import androidx.annotation.Nullable;
     24 import android.text.TextUtils;
     25 import android.util.SparseArray;
     26 
     27 import java.util.List;
     28 
     29 /**
     30  * Content URI helper.
     31  *
     32  * Helper object to parse content URI passed to content provider. A helper object is instantiated
     33  * via {@link Factory#newInstance(Uri)}, and a {@link Factory} object should be instantiated using
     34  * {@link FactoryBuilder}.
     35  *
     36  * A content URI is assumed to have a format "content://authority/table[/id]?" where table is a
     37  * SQLite table name in content provider and id is a primary key.
     38  */
     39 final class UriHelper {
     40 
     41     static final class Factory {
     42         private final UriMatcher mUriMatcher;
     43         private final SparseArray<String> mUriTypeMap;
     44 
     45         public static FactoryBuilder builder() {
     46             return new FactoryBuilder();
     47         }
     48 
     49         private Factory(final FactoryBuilder builder) {
     50             mUriMatcher = builder.mUriMatcher;
     51             mUriTypeMap = builder.mUriTypeMap;
     52         }
     53 
     54         @NonNull
     55         UriHelper newInstance(final Uri uri) {
     56             if (mUriMatcher.match(uri) == UriMatcher.NO_MATCH) {
     57                 throw new IllegalArgumentException("Unknown URI: " + uri);
     58             }
     59             return new UriHelper(uri);
     60         }
     61 
     62         @Nullable
     63         String getTypeOf(final Uri uri) {
     64             return mUriTypeMap.get(mUriMatcher.match(uri), null);
     65         }
     66     }
     67 
     68     static final class FactoryBuilder {
     69         private final UriMatcher mUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
     70         private final SparseArray<String> mUriTypeMap = new SparseArray<>();
     71         private int mMatcherCode;
     72 
     73         private FactoryBuilder() {
     74             mMatcherCode = 0;
     75         }
     76 
     77         FactoryBuilder addUri(final String authority, final String path, final String type) {
     78             if (TextUtils.isEmpty(authority)) {
     79                 throw new IllegalArgumentException("Authority must not be empty");
     80             }
     81             if (TextUtils.isEmpty(path)) {
     82                 throw new IllegalArgumentException("Path must not be empty");
     83             }
     84             final int matcherCode = mMatcherCode++;
     85             mUriMatcher.addURI(authority, path, matcherCode);
     86             mUriTypeMap.append(matcherCode, type);
     87             return this;
     88         }
     89 
     90         Factory build() {
     91             if (mMatcherCode == 0) {
     92                 throw new IllegalStateException("No URI is defined");
     93             }
     94             return new Factory(this);
     95         }
     96     }
     97 
     98     /** Name of SQLite table specified by content uri. */
     99     @NonNull
    100     final String table;
    101 
    102     /** Primary id that is specified by content uri. Null if not. */
    103     @Nullable
    104     private final String mId;
    105 
    106     private UriHelper(final Uri uri) {
    107         final List<String> segments = uri.getPathSegments();
    108         table = segments.get(0);
    109         mId = (segments.size() >= 2) ? segments.get(1) : null;
    110     }
    111 
    112     /**
    113      * Composes selection SQL text from content uri and {@code selection} specified.
    114      * When content uri has a primary key, it needs to be composed with a selection text specified
    115      * as content provider parameter.
    116      *
    117      * @param selection selection text specified as a parameter to content provider.
    118      * @return composed selection SQL text, null if no selection specified.
    119      */
    120     @Nullable
    121     String buildSelection(@Nullable final String selection) {
    122         if (mId == null) {
    123             return selection;
    124         }
    125         // A primary key is specified by uri, so that selection should be at least "_id = ?".
    126         final StringBuilder sb = new StringBuilder().append(BaseColumns._ID).append(" = ?");
    127         if (selection != null) {
    128             // Selection is also specified as a parameter to content provider, so that it should be
    129             // appended with AND, such that "_id = ? AND (selection_text)".
    130             sb.append(" AND (").append(selection).append(")");
    131         }
    132         return sb.toString();
    133     }
    134 
    135     /**
    136      * Composes selection argument array from context uri and {@code selectionArgs} specified.
    137      * When content uri has a primary key, it needs to be provided in a final selection argument
    138      * array.
    139      *
    140      * @param selectionArgs selection argument array specified as a parameter to content provider.
    141      * @return composed selection argument array, null if selection argument is unnecessary.
    142      */
    143     @Nullable
    144     String[] buildSelectionArgs(@Nullable final String[] selectionArgs) {
    145         if (mId == null) {
    146             return selectionArgs;
    147         }
    148         // A primary key is specified by uri but not as a parameter to content provider, the primary
    149         // key value should be the sole selection argument.
    150         if (selectionArgs == null || selectionArgs.length == 0) {
    151             return new String[]{ mId };
    152         }
    153         // Selection args are also specified as a parameter to content provider, the primary key
    154         // value should be prepended to those selection args.
    155         final String[] args = new String[selectionArgs.length + 1];
    156         System.arraycopy(selectionArgs, 0, args, 1, selectionArgs.length);
    157         args[0] = mId;
    158         return args;
    159     }
    160 }
    161