Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 1055ab3b authored by Fan Zhang's avatar Fan Zhang Committed by android-build-merger
Browse files

Merge "Reuse xml parser logic to scrape preference xml files." into pi-dev am: e8acc0c4

am: e9610ead

Change-Id: If0dc5c9d0457b7d134edcb8dce8bdf43e3cdabae
parents d0bd7e7d e9610ead
Loading
Loading
Loading
Loading
+3 −1
Original line number Diff line number Diff line
@@ -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<BasePreferenceController> controllers = new ArrayList<>();
        List<Bundle> 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;
+66 −6
Original line number Diff line number Diff line
@@ -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<String> 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<Bundle> extractMetadata(Context context, int xmlResId)
    public static List<Bundle> extractMetadata(Context context, @XmlRes int xmlResId, int flags)
            throws IOException, XmlPullParserException {
        final List<Bundle> 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);
            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);
+24 −20
Original line number Diff line number Diff line
@@ -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<Bundle> 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:
                // <pref ....>
                //      <intent action="blab"/> </pref>
                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();
        }
+71 −6
Original line number Diff line number Diff line
/*
 * 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<Bundle> metadata =
            PreferenceXmlParserUtils.extractMetadata(mContext, R.xml.location_settings);
        List<Bundle> 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<Bundle> 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<Bundle> 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<Bundle> 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<Bundle> 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<Bundle> 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
+17 −36
Original line number Diff line number Diff line
@@ -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<String> IGNORE_PREF_TYPES = Arrays.asList(
            "com.android.settingslib.widget.FooterPreference");
    private static final List<String> SUPPORTED_PREF_TYPES = Arrays.asList(
            "Preference", "PreferenceCategory", "PreferenceScreen");

    private static final List<String> 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.");
            final List<Bundle> metadata = PreferenceXmlParserUtils.extractMetadata(mContext,
                    sir.xmlResId,
                    MetadataFlag.FLAG_INCLUDE_PREF_SCREEN
                            | MetadataFlag.FLAG_NEED_KEY
                            | MetadataFlag.FLAG_NEED_PREF_TYPE);

            for (Bundle bundle : metadata) {
                final String type = bundle.getString(PreferenceXmlParserUtils.METADATA_PREF_TYPE);
                if (IGNORE_PREF_TYPES.contains(type)) {
                    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 (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));
            }
        }
    }