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 com.android.settings.core.PreferenceXmlParserUtils.METADATA_KEY; 20 import static com.android.settings.core.PreferenceXmlParserUtils.METADATA_SEARCHABLE; 21 import static com.android.settings.core.PreferenceXmlParserUtils.MetadataFlag.FLAG_INCLUDE_PREF_SCREEN; 22 import static com.android.settings.core.PreferenceXmlParserUtils.MetadataFlag.FLAG_NEED_KEY; 23 import static com.android.settings.core.PreferenceXmlParserUtils.MetadataFlag.FLAG_NEED_SEARCHABLE; 24 25 import android.annotation.XmlRes; 26 import android.content.Context; 27 import android.os.Bundle; 28 import android.provider.SearchIndexableResource; 29 import android.util.Log; 30 31 import androidx.annotation.CallSuper; 32 import androidx.annotation.VisibleForTesting; 33 34 import com.android.settings.core.BasePreferenceController; 35 import com.android.settings.core.PreferenceControllerListHelper; 36 import com.android.settings.core.PreferenceControllerMixin; 37 import com.android.settings.core.PreferenceXmlParserUtils; 38 import com.android.settingslib.core.AbstractPreferenceController; 39 import com.android.settingslib.search.Indexable; 40 import com.android.settingslib.search.SearchIndexableRaw; 41 42 import org.xmlpull.v1.XmlPullParserException; 43 44 import java.io.IOException; 45 import java.util.ArrayList; 46 import java.util.Arrays; 47 import java.util.List; 48 49 /** 50 * A basic SearchIndexProvider that returns no data to index. 51 */ 52 public class BaseSearchIndexProvider implements Indexable.SearchIndexProvider { 53 54 private static final String TAG = "BaseSearchIndex"; 55 private int mXmlRes = 0; 56 BaseSearchIndexProvider()57 public BaseSearchIndexProvider() { 58 } 59 BaseSearchIndexProvider(int xmlRes)60 public BaseSearchIndexProvider(int xmlRes) { 61 mXmlRes = xmlRes; 62 } 63 64 @Override getXmlResourcesToIndex(Context context, boolean enabled)65 public List<SearchIndexableResource> getXmlResourcesToIndex(Context context, boolean enabled) { 66 if (mXmlRes != 0) { 67 final SearchIndexableResource sir = new SearchIndexableResource(context); 68 sir.xmlResId = mXmlRes; 69 return Arrays.asList(sir); 70 } 71 return null; 72 } 73 74 @Override getRawDataToIndex(Context context, boolean enabled)75 public List<SearchIndexableRaw> getRawDataToIndex(Context context, boolean enabled) { 76 return null; 77 } 78 79 @Override 80 @CallSuper getDynamicRawDataToIndex(Context context, boolean enabled)81 public List<SearchIndexableRaw> getDynamicRawDataToIndex(Context context, boolean enabled) { 82 final List<SearchIndexableRaw> dynamicRaws = new ArrayList<>(); 83 if (!isPageSearchEnabled(context)) { 84 // Entire page should be suppressed, do not add dynamic raw data. 85 return dynamicRaws; 86 } 87 final List<AbstractPreferenceController> controllers = getPreferenceControllers(context); 88 if (controllers == null || controllers.isEmpty()) { 89 return dynamicRaws; 90 } 91 for (AbstractPreferenceController controller : controllers) { 92 if (controller instanceof PreferenceControllerMixin) { 93 ((PreferenceControllerMixin) controller).updateDynamicRawDataToIndex(dynamicRaws); 94 } else if (controller instanceof BasePreferenceController) { 95 ((BasePreferenceController) controller).updateDynamicRawDataToIndex(dynamicRaws); 96 } else { 97 Log.e(TAG, controller.getClass().getName() 98 + " must implement " + PreferenceControllerMixin.class.getName() 99 + " treating the dynamic indexable"); 100 } 101 } 102 return dynamicRaws; 103 } 104 105 @Override 106 @CallSuper getNonIndexableKeys(Context context)107 public List<String> getNonIndexableKeys(Context context) { 108 if (!isPageSearchEnabled(context)) { 109 // Entire page should be suppressed, mark all keys from this page as non-indexable. 110 return getNonIndexableKeysFromXml(context, true /* suppressAllPage */); 111 } 112 final List<String> nonIndexableKeys = new ArrayList<>(); 113 nonIndexableKeys.addAll(getNonIndexableKeysFromXml(context, false /* suppressAllPage */)); 114 final List<AbstractPreferenceController> controllers = getPreferenceControllers(context); 115 if (controllers != null && !controllers.isEmpty()) { 116 for (AbstractPreferenceController controller : controllers) { 117 if (controller instanceof PreferenceControllerMixin) { 118 ((PreferenceControllerMixin) controller) 119 .updateNonIndexableKeys(nonIndexableKeys); 120 } else if (controller instanceof BasePreferenceController) { 121 ((BasePreferenceController) controller).updateNonIndexableKeys( 122 nonIndexableKeys); 123 } else { 124 Log.e(TAG, controller.getClass().getName() 125 + " must implement " + PreferenceControllerMixin.class.getName() 126 + " treating the key non-indexable"); 127 nonIndexableKeys.add(controller.getPreferenceKey()); 128 } 129 } 130 } 131 return nonIndexableKeys; 132 } 133 getPreferenceControllers(Context context)134 public List<AbstractPreferenceController> getPreferenceControllers(Context context) { 135 List<AbstractPreferenceController> controllersFromCode = new ArrayList<>(); 136 try { 137 controllersFromCode = createPreferenceControllers(context); 138 } catch (Exception e) { 139 Log.w(TAG, "Error initial controller"); 140 } 141 142 final List<SearchIndexableResource> res = getXmlResourcesToIndex(context, true); 143 if (res == null || res.isEmpty()) { 144 return controllersFromCode; 145 } 146 List<BasePreferenceController> controllersFromXml = new ArrayList<>(); 147 for (SearchIndexableResource sir : res) { 148 controllersFromXml.addAll(PreferenceControllerListHelper 149 .getPreferenceControllersFromXml(context, sir.xmlResId)); 150 } 151 controllersFromXml = PreferenceControllerListHelper.filterControllers(controllersFromXml, 152 controllersFromCode); 153 final List<AbstractPreferenceController> allControllers = new ArrayList<>(); 154 if (controllersFromCode != null) { 155 allControllers.addAll(controllersFromCode); 156 } 157 allControllers.addAll(controllersFromXml); 158 return allControllers; 159 } 160 161 /** 162 * Creates a list of {@link AbstractPreferenceController} programatically. 163 * <p/> 164 * This list should create controllers that are not defined in xml as a Slice controller. 165 */ createPreferenceControllers(Context context)166 public List<AbstractPreferenceController> createPreferenceControllers(Context context) { 167 return null; 168 } 169 170 /** 171 * Returns true if the page should be considered in search query. If return false, entire page 172 * will be suppressed during search query. 173 */ isPageSearchEnabled(Context context)174 protected boolean isPageSearchEnabled(Context context) { 175 return true; 176 } 177 178 /** 179 * Get all non-indexable keys from xml. If {@param suppressAllPage} is set, all keys are 180 * considered non-indexable. Otherwise, only keys with searchable="false" are included. 181 */ getNonIndexableKeysFromXml(Context context, boolean suppressAllPage)182 private List<String> getNonIndexableKeysFromXml(Context context, boolean suppressAllPage) { 183 final List<SearchIndexableResource> resources = getXmlResourcesToIndex( 184 context, true /* not used*/); 185 if (resources == null || resources.isEmpty()) { 186 return new ArrayList<>(); 187 } 188 final List<String> nonIndexableKeys = new ArrayList<>(); 189 for (SearchIndexableResource res : resources) { 190 nonIndexableKeys.addAll( 191 getNonIndexableKeysFromXml(context, res.xmlResId, suppressAllPage)); 192 } 193 return nonIndexableKeys; 194 } 195 196 @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) getNonIndexableKeysFromXml(Context context, @XmlRes int xmlResId, boolean suppressAllPage)197 public List<String> getNonIndexableKeysFromXml(Context context, @XmlRes int xmlResId, 198 boolean suppressAllPage) { 199 return getKeysFromXml(context, xmlResId, suppressAllPage); 200 } 201 getKeysFromXml(Context context, @XmlRes int xmlResId, boolean suppressAllPage)202 private List<String> getKeysFromXml(Context context, @XmlRes int xmlResId, 203 boolean suppressAllPage) { 204 final List<String> keys = new ArrayList<>(); 205 try { 206 final List<Bundle> metadata = PreferenceXmlParserUtils.extractMetadata(context, 207 xmlResId, FLAG_NEED_KEY | FLAG_INCLUDE_PREF_SCREEN | FLAG_NEED_SEARCHABLE); 208 for (Bundle bundle : metadata) { 209 if (suppressAllPage || !bundle.getBoolean(METADATA_SEARCHABLE, true)) { 210 keys.add(bundle.getString(METADATA_KEY)); 211 } 212 } 213 } catch (IOException | XmlPullParserException e) { 214 Log.w(TAG, "Error parsing non-indexable from xml " + xmlResId); 215 } 216 return keys; 217 } 218 } 219