Declare "searchable" attribute for preferences.

Now we can easily mark a preference nonIndexable in xml instead of
adding key into searchIndexProvider.

Bug: 112608186
Test: robotests
Change-Id: I0ff16d44bb7b6ad148d3d35f09ca0da0163f73f4
This commit is contained in:
Fan Zhang
2018-08-14 16:25:54 -07:00
parent 097cfa7251
commit a79c377fbc
28 changed files with 201 additions and 148 deletions

View File

@@ -69,6 +69,8 @@
<declare-styleable name="Preference"> <declare-styleable name="Preference">
<!-- Synonyms for search results --> <!-- Synonyms for search results -->
<attr name="keywords" format="string" /> <attr name="keywords" format="string" />
<!-- Whether or not the preference is searchable, by default it's true. -->
<attr name="searchable" format="boolean" />
<!-- Classname of a PreferenceController corresponding to the preference --> <!-- Classname of a PreferenceController corresponding to the preference -->
<attr name="controller" format="string" /> <attr name="controller" format="string" />
<!-- {@code true} when the controller declared represents a slice from {@link android.app.SettingsSliceContract} --> <!-- {@code true} when the controller declared represents a slice from {@link android.app.SettingsSliceContract} -->

View File

@@ -71,6 +71,7 @@
android:key="special_access" android:key="special_access"
android:fragment="com.android.settings.applications.specialaccess.SpecialAccessSettings" android:fragment="com.android.settings.applications.specialaccess.SpecialAccessSettings"
android:title="@string/special_access" android:title="@string/special_access"
android:order="20" /> android:order="20"
settings:searchable="false"/>
</PreferenceScreen> </PreferenceScreen>

View File

@@ -25,7 +25,7 @@
android:key="assist_and_voice_input" android:key="assist_and_voice_input"
android:title="@string/assist_and_voice_input_title" android:title="@string/assist_and_voice_input_title"
android:fragment="com.android.settings.applications.assist.ManageAssist" android:fragment="com.android.settings.applications.assist.ManageAssist"
settings:keywords="@string/keywords_assist_input"/> settings:searchable="false"/>
<com.android.settings.widget.AppPreference <com.android.settings.widget.AppPreference
android:key="default_browser" android:key="default_browser"
@@ -86,7 +86,8 @@
<com.android.settings.widget.AppPreference <com.android.settings.widget.AppPreference
android:key="work_default_browser" android:key="work_default_browser"
android:title="@string/default_browser_title" android:title="@string/default_browser_title"
android:fragment="com.android.settings.applications.defaultapps.DefaultBrowserPicker"> android:fragment="com.android.settings.applications.defaultapps.DefaultBrowserPicker"
settings:searchable="false">
<extra android:name="for_work" android:value="true" /> <extra android:name="for_work" android:value="true" />
</com.android.settings.widget.AppPreference> </com.android.settings.widget.AppPreference>
@@ -94,7 +95,7 @@
android:key="work_default_phone_app" android:key="work_default_phone_app"
android:title="@string/default_phone_title" android:title="@string/default_phone_title"
android:fragment="com.android.settings.applications.defaultapps.DefaultPhonePicker" android:fragment="com.android.settings.applications.defaultapps.DefaultPhonePicker"
settings:keywords="@string/keywords_default_phone_app"> settings:searchable="false">
<extra android:name="for_work" android:value="true" /> <extra android:name="for_work" android:value="true" />
</com.android.settings.widget.AppPreference> </com.android.settings.widget.AppPreference>

View File

@@ -32,7 +32,7 @@
<com.android.settings.widget.WorkOnlyCategory <com.android.settings.widget.WorkOnlyCategory
android:key="work_app_defaults" android:key="autofill_work_app_defaults"
android:title="@string/default_for_work"> android:title="@string/default_for_work">
<com.android.settings.widget.GearPreference <com.android.settings.widget.GearPreference

View File

@@ -183,7 +183,8 @@
<Preference <Preference
android:key="feature_flags_dashboard" android:key="feature_flags_dashboard"
android:title="@string/feature_flags_dashboard_title" android:title="@string/feature_flags_dashboard_title"
android:fragment="com.android.settings.development.featureflags.FeatureFlagsDashboard" /> android:fragment="com.android.settings.development.featureflags.FeatureFlagsDashboard"
settings:searchable="false" />
<SwitchPreference <SwitchPreference
android:key="enable_gpu_debug_layers" android:key="enable_gpu_debug_layers"

View File

@@ -19,7 +19,8 @@
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:settings="http://schemas.android.com/apk/res-auto" xmlns:settings="http://schemas.android.com/apk/res-auto"
android:key="manage_assist_screen" android:key="manage_assist_screen"
android:title="@string/assist_and_voice_input_title"> android:title="@string/assist_and_voice_input_title"
settings:keywords="@string/keywords_assist_input">
<com.android.settings.widget.GearPreference <com.android.settings.widget.GearPreference
android:key="default_assist" android:key="default_assist"

View File

@@ -97,13 +97,5 @@ public class AppAndNotificationDashboardFragment extends DashboardFragment {
Context context) { Context context) {
return buildPreferenceControllers(context, null, null /* host */); return buildPreferenceControllers(context, null, null /* host */);
} }
@Override
public List<String> getNonIndexableKeys(Context context) {
List<String> keys = super.getNonIndexableKeys(context);
keys.add((new SpecialAppAccessPreferenceController(context))
.getPreferenceKey());
return keys;
}
}; };
} }

