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 40 import org.xmlpull.v1.XmlPullParserException; 41 42 import java.io.IOException; 43 import java.util.ArrayList; 44 import java.util.List; 45 46 /** 47 * A basic SearchIndexProvider that returns no data to index. 48 */ 49 public class BaseSearchIndexProvider implements Indexable.SearchIndexProvider { 50 51 private static final String TAG = "BaseSearchIndex"; 52 BaseSearchIndexProvider()53 public BaseSearchIndexProvider() { 54 } 55 56 @Override getXmlResourcesToIndex(Context context, boolean enabled)57 public List<SearchIndexableResource> getXmlResourcesToIndex(Context context, boolean enabled) { 58 return null; 59 } 60 61 @Override getRawDataToIndex(Context context, boolean enabled)62 public List<SearchIndexableRaw> getRawDataToIndex(Context context, boolean enabled) { 63 return null; 64 } 65 66 @Override 67 @CallSuper getNonIndexableKeys(Context context)68 public List<String> getNonIndexableKeys(Context context) { 69 if (!isPageSearchEnabled(context)) { 70 // Entire page should be suppressed, mark all keys from this page as non-indexable. 71 return getNonIndexableKeysFromXml(context, true /* suppressAllPage */); 72 } 73 final List<String> nonIndexableKeys = new ArrayList<>(); 74 nonIndexableKeys.addAll(getNonIndexableKeysFromXml(context, false /* suppressAllPage */)); 75 final List<AbstractPreferenceController> controllers = getPreferenceControllers(context); 76 if (controllers != null && !controllers.isEmpty()) { 77 for (AbstractPreferenceController controller : controllers) { 78 if (controller instanceof PreferenceControllerMixin) { 79 ((PreferenceControllerMixin) controller) 80 .updateNonIndexableKeys(nonIndexableKeys); 81 } else if (controller instanceof BasePreferenceController) { 82 ((BasePreferenceController) controller).updateNonIndexableKeys( 83 nonIndexableKeys); 84 } else { 85 Log.e(TAG, controller.getClass().getName() 86 + " must implement " + PreferenceControllerMixin.class.getName() 87 + " treating the key non-indexable"); 88 nonIndexableKeys.add(controller.getPreferenceKey()); 89 } 90 } 91 } 92 return nonIndexableKeys; 93 } 94 95 @Override getPreferenceControllers(Context context)96 public List<AbstractPreferenceController> getPreferenceControllers(Context context) { 97 final List<AbstractPreferenceController> controllersFromCode = 98 createPreferenceControllers(context); 99 final List<SearchIndexableResource> res = getXmlResourcesToIndex(context, true); 100 if (res == null || res.isEmpty()) { 101 return controllersFromCode; 102 } 103 List<BasePreferenceController> controllersFromXml = new ArrayList<>(); 104 for (SearchIndexableResource sir : res) { 105 controllersFromXml.addAll(PreferenceControllerListHelper 106 .getPreferenceControllersFromXml(context, sir.xmlResId)); 107 } 108 controllersFromXml = PreferenceControllerListHelper.filterControllers(controllersFromXml, 109 controllersFromCode); 110 final List<AbstractPreferenceController> allControllers = new ArrayList<>(); 111 if (controllersFromCode != null) { 112 allControllers.addAll(controllersFromCode); 113 } 114 allControllers.addAll(controllersFromXml); 115 return allControllers; 116 } 117 118 /** 119 * Creates a list of {@link AbstractPreferenceController} programatically. 120 * <p/> 121 * This list should create controllers that are not defined in xml as a Slice controller. 122 */ createPreferenceControllers(Context context)123 public List<AbstractPreferenceController> createPreferenceControllers(Context context) { 124 return null; 125 } 126 127 /** 128 * Returns true if the page should be considered in search query. If return false, entire page 129 * will be suppressed during search query. 130 */ isPageSearchEnabled(Context context)131 protected boolean isPageSearchEnabled(Context context) { 132 return true; 133 } 134 135 /** 136 * Get all non-indexable keys from xml. If {@param suppressAllPage} is set, all keys are 137 * considered non-indexable. Otherwise, only keys with searchable="false" are included. 138 */ getNonIndexableKeysFromXml(Context context, boolean suppressAllPage)139 private List<String> getNonIndexableKeysFromXml(Context context, boolean suppressAllPage) { 140 final List<SearchIndexableResource> resources = getXmlResourcesToIndex( 141 context, true /* not used*/); 142 if (resources == null || resources.isEmpty()) { 143 return new ArrayList<>(); 144 } 145 final List<String> nonIndexableKeys = new ArrayList<>(); 146 for (SearchIndexableResource res : resources) { 147 nonIndexableKeys.addAll( 148 getNonIndexableKeysFromXml(context, res.xmlResId, suppressAllPage)); 149 } 150 return nonIndexableKeys; 151 } 152 153 @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) getNonIndexableKeysFromXml(Context context, @XmlRes int xmlResId, boolean suppressAllPage)154 public List<String> getNonIndexableKeysFromXml(Context context, @XmlRes int xmlResId, 155 boolean suppressAllPage) { 156 return getKeysFromXml(context, xmlResId, suppressAllPage); 157 } 158 getKeysFromXml(Context context, @XmlRes int xmlResId, boolean suppressAllPage)159 private List<String> getKeysFromXml(Context context, @XmlRes int xmlResId, 160 boolean suppressAllPage) { 161 final List<String> keys = new ArrayList<>(); 162 try { 163 final List<Bundle> metadata = PreferenceXmlParserUtils.extractMetadata(context, 164 xmlResId, FLAG_NEED_KEY | FLAG_INCLUDE_PREF_SCREEN | FLAG_NEED_SEARCHABLE); 165 for (Bundle bundle : metadata) { 166 if (suppressAllPage || !bundle.getBoolean(METADATA_SEARCHABLE, true)) { 167 keys.add(bundle.getString(METADATA_KEY)); 168 } 169 } 170 } catch (IOException | XmlPullParserException e) { 171 Log.w(TAG, "Error parsing non-indexable from xml " + xmlResId); 172 } 173 return keys; 174 } 175 } 176