diff --git a/res/xml/pick_up_gesture_settings.xml b/res/xml/pick_up_gesture_settings.xml index 0b4a1de367c..e1414cd5857 100644 --- a/res/xml/pick_up_gesture_settings.xml +++ b/res/xml/pick_up_gesture_settings.xml @@ -18,6 +18,7 @@ diff --git a/src/com/android/settings/search/SearchIndexableResources.java b/src/com/android/settings/search/SearchIndexableResources.java index 7252e2d492b..89924bbec41 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.tts.TtsEnginePreferenceFragment; 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 @@ public final class SearchIndexableResources { 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 00000000000..2236b94916b --- /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 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. + *

+ * So in this test we are checking preferences participating in search. + *

+ * Note: Preference is not limited to just object. Everything in preference xml + * should have a key. + */ + @Test + public void allPreferencesShouldHaveUniqueKey() + throws IOException, XmlPullParserException, Resources.NotFoundException { + final Set uniqueKeys = new HashSet<>(); + final Set nullKeyClasses = new HashSet<>(); + final Set 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 uniqueKeys, Set duplicatedKeys, + Set nullKeyClasses, SearchIndexableResource page) + throws IOException, XmlPullParserException, Resources.NotFoundException { + final Class clazz = DatabaseIndexingUtils.getIndexableClass(page.className); + + final Indexable.SearchIndexProvider provider = + DatabaseIndexingUtils.getSearchIndexProvider(clazz); + final List 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)); + } + } +}