View File

@@ -101,16 +101,6 @@ public class DefaultAppSettings extends DashboardFragment {
return Arrays.asList(sir); return Arrays.asList(sir);
} }
@Override
public List<String> getNonIndexableKeys(Context context) {
List<String> keys = super.getNonIndexableKeys(context);
keys.add(KEY_ASSIST_VOICE_INPUT);
// TODO (b/38230148) Remove these keys when we can differentiate work results
keys.add(DefaultWorkPhonePreferenceController.KEY);
keys.add(DefaultWorkBrowserPreferenceController.KEY);
return keys;
}
@Override @Override
public List<AbstractPreferenceController> createPreferenceControllers( public List<AbstractPreferenceController> createPreferenceControllers(
Context context) { Context context) {

View File

@@ -40,7 +40,7 @@ public class BackupSettingsActivityPreferenceController extends BasePreferenceCo
@Override @Override
public int getAvailabilityStatus() { public int getAvailabilityStatus() {
return mUm.isAdminUser() return mUm.isAdminUser()
? AVAILABLE ? AVAILABLE_UNSEARCHABLE
: UNSUPPORTED_ON_DEVICE; : UNSUPPORTED_ON_DEVICE;
} }

View File

@@ -274,6 +274,10 @@ public abstract class BasePreferenceController extends AbstractPreferenceControl
Log.w(TAG, "Skipping updateNonIndexableKeys due to empty key " + toString()); Log.w(TAG, "Skipping updateNonIndexableKeys due to empty key " + toString());
return; return;
} }
if (keys.contains(key)) {
Log.w(TAG, "Skipping updateNonIndexableKeys, key already in list. " + toString());
return;
}
keys.add(key); keys.add(key);
} }
} }

View File

@@ -42,7 +42,12 @@ public interface PreferenceControllerMixin {
final String key = ((AbstractPreferenceController) this).getPreferenceKey(); final String key = ((AbstractPreferenceController) this).getPreferenceKey();
if (TextUtils.isEmpty(key)) { if (TextUtils.isEmpty(key)) {
Log.w(TAG, Log.w(TAG,
"Skipping updateNonIndexableKeys due to empty key " + this.toString()); "Skipping updateNonIndexableKeys due to empty key " + toString());
return;
}
if (keys.contains(key)) {
Log.w(TAG, "Skipping updateNonIndexableKeys, key already in list. "
+ toString());
return; return;
} }
keys.add(key); keys.add(key);

View File

@@ -29,6 +29,9 @@ import android.util.Log;
import android.util.TypedValue; import android.util.TypedValue;
import android.util.Xml; import android.util.Xml;
import androidx.annotation.IntDef;
import androidx.annotation.VisibleForTesting;
import com.android.settings.R; import com.android.settings.R;
import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParser;
@@ -41,9 +44,6 @@ import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import androidx.annotation.IntDef;
import androidx.annotation.VisibleForTesting;
/** /**
* Utility class to parse elements of XML preferences * Utility class to parse elements of XML preferences
*/ */
@@ -53,7 +53,8 @@ public class PreferenceXmlParserUtils {
@VisibleForTesting @VisibleForTesting
static final String PREF_SCREEN_TAG = "PreferenceScreen"; static final String PREF_SCREEN_TAG = "PreferenceScreen";
private static final List<String> SUPPORTED_PREF_TYPES = Arrays.asList( private static final List<String> SUPPORTED_PREF_TYPES = Arrays.asList(
"Preference", "PreferenceCategory", "PreferenceScreen"); "Preference", "PreferenceCategory", "PreferenceScreen",
"com.android.settings.widget.WorkOnlyCategory");
/** /**
* Flag definition to indicate which metadata should be extracted when * Flag definition to indicate which metadata should be extracted when
@@ -67,7 +68,8 @@ public class PreferenceXmlParserUtils {
MetadataFlag.FLAG_NEED_PREF_CONTROLLER, MetadataFlag.FLAG_NEED_PREF_CONTROLLER,
MetadataFlag.FLAG_NEED_PREF_TITLE, MetadataFlag.FLAG_NEED_PREF_TITLE,
MetadataFlag.FLAG_NEED_PREF_SUMMARY, MetadataFlag.FLAG_NEED_PREF_SUMMARY,
MetadataFlag.FLAG_NEED_PREF_ICON}) MetadataFlag.FLAG_NEED_PREF_ICON,
MetadataFlag.FLAG_NEED_SEARCHABLE})
@Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE)
public @interface MetadataFlag { public @interface MetadataFlag {
int FLAG_INCLUDE_PREF_SCREEN = 1; int FLAG_INCLUDE_PREF_SCREEN = 1;
@@ -79,6 +81,7 @@ public class PreferenceXmlParserUtils {
int FLAG_NEED_PREF_ICON = 1 << 6; int FLAG_NEED_PREF_ICON = 1 << 6;
int FLAG_NEED_PLATFORM_SLICE_FLAG = 1 << 7; int FLAG_NEED_PLATFORM_SLICE_FLAG = 1 << 7;
int FLAG_NEED_KEYWORDS = 1 << 8; int FLAG_NEED_KEYWORDS = 1 << 8;
int FLAG_NEED_SEARCHABLE = 1 << 9;
} }
public static final String METADATA_PREF_TYPE = "type"; public static final String METADATA_PREF_TYPE = "type";
@@ -89,6 +92,7 @@ public class PreferenceXmlParserUtils {
public static final String METADATA_ICON = "icon"; public static final String METADATA_ICON = "icon";
public static final String METADATA_PLATFORM_SLICE_FLAG = "platform_slice"; public static final String METADATA_PLATFORM_SLICE_FLAG = "platform_slice";
public static final String METADATA_KEYWORDS = "keywords"; public static final String METADATA_KEYWORDS = "keywords";
public static final String METADATA_SEARCHABLE = "searchable";
private static final String ENTRIES_SEPARATOR = "|"; private static final String ENTRIES_SEPARATOR = "|";
@@ -154,18 +158,6 @@ public class PreferenceXmlParserUtils {
R.styleable.Preference_controller); R.styleable.Preference_controller);
} }
/**
* Call {@link #extractMetadata(Context, int, int)} with {@link #METADATA_ICON} instead.
*/
@Deprecated
public static int getDataIcon(Context context, AttributeSet attrs) {
final TypedArray ta = context.obtainStyledAttributes(attrs,
com.android.internal.R.styleable.Preference);
final int dataIcon = ta.getResourceId(com.android.internal.R.styleable.Icon_icon, 0);
ta.recycle();
return dataIcon;
}
/** /**
* Extracts metadata from preference xml and put them into a {@link Bundle}. * Extracts metadata from preference xml and put them into a {@link Bundle}.
* *
@@ -232,6 +224,10 @@ public class PreferenceXmlParserUtils {
if (hasFlag(flags, MetadataFlag.FLAG_NEED_KEYWORDS)) { if (hasFlag(flags, MetadataFlag.FLAG_NEED_KEYWORDS)) {
preferenceMetadata.putString(METADATA_KEYWORDS, getKeywords(preferenceAttributes)); preferenceMetadata.putString(METADATA_KEYWORDS, getKeywords(preferenceAttributes));
} }
if (hasFlag(flags, MetadataFlag.FLAG_NEED_SEARCHABLE)) {
preferenceMetadata.putBoolean(METADATA_SEARCHABLE,
isSearchable(preferenceAttributes));
}
metadata.add(preferenceMetadata); metadata.add(preferenceMetadata);
preferenceAttributes.recycle(); preferenceAttributes.recycle();
@@ -312,6 +308,10 @@ public class PreferenceXmlParserUtils {
return styledAttributes.getBoolean(R.styleable.Preference_platform_slice, false /* def */); return styledAttributes.getBoolean(R.styleable.Preference_platform_slice, false /* def */);
} }
private static boolean isSearchable(TypedArray styledAttributes) {
return styledAttributes.getBoolean(R.styleable.Preference_searchable, true /* default */);
}
private static String getKeywords(TypedArray styleAttributes) { private static String getKeywords(TypedArray styleAttributes) {
return styleAttributes.getString(R.styleable.Preference_keywords); return styleAttributes.getString(R.styleable.Preference_keywords);
} }

