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