Home | History | Annotate | Download | only in core
      1 /*
      2  * Copyright (C) 2017 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.settings.core;
     18 
     19 import android.annotation.NonNull;
     20 import android.annotation.Nullable;
     21 import android.annotation.XmlRes;
     22 import android.content.Context;
     23 import android.content.res.TypedArray;
     24 import android.content.res.XmlResourceParser;
     25 import android.os.Bundle;
     26 import android.support.annotation.IntDef;
     27 import android.support.annotation.VisibleForTesting;
     28 import android.text.TextUtils;
     29 import android.util.AttributeSet;
     30 import android.util.Log;
     31 import android.util.TypedValue;
     32 import android.util.Xml;
     33 
     34 import com.android.settings.R;
     35 
     36 import org.xmlpull.v1.XmlPullParser;
     37 import org.xmlpull.v1.XmlPullParserException;
     38 
     39 import java.io.IOException;
     40 import java.lang.annotation.Retention;
     41 import java.lang.annotation.RetentionPolicy;
     42 import java.util.ArrayList;
     43 import java.util.Arrays;
     44 import java.util.List;
     45 
     46 /**
     47  * Utility class to parse elements of XML preferences
     48  */
     49 public class PreferenceXmlParserUtils {
     50 
     51     private static final String TAG = "PreferenceXmlParserUtil";
     52     @VisibleForTesting
     53     static final String PREF_SCREEN_TAG = "PreferenceScreen";
     54     private static final List<String> SUPPORTED_PREF_TYPES = Arrays.asList(
     55             "Preference", "PreferenceCategory", "PreferenceScreen");
     56 
     57     /**
     58      * Flag definition to indicate which metadata should be extracted when
     59      * {@link #extractMetadata(Context, int, int)} is called. The flags can be combined by using |
     60      * (binary or).
     61      */
     62     @IntDef(flag = true, value = {
     63             MetadataFlag.FLAG_INCLUDE_PREF_SCREEN,
     64             MetadataFlag.FLAG_NEED_KEY,
     65             MetadataFlag.FLAG_NEED_PREF_TYPE,
     66             MetadataFlag.FLAG_NEED_PREF_CONTROLLER,
     67             MetadataFlag.FLAG_NEED_PREF_TITLE,
     68             MetadataFlag.FLAG_NEED_PREF_SUMMARY,
     69             MetadataFlag.FLAG_NEED_PREF_ICON})
     70     @Retention(RetentionPolicy.SOURCE)
     71     public @interface MetadataFlag {
     72         int FLAG_INCLUDE_PREF_SCREEN = 1;
     73         int FLAG_NEED_KEY = 1 << 1;
     74         int FLAG_NEED_PREF_TYPE = 1 << 2;
     75         int FLAG_NEED_PREF_CONTROLLER = 1 << 3;
     76         int FLAG_NEED_PREF_TITLE = 1 << 4;
     77         int FLAG_NEED_PREF_SUMMARY = 1 << 5;
     78         int FLAG_NEED_PREF_ICON = 1 << 6;
     79         int FLAG_NEED_PLATFORM_SLICE_FLAG = 1 << 7;
     80         int FLAG_NEED_KEYWORDS = 1 << 8;
     81     }
     82 
     83     public static final String METADATA_PREF_TYPE = "type";
     84     public static final String METADATA_KEY = "key";
     85     public static final String METADATA_CONTROLLER = "controller";
     86     public static final String METADATA_TITLE = "title";
     87     public static final String METADATA_SUMMARY = "summary";
     88     public static final String METADATA_ICON = "icon";
     89     public static final String METADATA_PLATFORM_SLICE_FLAG = "platform_slice";
     90     public static final String METADATA_KEYWORDS = "keywords";
     91 
     92     private static final String ENTRIES_SEPARATOR = "|";
     93 
     94     /**
     95      * Call {@link #extractMetadata(Context, int, int)} with {@link #METADATA_KEY} instead.
     96      */
     97     @Deprecated
     98     public static String getDataKey(Context context, AttributeSet attrs) {
     99         return getStringData(context, attrs,
    100                 com.android.internal.R.styleable.Preference,
    101                 com.android.internal.R.styleable.Preference_key);
    102     }
    103 
    104     /**
    105      * Call {@link #extractMetadata(Context, int, int)} with {@link #METADATA_TITLE} instead.
    106      */
    107     @Deprecated
    108     public static String getDataTitle(Context context, AttributeSet attrs) {
    109         return getStringData(context, attrs,
    110                 com.android.internal.R.styleable.Preference,
    111                 com.android.internal.R.styleable.Preference_title);
    112     }
    113 
    114     /**
    115      * Call {@link #extractMetadata(Context, int, int)} with {@link #METADATA_SUMMARY} instead.
    116      */
    117     @Deprecated
    118     public static String getDataSummary(Context context, AttributeSet attrs) {
    119         return getStringData(context, attrs,
    120                 com.android.internal.R.styleable.Preference,
    121                 com.android.internal.R.styleable.Preference_summary);
    122     }
    123 
    124     public static String getDataSummaryOn(Context context, AttributeSet attrs) {
    125         return getStringData(context, attrs,
    126                 com.android.internal.R.styleable.CheckBoxPreference,
    127                 com.android.internal.R.styleable.CheckBoxPreference_summaryOn);
    128     }
    129 
    130     public static String getDataSummaryOff(Context context, AttributeSet attrs) {
    131         return getStringData(context, attrs,
    132                 com.android.internal.R.styleable.CheckBoxPreference,
    133                 com.android.internal.R.styleable.CheckBoxPreference_summaryOff);
    134     }
    135 
    136     public static String getDataEntries(Context context, AttributeSet attrs) {
    137         return getDataEntries(context, attrs,
    138                 com.android.internal.R.styleable.ListPreference,
    139                 com.android.internal.R.styleable.ListPreference_entries);
    140     }
    141 
    142     public static String getDataKeywords(Context context, AttributeSet attrs) {
    143         return getStringData(context, attrs, R.styleable.Preference,
    144                 R.styleable.Preference_keywords);
    145     }
    146 
    147     /**
    148      * Call {@link #extractMetadata(Context, int, int)} with {@link #METADATA_CONTROLLER} instead.
    149      */
    150     @Deprecated
    151     public static String getController(Context context, AttributeSet attrs) {
    152         return getStringData(context, attrs, R.styleable.Preference,
    153                 R.styleable.Preference_controller);
    154     }
    155 
    156     /**
    157      * Call {@link #extractMetadata(Context, int, int)} with {@link #METADATA_ICON} instead.
    158      */
    159     @Deprecated
    160     public static int getDataIcon(Context context, AttributeSet attrs) {
    161         final TypedArray ta = context.obtainStyledAttributes(attrs,
    162                 com.android.internal.R.styleable.Preference);
    163         final int dataIcon = ta.getResourceId(com.android.internal.R.styleable.Icon_icon, 0);
    164         ta.recycle();
    165         return dataIcon;
    166     }
    167 
    168     /**
    169      * Extracts metadata from preference xml and put them into a {@link Bundle}.
    170      *
    171      * @param xmlResId xml res id of a preference screen
    172      * @param flags    Should be one or more of {@link MetadataFlag}.
    173      */
    174     @NonNull
    175     public static List<Bundle> extractMetadata(Context context, @XmlRes int xmlResId, int flags)
    176             throws IOException, XmlPullParserException {
    177         final List<Bundle> metadata = new ArrayList<>();
    178         if (xmlResId <= 0) {
    179             Log.d(TAG, xmlResId + " is invalid.");
    180             return metadata;
    181         }
    182         final XmlResourceParser parser = context.getResources().getXml(xmlResId);
    183 
    184         int type;
    185         while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
    186                 && type != XmlPullParser.START_TAG) {
    187             // Parse next until start tag is found
    188         }
    189         final int outerDepth = parser.getDepth();
    190 
    191         do {
    192             if (type != XmlPullParser.START_TAG) {
    193                 continue;
    194             }
    195             final String nodeName = parser.getName();
    196             if (!hasFlag(flags, MetadataFlag.FLAG_INCLUDE_PREF_SCREEN)
    197                     && TextUtils.equals(PREF_SCREEN_TAG, nodeName)) {
    198                 continue;
    199             }
    200             if (!SUPPORTED_PREF_TYPES.contains(nodeName) && !nodeName.endsWith("Preference")) {
    201                 continue;
    202             }
    203             final Bundle preferenceMetadata = new Bundle();
    204             final AttributeSet attrs = Xml.asAttributeSet(parser);
    205             final TypedArray preferenceAttributes = context.obtainStyledAttributes(attrs,
    206                     R.styleable.Preference);
    207 
    208             if (hasFlag(flags, MetadataFlag.FLAG_NEED_PREF_TYPE)) {
    209                 preferenceMetadata.putString(METADATA_PREF_TYPE, nodeName);
    210             }
    211             if (hasFlag(flags, MetadataFlag.FLAG_NEED_KEY)) {
    212                 preferenceMetadata.putString(METADATA_KEY, getKey(preferenceAttributes));
    213             }
    214             if (hasFlag(flags, MetadataFlag.FLAG_NEED_PREF_CONTROLLER)) {
    215                 preferenceMetadata.putString(METADATA_CONTROLLER,
    216                         getController(preferenceAttributes));
    217             }
    218             if (hasFlag(flags, MetadataFlag.FLAG_NEED_PREF_TITLE)) {
    219                 preferenceMetadata.putString(METADATA_TITLE, getTitle(preferenceAttributes));
    220             }
    221             if (hasFlag(flags, MetadataFlag.FLAG_NEED_PREF_SUMMARY)) {
    222                 preferenceMetadata.putString(METADATA_SUMMARY, getSummary(preferenceAttributes));
    223             }
    224             if (hasFlag(flags, MetadataFlag.FLAG_NEED_PREF_ICON)) {
    225                 preferenceMetadata.putInt(METADATA_ICON, getIcon(preferenceAttributes));
    226             }
    227             if (hasFlag(flags, MetadataFlag.FLAG_NEED_PLATFORM_SLICE_FLAG)) {
    228                 preferenceMetadata.putBoolean(METADATA_PLATFORM_SLICE_FLAG,
    229                         getPlatformSlice(preferenceAttributes));
    230             }
    231             if (hasFlag(flags, MetadataFlag.FLAG_NEED_KEYWORDS)) {
    232                 preferenceMetadata.putString(METADATA_KEYWORDS, getKeywords(preferenceAttributes));
    233             }
    234             metadata.add(preferenceMetadata);
    235 
    236             preferenceAttributes.recycle();
    237         } while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
    238                 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth));
    239         parser.close();
    240         return metadata;
    241     }
    242 
    243     /**
    244      * Returns the fragment name if this preference launches a child fragment.
    245      */
    246     public static String getDataChildFragment(Context context, AttributeSet attrs) {
    247         return getStringData(context, attrs, R.styleable.Preference,
    248                 R.styleable.Preference_android_fragment);
    249     }
    250 
    251     /**
    252      * Call {@link #extractMetadata(Context, int, int)} with a {@link MetadataFlag} instead.
    253      */
    254     @Deprecated
    255     @Nullable
    256     private static String getStringData(Context context, AttributeSet set, int[] attrs, int resId) {
    257         final TypedArray ta = context.obtainStyledAttributes(set, attrs);
    258         String data = ta.getString(resId);
    259         ta.recycle();
    260         return data;
    261     }
    262 
    263     private static boolean hasFlag(int flags, @MetadataFlag int flag) {
    264         return (flags & flag) != 0;
    265     }
    266 
    267     private static String getDataEntries(Context context, AttributeSet set, int[] attrs,
    268             int resId) {
    269         final TypedArray sa = context.obtainStyledAttributes(set, attrs);
    270         final TypedValue tv = sa.peekValue(resId);
    271         sa.recycle();
    272         String[] data = null;
    273         if (tv != null && tv.type == TypedValue.TYPE_REFERENCE) {
    274             if (tv.resourceId != 0) {
    275                 data = context.getResources().getStringArray(tv.resourceId);
    276             }
    277         }
    278         final int count = (data == null) ? 0 : data.length;
    279         if (count == 0) {
    280             return null;
    281         }
    282         final StringBuilder result = new StringBuilder();
    283         for (int n = 0; n < count; n++) {
    284             result.append(data[n]);
    285             result.append(ENTRIES_SEPARATOR);
    286         }
    287         return result.toString();
    288     }
    289 
    290     private static String getKey(TypedArray styledAttributes) {
    291         return styledAttributes.getString(com.android.internal.R.styleable.Preference_key);
    292     }
    293 
    294     private static String getTitle(TypedArray styledAttributes) {
    295         return styledAttributes.getString(com.android.internal.R.styleable.Preference_title);
    296     }
    297 
    298     private static String getSummary(TypedArray styledAttributes) {
    299         return styledAttributes.getString(com.android.internal.R.styleable.Preference_summary);
    300     }
    301 
    302     private static String getController(TypedArray styledAttributes) {
    303         return styledAttributes.getString(R.styleable.Preference_controller);
    304     }
    305 
    306     private static int getIcon(TypedArray styledAttributes) {
    307         return styledAttributes.getResourceId(com.android.internal.R.styleable.Icon_icon, 0);
    308     }
    309 
    310     private static boolean getPlatformSlice(TypedArray styledAttributes) {
    311         return styledAttributes.getBoolean(R.styleable.Preference_platform_slice, false /* def */);
    312     }
    313 
    314     private static String getKeywords(TypedArray styleAttributes) {
    315         return styleAttributes.getString(R.styleable.Preference_keywords);
    316     }
    317 }
    318