View File

@@ -21,6 +21,7 @@ import android.content.Context;
import android.database.ContentObserver; import android.database.ContentObserver;
import android.net.Uri; import android.net.Uri;
import android.os.Handler; import android.os.Handler;
import android.os.Looper;
import android.os.UserHandle; import android.os.UserHandle;
import android.provider.Settings; import android.provider.Settings;
@@ -30,7 +31,7 @@ final class AutofillDeveloperSettingsObserver extends ContentObserver {
private final ContentResolver mResolver; private final ContentResolver mResolver;
public AutofillDeveloperSettingsObserver(Context context, Runnable changeCallback) { public AutofillDeveloperSettingsObserver(Context context, Runnable changeCallback) {
super(new Handler()); super(new Handler(Looper.getMainLooper()));
mResolver = context.getContentResolver(); mResolver = context.getContentResolver();
mChangeCallback = changeCallback; mChangeCallback = changeCallback;

View File

@@ -47,6 +47,9 @@ public class SwipeUpPreferenceController extends GesturePreferenceController {
final ComponentName recentsComponentName = ComponentName.unflattenFromString( final ComponentName recentsComponentName = ComponentName.unflattenFromString(
context.getString(R.string.config_recentsComponentName)); context.getString(R.string.config_recentsComponentName));
if (recentsComponentName == null) {
return false;
}
final Intent quickStepIntent = new Intent(ACTION_QUICKSTEP) final Intent quickStepIntent = new Intent(ACTION_QUICKSTEP)
.setPackage(recentsComponentName.getPackageName()); .setPackage(recentsComponentName.getPackageName());
if (context.getPackageManager().resolveService(quickStepIntent, if (context.getPackageManager().resolveService(quickStepIntent,

View File

@@ -108,7 +108,7 @@ public class LanguageAndInputSettings extends DashboardFragment {
// Pointer and Tts // Pointer and Tts
final TtsPreferenceController ttsPreferenceController = final TtsPreferenceController ttsPreferenceController =
new TtsPreferenceController(context, new TtsEngines(context)); new TtsPreferenceController(context, KEY_TEXT_TO_SPEECH);
controllers.add(ttsPreferenceController); controllers.add(ttsPreferenceController);
final PointerSpeedController pointerController = new PointerSpeedController(context); final PointerSpeedController pointerController = new PointerSpeedController(context);
controllers.add(pointerController); controllers.add(pointerController);
@@ -180,7 +180,6 @@ public class LanguageAndInputSettings extends DashboardFragment {
public List<String> getNonIndexableKeys(Context context) { public List<String> getNonIndexableKeys(Context context) {
List<String> keys = super.getNonIndexableKeys(context); List<String> keys = super.getNonIndexableKeys(context);
// Duplicates in summary and details pages. // Duplicates in summary and details pages.
keys.add(KEY_TEXT_TO_SPEECH);
keys.add(KEY_PHYSICAL_KEYBOARD); keys.add(KEY_PHYSICAL_KEYBOARD);
return keys; return keys;
} }

View File

@@ -19,31 +19,26 @@ package com.android.settings.language;
import android.content.Context; import android.content.Context;
import android.speech.tts.TtsEngines; import android.speech.tts.TtsEngines;
import androidx.annotation.VisibleForTesting;
import com.android.settings.R; import com.android.settings.R;
import com.android.settings.core.PreferenceControllerMixin; import com.android.settings.core.BasePreferenceController;
import com.android.settingslib.core.AbstractPreferenceController;
public class TtsPreferenceController extends AbstractPreferenceController public class TtsPreferenceController extends BasePreferenceController {
implements PreferenceControllerMixin {
private static final String KEY_VOICE_CATEGORY = "voice_category"; @VisibleForTesting
private static final String KEY_TTS_SETTINGS = "tts_settings_summary"; TtsEngines mTtsEngines;
private final TtsEngines mTtsEngines; public TtsPreferenceController(Context context, String key) {
super(context, key);
public TtsPreferenceController(Context context, TtsEngines ttsEngines) { mTtsEngines = new TtsEngines(context);
super(context);
mTtsEngines = ttsEngines;
} }
@Override @Override
public boolean isAvailable() { public int getAvailabilityStatus() {
return !mTtsEngines.getEngines().isEmpty() && return !mTtsEngines.getEngines().isEmpty() &&
mContext.getResources().getBoolean(R.bool.config_show_tts_settings_summary); mContext.getResources().getBoolean(R.bool.config_show_tts_settings_summary)
} ? AVAILABLE_UNSEARCHABLE
: CONDITIONALLY_UNAVAILABLE;
@Override
public String getPreferenceKey() {
return KEY_TTS_SETTINGS;
} }
} }

View File

@@ -16,14 +16,21 @@
package com.android.settings.search; package com.android.settings.search;
import static com.android.settings.core.PreferenceXmlParserUtils.METADATA_KEY;
import static com.android.settings.core.PreferenceXmlParserUtils.METADATA_SEARCHABLE;
import static com.android.settings.core.PreferenceXmlParserUtils.MetadataFlag
.FLAG_INCLUDE_PREF_SCREEN;
import static com.android.settings.core.PreferenceXmlParserUtils.MetadataFlag.FLAG_NEED_KEY;
import static com.android.settings.core.PreferenceXmlParserUtils.MetadataFlag.FLAG_NEED_SEARCHABLE;
import android.annotation.XmlRes; import android.annotation.XmlRes;
import android.content.Context; import android.content.Context;
import android.content.res.XmlResourceParser; import android.os.Bundle;
import android.provider.SearchIndexableResource; import android.provider.SearchIndexableResource;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log; import android.util.Log;
import android.util.Xml;
import androidx.annotation.CallSuper;
import androidx.annotation.VisibleForTesting;
import com.android.settings.core.BasePreferenceController; import com.android.settings.core.BasePreferenceController;
import com.android.settings.core.PreferenceControllerListHelper; import com.android.settings.core.PreferenceControllerListHelper;
@@ -31,16 +38,12 @@ import com.android.settings.core.PreferenceControllerMixin;
import com.android.settings.core.PreferenceXmlParserUtils; import com.android.settings.core.PreferenceXmlParserUtils;
import com.android.settingslib.core.AbstractPreferenceController; import com.android.settingslib.core.AbstractPreferenceController;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import androidx.annotation.CallSuper;
import androidx.annotation.VisibleForTesting;
/** /**
* A basic SearchIndexProvider that returns no data to index. * A basic SearchIndexProvider that returns no data to index.
*/ */
@@ -66,11 +69,12 @@ public class BaseSearchIndexProvider implements Indexable.SearchIndexProvider {
public List<String> getNonIndexableKeys(Context context) { public List<String> getNonIndexableKeys(Context context) {
if (!isPageSearchEnabled(context)) { if (!isPageSearchEnabled(context)) {
// Entire page should be suppressed, mark all keys from this page as non-indexable. // Entire page should be suppressed, mark all keys from this page as non-indexable.
return getNonIndexableKeysFromXml(context); return getNonIndexableKeysFromXml(context, true /* suppressAllPage */);
} }
final List<String> nonIndexableKeys = new ArrayList<>();
nonIndexableKeys.addAll(getNonIndexableKeysFromXml(context, false /* suppressAllPage */));
final List<AbstractPreferenceController> controllers = getPreferenceControllers(context); final List<AbstractPreferenceController> controllers = getPreferenceControllers(context);
if (controllers != null && !controllers.isEmpty()) { if (controllers != null && !controllers.isEmpty()) {
final List<String> nonIndexableKeys = new ArrayList<>();
for (AbstractPreferenceController controller : controllers) { for (AbstractPreferenceController controller : controllers) {
if (controller instanceof PreferenceControllerMixin) { if (controller instanceof PreferenceControllerMixin) {
((PreferenceControllerMixin) controller) ((PreferenceControllerMixin) controller)
@@ -85,10 +89,8 @@ public class BaseSearchIndexProvider implements Indexable.SearchIndexProvider {
nonIndexableKeys.add(controller.getPreferenceKey()); nonIndexableKeys.add(controller.getPreferenceKey());
} }
} }
return nonIndexableKeys;
} else {
return new ArrayList<>();
} }
return nonIndexableKeys;
} }
@Override @Override
@@ -131,7 +133,11 @@ public class BaseSearchIndexProvider implements Indexable.SearchIndexProvider {
return true; return true;
} }
private List<String> getNonIndexableKeysFromXml(Context context) { /**
* Get all non-indexable keys from xml. If {@param suppressAllPage} is set, all keys are
* considered non-indexable. Otherwise, only keys with searchable="false" are included.
*/
private List<String> getNonIndexableKeysFromXml(Context context, boolean suppressAllPage) {
final List<SearchIndexableResource> resources = getXmlResourcesToIndex( final List<SearchIndexableResource> resources = getXmlResourcesToIndex(
context, true /* not used*/); context, true /* not used*/);
if (resources == null || resources.isEmpty()) { if (resources == null || resources.isEmpty()) {
@@ -139,27 +145,32 @@ public class BaseSearchIndexProvider implements Indexable.SearchIndexProvider {
} }
final List<String> nonIndexableKeys = new ArrayList<>(); final List<String> nonIndexableKeys = new ArrayList<>();
for (SearchIndexableResource res : resources) { for (SearchIndexableResource res : resources) {
nonIndexableKeys.addAll(getNonIndexableKeysFromXml(context, res.xmlResId)); nonIndexableKeys.addAll(
getNonIndexableKeysFromXml(context, res.xmlResId, suppressAllPage));
} }
return nonIndexableKeys; return nonIndexableKeys;
} }
@VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
public List<String> getNonIndexableKeysFromXml(Context context, @XmlRes int xmlResId) { public List<String> getNonIndexableKeysFromXml(Context context, @XmlRes int xmlResId,
final List<String> nonIndexableKeys = new ArrayList<>(); boolean suppressAllPage) {
final XmlResourceParser parser = context.getResources().getXml(xmlResId); return getKeysFromXml(context, xmlResId, suppressAllPage);
final AttributeSet attrs = Xml.asAttributeSet(parser); }
private List<String> getKeysFromXml(Context context, @XmlRes int xmlResId,
boolean suppressAllPage) {
final List<String> keys = new ArrayList<>();
try { try {
while (parser.next() != XmlPullParser.END_DOCUMENT) { final List<Bundle> metadata = PreferenceXmlParserUtils.extractMetadata(context,
final String key = PreferenceXmlParserUtils.getDataKey(context, attrs); xmlResId, FLAG_NEED_KEY | FLAG_INCLUDE_PREF_SCREEN | FLAG_NEED_SEARCHABLE);
if (!TextUtils.isEmpty(key)) { for (Bundle bundle : metadata) {
nonIndexableKeys.add(key); if (suppressAllPage || !bundle.getBoolean(METADATA_SEARCHABLE, true)) {
keys.add(bundle.getString(METADATA_KEY));
} }
} }
} catch (IOException | XmlPullParserException e) { } catch (IOException | XmlPullParserException e) {
Log.w(TAG, "Error parsing non-indexable from xml " + xmlResId); Log.w(TAG, "Error parsing non-indexable from xml " + xmlResId);
} }
return nonIndexableKeys; return keys;
} }
} }

View File

@@ -101,8 +101,6 @@ public class SystemDashboardFragment extends DashboardFragment {
@Override @Override
public List<String> getNonIndexableKeys(Context context) { public List<String> getNonIndexableKeys(Context context) {
List<String> keys = super.getNonIndexableKeys(context); List<String> keys = super.getNonIndexableKeys(context);
keys.add((new BackupSettingsActivityPreferenceController(
context).getPreferenceKey()));
keys.add(KEY_RESET); keys.add(KEY_RESET);
return keys; return keys;
} }

View File

@@ -49,6 +49,13 @@ import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.widget.SimpleAdapter; import android.widget.SimpleAdapter;
import androidx.annotation.VisibleForTesting;
import androidx.annotation.WorkerThread;
import androidx.appcompat.app.AlertDialog;
import androidx.preference.Preference;
import androidx.preference.PreferenceGroup;
import androidx.preference.PreferenceScreen;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.util.UserIcons; import com.android.internal.util.UserIcons;
import com.android.internal.widget.LockPatternUtils; import com.android.internal.widget.LockPatternUtils;
@@ -76,13 +83,6 @@ import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import androidx.annotation.VisibleForTesting;
import androidx.annotation.WorkerThread;
import androidx.appcompat.app.AlertDialog;
import androidx.preference.Preference;
import androidx.preference.PreferenceGroup;
import androidx.preference.PreferenceScreen;
/** /**
* Screen that manages the list of users on the device. * Screen that manages the list of users on the device.
* Guest user is an always visible entry, even if the guest is not currently * Guest user is an always visible entry, even if the guest is not currently
@@ -635,8 +635,8 @@ public class UserSettings extends SettingsPreferenceFragment
AlertDialog.Builder builder = new AlertDialog.Builder(context); AlertDialog.Builder builder = new AlertDialog.Builder(context);
SimpleAdapter adapter = new SimpleAdapter(builder.getContext(), SimpleAdapter adapter = new SimpleAdapter(builder.getContext(),
data, R.layout.two_line_list_item, data, R.layout.two_line_list_item,
new String[] {KEY_TITLE, KEY_SUMMARY}, new String[]{KEY_TITLE, KEY_SUMMARY},
new int[] {R.id.title, R.id.summary}); new int[]{R.id.title, R.id.summary});
builder.setTitle(R.string.user_add_user_type_title); builder.setTitle(R.string.user_add_user_type_title);
builder.setAdapter(adapter, builder.setAdapter(adapter,
new DialogInterface.OnClickListener() { new DialogInterface.OnClickListener() {
@@ -1238,8 +1238,10 @@ public class UserSettings extends SettingsPreferenceFragment
} }
@Override @Override
public List<String> getNonIndexableKeysFromXml(Context context, int xmlResId) { public List<String> getNonIndexableKeysFromXml(Context context, int xmlResId,
final List<String> niks = super.getNonIndexableKeysFromXml(context, xmlResId); boolean suppressAllPage) {
final List<String> niks = super.getNonIndexableKeysFromXml(context, xmlResId,
suppressAllPage);
new AddUserWhenLockedPreferenceController(context, KEY_ADD_USER_WHEN_LOCKED) new AddUserWhenLockedPreferenceController(context, KEY_ADD_USER_WHEN_LOCKED)
.updateNonIndexableKeys(niks); .updateNonIndexableKeys(niks);
new AutoSyncDataPreferenceController(context, null /* parent */) new AutoSyncDataPreferenceController(context, null /* parent */)

View File

@@ -59,5 +59,6 @@
android:title="pref_title_5" android:title="pref_title_5"
android:summaryOn="summary_on" android:summaryOn="summary_on"
android:summaryOff="summary_off" android:summaryOff="summary_off"
settings:searchable="false"
settings:keywords="keywords1, keywords2, keywords3" /> settings:keywords="keywords1, keywords2, keywords3" />
</PreferenceScreen> </PreferenceScreen>

View File

@@ -17,6 +17,7 @@
package com.android.settings.applications; package com.android.settings.applications;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy; import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
@@ -26,7 +27,6 @@ import android.os.UserManager;
import com.android.settings.notification.EmergencyBroadcastPreferenceController; import com.android.settings.notification.EmergencyBroadcastPreferenceController;
import com.android.settings.testutils.SettingsRobolectricTestRunner; import com.android.settings.testutils.SettingsRobolectricTestRunner;
import com.android.settings.testutils.XmlTestUtils;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
@@ -42,19 +42,16 @@ public class AppAndNotificationDashboardFragmentTest {
@Test @Test
@Config(shadows = {ShadowEmergencyBroadcastPreferenceController.class}) @Config(shadows = {ShadowEmergencyBroadcastPreferenceController.class})
public void testNonIndexableKeys_existInXmlLayout() { public void getNonIndexableKeys_shouldIncludeSpecialAppAccess() {
final Context context = spy(RuntimeEnvironment.application); final Context context = spy(RuntimeEnvironment.application);
UserManager manager = mock(UserManager.class); UserManager manager = mock(UserManager.class);
when(manager.isAdminUser()).thenReturn(true); when(manager.isAdminUser()).thenReturn(true);
when(context.getSystemService(Context.USER_SERVICE)).thenReturn(manager); when(context.getSystemService(Context.USER_SERVICE)).thenReturn(manager);
final List<String> niks = AppAndNotificationDashboardFragment.SEARCH_INDEX_DATA_PROVIDER final List<String> niks = AppAndNotificationDashboardFragment.SEARCH_INDEX_DATA_PROVIDER
.getNonIndexableKeys(context); .getNonIndexableKeys(context);
AppAndNotificationDashboardFragment fragment = new AppAndNotificationDashboardFragment();
final int xmlId = fragment.getPreferenceScreenResId();
final List<String> keys = XmlTestUtils.getKeysFromPreferenceXml(context, xmlId); assertThat(niks).contains(
new SpecialAppAccessPreferenceController(context).getPreferenceKey());
assertThat(keys).containsAllIn(niks);
} }
@Implements(EmergencyBroadcastPreferenceController.class) @Implements(EmergencyBroadcastPreferenceController.class)

View File

@@ -165,7 +165,7 @@ public class DefaultAppSettingsTest {
final List<String> niks = DefaultAppSettings.SEARCH_INDEX_DATA_PROVIDER final List<String> niks = DefaultAppSettings.SEARCH_INDEX_DATA_PROVIDER
.getNonIndexableKeys(context); .getNonIndexableKeys(context);
final int xmlId = (new DefaultAppSettings()).getPreferenceScreenResId(); final int xmlId = new DefaultAppSettings().getPreferenceScreenResId();
final List<String> keys = XmlTestUtils.getKeysFromPreferenceXml(context, xmlId); final List<String> keys = XmlTestUtils.getKeysFromPreferenceXml(context, xmlId);

View File

@@ -16,7 +16,10 @@
package com.android.settings.core; package com.android.settings.core;
import static com.android.settings.core.PreferenceXmlParserUtils.METADATA_KEY;
import static com.android.settings.core.PreferenceXmlParserUtils.METADATA_KEYWORDS; import static com.android.settings.core.PreferenceXmlParserUtils.METADATA_KEYWORDS;
import static com.android.settings.core.PreferenceXmlParserUtils.METADATA_SEARCHABLE;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import android.content.Context; import android.content.Context;
@@ -247,8 +250,7 @@ public class PreferenceXmlParserUtilsTest {
@Test @Test
@Config(qualifiers = "mcc999") @Config(qualifiers = "mcc999")
public void extractMetadata_requestIncludesKeywords_shouldContainKeywords() public void extractMetadata_requestIncludesKeywords_shouldContainKeywords() throws Exception {
throws IOException, XmlPullParserException {
final String expectedKeywords = "a, b, c"; final String expectedKeywords = "a, b, c";
final List<Bundle> metadata = PreferenceXmlParserUtils.extractMetadata(mContext, final List<Bundle> metadata = PreferenceXmlParserUtils.extractMetadata(mContext,
R.xml.location_settings, R.xml.location_settings,
@@ -260,6 +262,32 @@ public class PreferenceXmlParserUtilsTest {
assertThat(keywords).isEqualTo(expectedKeywords); assertThat(keywords).isEqualTo(expectedKeywords);
} }
@Test
public void extractMetadata_requestSearchable_shouldDefaultToTrue() throws Exception {
final List<Bundle> metadata = PreferenceXmlParserUtils.extractMetadata(mContext,
R.xml.display_settings, MetadataFlag.FLAG_NEED_SEARCHABLE);
for (Bundle bundle : metadata) {
assertThat(bundle.getBoolean(METADATA_SEARCHABLE)).isTrue();
}
}
@Test
@Config(qualifiers = "mcc999")
public void extractMetadata_requestSearchable_shouldReturnAttributeValue() throws Exception {
final List<Bundle> metadata = PreferenceXmlParserUtils.extractMetadata(mContext,
R.xml.display_settings,
MetadataFlag.FLAG_NEED_KEY | MetadataFlag.FLAG_NEED_SEARCHABLE);
boolean foundKey = false;
for (Bundle bundle : metadata) {
if (TextUtils.equals(bundle.getString(METADATA_KEY), "pref_key_5")) {
assertThat(bundle.getBoolean(METADATA_SEARCHABLE)).isFalse();
foundKey = true;
break;
}
}
assertThat(foundKey).isTrue();
}
/** /**
* @param resId the ID for the XML preference * @param resId the ID for the XML preference
* @return an XML resource parser that points to the start tag * @return an XML resource parser that points to the start tag

View File

@@ -17,6 +17,7 @@
package com.android.settings.language; package com.android.settings.language;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy; import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
@@ -25,6 +26,9 @@ import android.content.Context;
import android.speech.tts.TextToSpeech; import android.speech.tts.TextToSpeech;
import android.speech.tts.TtsEngines; import android.speech.tts.TtsEngines;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
import com.android.settings.testutils.SettingsRobolectricTestRunner; import com.android.settings.testutils.SettingsRobolectricTestRunner;
import org.junit.Before; import org.junit.Before;
@@ -38,9 +42,6 @@ import org.robolectric.annotation.Config;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
@RunWith(SettingsRobolectricTestRunner.class) @RunWith(SettingsRobolectricTestRunner.class)
public class TtsPreferenceControllerTest { public class TtsPreferenceControllerTest {
@@ -58,7 +59,8 @@ public class TtsPreferenceControllerTest {
MockitoAnnotations.initMocks(this); MockitoAnnotations.initMocks(this);
mContext = spy(RuntimeEnvironment.application); mContext = spy(RuntimeEnvironment.application);
mController = new TtsPreferenceController(mContext, mTtsEngines); mController = new TtsPreferenceController(mContext, "test_key");
mController.mTtsEngines = mTtsEngines;
mPreference = new Preference(RuntimeEnvironment.application); mPreference = new Preference(RuntimeEnvironment.application);
mPreference.setKey(mController.getPreferenceKey()); mPreference.setKey(mController.getPreferenceKey());
when(mScreen.findPreference(mPreference.getKey())).thenReturn(mPreference); when(mScreen.findPreference(mPreference.getKey())).thenReturn(mPreference);

View File

@@ -169,4 +169,24 @@ public class BaseSearchIndexProviderTest {
assertThat(nonIndexableKeys).contains("status_header"); assertThat(nonIndexableKeys).contains("status_header");
} }
@Test
@Config(qualifiers = "mcc999")
public void getNonIndexableKeys_hasSearchableAttributeInXml_shouldSuppressUnsearchable() {
final BaseSearchIndexProvider provider = new BaseSearchIndexProvider() {
@Override
public List<SearchIndexableResource> getXmlResourcesToIndex(Context context,
boolean enabled) {
final SearchIndexableResource sir = new SearchIndexableResource(context);
sir.xmlResId = R.xml.display_settings;
return Collections.singletonList(sir);
}
};
final List<String> nonIndexableKeys =
provider.getNonIndexableKeys(RuntimeEnvironment.application);
assertThat(nonIndexableKeys).contains("pref_key_5");
}
} }

View File

@@ -1,6 +1,7 @@
package com.android.settings.search; package com.android.settings.search;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy; import static org.mockito.Mockito.spy;
@@ -21,6 +22,9 @@ import org.junit.runner.RunWith;
import org.robolectric.RuntimeEnvironment; import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config; import org.robolectric.annotation.Config;
import java.util.ArrayList;
import java.util.List;
@RunWith(SettingsRobolectricTestRunner.class) @RunWith(SettingsRobolectricTestRunner.class)
public class SettingsSearchIndexablesProviderTest { public class SettingsSearchIndexablesProviderTest {
@@ -96,16 +100,19 @@ public class SettingsSearchIndexablesProviderTest {
@Test @Test
@Config(qualifiers = "mcc999") @Config(qualifiers = "mcc999")
public void testNonIndexablesColumnFetched() { public void testNonIndexablesColumnFetched() {
Uri rawUri = Uri.parse("content://" + BASE_AUTHORITY + "/" + final Uri rawUri = Uri.parse("content://" + BASE_AUTHORITY + "/" +
SearchIndexablesContract.NON_INDEXABLES_KEYS_PATH); SearchIndexablesContract.NON_INDEXABLES_KEYS_PATH);
final Cursor cursor = mProvider.query(rawUri, final List<String> keys = new ArrayList<>();
SearchIndexablesContract.NON_INDEXABLES_KEYS_COLUMNS, null, null, null);
cursor.moveToFirst(); try (Cursor cursor = mProvider.query(rawUri,
assertThat(cursor.getCount()).isEqualTo(2); SearchIndexablesContract.NON_INDEXABLES_KEYS_COLUMNS, null, null, null)) {
assertThat(cursor.getString(0)).isEqualTo("pref_key_1"); while (cursor.moveToNext()) {
cursor.moveToNext(); keys.add(cursor.getString(0));
assertThat(cursor.getString(0)).isEqualTo("pref_key_3"); }
}
assertThat(keys).hasSize(3);
assertThat(keys).containsAllOf("pref_key_1", "pref_key_3", "pref_key_5");
} }
} }

View File

@@ -73,7 +73,7 @@ public class EncryptionAndCredentialTest {
final List<String> expectedKeys = new ArrayList<>(); final List<String> expectedKeys = new ArrayList<>();
for (SearchIndexableResource res : index) { for (SearchIndexableResource res : index) {
expectedKeys.addAll(((BaseSearchIndexProvider) SEARCH_INDEX_DATA_PROVIDER) expectedKeys.addAll(((BaseSearchIndexProvider) SEARCH_INDEX_DATA_PROVIDER)
.getNonIndexableKeysFromXml(mContext, res.xmlResId)); .getNonIndexableKeysFromXml(mContext, res.xmlResId, true /* suppressAll */));
} }
final List<String> keys = SEARCH_INDEX_DATA_PROVIDER.getNonIndexableKeys(mContext); final List<String> keys = SEARCH_INDEX_DATA_PROVIDER.getNonIndexableKeys(mContext);

View File

@@ -1,15 +1,16 @@
package com.android.settings.testutils; package com.android.settings.testutils;
import static com.android.settings.core.PreferenceXmlParserUtils.METADATA_KEY;
import static com.android.settings.core.PreferenceXmlParserUtils.MetadataFlag
.FLAG_INCLUDE_PREF_SCREEN;
import static com.android.settings.core.PreferenceXmlParserUtils.MetadataFlag.FLAG_NEED_KEY;
import android.content.Context; import android.content.Context;
import android.content.res.Resources; import android.os.Bundle;
import android.content.res.XmlResourceParser;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Xml;
import com.android.settings.core.PreferenceXmlParserUtils; import com.android.settings.core.PreferenceXmlParserUtils;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlPullParserException;
import java.util.ArrayList; import java.util.ArrayList;
@@ -25,30 +26,21 @@ public class XmlTestUtils {
* on the screen. * on the screen.
* *
* @param context of the preference screen. * @param context of the preference screen.
* @param xmlId of the Preference Xml to be parsed. * @param xmlId of the Preference Xml to be parsed.
* @return List of all keys in the preference Xml * @return List of all keys in the preference Xml
*/ */
public static List<String> getKeysFromPreferenceXml(Context context, int xmlId) { public static List<String> getKeysFromPreferenceXml(Context context, int xmlId) {
final XmlResourceParser parser = context.getResources().getXml(xmlId);
final AttributeSet attrs = Xml.asAttributeSet(parser);
final List<String> keys = new ArrayList<>(); final List<String> keys = new ArrayList<>();
String key;
try { try {
while (parser.next() != XmlPullParser.END_DOCUMENT) { List<Bundle> metadata = PreferenceXmlParserUtils.extractMetadata(context, xmlId,
try { FLAG_NEED_KEY | FLAG_INCLUDE_PREF_SCREEN);
key = PreferenceXmlParserUtils.getDataKey(context, attrs); for (Bundle bundle : metadata) {
if (!TextUtils.isEmpty(key)) { final String key = bundle.getString(METADATA_KEY);
keys.add(key); if (!TextUtils.isEmpty(key)) {
} keys.add(key);
} catch (NullPointerException e) {
continue;
} catch (Resources.NotFoundException e) {
continue;
} }
} }
} catch (java.io.IOException e) { } catch (java.io.IOException | XmlPullParserException e) {
return null;
} catch (XmlPullParserException e) {
return null; return null;
} }