1 /* 2 * Copyright (C) 2014 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.provider; 18 19 import android.annotation.Nullable; 20 import android.annotation.SystemApi; 21 import android.app.slice.Slice; 22 import android.content.ContentProvider; 23 import android.content.ContentValues; 24 import android.content.Context; 25 import android.content.UriMatcher; 26 import android.content.pm.ProviderInfo; 27 import android.database.Cursor; 28 import android.net.Uri; 29 import android.util.Log; 30 31 /** 32 * Base class for a search indexable provider. Such provider offers data to be indexed either 33 * as a reference to an XML file (like a {@link android.preference.PreferenceScreen}) or either 34 * as some raw data. 35 * 36 * @see SearchIndexableResource 37 * @see SearchIndexableData 38 * @see SearchIndexablesContract 39 * 40 * To create a search indexables provider, extend this class, then implement the abstract methods, 41 * and add it to your manifest like this: 42 * 43 * <pre class="prettyprint"><manifest> 44 * ... 45 * <application> 46 * ... 47 * <provider 48 * android:name="com.example.MyIndexablesProvider" 49 * android:authorities="com.example.myindexablesprovider" 50 * android:exported="true" 51 * android:grantUriPermissions="true" 52 * android:permission="android.permission.READ_SEARCH_INDEXABLES" 53 * <intent-filter> 54 * <action android:name="android.content.action.SEARCH_INDEXABLES_PROVIDER" /> 55 * </intent-filter> 56 * </provider> 57 * ... 58 * </application> 59 *</manifest></pre> 60 * <p> 61 * When defining your provider, you must protect it with 62 * {@link android.Manifest.permission#READ_SEARCH_INDEXABLES}, which is a permission only the system 63 * can obtain. 64 * </p> 65 * 66 * @hide 67 */ 68 @SystemApi 69 public abstract class SearchIndexablesProvider extends ContentProvider { 70 private static final String TAG = "IndexablesProvider"; 71 72 private String mAuthority; 73 private UriMatcher mMatcher; 74 75 private static final int MATCH_RES_CODE = 1; 76 private static final int MATCH_RAW_CODE = 2; 77 private static final int MATCH_NON_INDEXABLE_KEYS_CODE = 3; 78 private static final int MATCH_SITE_MAP_PAIRS_CODE = 4; 79 private static final int MATCH_SLICE_URI_PAIRS_CODE = 5; 80 private static final int MATCH_DYNAMIC_RAW_CODE = 6; 81 82 /** 83 * Implementation is provided by the parent class. 84 */ 85 @Override attachInfo(Context context, ProviderInfo info)86 public void attachInfo(Context context, ProviderInfo info) { 87 mAuthority = info.authority; 88 89 mMatcher = new UriMatcher(UriMatcher.NO_MATCH); 90 mMatcher.addURI(mAuthority, SearchIndexablesContract.INDEXABLES_XML_RES_PATH, 91 MATCH_RES_CODE); 92 mMatcher.addURI(mAuthority, SearchIndexablesContract.INDEXABLES_RAW_PATH, 93 MATCH_RAW_CODE); 94 mMatcher.addURI(mAuthority, SearchIndexablesContract.NON_INDEXABLES_KEYS_PATH, 95 MATCH_NON_INDEXABLE_KEYS_CODE); 96 mMatcher.addURI(mAuthority, SearchIndexablesContract.SITE_MAP_PAIRS_PATH, 97 MATCH_SITE_MAP_PAIRS_CODE); 98 mMatcher.addURI(mAuthority, SearchIndexablesContract.SLICE_URI_PAIRS_PATH, 99 MATCH_SLICE_URI_PAIRS_CODE); 100 mMatcher.addURI(mAuthority, SearchIndexablesContract.DYNAMIC_INDEXABLES_RAW_PATH, 101 MATCH_DYNAMIC_RAW_CODE); 102 103 // Sanity check our setup 104 if (!info.exported) { 105 throw new SecurityException("Provider must be exported"); 106 } 107 if (!info.grantUriPermissions) { 108 throw new SecurityException("Provider must grantUriPermissions"); 109 } 110 if (!android.Manifest.permission.READ_SEARCH_INDEXABLES.equals(info.readPermission)) { 111 throw new SecurityException("Provider must be protected by READ_SEARCH_INDEXABLES"); 112 } 113 114 super.attachInfo(context, info); 115 } 116 117 @Override query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)118 public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, 119 String sortOrder) { 120 try { 121 switch (mMatcher.match(uri)) { 122 case MATCH_RES_CODE: 123 return queryXmlResources(null); 124 case MATCH_RAW_CODE: 125 return queryRawData(null); 126 case MATCH_NON_INDEXABLE_KEYS_CODE: 127 return queryNonIndexableKeys(null); 128 case MATCH_SITE_MAP_PAIRS_CODE: 129 return querySiteMapPairs(); 130 case MATCH_SLICE_URI_PAIRS_CODE: 131 return querySliceUriPairs(); 132 case MATCH_DYNAMIC_RAW_CODE: 133 return queryDynamicRawData(null); 134 default: 135 throw new UnsupportedOperationException("Unknown Uri " + uri); 136 } 137 } catch (UnsupportedOperationException e) { 138 throw e; 139 } catch (Exception e) { 140 Log.e(TAG, "Provider querying exception:", e); 141 return null; 142 } 143 } 144 145 /** 146 * Returns all {@link android.provider.SearchIndexablesContract.XmlResource}. 147 * 148 * Those are Xml resource IDs to some {@link android.preference.PreferenceScreen}. 149 * 150 * @param projection list of {@link android.provider.SearchIndexablesContract.XmlResource} 151 * columns to put into the cursor. If {@code null} all supported columns 152 * should be included. 153 */ queryXmlResources(String[] projection)154 public abstract Cursor queryXmlResources(String[] projection); 155 156 /** 157 * Returns all {@link android.provider.SearchIndexablesContract.RawData}. 158 * 159 * Those are the raw indexable data. 160 * 161 * @param projection list of {@link android.provider.SearchIndexablesContract.RawData} columns 162 * to put into the cursor. If {@code null} all supported columns should be 163 * included. 164 */ queryRawData(String[] projection)165 public abstract Cursor queryRawData(String[] projection); 166 167 /** 168 * Returns all {@link android.provider.SearchIndexablesContract.NonIndexableKey}. 169 * 170 * Those are the non indexable data keys. 171 * 172 * @param projection list of {@link android.provider.SearchIndexablesContract.NonIndexableKey} 173 * columns to put into the cursor. If {@code null} all supported columns 174 * should be included. 175 */ queryNonIndexableKeys(String[] projection)176 public abstract Cursor queryNonIndexableKeys(String[] projection); 177 178 /** 179 * Returns a {@link Cursor}, where rows are [parent class, child class] entries to form a site 180 * map. The list of pairs should be as complete as possible. 181 * 182 * @hide 183 */ querySiteMapPairs()184 public Cursor querySiteMapPairs() { 185 // By default no-op. 186 return null; 187 } 188 189 /** 190 * Returns a {@link Cursor} linking {@link Slice} {@link Uri Uris} to the 191 * corresponding Settings key. 192 */ 193 @Nullable querySliceUriPairs()194 public Cursor querySliceUriPairs() { 195 // By default no-op; 196 return null; 197 } 198 199 /** 200 * Returns all {@link android.provider.SearchIndexablesContract.RawData}. 201 * 202 * Those are the dynamic raw indexable data. 203 * 204 * @param projection list of {@link android.provider.SearchIndexablesContract.RawData} columns 205 * to put into the cursor. If {@code null} all supported columns should be 206 * included. 207 */ 208 @Nullable queryDynamicRawData(@ullable String[] projection)209 public Cursor queryDynamicRawData(@Nullable String[] projection) { 210 // By default no-op; 211 return null; 212 } 213 214 @Override getType(Uri uri)215 public String getType(Uri uri) { 216 switch (mMatcher.match(uri)) { 217 case MATCH_RES_CODE: 218 return SearchIndexablesContract.XmlResource.MIME_TYPE; 219 case MATCH_RAW_CODE: 220 case MATCH_DYNAMIC_RAW_CODE: 221 return SearchIndexablesContract.RawData.MIME_TYPE; 222 case MATCH_NON_INDEXABLE_KEYS_CODE: 223 return SearchIndexablesContract.NonIndexableKey.MIME_TYPE; 224 default: 225 throw new IllegalArgumentException("Unknown URI " + uri); 226 } 227 } 228 229 /** 230 * Implementation is provided by the parent class. Throws by default, and cannot be overridden. 231 */ 232 @Override insert(Uri uri, ContentValues values)233 public final Uri insert(Uri uri, ContentValues values) { 234 throw new UnsupportedOperationException("Insert not supported"); 235 } 236 237 /** 238 * Implementation is provided by the parent class. Throws by default, and cannot be overridden. 239 */ 240 @Override delete(Uri uri, String selection, String[] selectionArgs)241 public final int delete(Uri uri, String selection, String[] selectionArgs) { 242 throw new UnsupportedOperationException("Delete not supported"); 243 } 244 245 /** 246 * Implementation is provided by the parent class. Throws by default, and cannot be overridden. 247 */ 248 @Override update( Uri uri, ContentValues values, String selection, String[] selectionArgs)249 public final int update( 250 Uri uri, ContentValues values, String selection, String[] selectionArgs) { 251 throw new UnsupportedOperationException("Update not supported"); 252 } 253 } 254