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