1 /* 2 * Copyright (C) 2018 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.car.settings.common; 18 19 import android.annotation.NonNull; 20 import android.annotation.XmlRes; 21 import android.content.Context; 22 import android.content.res.TypedArray; 23 import android.content.res.XmlResourceParser; 24 import android.os.Bundle; 25 import android.util.AttributeSet; 26 import android.util.Xml; 27 28 import androidx.annotation.IntDef; 29 30 import com.android.car.settings.R; 31 32 import org.xmlpull.v1.XmlPullParser; 33 import org.xmlpull.v1.XmlPullParserException; 34 35 import java.io.IOException; 36 import java.lang.annotation.Retention; 37 import java.lang.annotation.RetentionPolicy; 38 import java.util.ArrayList; 39 import java.util.Arrays; 40 import java.util.List; 41 42 /** 43 * Utility class to parse elements of XML preferences. This is a reduced version of {@code com 44 * .android.settings.core.PreferenceXmlParserUtils}. 45 */ 46 public class PreferenceXmlParser { 47 48 private static final Logger LOG = new Logger(PreferenceXmlParser.class); 49 50 private static final String PREF_TAG_ENDS_WITH = "Preference"; 51 private static final String PREF_GROUP_TAG_ENDS_WITH = "PreferenceGroup"; 52 private static final String PREF_CATEGORY_TAG_ENDS_WITH = "PreferenceCategory"; 53 private static final List<String> SUPPORTED_PREF_TYPES = Arrays.asList("Preference", 54 "PreferenceCategory", "PreferenceScreen"); 55 56 /** 57 * Flag definition to indicate which metadata should be extracted when 58 * {@link #extractMetadata(Context, int, int)} is called. The flags can be combined by using | 59 * (binary or). 60 */ 61 @IntDef(flag = true, value = { 62 MetadataFlag.FLAG_NEED_KEY, 63 MetadataFlag.FLAG_NEED_PREF_CONTROLLER, 64 MetadataFlag.FLAG_NEED_SEARCHABLE}) 65 @Retention(RetentionPolicy.SOURCE) 66 public @interface MetadataFlag { 67 int FLAG_NEED_KEY = 1; 68 int FLAG_NEED_PREF_CONTROLLER = 1 << 1; 69 int FLAG_NEED_SEARCHABLE = 1 << 9; 70 } 71 72 public static final String METADATA_KEY = "key"; 73 public static final String METADATA_SEARCHABLE = "searchable"; 74 static final String METADATA_CONTROLLER = "controller"; 75 76 /** 77 * Extracts metadata from each preference XML and puts them into a {@link Bundle}. 78 * 79 * @param xmlResId xml res id of a preference screen 80 * @param flags one or more of {@link MetadataFlag} 81 * @return a list of Bundles containing the extracted metadata 82 */ 83 @NonNull extractMetadata(Context context, @XmlRes int xmlResId, int flags)84 public static List<Bundle> extractMetadata(Context context, @XmlRes int xmlResId, int flags) 85 throws IOException, XmlPullParserException { 86 final List<Bundle> metadata = new ArrayList<>(); 87 if (xmlResId <= 0) { 88 LOG.d(xmlResId + " is invalid."); 89 return metadata; 90 } 91 final XmlResourceParser parser = context.getResources().getXml(xmlResId); 92 93 int type; 94 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT 95 && type != XmlPullParser.START_TAG) { 96 // Parse next until start tag is found 97 } 98 final int outerDepth = parser.getDepth(); 99 100 do { 101 if (type != XmlPullParser.START_TAG) { 102 continue; 103 } 104 final String nodeName = parser.getName(); 105 if (!SUPPORTED_PREF_TYPES.contains(nodeName) && !nodeName.endsWith(PREF_TAG_ENDS_WITH) 106 && !nodeName.endsWith(PREF_GROUP_TAG_ENDS_WITH) 107 && !nodeName.endsWith(PREF_CATEGORY_TAG_ENDS_WITH)) { 108 continue; 109 } 110 final Bundle preferenceMetadata = new Bundle(); 111 final AttributeSet attrs = Xml.asAttributeSet(parser); 112 final TypedArray preferenceAttributes = context.obtainStyledAttributes(attrs, 113 R.styleable.Preference); 114 115 if (hasFlag(flags, MetadataFlag.FLAG_NEED_KEY)) { 116 preferenceMetadata.putString(METADATA_KEY, getKey(preferenceAttributes)); 117 } 118 if (hasFlag(flags, MetadataFlag.FLAG_NEED_PREF_CONTROLLER)) { 119 preferenceMetadata.putString(METADATA_CONTROLLER, 120 getController(preferenceAttributes)); 121 } 122 if (hasFlag(flags, MetadataFlag.FLAG_NEED_SEARCHABLE)) { 123 preferenceMetadata.putBoolean(METADATA_SEARCHABLE, 124 isSearchable(preferenceAttributes)); 125 } 126 metadata.add(preferenceMetadata); 127 128 preferenceAttributes.recycle(); 129 } while ((type = parser.next()) != XmlPullParser.END_DOCUMENT 130 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)); 131 parser.close(); 132 133 return metadata; 134 } 135 hasFlag(int flags, @MetadataFlag int flag)136 private static boolean hasFlag(int flags, @MetadataFlag int flag) { 137 return (flags & flag) != 0; 138 } 139 getKey(TypedArray styledAttributes)140 private static String getKey(TypedArray styledAttributes) { 141 return styledAttributes.getString(com.android.internal.R.styleable.Preference_key); 142 } 143 getController(TypedArray styledAttributes)144 private static String getController(TypedArray styledAttributes) { 145 return styledAttributes.getString(R.styleable.Preference_controller); 146 } 147 isSearchable(TypedArray styledAttributes)148 private static boolean isSearchable(TypedArray styledAttributes) { 149 return styledAttributes.getBoolean(R.styleable.Preference_searchable, true); 150 } 151 } 152