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 18 package com.android.settings.search; 19 20 import android.content.Context; 21 import android.content.Intent; 22 import android.net.Uri; 23 import android.os.Bundle; 24 import android.util.ArrayMap; 25 import android.util.Log; 26 27 import com.android.internal.logging.nano.MetricsProto; 28 import com.android.settings.SettingsActivity; 29 import com.android.settings.core.BasePreferenceController; 30 import com.android.settings.core.PreferenceControllerMixin; 31 import com.android.settings.core.SubSettingLauncher; 32 import com.android.settingslib.core.AbstractPreferenceController; 33 34 import java.lang.reflect.Field; 35 import java.util.List; 36 import java.util.Map; 37 38 /** 39 * Utility class for {@like DatabaseIndexingManager} to handle the mapping between Payloads 40 * and Preference controllers, and managing indexable classes. 41 */ 42 public class DatabaseIndexingUtils { 43 44 private static final String TAG = "IndexingUtil"; 45 46 public static final String FIELD_NAME_SEARCH_INDEX_DATA_PROVIDER = 47 "SEARCH_INDEX_DATA_PROVIDER"; 48 49 /** 50 * Builds intent that launches the search destination as a sub-setting. 51 */ buildSearchResultPageIntent(Context context, String className, String key, String screenTitle)52 public static Intent buildSearchResultPageIntent(Context context, String className, String key, 53 String screenTitle) { 54 return buildSearchResultPageIntent(context, className, key, screenTitle, 55 MetricsProto.MetricsEvent.DASHBOARD_SEARCH_RESULTS); 56 } 57 buildSearchResultPageIntent(Context context, String className, String key, String screenTitle, int sourceMetricsCategory)58 public static Intent buildSearchResultPageIntent(Context context, String className, String key, 59 String screenTitle, int sourceMetricsCategory) { 60 final Bundle args = new Bundle(); 61 args.putString(SettingsActivity.EXTRA_FRAGMENT_ARG_KEY, key); 62 final Intent searchDestination = new SubSettingLauncher(context) 63 .setDestination(className) 64 .setArguments(args) 65 .setTitle(screenTitle) 66 .setSourceMetricsCategory(sourceMetricsCategory) 67 .toIntent(); 68 searchDestination.putExtra(SettingsActivity.EXTRA_FRAGMENT_ARG_KEY, key) 69 .setAction("com.android.settings.SEARCH_RESULT_TRAMPOLINE") 70 .setComponent(null); 71 return searchDestination; 72 } 73 74 /** 75 * @param className which wil provide the map between from {@link Uri}s to 76 * {@link PreferenceControllerMixin} 77 * @return A map between {@link Uri}s and {@link PreferenceControllerMixin}s to get the payload 78 * types for Settings. 79 */ getPayloadKeyMap(String className, Context context)80 public static Map<String, ResultPayload> getPayloadKeyMap(String className, Context context) { 81 ArrayMap<String, ResultPayload> map = new ArrayMap<>(); 82 if (context == null) { 83 return map; 84 } 85 86 final Class<?> clazz = getIndexableClass(className); 87 88 if (clazz == null) { 89 Log.d(TAG, "SearchIndexableResource '" + className + 90 "' should implement the " + Indexable.class.getName() + " interface!"); 91 return map; 92 } 93 94 // Will be non null only for a Local provider implementing a 95 // SEARCH_INDEX_DATA_PROVIDER field 96 final Indexable.SearchIndexProvider provider = getSearchIndexProvider(clazz); 97 98 final List<AbstractPreferenceController> controllers = 99 provider.getPreferenceControllers(context); 100 101 if (controllers == null) { 102 return map; 103 } 104 105 for (AbstractPreferenceController controller : controllers) { 106 ResultPayload payload; 107 if (controller instanceof PreferenceControllerMixin) { 108 payload = ((PreferenceControllerMixin) controller).getResultPayload(); 109 110 } else if (controller instanceof BasePreferenceController) { 111 payload = ((BasePreferenceController) controller).getResultPayload(); 112 } else { 113 throw new IllegalStateException(controller.getClass().getName() 114 + " must implement " + PreferenceControllerMixin.class.getName()); 115 } 116 if (payload != null) { 117 map.put(controller.getPreferenceKey(), payload); 118 } 119 } 120 121 return map; 122 } 123 getIndexableClass(String className)124 public static Class<?> getIndexableClass(String className) { 125 final Class<?> clazz; 126 try { 127 clazz = Class.forName(className); 128 } catch (ClassNotFoundException e) { 129 Log.d(TAG, "Cannot find class: " + className); 130 return null; 131 } 132 return isIndexableClass(clazz) ? clazz : null; 133 } 134 isIndexableClass(final Class<?> clazz)135 public static boolean isIndexableClass(final Class<?> clazz) { 136 return (clazz != null) && Indexable.class.isAssignableFrom(clazz); 137 } 138 getSearchIndexProvider(final Class<?> clazz)139 public static Indexable.SearchIndexProvider getSearchIndexProvider(final Class<?> clazz) { 140 try { 141 final Field f = clazz.getField(FIELD_NAME_SEARCH_INDEX_DATA_PROVIDER); 142 return (Indexable.SearchIndexProvider) f.get(null); 143 } catch (NoSuchFieldException e) { 144 Log.d(TAG, "Cannot find field '" + FIELD_NAME_SEARCH_INDEX_DATA_PROVIDER + "'"); 145 } catch (SecurityException se) { 146 Log.d(TAG, "Security exception for field '" + 147 FIELD_NAME_SEARCH_INDEX_DATA_PROVIDER + "'"); 148 } catch (IllegalAccessException e) { 149 Log.d(TAG, "Illegal access to field '" + FIELD_NAME_SEARCH_INDEX_DATA_PROVIDER + "'"); 150 } catch (IllegalArgumentException e) { 151 Log.d(TAG, "Illegal argument when accessing field '" + 152 FIELD_NAME_SEARCH_INDEX_DATA_PROVIDER + "'"); 153 } 154 return null; 155 } 156 }