Merge "Add a test to enforce unique id for preferences (in search)"
diff --git a/res/xml/pick_up_gesture_settings.xml b/res/xml/pick_up_gesture_settings.xml
index 0b4a1de..e1414cd 100644
--- a/res/xml/pick_up_gesture_settings.xml
+++ b/res/xml/pick_up_gesture_settings.xml
@@ -18,6 +18,7 @@
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:key="gesture_pick_up_screen"
android:title="@string/ambient_display_pickup_title">
<com.android.settings.widget.VideoPreference
diff --git a/res/xml/tts_engine_picker.xml b/res/xml/tts_engine_picker.xml
index c0a464c..92bfede 100644
--- a/res/xml/tts_engine_picker.xml
+++ b/res/xml/tts_engine_picker.xml
@@ -15,6 +15,7 @@
-->
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+ android:key="tts_engine_picker_screen"
android:title="@string/tts_engine_preference_title">
<PreferenceCategory android:key="tts_engine_preference_category"/>
diff --git a/src/com/android/settings/search/SearchIndexableResources.java b/src/com/android/settings/search/SearchIndexableResources.java
index 7252e2d..89924bb 100644
--- a/src/com/android/settings/search/SearchIndexableResources.java
+++ b/src/com/android/settings/search/SearchIndexableResources.java
@@ -86,7 +86,6 @@
import com.android.settings.users.UserSettings;
import com.android.settings.wallpaper.WallpaperTypeSettings;
import com.android.settings.wifi.ConfigureWifiSettings;
-import com.android.settings.wifi.SavedAccessPointsWifiSettings;
import com.android.settings.wifi.WifiSettings;
import java.util.Collection;
@@ -129,7 +128,6 @@
addIndex(WifiSettings.class);
addIndex(NetworkDashboardFragment.class);
addIndex(ConfigureWifiSettings.class);
- addIndex(SavedAccessPointsWifiSettings.class);
addIndex(BluetoothSettings.class);
addIndex(SimSettings.class);
addIndex(DataUsageSummary.class);
diff --git a/tests/unit/src/com/android/settings/UniquePreferenceTest.java b/tests/unit/src/com/android/settings/UniquePreferenceTest.java
new file mode 100644
index 0000000..2236b94
--- /dev/null
+++ b/tests/unit/src/com/android/settings/UniquePreferenceTest.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings;
+
+import static junit.framework.Assert.fail;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.XmlResourceParser;
+import android.provider.SearchIndexableResource;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.MediumTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.Xml;
+
+import com.android.settings.search.DatabaseIndexingUtils;
+import com.android.settings.search.Indexable;
+import com.android.settings.search.SearchIndexableResources;
+import com.android.settings.search.XmlParserUtils;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+@RunWith(AndroidJUnit4.class)
+@MediumTest
+public class UniquePreferenceTest {
+
+ private static final String TAG = "UniquePreferenceTest";
+ private static final List<String> SUPPORTED_PREF_TYPES = Arrays.asList(
+ "Preference", "PreferenceCategory", "PreferenceScreen");
+
+ private Context mContext;
+
+
+ @Before
+ public void setUp() {
+ mContext = InstrumentationRegistry.getTargetContext();
+ }
+
+ /**
+ * All preferences should have their unique key. It's especially important for many parts of
+ * Settings to work properly: we assume pref keys are unique in displaying, search ranking,\
+ * search result suppression, and many other areas.
+ * <p/>
+ * So in this test we are checking preferences participating in search.
+ * <p/>
+ * Note: Preference is not limited to just <Preference/> object. Everything in preference xml
+ * should have a key.
+ */
+ @Test
+ public void allPreferencesShouldHaveUniqueKey()
+ throws IOException, XmlPullParserException, Resources.NotFoundException {
+ final Set<String> uniqueKeys = new HashSet<>();
+ final Set<String> nullKeyClasses = new HashSet<>();
+ final Set<String> duplicatedKeys = new HashSet<>();
+ for (SearchIndexableResource sir : SearchIndexableResources.values()) {
+ verifyPreferenceIdInXml(uniqueKeys, duplicatedKeys, nullKeyClasses, sir);
+ }
+
+ if (!nullKeyClasses.isEmpty()) {
+ final StringBuilder nullKeyErrors = new StringBuilder()
+ .append("Each preference must have a key, ")
+ .append("the following classes have pref without keys:\n");
+ for (String c : nullKeyClasses) {
+ nullKeyErrors.append(c).append("\n");
+ }
+ fail(nullKeyErrors.toString());
+ }
+
+ if (!duplicatedKeys.isEmpty()) {
+ final StringBuilder dupeKeysError = new StringBuilder(
+ "The following keys are not unique\n");
+ for (String c : duplicatedKeys) {
+ dupeKeysError.append(c).append("\n");
+ }
+ fail(dupeKeysError.toString());
+ }
+ }
+
+ private void verifyPreferenceIdInXml(Set<String> uniqueKeys, Set<String> duplicatedKeys,
+ Set<String> nullKeyClasses, SearchIndexableResource page)
+ throws IOException, XmlPullParserException, Resources.NotFoundException {
+ final Class<?> clazz = DatabaseIndexingUtils.getIndexableClass(page.className);
+
+ final Indexable.SearchIndexProvider provider =
+ DatabaseIndexingUtils.getSearchIndexProvider(clazz);
+ final List<SearchIndexableResource> resourcesToIndex =
+ provider.getXmlResourcesToIndex(mContext, true);
+ if (resourcesToIndex == null) {
+ Log.d(TAG, page.className + "is not providing SearchIndexableResource, skipping");
+ return;
+ }
+
+ for (SearchIndexableResource sir : resourcesToIndex) {
+ if (sir.xmlResId <= 0) {
+ Log.d(TAG, page.className + " doesn't have a valid xml to index.");
+ continue;
+ }
+ final XmlResourceParser parser = mContext.getResources().getXml(sir.xmlResId);
+
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && type != XmlPullParser.START_TAG) {
+ // Parse next until start tag is found
+ }
+ final int outerDepth = parser.getDepth();
+
+ do {
+ if (type != XmlPullParser.START_TAG) {
+ continue;
+ }
+ final String nodeName = parser.getName();
+ if (!SUPPORTED_PREF_TYPES.contains(nodeName) && !nodeName.endsWith("Preference")) {
+ continue;
+ }
+ final AttributeSet attrs = Xml.asAttributeSet(parser);
+ final String key = XmlParserUtils.getDataKey(mContext, attrs);
+ if (TextUtils.isEmpty(key)) {
+ Log.e(TAG, "Every preference must have an key; found null key"
+ + " in " + page.className
+ + " at " + parser.getPositionDescription());
+ nullKeyClasses.add(page.className);
+ continue;
+ }
+ if (uniqueKeys.contains(key)) {
+ Log.e(TAG, "Every preference key must unique; found " + nodeName
+ + " in " + page.className
+ + " at " + parser.getPositionDescription());
+ duplicatedKeys.add(key);
+ }
+ uniqueKeys.add(key);
+ } while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth));
+ }
+ }
+}