diff --git a/src/com/android/settings/core/PreferenceControllerListHelper.java b/src/com/android/settings/core/PreferenceControllerListHelper.java index bec7c094ea2..a4808a1380b 100644 --- a/src/com/android/settings/core/PreferenceControllerListHelper.java +++ b/src/com/android/settings/core/PreferenceControllerListHelper.java @@ -26,6 +26,7 @@ import android.os.Bundle; import android.text.TextUtils; import android.util.Log; +import com.android.settings.core.PreferenceXmlParserUtils.MetadataFlag; import com.android.settingslib.core.AbstractPreferenceController; import org.xmlpull.v1.XmlPullParserException; @@ -52,7 +53,8 @@ public class PreferenceControllerListHelper { final List controllers = new ArrayList<>(); List preferenceMetadata; try { - preferenceMetadata = PreferenceXmlParserUtils.extractMetadata(context, xmlResId); + preferenceMetadata = PreferenceXmlParserUtils.extractMetadata(context, xmlResId, + MetadataFlag.FLAG_NEED_KEY | MetadataFlag.FLAG_NEED_PREF_CONTROLLER); } catch (IOException | XmlPullParserException e) { Log.e(TAG, "Failed to parse preference xml for getting controllers"); return controllers; diff --git a/src/com/android/settings/core/PreferenceXmlParserUtils.java b/src/com/android/settings/core/PreferenceXmlParserUtils.java index e07ca9bd5cd..7afe8d8fca3 100644 --- a/src/com/android/settings/core/PreferenceXmlParserUtils.java +++ b/src/com/android/settings/core/PreferenceXmlParserUtils.java @@ -18,10 +18,14 @@ package com.android.settings.core; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.XmlRes; import android.content.Context; import android.content.res.TypedArray; import android.content.res.XmlResourceParser; import android.os.Bundle; +import android.support.annotation.IntDef; +import android.support.annotation.VisibleForTesting; +import android.text.TextUtils; import android.util.AttributeSet; import android.util.Log; import android.util.TypedValue; @@ -33,6 +37,8 @@ import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -43,12 +49,41 @@ import java.util.List; public class PreferenceXmlParserUtils { private static final String TAG = "PreferenceXmlParserUtil"; - + @VisibleForTesting + static final String PREF_SCREEN_TAG = "PreferenceScreen"; private static final List SUPPORTED_PREF_TYPES = Arrays.asList( "Preference", "PreferenceCategory", "PreferenceScreen"); + /** + * Flag definition to indicate which metadata should be extracted when + * {@link #extractMetadata(Context, int, int)} is called. The flags can be combined by using | + * (binary or). + */ + @IntDef(flag = true, value = { + MetadataFlag.FLAG_INCLUDE_PREF_SCREEN, + MetadataFlag.FLAG_NEED_KEY, + MetadataFlag.FLAG_NEED_PREF_TYPE, + MetadataFlag.FLAG_NEED_PREF_CONTROLLER, + MetadataFlag.FLAG_NEED_PREF_TITLE, + MetadataFlag.FLAG_NEED_PREF_SUMMARY, + MetadataFlag.FLAG_NEED_PREF_ICON}) + @Retention(RetentionPolicy.SOURCE) + public @interface MetadataFlag { + int FLAG_INCLUDE_PREF_SCREEN = 1; + int FLAG_NEED_KEY = 1 << 1; + int FLAG_NEED_PREF_TYPE = 1 << 2; + int FLAG_NEED_PREF_CONTROLLER = 1 << 3; + int FLAG_NEED_PREF_TITLE = 1 << 4; + int FLAG_NEED_PREF_SUMMARY = 1 << 5; + int FLAG_NEED_PREF_ICON = 1 << 6; + } + + public static final String METADATA_PREF_TYPE = "type"; public static final String METADATA_KEY = "key"; public static final String METADATA_CONTROLLER = "controller"; + public static final String METADATA_TITLE = "title"; + public static final String METADATA_SUMMARY = "summary"; + public static final String METADATA_ICON = "icon"; private static final String ENTRIES_SEPARATOR = "|"; @@ -107,11 +142,11 @@ public class PreferenceXmlParserUtils { /** * Extracts metadata from preference xml and put them into a {@link Bundle}. * - * TODO(zhfan): Similar logic exists in {@link SliceBuilderUtils} and - * {@link UniquePreferenceTest}. Need refactoring to consolidate them all. + * @param xmlResId xml res id of a preference screen + * @param flags Should be one or more of {@link MetadataFlag}. */ @NonNull - public static List extractMetadata(Context context, int xmlResId) + public static List extractMetadata(Context context, @XmlRes int xmlResId, int flags) throws IOException, XmlPullParserException { final List metadata = new ArrayList<>(); if (xmlResId <= 0) { @@ -132,16 +167,37 @@ public class PreferenceXmlParserUtils { continue; } final String nodeName = parser.getName(); + if (!hasFlag(flags, MetadataFlag.FLAG_INCLUDE_PREF_SCREEN) + && TextUtils.equals(PREF_SCREEN_TAG, nodeName)) { + continue; + } if (!SUPPORTED_PREF_TYPES.contains(nodeName) && !nodeName.endsWith("Preference")) { continue; } final Bundle preferenceMetadata = new Bundle(); final AttributeSet attrs = Xml.asAttributeSet(parser); - preferenceMetadata.putString(METADATA_KEY, getDataKey(context, attrs)); - preferenceMetadata.putString(METADATA_CONTROLLER, getController(context, attrs)); + if (hasFlag(flags, MetadataFlag.FLAG_NEED_PREF_TYPE)) { + preferenceMetadata.putString(METADATA_PREF_TYPE, nodeName); + } + if (hasFlag(flags, MetadataFlag.FLAG_NEED_KEY)) { + preferenceMetadata.putString(METADATA_KEY, getDataKey(context, attrs)); + } + if (hasFlag(flags, MetadataFlag.FLAG_NEED_PREF_CONTROLLER)) { + preferenceMetadata.putString(METADATA_CONTROLLER, getController(context, attrs)); + } + if (hasFlag(flags, MetadataFlag.FLAG_NEED_PREF_TITLE)) { + preferenceMetadata.putString(METADATA_TITLE, getDataTitle(context, attrs)); + } + if (hasFlag(flags, MetadataFlag.FLAG_NEED_PREF_SUMMARY)) { + preferenceMetadata.putString(METADATA_SUMMARY, getDataSummary(context, attrs)); + } + if (hasFlag(flags, MetadataFlag.FLAG_NEED_PREF_ICON)) { + preferenceMetadata.putInt(METADATA_ICON, getDataIcon(context, attrs)); + } metadata.add(preferenceMetadata); } while ((type = parser.next()) != XmlPullParser.END_DOCUMENT && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)); + parser.close(); return metadata; } @@ -161,6 +217,10 @@ public class PreferenceXmlParserUtils { return data; } + private static boolean hasFlag(int flags, @MetadataFlag int flag) { + return (flags & flag) != 0; + } + private static String getDataEntries(Context context, AttributeSet set, int[] attrs, int resId) { final TypedArray sa = context.obtainStyledAttributes(set, attrs); diff --git a/src/com/android/settings/slices/SliceDataConverter.java b/src/com/android/settings/slices/SliceDataConverter.java index d77f427e282..e7b53d03940 100644 --- a/src/com/android/settings/slices/SliceDataConverter.java +++ b/src/com/android/settings/slices/SliceDataConverter.java @@ -16,21 +16,28 @@ package com.android.settings.slices; +import static com.android.settings.core.PreferenceXmlParserUtils.METADATA_CONTROLLER; +import static com.android.settings.core.PreferenceXmlParserUtils.METADATA_ICON; +import static com.android.settings.core.PreferenceXmlParserUtils.METADATA_KEY; +import static com.android.settings.core.PreferenceXmlParserUtils.METADATA_SUMMARY; +import static com.android.settings.core.PreferenceXmlParserUtils.METADATA_TITLE; + import android.content.Context; import android.content.res.Resources; import android.content.res.XmlResourceParser; +import android.os.Bundle; import android.provider.SearchIndexableResource; -import android.support.annotation.DrawableRes; import android.text.TextUtils; import android.util.AttributeSet; import android.util.Log; import android.util.Xml; +import com.android.settings.core.PreferenceXmlParserUtils; +import com.android.settings.core.PreferenceXmlParserUtils.MetadataFlag; import com.android.settings.dashboard.DashboardFragment; import com.android.settings.overlay.FeatureFactory; import com.android.settings.search.DatabaseIndexingUtils; import com.android.settings.search.Indexable.SearchIndexProvider; -import com.android.settings.core.PreferenceXmlParserUtils; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -144,35 +151,32 @@ class SliceDataConverter { + nodeName + " at " + parser.getPositionDescription()); } - final int outerDepth = parser.getDepth(); final AttributeSet attrs = Xml.asAttributeSet(parser); final String screenTitle = PreferenceXmlParserUtils.getDataTitle(mContext, attrs); // TODO (b/67996923) Investigate if we need headers for Slices, since they never // correspond to an actual setting. - while ((type = parser.next()) != XmlPullParser.END_DOCUMENT - && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { - if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { - continue; - } + final List metadata = PreferenceXmlParserUtils.extractMetadata(mContext, + xmlResId, + MetadataFlag.FLAG_NEED_KEY + | MetadataFlag.FLAG_NEED_PREF_CONTROLLER + | MetadataFlag.FLAG_NEED_PREF_TYPE + | MetadataFlag.FLAG_NEED_PREF_TITLE + | MetadataFlag.FLAG_NEED_PREF_ICON + | MetadataFlag.FLAG_NEED_PREF_SUMMARY); + for (Bundle bundle : metadata) { // TODO (b/67996923) Non-controller Slices should become intent-only slices. // Note that without a controller, dynamic summaries are impossible. - // TODO (b/67996923) This will not work if preferences have nested intents: - // - // - final String controllerClassName = PreferenceXmlParserUtils.getController(mContext, - attrs); + final String controllerClassName = bundle.getString(METADATA_CONTROLLER); if (TextUtils.isEmpty(controllerClassName)) { continue; } - - final String title = PreferenceXmlParserUtils.getDataTitle(mContext, attrs); - final String key = PreferenceXmlParserUtils.getDataKey(mContext, attrs); - @DrawableRes final int iconResId = PreferenceXmlParserUtils.getDataIcon(mContext, - attrs); - final String summary = PreferenceXmlParserUtils.getDataSummary(mContext, attrs); + final String key = bundle.getString(METADATA_KEY); + final String title = bundle.getString(METADATA_TITLE); + final String summary = bundle.getString(METADATA_SUMMARY); + final int iconResId = bundle.getInt(METADATA_ICON); final int sliceType = SliceBuilderUtils.getSliceType(mContext, controllerClassName, key); @@ -194,7 +198,7 @@ class SliceDataConverter { } catch (IOException e) { Log.w(TAG, "IO Error parsing PreferenceScreen: ", e); } catch (Resources.NotFoundException e) { - Log.w(TAG, "Resoucre not found error parsing PreferenceScreen: ", e); + Log.w(TAG, "Resource not found error parsing PreferenceScreen: ", e); } finally { if (parser != null) parser.close(); } diff --git a/tests/robotests/src/com/android/settings/search/PreferenceXmlParserUtilTest.java b/tests/robotests/src/com/android/settings/core/PreferenceXmlParserUtilTest.java similarity index 71% rename from tests/robotests/src/com/android/settings/search/PreferenceXmlParserUtilTest.java rename to tests/robotests/src/com/android/settings/core/PreferenceXmlParserUtilTest.java index 0c5f7882eb6..458bc023209 100644 --- a/tests/robotests/src/com/android/settings/search/PreferenceXmlParserUtilTest.java +++ b/tests/robotests/src/com/android/settings/core/PreferenceXmlParserUtilTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016 The Android Open Source Project + * Copyright (C) 2018 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. @@ -12,21 +12,21 @@ * 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.search; +package com.android.settings.core; import static com.google.common.truth.Truth.assertThat; import android.content.Context; import android.content.res.XmlResourceParser; import android.os.Bundle; +import android.text.TextUtils; import android.util.AttributeSet; import android.util.Xml; import com.android.settings.R; -import com.android.settings.core.PreferenceXmlParserUtils; +import com.android.settings.core.PreferenceXmlParserUtils.MetadataFlag; import com.android.settings.testutils.SettingsRobolectricTestRunner; import org.junit.Before; @@ -169,8 +169,9 @@ public class PreferenceXmlParserUtilTest { @Config(qualifiers = "mcc999") public void extractMetadata_shouldContainKeyAndControllerName() throws IOException, XmlPullParserException { - final List metadata = - PreferenceXmlParserUtils.extractMetadata(mContext, R.xml.location_settings); + List metadata = PreferenceXmlParserUtils.extractMetadata(mContext, + R.xml.location_settings, + MetadataFlag.FLAG_NEED_KEY | MetadataFlag.FLAG_NEED_PREF_CONTROLLER); assertThat(metadata).isNotEmpty(); for (Bundle bundle : metadata) { @@ -179,6 +180,70 @@ public class PreferenceXmlParserUtilTest { } } + @Test + @Config(qualifiers = "mcc999") + public void extractMetadata_requestTitle_shouldContainTitle() + throws IOException, XmlPullParserException { + List metadata = PreferenceXmlParserUtils.extractMetadata(mContext, + R.xml.location_settings, MetadataFlag.FLAG_NEED_PREF_TITLE); + for (Bundle bundle : metadata) { + assertThat(bundle.getString(PreferenceXmlParserUtils.METADATA_TITLE)).isNotNull(); + } + } + + @Test + @Config(qualifiers = "mcc999") + public void extractMetadata_requestSummary_shouldContainSummary() + throws IOException, XmlPullParserException { + List metadata = PreferenceXmlParserUtils.extractMetadata(mContext, + R.xml.location_settings, MetadataFlag.FLAG_NEED_PREF_SUMMARY); + for (Bundle bundle : metadata) { + assertThat(bundle.getString(PreferenceXmlParserUtils.METADATA_SUMMARY)).isNotNull(); + } + } + + @Test + @Config(qualifiers = "mcc999") + public void extractMetadata_requestIcon_shouldContainIcon() + throws IOException, XmlPullParserException { + List metadata = PreferenceXmlParserUtils.extractMetadata(mContext, + R.xml.location_settings, MetadataFlag.FLAG_NEED_PREF_ICON); + for (Bundle bundle : metadata) { + assertThat(bundle.getInt(PreferenceXmlParserUtils.METADATA_ICON)).isNotEqualTo(0); + } + } + + @Test + @Config(qualifiers = "mcc999") + public void extractMetadata_requestPrefType_shouldContainPrefType() + throws IOException, XmlPullParserException { + List metadata = PreferenceXmlParserUtils.extractMetadata(mContext, + R.xml.location_settings, MetadataFlag.FLAG_NEED_PREF_TYPE); + for (Bundle bundle : metadata) { + assertThat(bundle.getString(PreferenceXmlParserUtils.METADATA_PREF_TYPE)).isNotNull(); + } + } + + @Test + @Config(qualifiers = "mcc999") + public void extractMetadata_requestIncludeScreen_shouldContainScreen() + throws IOException, XmlPullParserException { + List metadata = PreferenceXmlParserUtils.extractMetadata(mContext, + R.xml.location_settings, + MetadataFlag.FLAG_NEED_PREF_TYPE | MetadataFlag.FLAG_INCLUDE_PREF_SCREEN); + + boolean hasPreferenceScreen = false; + for (Bundle bundle : metadata) { + if (TextUtils.equals(bundle.getString(PreferenceXmlParserUtils.METADATA_PREF_TYPE), + PreferenceXmlParserUtils.PREF_SCREEN_TAG)) { + hasPreferenceScreen = true; + break; + } + } + + assertThat(hasPreferenceScreen).isTrue(); + } + /** * @param resId the ID for the XML preference * @return an XML resource parser that points to the start tag diff --git a/tests/unit/src/com/android/settings/core/UniquePreferenceTest.java b/tests/unit/src/com/android/settings/core/UniquePreferenceTest.java index e2087098230..98ffddd2aa6 100644 --- a/tests/unit/src/com/android/settings/core/UniquePreferenceTest.java +++ b/tests/unit/src/com/android/settings/core/UniquePreferenceTest.java @@ -20,17 +20,16 @@ import static junit.framework.Assert.fail; import android.content.Context; import android.content.res.Resources; -import android.content.res.XmlResourceParser; +import android.os.Bundle; import android.platform.test.annotations.Presubmit; 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.core.PreferenceXmlParserUtils.MetadataFlag; import com.android.settings.overlay.FeatureFactory; import com.android.settings.search.DatabaseIndexingUtils; import com.android.settings.search.Indexable; @@ -40,7 +39,6 @@ import com.android.settings.search.SearchIndexableResources; 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; @@ -56,8 +54,7 @@ public class UniquePreferenceTest { private static final String TAG = "UniquePreferenceTest"; private static final List IGNORE_PREF_TYPES = Arrays.asList( "com.android.settingslib.widget.FooterPreference"); - private static final List SUPPORTED_PREF_TYPES = Arrays.asList( - "Preference", "PreferenceCategory", "PreferenceScreen"); + private static final List WHITELISTED_DUPLICATE_KEYS = Arrays.asList( "owner_info_settings", // Lock screen message in security - multiple xml files // contain this because security page is constructed by @@ -177,48 +174,32 @@ public class UniquePreferenceTest { } for (SearchIndexableResource sir : resourcesToIndex) { - if (sir.xmlResId <= 0) { - Log.d(TAG, className + " doesn't have a valid xml to index."); - continue; - } - final XmlResourceParser parser = mContext.getResources().getXml(sir.xmlResId); + final List metadata = PreferenceXmlParserUtils.extractMetadata(mContext, + sir.xmlResId, + MetadataFlag.FLAG_INCLUDE_PREF_SCREEN + | MetadataFlag.FLAG_NEED_KEY + | MetadataFlag.FLAG_NEED_PREF_TYPE); - 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) { + for (Bundle bundle : metadata) { + final String type = bundle.getString(PreferenceXmlParserUtils.METADATA_PREF_TYPE); + if (IGNORE_PREF_TYPES.contains(type)) { continue; } - final String nodeName = parser.getName(); - if (IGNORE_PREF_TYPES.contains(nodeName)) { - continue; - } - if (!SUPPORTED_PREF_TYPES.contains(nodeName) && !nodeName.endsWith("Preference")) { - continue; - } - final AttributeSet attrs = Xml.asAttributeSet(parser); - final String key = PreferenceXmlParserUtils.getDataKey(mContext, attrs); + final String key = bundle.getString(PreferenceXmlParserUtils.METADATA_KEY); if (TextUtils.isEmpty(key)) { Log.e(TAG, "Every preference must have an key; found null key" - + " in " + className - + " at " + parser.getPositionDescription()); + + " in " + className); nullKeyClasses.add(className); continue; } if (uniqueKeys.contains(key) && !WHITELISTED_DUPLICATE_KEYS.contains(key)) { - Log.e(TAG, "Every preference key must unique; found " + nodeName + Log.e(TAG, "Every preference key must unique; found " + " in " + className - + " at " + parser.getPositionDescription()); + + " / " + key); duplicatedKeys.add(key); } uniqueKeys.add(key); - } while ((type = parser.next()) != XmlPullParser.END_DOCUMENT - && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)); + } } }