1 /* 2 * Copyright (C) 2020 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.server.pm; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.util.Slog; 22 import android.util.Xml; 23 24 import com.android.modules.utils.TypedXmlPullParser; 25 import com.android.modules.utils.TypedXmlSerializer; 26 27 import org.xmlpull.v1.XmlPullParser; 28 import org.xmlpull.v1.XmlPullParserException; 29 30 import java.io.IOException; 31 import java.io.InputStream; 32 import java.nio.charset.StandardCharsets; 33 import java.util.Stack; 34 35 /** 36 * A very specialized serialization/parsing wrapper around {@link TypedXmlSerializer} and {@link 37 * TypedXmlPullParser} intended for use with PackageManager related settings files. 38 * Assumptions/chosen behaviors: 39 * <ul> 40 * <li>No namespace support</li> 41 * <li>Data for a parent object is stored as attributes</li> 42 * <li>All attribute read methods return a default false, -1, or null</li> 43 * <li>Default values will not be written</li> 44 * <li>Children are sub-elements</li> 45 * <li>Collections are repeated sub-elements, no attribute support for collections</li> 46 * </ul> 47 */ 48 public class SettingsXml { 49 50 private static final String TAG = "SettingsXml"; 51 52 private static final boolean DEBUG_THROW_EXCEPTIONS = false; 53 54 private static final String FEATURE_INDENT = 55 "http://xmlpull.org/v1/doc/features.html#indent-output"; 56 57 private static final int DEFAULT_NUMBER = -1; 58 serializer(TypedXmlSerializer serializer)59 public static Serializer serializer(TypedXmlSerializer serializer) { 60 return new Serializer(serializer); 61 } 62 parser(TypedXmlPullParser parser)63 public static ReadSection parser(TypedXmlPullParser parser) 64 throws IOException, XmlPullParserException { 65 return new ReadSectionImpl(parser); 66 } 67 68 public static class Serializer implements AutoCloseable { 69 70 @NonNull 71 private final TypedXmlSerializer mXmlSerializer; 72 73 private final WriteSectionImpl mWriteSection; 74 Serializer(TypedXmlSerializer serializer)75 private Serializer(TypedXmlSerializer serializer) { 76 mXmlSerializer = serializer; 77 mWriteSection = new WriteSectionImpl(mXmlSerializer); 78 } 79 startSection(@onNull String sectionName)80 public WriteSection startSection(@NonNull String sectionName) throws IOException { 81 return mWriteSection.startSection(sectionName); 82 } 83 84 @Override close()85 public void close() throws IOException { 86 mWriteSection.closeCompletely(); 87 mXmlSerializer.flush(); 88 } 89 } 90 91 public interface ReadSection extends AutoCloseable { 92 93 @NonNull getName()94 String getName(); 95 96 @NonNull getDescription()97 String getDescription(); 98 has(String attrName)99 boolean has(String attrName); 100 101 @Nullable getString(String attrName)102 String getString(String attrName); 103 104 /** 105 * @return value as String or {@param defaultValue} if doesn't exist 106 */ 107 @NonNull getString(String attrName, @NonNull String defaultValue)108 String getString(String attrName, @NonNull String defaultValue); 109 110 /** 111 * @return value as boolean or false if doesn't exist 112 */ getBoolean(String attrName)113 boolean getBoolean(String attrName); 114 115 /** 116 * @return value as boolean or {@param defaultValue} if doesn't exist 117 */ getBoolean(String attrName, boolean defaultValue)118 boolean getBoolean(String attrName, boolean defaultValue); 119 120 /** 121 * @return value as int or {@link #DEFAULT_NUMBER} if doesn't exist 122 */ getInt(String attrName)123 int getInt(String attrName); 124 125 /** 126 * @return value as int or {@param defaultValue} if doesn't exist 127 */ getInt(String attrName, int defaultValue)128 int getInt(String attrName, int defaultValue); 129 130 /** 131 * @return value as long or {@link #DEFAULT_NUMBER} if doesn't exist 132 */ getLong(String attrName)133 long getLong(String attrName); 134 135 /** 136 * @return value as long or {@param defaultValue} if doesn't exist 137 */ getLong(String attrName, int defaultValue)138 long getLong(String attrName, int defaultValue); 139 children()140 ChildSection children(); 141 } 142 143 /** 144 * <pre><code> 145 * ChildSection child = parentSection.children(); 146 * while (child.moveToNext(TAG_CHILD)) { 147 * String readValue = child.getString(...); 148 * ... 149 * } 150 * </code></pre> 151 */ 152 public interface ChildSection extends ReadSection { moveToNext()153 boolean moveToNext(); 154 moveToNext(@onNull String expectedChildTagName)155 boolean moveToNext(@NonNull String expectedChildTagName); 156 } 157 158 public static class ReadSectionImpl implements ChildSection { 159 160 @Nullable 161 private final InputStream mInput; 162 163 @NonNull 164 private final TypedXmlPullParser mParser; 165 166 @NonNull 167 private final Stack<Integer> mDepthStack = new Stack<>(); 168 ReadSectionImpl(@onNull InputStream input)169 public ReadSectionImpl(@NonNull InputStream input) 170 throws IOException, XmlPullParserException { 171 mInput = input; 172 mParser = Xml.newFastPullParser(); 173 mParser.setInput(mInput, StandardCharsets.UTF_8.name()); 174 moveToFirstTag(); 175 } 176 ReadSectionImpl(@onNull TypedXmlPullParser parser)177 public ReadSectionImpl(@NonNull TypedXmlPullParser parser) 178 throws IOException, XmlPullParserException { 179 mInput = null; 180 mParser = parser; 181 moveToFirstTag(); 182 } 183 moveToFirstTag()184 private void moveToFirstTag() throws IOException, XmlPullParserException { 185 if (mParser.getEventType() == XmlPullParser.START_TAG) { 186 return; 187 } 188 189 int type; 190 //noinspection StatementWithEmptyBody 191 while ((type = mParser.next()) != XmlPullParser.START_TAG 192 && type != XmlPullParser.END_DOCUMENT) { 193 } 194 } 195 196 @NonNull 197 @Override getName()198 public String getName() { 199 return mParser.getName(); 200 } 201 202 @NonNull 203 @Override getDescription()204 public String getDescription() { 205 return mParser.getPositionDescription(); 206 } 207 208 @Override has(String attrName)209 public boolean has(String attrName) { 210 return mParser.getAttributeValue(null, attrName) != null; 211 } 212 213 @Nullable 214 @Override getString(String attrName)215 public String getString(String attrName) { 216 return mParser.getAttributeValue(null, attrName); 217 } 218 219 @NonNull 220 @Override getString(String attrName, @NonNull String defaultValue)221 public String getString(String attrName, @NonNull String defaultValue) { 222 String value = mParser.getAttributeValue(null, attrName); 223 if (value == null) { 224 return defaultValue; 225 } 226 return value; 227 } 228 229 @Override getBoolean(String attrName)230 public boolean getBoolean(String attrName) { 231 return getBoolean(attrName, false); 232 } 233 234 @Override getBoolean(String attrName, boolean defaultValue)235 public boolean getBoolean(String attrName, boolean defaultValue) { 236 return mParser.getAttributeBoolean(null, attrName, defaultValue); 237 } 238 239 @Override getInt(String attrName)240 public int getInt(String attrName) { 241 return getInt(attrName, DEFAULT_NUMBER); 242 } 243 244 @Override getInt(String attrName, int defaultValue)245 public int getInt(String attrName, int defaultValue) { 246 return mParser.getAttributeInt(null, attrName, defaultValue); 247 } 248 249 @Override getLong(String attrName)250 public long getLong(String attrName) { 251 return getLong(attrName, DEFAULT_NUMBER); 252 } 253 254 @Override getLong(String attrName, int defaultValue)255 public long getLong(String attrName, int defaultValue) { 256 return mParser.getAttributeLong(null, attrName, defaultValue); 257 } 258 259 @Override children()260 public ChildSection children() { 261 mDepthStack.push(mParser.getDepth()); 262 return this; 263 } 264 265 @Override moveToNext()266 public boolean moveToNext() { 267 return moveToNextInternal(null); 268 } 269 270 @Override moveToNext(@onNull String expectedChildTagName)271 public boolean moveToNext(@NonNull String expectedChildTagName) { 272 return moveToNextInternal(expectedChildTagName); 273 } 274 moveToNextInternal(@ullable String expectedChildTagName)275 private boolean moveToNextInternal(@Nullable String expectedChildTagName) { 276 try { 277 int depth = mDepthStack.peek(); 278 boolean hasTag = false; 279 int type; 280 while (!hasTag 281 && (type = mParser.next()) != XmlPullParser.END_DOCUMENT 282 && (type != XmlPullParser.END_TAG || mParser.getDepth() > depth)) { 283 if (type != XmlPullParser.START_TAG) { 284 continue; 285 } 286 287 if (expectedChildTagName != null 288 && !expectedChildTagName.equals(mParser.getName())) { 289 continue; 290 } 291 292 hasTag = true; 293 } 294 295 if (!hasTag) { 296 mDepthStack.pop(); 297 } 298 299 return hasTag; 300 } catch (Exception ignored) { 301 return false; 302 } 303 } 304 305 @Override close()306 public void close() throws Exception { 307 if (mDepthStack.isEmpty()) { 308 Slog.wtf(TAG, "Children depth stack was not empty, data may have been lost", 309 new Exception()); 310 } 311 if (mInput != null) { 312 mInput.close(); 313 } 314 } 315 } 316 317 public interface WriteSection extends AutoCloseable { 318 startSection(@onNull String sectionName)319 WriteSection startSection(@NonNull String sectionName) throws IOException; 320 attribute(String attrName, @Nullable String value)321 WriteSection attribute(String attrName, @Nullable String value) throws IOException; 322 attribute(String attrName, int value)323 WriteSection attribute(String attrName, int value) throws IOException; 324 attribute(String attrName, long value)325 WriteSection attribute(String attrName, long value) throws IOException; 326 attribute(String attrName, boolean value)327 WriteSection attribute(String attrName, boolean value) throws IOException; 328 329 @Override close()330 void close() throws IOException; 331 finish()332 void finish() throws IOException; 333 } 334 335 private static class WriteSectionImpl implements WriteSection { 336 337 @NonNull 338 private final TypedXmlSerializer mXmlSerializer; 339 340 @NonNull 341 private final Stack<String> mTagStack = new Stack<>(); 342 WriteSectionImpl(@onNull TypedXmlSerializer xmlSerializer)343 private WriteSectionImpl(@NonNull TypedXmlSerializer xmlSerializer) { 344 mXmlSerializer = xmlSerializer; 345 } 346 347 @Override startSection(@onNull String sectionName)348 public WriteSection startSection(@NonNull String sectionName) throws IOException { 349 // Try to start the tag first before we push it to the stack 350 mXmlSerializer.startTag(null, sectionName); 351 mTagStack.push(sectionName); 352 return this; 353 } 354 355 @Override attribute(String attrName, String value)356 public WriteSection attribute(String attrName, String value) throws IOException { 357 if (value != null) { 358 mXmlSerializer.attribute(null, attrName, value); 359 } 360 return this; 361 } 362 363 @Override attribute(String attrName, int value)364 public WriteSection attribute(String attrName, int value) throws IOException { 365 if (value != DEFAULT_NUMBER) { 366 mXmlSerializer.attributeInt(null, attrName, value); 367 } 368 return this; 369 } 370 371 @Override attribute(String attrName, long value)372 public WriteSection attribute(String attrName, long value) throws IOException { 373 if (value != DEFAULT_NUMBER) { 374 mXmlSerializer.attributeLong(null, attrName, value); 375 } 376 return this; 377 } 378 379 @Override attribute(String attrName, boolean value)380 public WriteSection attribute(String attrName, boolean value) throws IOException { 381 if (value) { 382 mXmlSerializer.attributeBoolean(null, attrName, value); 383 } 384 return this; 385 } 386 387 @Override finish()388 public void finish() throws IOException { 389 close(); 390 } 391 392 @Override close()393 public void close() throws IOException { 394 mXmlSerializer.endTag(null, mTagStack.pop()); 395 } 396 closeCompletely()397 private void closeCompletely() throws IOException { 398 if (DEBUG_THROW_EXCEPTIONS && mTagStack != null && !mTagStack.isEmpty()) { 399 throw new IllegalStateException( 400 "tag stack is not empty when closing, contains " + mTagStack); 401 } else if (mTagStack != null) { 402 while (!mTagStack.isEmpty()) { 403 close(); 404 } 405 } 406 } 407 } 408 } 409