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.car.CarOccupantZoneManager; 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.util.AttributeSet; 27 import android.util.Xml; 28 29 import androidx.annotation.IntDef; 30 31 import com.android.car.settings.R; 32 33 import org.xmlpull.v1.XmlPullParser; 34 import org.xmlpull.v1.XmlPullParserException; 35 36 import java.io.IOException; 37 import java.lang.annotation.Retention; 38 import java.lang.annotation.RetentionPolicy; 39 import java.util.ArrayList; 40 import java.util.Arrays; 41 import java.util.List; 42 43 /** 44 * Utility class to parse elements of XML preferences. This is a reduced version of {@code com 45 * .android.settings.core.PreferenceXmlParserUtils}. 46 */ 47 public class PreferenceXmlParser { 48 49 private static final Logger LOG = new Logger(PreferenceXmlParser.class); 50 51 private static final String PREF_TAG_ENDS_WITH = "Preference"; 52 private static final String PREF_GROUP_TAG_ENDS_WITH = "PreferenceGroup"; 53 private static final String PREF_CATEGORY_TAG_ENDS_WITH = "PreferenceCategory"; 54 private static final List<String> SUPPORTED_PREF_TYPES = Arrays.asList("Preference", 55 "PreferenceCategory", "PreferenceScreen"); 56 57 public static final String PREF_AVAILABILITY_STATUS_WRITE = "write"; 58 public static final String PREF_AVAILABILITY_STATUS_READ = "read"; 59 public static final String PREF_AVAILABILITY_STATUS_HIDDEN = "hidden"; 60 public static final List<String> SUPPORTED_AVAILABILITY_STATUS = 61 Arrays.asList(PREF_AVAILABILITY_STATUS_WRITE, PREF_AVAILABILITY_STATUS_READ, 62 PREF_AVAILABILITY_STATUS_HIDDEN); 63 64 /** 65 * Flag definition to indicate which metadata should be extracted when 66 * {@link #extractMetadata(Context, int, int)} is called. The flags can be combined by using | 67 * (binary or). 68 */ 69 @IntDef(flag = true, value = { 70 MetadataFlag.FLAG_NEED_KEY, 71 MetadataFlag.FLAG_NEED_PREF_CONTROLLER, 72 MetadataFlag.FLAG_NEED_SEARCHABLE, 73 MetadataFlag.FLAG_NEED_PREF_DRIVER, 74 MetadataFlag.FLAG_NEED_PREF_FRONT_PASSENGER, 75 MetadataFlag.FLAG_NEED_PREF_REAR_PASSENGER}) 76 @Retention(RetentionPolicy.SOURCE) 77 public @interface MetadataFlag { 78 int FLAG_NEED_KEY = 1; 79 int FLAG_NEED_PREF_CONTROLLER = 1 << 1; 80 int FLAG_NEED_SEARCHABLE = 1 << 9; 81 int FLAG_NEED_PREF_DRIVER = 1 << 10; 82 int FLAG_NEED_PREF_FRONT_PASSENGER = 1 << 11; 83 int FLAG_NEED_PREF_REAR_PASSENGER = 1 << 12; 84 } 85 86 public static final String METADATA_KEY = "key"; 87 public static final String METADATA_SEARCHABLE = "searchable"; 88 static final String METADATA_CONTROLLER = "controller"; 89 public static final String METADATA_OCCUPANT_ZONE = "occupant_zone"; 90 91 /** 92 * Extracts metadata from each preference XML and puts them into a {@link Bundle}. 93 * 94 * @param xmlResId xml res id of a preference screen 95 * @param flags one or more of {@link MetadataFlag} 96 * @return a list of Bundles containing the extracted metadata 97 */ 98 @NonNull extractMetadata(Context context, @XmlRes int xmlResId, int flags)99 public static List<Bundle> extractMetadata(Context context, @XmlRes int xmlResId, int flags) 100 throws IOException, XmlPullParserException { 101 final List<Bundle> metadata = new ArrayList<>(); 102 if (xmlResId <= 0) { 103 LOG.d(xmlResId + " is invalid."); 104 return metadata; 105 } 106 final XmlResourceParser parser = context.getResources().getXml(xmlResId); 107 108 int type; 109 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT 110 && type != XmlPullParser.START_TAG) { 111 // Parse next until start tag is found 112 } 113 final int outerDepth = parser.getDepth(); 114 115 do { 116 if (type != XmlPullParser.START_TAG) { 117 continue; 118 } 119 final String nodeName = parser.getName(); 120 if (!SUPPORTED_PREF_TYPES.contains(nodeName) && !nodeName.endsWith(PREF_TAG_ENDS_WITH) 121 && !nodeName.endsWith(PREF_GROUP_TAG_ENDS_WITH) 122 && !nodeName.endsWith(PREF_CATEGORY_TAG_ENDS_WITH)) { 123 continue; 124 } 125 final Bundle preferenceMetadata = new Bundle(); 126 final AttributeSet attrs = Xml.asAttributeSet(parser); 127 final TypedArray preferenceAttributes = context.obtainStyledAttributes(attrs, 128 R.styleable.Preference); 129 130 if (hasFlag(flags, MetadataFlag.FLAG_NEED_KEY)) { 131 preferenceMetadata.putString(METADATA_KEY, getKey(preferenceAttributes)); 132 } 133 if (hasFlag(flags, MetadataFlag.FLAG_NEED_PREF_CONTROLLER)) { 134 preferenceMetadata.putString(METADATA_CONTROLLER, 135 getController(preferenceAttributes)); 136 } 137 if (hasFlag(flags, MetadataFlag.FLAG_NEED_SEARCHABLE)) { 138 preferenceMetadata.putBoolean(METADATA_SEARCHABLE, 139 isSearchable(preferenceAttributes)); 140 } 141 if (hasFlag(flags, MetadataFlag.FLAG_NEED_PREF_DRIVER)) { 142 preferenceMetadata.putString(METADATA_OCCUPANT_ZONE, 143 getDriver(preferenceAttributes)); 144 } else if (hasFlag(flags, MetadataFlag.FLAG_NEED_PREF_FRONT_PASSENGER)) { 145 preferenceMetadata.putString(METADATA_OCCUPANT_ZONE, 146 getFrontPassenger(preferenceAttributes)); 147 } else if (hasFlag(flags, MetadataFlag.FLAG_NEED_PREF_REAR_PASSENGER)) { 148 preferenceMetadata.putString(METADATA_OCCUPANT_ZONE, 149 getRearPassenger(preferenceAttributes)); 150 } 151 metadata.add(preferenceMetadata); 152 153 preferenceAttributes.recycle(); 154 } while ((type = parser.next()) != XmlPullParser.END_DOCUMENT 155 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)); 156 parser.close(); 157 158 return metadata; 159 } 160 161 /** 162 * Gets metadata flag from current zone type. 163 * 164 * @return a flag {@link MetadataFlag} that determines which zone attribute 165 * should be extracted from xml. 166 */ getMetadataFlagForOccupantZoneType(int zoneType)167 public static int getMetadataFlagForOccupantZoneType(int zoneType) { 168 switch (zoneType) { 169 case CarOccupantZoneManager.OCCUPANT_TYPE_FRONT_PASSENGER: 170 return PreferenceXmlParser.MetadataFlag.FLAG_NEED_PREF_FRONT_PASSENGER; 171 case CarOccupantZoneManager.OCCUPANT_TYPE_REAR_PASSENGER: 172 return PreferenceXmlParser.MetadataFlag.FLAG_NEED_PREF_REAR_PASSENGER; 173 case CarOccupantZoneManager.OCCUPANT_TYPE_DRIVER: // fall through 174 default: 175 return PreferenceXmlParser.MetadataFlag.FLAG_NEED_PREF_DRIVER; 176 } 177 } 178 hasFlag(int flags, @MetadataFlag int flag)179 private static boolean hasFlag(int flags, @MetadataFlag int flag) { 180 return (flags & flag) != 0; 181 } 182 getKey(TypedArray styledAttributes)183 private static String getKey(TypedArray styledAttributes) { 184 return styledAttributes.getString(com.android.internal.R.styleable.Preference_key); 185 } 186 getController(TypedArray styledAttributes)187 private static String getController(TypedArray styledAttributes) { 188 return styledAttributes.getString(R.styleable.Preference_controller); 189 } 190 isSearchable(TypedArray styledAttributes)191 private static boolean isSearchable(TypedArray styledAttributes) { 192 return styledAttributes.getBoolean(R.styleable.Preference_searchable, true); 193 } 194 getDriver(TypedArray styledAttributes)195 private static String getDriver(TypedArray styledAttributes) { 196 return styledAttributes.getString(R.styleable.Preference_occupant_driver); 197 } 198 getFrontPassenger(TypedArray styledAttributes)199 private static String getFrontPassenger(TypedArray styledAttributes) { 200 return styledAttributes.getString(R.styleable.Preference_occupant_front_passenger); 201 } 202 getRearPassenger(TypedArray styledAttributes)203 private static String getRearPassenger(TypedArray styledAttributes) { 204 return styledAttributes.getString(R.styleable.Preference_occupant_rear_passenger); 205 } 206 } 207