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