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