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 com.android.settings.search; 18 19 import static android.provider.SearchIndexablesContract.COLUMN_INDEX_NON_INDEXABLE_KEYS_KEY_VALUE; 20 import static android.provider.SearchIndexablesContract.COLUMN_INDEX_RAW_CLASS_NAME; 21 import static android.provider.SearchIndexablesContract.COLUMN_INDEX_RAW_ENTRIES; 22 import static android.provider.SearchIndexablesContract.COLUMN_INDEX_RAW_ICON_RESID; 23 import static android.provider.SearchIndexablesContract.COLUMN_INDEX_RAW_INTENT_ACTION; 24 import static android.provider.SearchIndexablesContract.COLUMN_INDEX_RAW_INTENT_TARGET_CLASS; 25 import static android.provider.SearchIndexablesContract.COLUMN_INDEX_RAW_INTENT_TARGET_PACKAGE; 26 import static android.provider.SearchIndexablesContract.COLUMN_INDEX_RAW_KEY; 27 import static android.provider.SearchIndexablesContract.COLUMN_INDEX_RAW_KEYWORDS; 28 import static android.provider.SearchIndexablesContract.COLUMN_INDEX_RAW_SCREEN_TITLE; 29 import static android.provider.SearchIndexablesContract.COLUMN_INDEX_RAW_SUMMARY_OFF; 30 import static android.provider.SearchIndexablesContract.COLUMN_INDEX_RAW_SUMMARY_ON; 31 import static android.provider.SearchIndexablesContract.COLUMN_INDEX_RAW_TITLE; 32 import static android.provider.SearchIndexablesContract.COLUMN_INDEX_RAW_USER_ID; 33 import static android.provider.SearchIndexablesContract.COLUMN_INDEX_XML_RES_CLASS_NAME; 34 import static android.provider.SearchIndexablesContract.COLUMN_INDEX_XML_RES_ICON_RESID; 35 import static android.provider.SearchIndexablesContract.COLUMN_INDEX_XML_RES_INTENT_ACTION; 36 import static android.provider.SearchIndexablesContract.COLUMN_INDEX_XML_RES_INTENT_TARGET_CLASS; 37 import static android.provider.SearchIndexablesContract.COLUMN_INDEX_XML_RES_INTENT_TARGET_PACKAGE; 38 import static android.provider.SearchIndexablesContract.COLUMN_INDEX_XML_RES_RANK; 39 import static android.provider.SearchIndexablesContract.COLUMN_INDEX_XML_RES_RESID; 40 import static android.provider.SearchIndexablesContract.INDEXABLES_RAW_COLUMNS; 41 import static android.provider.SearchIndexablesContract.INDEXABLES_XML_RES_COLUMNS; 42 import static android.provider.SearchIndexablesContract.NON_INDEXABLES_KEYS_COLUMNS; 43 import static android.provider.SearchIndexablesContract.SITE_MAP_COLUMNS; 44 import static android.provider.SearchIndexablesContract.SLICE_URI_PAIRS_COLUMNS; 45 46 import static com.android.settings.dashboard.DashboardFragmentRegistry.CATEGORY_KEY_TO_PARENT_MAP; 47 48 import android.content.ContentResolver; 49 import android.content.Context; 50 import android.database.Cursor; 51 import android.database.MatrixCursor; 52 import android.net.Uri; 53 import android.provider.SearchIndexableResource; 54 import android.provider.SearchIndexablesContract; 55 import android.provider.SearchIndexablesProvider; 56 import android.provider.SettingsSlicesContract; 57 import android.text.TextUtils; 58 import android.util.ArraySet; 59 import android.util.Log; 60 61 import androidx.slice.SliceViewManager; 62 63 import com.android.settings.SettingsActivity; 64 import com.android.settings.overlay.FeatureFactory; 65 import com.android.settings.slices.SettingsSliceProvider; 66 import com.android.settingslib.drawer.DashboardCategory; 67 import com.android.settingslib.drawer.Tile; 68 69 import java.util.ArrayList; 70 import java.util.Collection; 71 import java.util.List; 72 73 public class SettingsSearchIndexablesProvider extends SearchIndexablesProvider { 74 75 public static final boolean DEBUG = false; 76 77 /** 78 * Flag for a system property which checks if we should crash if there are issues in the 79 * indexing pipeline. 80 */ 81 public static final String SYSPROP_CRASH_ON_ERROR = 82 "debug.com.android.settings.search.crash_on_error"; 83 84 private static final String TAG = "SettingsSearchProvider"; 85 86 private static final Collection<String> INVALID_KEYS; 87 88 static { 89 INVALID_KEYS = new ArraySet<>(); 90 INVALID_KEYS.add(null); 91 INVALID_KEYS.add(""); 92 } 93 94 @Override onCreate()95 public boolean onCreate() { 96 return true; 97 } 98 99 @Override queryXmlResources(String[] projection)100 public Cursor queryXmlResources(String[] projection) { 101 MatrixCursor cursor = new MatrixCursor(INDEXABLES_XML_RES_COLUMNS); 102 final List<SearchIndexableResource> resources = 103 getSearchIndexableResourcesFromProvider(getContext()); 104 for (SearchIndexableResource val : resources) { 105 Object[] ref = new Object[INDEXABLES_XML_RES_COLUMNS.length]; 106 ref[COLUMN_INDEX_XML_RES_RANK] = val.rank; 107 ref[COLUMN_INDEX_XML_RES_RESID] = val.xmlResId; 108 ref[COLUMN_INDEX_XML_RES_CLASS_NAME] = val.className; 109 ref[COLUMN_INDEX_XML_RES_ICON_RESID] = val.iconResId; 110 ref[COLUMN_INDEX_XML_RES_INTENT_ACTION] = val.intentAction; 111 ref[COLUMN_INDEX_XML_RES_INTENT_TARGET_PACKAGE] = val.intentTargetPackage; 112 ref[COLUMN_INDEX_XML_RES_INTENT_TARGET_CLASS] = null; // intent target class 113 cursor.addRow(ref); 114 } 115 116 return cursor; 117 } 118 119 @Override queryRawData(String[] projection)120 public Cursor queryRawData(String[] projection) { 121 MatrixCursor cursor = new MatrixCursor(INDEXABLES_RAW_COLUMNS); 122 final List<SearchIndexableRaw> raws = getSearchIndexableRawFromProvider(getContext()); 123 for (SearchIndexableRaw val : raws) { 124 Object[] ref = new Object[INDEXABLES_RAW_COLUMNS.length]; 125 ref[COLUMN_INDEX_RAW_TITLE] = val.title; 126 ref[COLUMN_INDEX_RAW_SUMMARY_ON] = val.summaryOn; 127 ref[COLUMN_INDEX_RAW_SUMMARY_OFF] = val.summaryOff; 128 ref[COLUMN_INDEX_RAW_ENTRIES] = val.entries; 129 ref[COLUMN_INDEX_RAW_KEYWORDS] = val.keywords; 130 ref[COLUMN_INDEX_RAW_SCREEN_TITLE] = val.screenTitle; 131 ref[COLUMN_INDEX_RAW_CLASS_NAME] = val.className; 132 ref[COLUMN_INDEX_RAW_ICON_RESID] = val.iconResId; 133 ref[COLUMN_INDEX_RAW_INTENT_ACTION] = val.intentAction; 134 ref[COLUMN_INDEX_RAW_INTENT_TARGET_PACKAGE] = val.intentTargetPackage; 135 ref[COLUMN_INDEX_RAW_INTENT_TARGET_CLASS] = val.intentTargetClass; 136 ref[COLUMN_INDEX_RAW_KEY] = val.key; 137 ref[COLUMN_INDEX_RAW_USER_ID] = val.userId; 138 cursor.addRow(ref); 139 } 140 141 return cursor; 142 } 143 144 /** 145 * Gets a combined list non-indexable keys that come from providers inside of settings. 146 * The non-indexable keys are used in Settings search at both index and update time to verify 147 * the validity of results in the database. 148 */ 149 @Override queryNonIndexableKeys(String[] projection)150 public Cursor queryNonIndexableKeys(String[] projection) { 151 MatrixCursor cursor = new MatrixCursor(NON_INDEXABLES_KEYS_COLUMNS); 152 final List<String> nonIndexableKeys = getNonIndexableKeysFromProvider(getContext()); 153 for (String nik : nonIndexableKeys) { 154 final Object[] ref = new Object[NON_INDEXABLES_KEYS_COLUMNS.length]; 155 ref[COLUMN_INDEX_NON_INDEXABLE_KEYS_KEY_VALUE] = nik; 156 cursor.addRow(ref); 157 } 158 159 return cursor; 160 } 161 162 @Override querySiteMapPairs()163 public Cursor querySiteMapPairs() { 164 final MatrixCursor cursor = new MatrixCursor(SITE_MAP_COLUMNS); 165 final Context context = getContext(); 166 // Loop through all IA categories and pages and build additional SiteMapPairs 167 final List<DashboardCategory> categories = FeatureFactory.getFactory(context) 168 .getDashboardFeatureProvider(context).getAllCategories(); 169 for (DashboardCategory category : categories) { 170 // Use the category key to look up parent (which page hosts this key) 171 final String parentClass = CATEGORY_KEY_TO_PARENT_MAP.get(category.key); 172 if (parentClass == null) { 173 continue; 174 } 175 // Build parent-child class pairs for all children listed under this key. 176 for (Tile tile : category.getTiles()) { 177 String childClass = null; 178 if (tile.getMetaData() != null) { 179 childClass = tile.getMetaData().getString( 180 SettingsActivity.META_DATA_KEY_FRAGMENT_CLASS); 181 } 182 if (childClass == null) { 183 continue; 184 } 185 cursor.newRow() 186 .add(SearchIndexablesContract.SiteMapColumns.PARENT_CLASS, parentClass) 187 .add(SearchIndexablesContract.SiteMapColumns.CHILD_CLASS, childClass); 188 } 189 } 190 // Done. 191 return cursor; 192 } 193 194 @Override querySliceUriPairs()195 public Cursor querySliceUriPairs() { 196 final SliceViewManager manager = SliceViewManager.getInstance(getContext()); 197 final MatrixCursor cursor = new MatrixCursor(SLICE_URI_PAIRS_COLUMNS); 198 final Uri baseUri = 199 new Uri.Builder() 200 .scheme(ContentResolver.SCHEME_CONTENT) 201 .authority(SettingsSliceProvider.SLICE_AUTHORITY) 202 .build(); 203 final Uri platformBaseUri = 204 new Uri.Builder() 205 .scheme(ContentResolver.SCHEME_CONTENT) 206 .authority(SettingsSlicesContract.AUTHORITY) 207 .build(); 208 209 final Collection<Uri> sliceUris = manager.getSliceDescendants(baseUri); 210 sliceUris.addAll(manager.getSliceDescendants(platformBaseUri)); 211 212 for (Uri uri : sliceUris) { 213 cursor.newRow() 214 .add(SearchIndexablesContract.SliceUriPairColumns.KEY, uri.getLastPathSegment()) 215 .add(SearchIndexablesContract.SliceUriPairColumns.SLICE_URI, uri); 216 } 217 218 return cursor; 219 } 220 getNonIndexableKeysFromProvider(Context context)221 private List<String> getNonIndexableKeysFromProvider(Context context) { 222 final Collection<Class> values = FeatureFactory.getFactory(context) 223 .getSearchFeatureProvider().getSearchIndexableResources().getProviderValues(); 224 final List<String> nonIndexableKeys = new ArrayList<>(); 225 226 for (Class<?> clazz : values) { 227 final long startTime = System.currentTimeMillis(); 228 Indexable.SearchIndexProvider provider = DatabaseIndexingUtils.getSearchIndexProvider( 229 clazz); 230 231 List<String> providerNonIndexableKeys; 232 try { 233 providerNonIndexableKeys = provider.getNonIndexableKeys(context); 234 } catch (Exception e) { 235 // Catch a generic crash. In the absence of the catch, the background thread will 236 // silently fail anyway, so we aren't losing information by catching the exception. 237 // We crash when the system property exists so that we can test if crashes need to 238 // be fixed. 239 // The gain is that if there is a crash in a specific controller, we don't lose all 240 // non-indexable keys, but we can still find specific crashes in development. 241 if (System.getProperty(SYSPROP_CRASH_ON_ERROR) != null) { 242 throw new RuntimeException(e); 243 } 244 Log.e(TAG, "Error trying to get non-indexable keys from: " + clazz.getName(), e); 245 continue; 246 } 247 248 if (providerNonIndexableKeys == null || providerNonIndexableKeys.isEmpty()) { 249 if (DEBUG) { 250 final long totalTime = System.currentTimeMillis() - startTime; 251 Log.d(TAG, "No indexable, total time " + totalTime); 252 } 253 continue; 254 } 255 256 if (providerNonIndexableKeys.removeAll(INVALID_KEYS)) { 257 Log.v(TAG, provider + " tried to add an empty non-indexable key"); 258 } 259 260 if (DEBUG) { 261 final long totalTime = System.currentTimeMillis() - startTime; 262 Log.d(TAG, "Non-indexables " + providerNonIndexableKeys.size() + ", total time " 263 + totalTime); 264 } 265 266 nonIndexableKeys.addAll(providerNonIndexableKeys); 267 } 268 269 return nonIndexableKeys; 270 } 271 getSearchIndexableResourcesFromProvider(Context context)272 private List<SearchIndexableResource> getSearchIndexableResourcesFromProvider(Context context) { 273 Collection<Class> values = FeatureFactory.getFactory(context) 274 .getSearchFeatureProvider().getSearchIndexableResources().getProviderValues(); 275 List<SearchIndexableResource> resourceList = new ArrayList<>(); 276 277 for (Class<?> clazz : values) { 278 Indexable.SearchIndexProvider provider = DatabaseIndexingUtils.getSearchIndexProvider( 279 clazz); 280 281 final List<SearchIndexableResource> resList = 282 provider.getXmlResourcesToIndex(context, true); 283 284 if (resList == null) { 285 continue; 286 } 287 288 for (SearchIndexableResource item : resList) { 289 item.className = TextUtils.isEmpty(item.className) 290 ? clazz.getName() 291 : item.className; 292 } 293 294 resourceList.addAll(resList); 295 } 296 297 return resourceList; 298 } 299 getSearchIndexableRawFromProvider(Context context)300 private List<SearchIndexableRaw> getSearchIndexableRawFromProvider(Context context) { 301 final Collection<Class> values = FeatureFactory.getFactory(context) 302 .getSearchFeatureProvider().getSearchIndexableResources().getProviderValues(); 303 final List<SearchIndexableRaw> rawList = new ArrayList<>(); 304 305 for (Class<?> clazz : values) { 306 Indexable.SearchIndexProvider provider = DatabaseIndexingUtils.getSearchIndexProvider( 307 clazz); 308 final List<SearchIndexableRaw> providerRaws = provider.getRawDataToIndex(context, 309 true /* enabled */); 310 311 if (providerRaws == null) { 312 continue; 313 } 314 315 for (SearchIndexableRaw raw : providerRaws) { 316 // The classname and intent information comes from the PreIndexData 317 // This will be more clear when provider conversion is done at PreIndex time. 318 raw.className = clazz.getName(); 319 320 } 321 rawList.addAll(providerRaws); 322 } 323 324 return rawList; 325 } 326 } 327