1 /*
2  * Copyright (C) 2006 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.internal.util;
18 
19 import android.annotation.NonNull;
20 import android.compat.annotation.UnsupportedAppUsage;
21 import android.graphics.Bitmap;
22 import android.graphics.Bitmap.CompressFormat;
23 import android.graphics.BitmapFactory;
24 import android.net.Uri;
25 import android.text.TextUtils;
26 import android.util.ArrayMap;
27 import android.util.Base64;
28 import android.util.Xml;
29 
30 import com.android.modules.utils.TypedXmlPullParser;
31 import com.android.modules.utils.TypedXmlSerializer;
32 
33 import libcore.util.HexEncoding;
34 
35 import org.xmlpull.v1.XmlPullParser;
36 import org.xmlpull.v1.XmlPullParserException;
37 import org.xmlpull.v1.XmlSerializer;
38 
39 import java.io.ByteArrayOutputStream;
40 import java.io.IOException;
41 import java.io.InputStream;
42 import java.io.OutputStream;
43 import java.net.ProtocolException;
44 import java.nio.charset.StandardCharsets;
45 import java.util.ArrayList;
46 import java.util.HashMap;
47 import java.util.HashSet;
48 import java.util.Iterator;
49 import java.util.List;
50 import java.util.Map;
51 import java.util.Set;
52 
53 /** {@hide} */
54 public class XmlUtils {
55     private static final String STRING_ARRAY_SEPARATOR = ":";
56 
57     @SuppressWarnings("AndroidFrameworkEfficientXml")
58     private static class ForcedTypedXmlSerializer extends XmlSerializerWrapper
59             implements TypedXmlSerializer {
ForcedTypedXmlSerializer(XmlSerializer wrapped)60         public ForcedTypedXmlSerializer(XmlSerializer wrapped) {
61             super(wrapped);
62         }
63 
64         @Override
attributeInterned(String namespace, String name, String value)65         public XmlSerializer attributeInterned(String namespace, String name, String value)
66                 throws IOException {
67             return attribute(namespace, name, value);
68         }
69 
70         @Override
attributeBytesHex(String namespace, String name, byte[] value)71         public XmlSerializer attributeBytesHex(String namespace, String name, byte[] value)
72                 throws IOException {
73             return attribute(namespace, name, HexDump.toHexString(value));
74         }
75 
76         @Override
attributeBytesBase64(String namespace, String name, byte[] value)77         public XmlSerializer attributeBytesBase64(String namespace, String name, byte[] value)
78                 throws IOException {
79             return attribute(namespace, name, Base64.encodeToString(value, Base64.NO_WRAP));
80         }
81 
82         @Override
attributeInt(String namespace, String name, int value)83         public XmlSerializer attributeInt(String namespace, String name, int value)
84                 throws IOException {
85             return attribute(namespace, name, Integer.toString(value));
86         }
87 
88         @Override
attributeIntHex(String namespace, String name, int value)89         public XmlSerializer attributeIntHex(String namespace, String name, int value)
90                 throws IOException {
91             return attribute(namespace, name, Integer.toString(value, 16));
92         }
93 
94         @Override
attributeLong(String namespace, String name, long value)95         public XmlSerializer attributeLong(String namespace, String name, long value)
96                 throws IOException {
97             return attribute(namespace, name, Long.toString(value));
98         }
99 
100         @Override
attributeLongHex(String namespace, String name, long value)101         public XmlSerializer attributeLongHex(String namespace, String name, long value)
102                 throws IOException {
103             return attribute(namespace, name, Long.toString(value, 16));
104         }
105 
106         @Override
attributeFloat(String namespace, String name, float value)107         public XmlSerializer attributeFloat(String namespace, String name, float value)
108                 throws IOException {
109             return attribute(namespace, name, Float.toString(value));
110         }
111 
112         @Override
attributeDouble(String namespace, String name, double value)113         public XmlSerializer attributeDouble(String namespace, String name, double value)
114                 throws IOException {
115             return attribute(namespace, name, Double.toString(value));
116         }
117 
118         @Override
attributeBoolean(String namespace, String name, boolean value)119         public XmlSerializer attributeBoolean(String namespace, String name, boolean value)
120                 throws IOException {
121             return attribute(namespace, name, Boolean.toString(value));
122         }
123     }
124 
125     /**
126      * Return a specialization of the given {@link XmlSerializer} which has
127      * explicit methods to support consistent and efficient conversion of
128      * primitive data types.
129      */
makeTyped(@onNull XmlSerializer xml)130     public static @NonNull TypedXmlSerializer makeTyped(@NonNull XmlSerializer xml) {
131         if (xml instanceof TypedXmlSerializer) {
132             return (TypedXmlSerializer) xml;
133         } else {
134             return new ForcedTypedXmlSerializer(xml);
135         }
136     }
137 
138     @SuppressWarnings("AndroidFrameworkEfficientXml")
139     private static class ForcedTypedXmlPullParser extends XmlPullParserWrapper
140             implements TypedXmlPullParser {
ForcedTypedXmlPullParser(XmlPullParser wrapped)141         public ForcedTypedXmlPullParser(XmlPullParser wrapped) {
142             super(wrapped);
143         }
144 
145         @Override
getAttributeBytesHex(int index)146         public byte[] getAttributeBytesHex(int index)
147                 throws XmlPullParserException {
148             try {
149                 return HexDump.hexStringToByteArray(getAttributeValue(index));
150             } catch (Exception e) {
151                 throw new XmlPullParserException(
152                         "Invalid attribute " + getAttributeName(index) + ": " + e);
153             }
154         }
155 
156         @Override
getAttributeBytesBase64(int index)157         public byte[] getAttributeBytesBase64(int index)
158                 throws XmlPullParserException {
159             try {
160                 return Base64.decode(getAttributeValue(index), Base64.NO_WRAP);
161             } catch (Exception e) {
162                 throw new XmlPullParserException(
163                         "Invalid attribute " + getAttributeName(index) + ": " + e);
164             }
165         }
166 
167         @Override
getAttributeInt(int index)168         public int getAttributeInt(int index)
169                 throws XmlPullParserException {
170             try {
171                 return Integer.parseInt(getAttributeValue(index));
172             } catch (Exception e) {
173                 throw new XmlPullParserException(
174                         "Invalid attribute " + getAttributeName(index) + ": " + e);
175             }
176         }
177 
178         @Override
getAttributeIntHex(int index)179         public int getAttributeIntHex(int index)
180                 throws XmlPullParserException {
181             try {
182                 return Integer.parseInt(getAttributeValue(index), 16);
183             } catch (Exception e) {
184                 throw new XmlPullParserException(
185                         "Invalid attribute " + getAttributeName(index) + ": " + e);
186             }
187         }
188 
189         @Override
getAttributeLong(int index)190         public long getAttributeLong(int index)
191                 throws XmlPullParserException {
192             try {
193                 return Long.parseLong(getAttributeValue(index));
194             } catch (Exception e) {
195                 throw new XmlPullParserException(
196                         "Invalid attribute " + getAttributeName(index) + ": " + e);
197             }
198         }
199 
200         @Override
getAttributeLongHex(int index)201         public long getAttributeLongHex(int index)
202                 throws XmlPullParserException {
203             try {
204                 return Long.parseLong(getAttributeValue(index), 16);
205             } catch (Exception e) {
206                 throw new XmlPullParserException(
207                         "Invalid attribute " + getAttributeName(index) + ": " + e);
208             }
209         }
210 
211         @Override
getAttributeFloat(int index)212         public float getAttributeFloat(int index)
213                 throws XmlPullParserException {
214             try {
215                 return Float.parseFloat(getAttributeValue(index));
216             } catch (Exception e) {
217                 throw new XmlPullParserException(
218                         "Invalid attribute " + getAttributeName(index) + ": " + e);
219             }
220         }
221 
222         @Override
getAttributeDouble(int index)223         public double getAttributeDouble(int index)
224                 throws XmlPullParserException {
225             try {
226                 return Double.parseDouble(getAttributeValue(index));
227             } catch (Exception e) {
228                 throw new XmlPullParserException(
229                         "Invalid attribute " + getAttributeName(index) + ": " + e);
230             }
231         }
232 
233         @Override
getAttributeBoolean(int index)234         public boolean getAttributeBoolean(int index)
235                 throws XmlPullParserException {
236             final String value = getAttributeValue(index);
237             if ("true".equalsIgnoreCase(value)) {
238                 return true;
239             } else if ("false".equalsIgnoreCase(value)) {
240                 return false;
241             } else {
242                 throw new XmlPullParserException(
243                         "Invalid attribute " + getAttributeName(index) + ": " + value);
244             }
245         }
246     }
247 
248     /**
249      * Return a specialization of the given {@link XmlPullParser} which has
250      * explicit methods to support consistent and efficient conversion of
251      * primitive data types.
252      */
makeTyped(@onNull XmlPullParser xml)253     public static @NonNull TypedXmlPullParser makeTyped(@NonNull XmlPullParser xml) {
254         if (xml instanceof TypedXmlPullParser) {
255             return (TypedXmlPullParser) xml;
256         } else {
257             return new ForcedTypedXmlPullParser(xml);
258         }
259     }
260 
261     @UnsupportedAppUsage
skipCurrentTag(XmlPullParser parser)262     public static void skipCurrentTag(XmlPullParser parser)
263             throws XmlPullParserException, IOException {
264         int outerDepth = parser.getDepth();
265         int type;
266         while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
267                && (type != XmlPullParser.END_TAG
268                        || parser.getDepth() > outerDepth)) {
269         }
270     }
271 
272     public static final int
convertValueToList(CharSequence value, String[] options, int defaultValue)273     convertValueToList(CharSequence value, String[] options, int defaultValue)
274     {
275         if (!TextUtils.isEmpty(value)) {
276             for (int i = 0; i < options.length; i++) {
277                 if (value.equals(options[i]))
278                     return i;
279             }
280         }
281 
282         return defaultValue;
283     }
284 
285     @UnsupportedAppUsage
286     public static final boolean
convertValueToBoolean(CharSequence value, boolean defaultValue)287     convertValueToBoolean(CharSequence value, boolean defaultValue)
288     {
289         boolean result = false;
290 
291         if (TextUtils.isEmpty(value)) {
292             return defaultValue;
293         }
294 
295         if (value.equals("1")
296         ||  value.equals("true")
297         ||  value.equals("TRUE"))
298             result = true;
299 
300         return result;
301     }
302 
303     @UnsupportedAppUsage
304     public static final int
convertValueToInt(CharSequence charSeq, int defaultValue)305     convertValueToInt(CharSequence charSeq, int defaultValue)
306     {
307         if (TextUtils.isEmpty(charSeq)) {
308             return defaultValue;
309         }
310 
311         String nm = charSeq.toString();
312 
313         // XXX This code is copied from Integer.decode() so we don't
314         // have to instantiate an Integer!
315 
316         int value;
317         int sign = 1;
318         int index = 0;
319         int len = nm.length();
320         int base = 10;
321 
322         if ('-' == nm.charAt(0)) {
323             sign = -1;
324             index++;
325         }
326 
327         if ('0' == nm.charAt(index)) {
328             //  Quick check for a zero by itself
329             if (index == (len - 1))
330                 return 0;
331 
332             char    c = nm.charAt(index + 1);
333 
334             if ('x' == c || 'X' == c) {
335                 index += 2;
336                 base = 16;
337             } else {
338                 index++;
339                 base = 8;
340             }
341         }
342         else if ('#' == nm.charAt(index))
343         {
344             index++;
345             base = 16;
346         }
347 
348         return Integer.parseInt(nm.substring(index), base) * sign;
349     }
350 
convertValueToUnsignedInt(String value, int defaultValue)351     public static int convertValueToUnsignedInt(String value, int defaultValue) {
352         if (TextUtils.isEmpty(value)) {
353             return defaultValue;
354         }
355 
356         return parseUnsignedIntAttribute(value);
357     }
358 
parseUnsignedIntAttribute(CharSequence charSeq)359     public static int parseUnsignedIntAttribute(CharSequence charSeq) {
360         String  value = charSeq.toString();
361 
362         long    bits;
363         int     index = 0;
364         int     len = value.length();
365         int     base = 10;
366 
367         if ('0' == value.charAt(index)) {
368             //  Quick check for zero by itself
369             if (index == (len - 1))
370                 return 0;
371 
372             char    c = value.charAt(index + 1);
373 
374             if ('x' == c || 'X' == c) {     //  check for hex
375                 index += 2;
376                 base = 16;
377             } else {                        //  check for octal
378                 index++;
379                 base = 8;
380             }
381         } else if ('#' == value.charAt(index)) {
382             index++;
383             base = 16;
384         }
385 
386         return (int) Long.parseLong(value.substring(index), base);
387     }
388 
389     /**
390      * Flatten a Map into an output stream as XML.  The map can later be
391      * read back with readMapXml().
392      *
393      * @param val The map to be flattened.
394      * @param out Where to write the XML data.
395      *
396      * @see #writeMapXml(Map, String, XmlSerializer)
397      * @see #writeListXml
398      * @see #writeValueXml
399      * @see #readMapXml
400      */
401     @UnsupportedAppUsage
writeMapXml(Map val, OutputStream out)402     public static final void writeMapXml(Map val, OutputStream out)
403             throws XmlPullParserException, java.io.IOException {
404         TypedXmlSerializer serializer = Xml.newFastSerializer();
405         serializer.setOutput(out, StandardCharsets.UTF_8.name());
406         serializer.startDocument(null, true);
407         serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
408         writeMapXml(val, null, serializer);
409         serializer.endDocument();
410     }
411 
412     /**
413      * Flatten a List into an output stream as XML.  The list can later be
414      * read back with readListXml().
415      *
416      * @param val The list to be flattened.
417      * @param out Where to write the XML data.
418      *
419      * @see #writeListXml(List, String, XmlSerializer)
420      * @see #writeMapXml
421      * @see #writeValueXml
422      * @see #readListXml
423      */
writeListXml(List val, OutputStream out)424     public static final void writeListXml(List val, OutputStream out)
425     throws XmlPullParserException, java.io.IOException
426     {
427         TypedXmlSerializer serializer = Xml.newFastSerializer();
428         serializer.setOutput(out, StandardCharsets.UTF_8.name());
429         serializer.startDocument(null, true);
430         serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
431         writeListXml(val, null, serializer);
432         serializer.endDocument();
433     }
434 
435     /**
436      * Flatten a Map into an XmlSerializer.  The map can later be read back
437      * with readThisMapXml().
438      *
439      * @param val The map to be flattened.
440      * @param name Name attribute to include with this list's tag, or null for
441      *             none.
442      * @param out XmlSerializer to write the map into.
443      *
444      * @see #writeMapXml(Map, OutputStream)
445      * @see #writeListXml
446      * @see #writeValueXml
447      * @see #readMapXml
448      */
writeMapXml(Map val, String name, TypedXmlSerializer out)449     public static final void writeMapXml(Map val, String name, TypedXmlSerializer out)
450             throws XmlPullParserException, java.io.IOException {
451         writeMapXml(val, name, out, null);
452     }
453 
454     /**
455      * Flatten a Map into an XmlSerializer.  The map can later be read back
456      * with readThisMapXml().
457      *
458      * @param val The map to be flattened.
459      * @param name Name attribute to include with this list's tag, or null for
460      *             none.
461      * @param out XmlSerializer to write the map into.
462      * @param callback Method to call when an Object type is not recognized.
463      *
464      * @see #writeMapXml(Map, OutputStream)
465      * @see #writeListXml
466      * @see #writeValueXml
467      * @see #readMapXml
468      *
469      * @hide
470      */
writeMapXml(Map val, String name, TypedXmlSerializer out, WriteMapCallback callback)471     public static final void writeMapXml(Map val, String name, TypedXmlSerializer out,
472             WriteMapCallback callback) throws XmlPullParserException, java.io.IOException {
473 
474         if (val == null) {
475             out.startTag(null, "null");
476             out.endTag(null, "null");
477             return;
478         }
479 
480         out.startTag(null, "map");
481         if (name != null) {
482             out.attribute(null, "name", name);
483         }
484 
485         writeMapXml(val, out, callback);
486 
487         out.endTag(null, "map");
488     }
489 
490     /**
491      * Flatten a Map into an XmlSerializer.  The map can later be read back
492      * with readThisMapXml(). This method presumes that the start tag and
493      * name attribute have already been written and does not write an end tag.
494      *
495      * @param val The map to be flattened.
496      * @param out XmlSerializer to write the map into.
497      *
498      * @see #writeMapXml(Map, OutputStream)
499      * @see #writeListXml
500      * @see #writeValueXml
501      * @see #readMapXml
502      *
503      * @hide
504      */
writeMapXml(Map val, TypedXmlSerializer out, WriteMapCallback callback)505     public static final void writeMapXml(Map val, TypedXmlSerializer out,
506             WriteMapCallback callback) throws XmlPullParserException, java.io.IOException {
507         if (val == null) {
508             return;
509         }
510 
511         Set s = val.entrySet();
512         Iterator i = s.iterator();
513 
514         while (i.hasNext()) {
515             Map.Entry e = (Map.Entry)i.next();
516             writeValueXml(e.getValue(), (String)e.getKey(), out, callback);
517         }
518     }
519 
520     /**
521      * Flatten a List into an XmlSerializer.  The list can later be read back
522      * with readThisListXml().
523      *
524      * @param val The list to be flattened.
525      * @param name Name attribute to include with this list's tag, or null for
526      *             none.
527      * @param out XmlSerializer to write the list into.
528      *
529      * @see #writeListXml(List, OutputStream)
530      * @see #writeMapXml
531      * @see #writeValueXml
532      * @see #readListXml
533      */
writeListXml(List val, String name, TypedXmlSerializer out)534     public static final void writeListXml(List val, String name, TypedXmlSerializer out)
535     throws XmlPullParserException, java.io.IOException
536     {
537         if (val == null) {
538             out.startTag(null, "null");
539             out.endTag(null, "null");
540             return;
541         }
542 
543         out.startTag(null, "list");
544         if (name != null) {
545             out.attribute(null, "name", name);
546         }
547 
548         int N = val.size();
549         int i=0;
550         while (i < N) {
551             writeValueXml(val.get(i), null, out);
552             i++;
553         }
554 
555         out.endTag(null, "list");
556     }
557 
writeSetXml(Set val, String name, TypedXmlSerializer out)558     public static final void writeSetXml(Set val, String name, TypedXmlSerializer out)
559             throws XmlPullParserException, java.io.IOException {
560         if (val == null) {
561             out.startTag(null, "null");
562             out.endTag(null, "null");
563             return;
564         }
565 
566         out.startTag(null, "set");
567         if (name != null) {
568             out.attribute(null, "name", name);
569         }
570 
571         for (Object v : val) {
572             writeValueXml(v, null, out);
573         }
574 
575         out.endTag(null, "set");
576     }
577 
578     /**
579      * Flatten a byte[] into an XmlSerializer.  The list can later be read back
580      * with readThisByteArrayXml().
581      *
582      * @param val The byte array to be flattened.
583      * @param name Name attribute to include with this array's tag, or null for
584      *             none.
585      * @param out XmlSerializer to write the array into.
586      *
587      * @see #writeMapXml
588      * @see #writeValueXml
589      */
writeByteArrayXml(byte[] val, String name, TypedXmlSerializer out)590     public static final void writeByteArrayXml(byte[] val, String name,
591             TypedXmlSerializer out)
592             throws XmlPullParserException, java.io.IOException {
593 
594         if (val == null) {
595             out.startTag(null, "null");
596             out.endTag(null, "null");
597             return;
598         }
599 
600         out.startTag(null, "byte-array");
601         if (name != null) {
602             out.attribute(null, "name", name);
603         }
604 
605         final int N = val.length;
606         out.attributeInt(null, "num", N);
607 
608         out.text(HexEncoding.encodeToString(val).toLowerCase());
609 
610         out.endTag(null, "byte-array");
611     }
612 
613     /**
614      * Flatten an int[] into an XmlSerializer.  The list can later be read back
615      * with readThisIntArrayXml().
616      *
617      * @param val The int array to be flattened.
618      * @param name Name attribute to include with this array's tag, or null for
619      *             none.
620      * @param out XmlSerializer to write the array into.
621      *
622      * @see #writeMapXml
623      * @see #writeValueXml
624      * @see #readThisIntArrayXml
625      */
writeIntArrayXml(int[] val, String name, TypedXmlSerializer out)626     public static final void writeIntArrayXml(int[] val, String name,
627             TypedXmlSerializer out)
628             throws XmlPullParserException, java.io.IOException {
629 
630         if (val == null) {
631             out.startTag(null, "null");
632             out.endTag(null, "null");
633             return;
634         }
635 
636         out.startTag(null, "int-array");
637         if (name != null) {
638             out.attribute(null, "name", name);
639         }
640 
641         final int N = val.length;
642         out.attributeInt(null, "num", N);
643 
644         for (int i=0; i<N; i++) {
645             out.startTag(null, "item");
646             out.attributeInt(null, "value", val[i]);
647             out.endTag(null, "item");
648         }
649 
650         out.endTag(null, "int-array");
651     }
652 
653     /**
654      * Flatten a long[] into an XmlSerializer.  The list can later be read back
655      * with readThisLongArrayXml().
656      *
657      * @param val The long array to be flattened.
658      * @param name Name attribute to include with this array's tag, or null for
659      *             none.
660      * @param out XmlSerializer to write the array into.
661      *
662      * @see #writeMapXml
663      * @see #writeValueXml
664      * @see #readThisIntArrayXml
665      */
writeLongArrayXml(long[] val, String name, TypedXmlSerializer out)666     public static final void writeLongArrayXml(long[] val, String name, TypedXmlSerializer out)
667             throws XmlPullParserException, java.io.IOException {
668 
669         if (val == null) {
670             out.startTag(null, "null");
671             out.endTag(null, "null");
672             return;
673         }
674 
675         out.startTag(null, "long-array");
676         if (name != null) {
677             out.attribute(null, "name", name);
678         }
679 
680         final int N = val.length;
681         out.attributeInt(null, "num", N);
682 
683         for (int i=0; i<N; i++) {
684             out.startTag(null, "item");
685             out.attributeLong(null, "value", val[i]);
686             out.endTag(null, "item");
687         }
688 
689         out.endTag(null, "long-array");
690     }
691 
692     /**
693      * Flatten a double[] into an XmlSerializer.  The list can later be read back
694      * with readThisDoubleArrayXml().
695      *
696      * @param val The double array to be flattened.
697      * @param name Name attribute to include with this array's tag, or null for
698      *             none.
699      * @param out XmlSerializer to write the array into.
700      *
701      * @see #writeMapXml
702      * @see #writeValueXml
703      * @see #readThisIntArrayXml
704      */
writeDoubleArrayXml(double[] val, String name, TypedXmlSerializer out)705     public static final void writeDoubleArrayXml(double[] val, String name, TypedXmlSerializer out)
706             throws XmlPullParserException, java.io.IOException {
707 
708         if (val == null) {
709             out.startTag(null, "null");
710             out.endTag(null, "null");
711             return;
712         }
713 
714         out.startTag(null, "double-array");
715         if (name != null) {
716             out.attribute(null, "name", name);
717         }
718 
719         final int N = val.length;
720         out.attributeInt(null, "num", N);
721 
722         for (int i=0; i<N; i++) {
723             out.startTag(null, "item");
724             out.attributeDouble(null, "value", val[i]);
725             out.endTag(null, "item");
726         }
727 
728         out.endTag(null, "double-array");
729     }
730 
731     /**
732      * Flatten a String[] into an XmlSerializer.  The list can later be read back
733      * with readThisStringArrayXml().
734      *
735      * @param val The String array to be flattened.
736      * @param name Name attribute to include with this array's tag, or null for
737      *             none.
738      * @param out XmlSerializer to write the array into.
739      *
740      * @see #writeMapXml
741      * @see #writeValueXml
742      * @see #readThisIntArrayXml
743      */
writeStringArrayXml(String[] val, String name, TypedXmlSerializer out)744     public static final void writeStringArrayXml(String[] val, String name, TypedXmlSerializer out)
745             throws XmlPullParserException, java.io.IOException {
746 
747         if (val == null) {
748             out.startTag(null, "null");
749             out.endTag(null, "null");
750             return;
751         }
752 
753         out.startTag(null, "string-array");
754         if (name != null) {
755             out.attribute(null, "name", name);
756         }
757 
758         final int N = val.length;
759         out.attributeInt(null, "num", N);
760 
761         for (int i=0; i<N; i++) {
762             out.startTag(null, "item");
763             out.attribute(null, "value", val[i]);
764             out.endTag(null, "item");
765         }
766 
767         out.endTag(null, "string-array");
768     }
769 
770     /**
771      * Flatten a boolean[] into an XmlSerializer.  The list can later be read back
772      * with readThisBooleanArrayXml().
773      *
774      * @param val The boolean array to be flattened.
775      * @param name Name attribute to include with this array's tag, or null for
776      *             none.
777      * @param out XmlSerializer to write the array into.
778      *
779      * @see #writeMapXml
780      * @see #writeValueXml
781      * @see #readThisIntArrayXml
782      */
writeBooleanArrayXml(boolean[] val, String name, TypedXmlSerializer out)783     public static final void writeBooleanArrayXml(boolean[] val, String name,
784             TypedXmlSerializer out) throws XmlPullParserException, java.io.IOException {
785 
786         if (val == null) {
787             out.startTag(null, "null");
788             out.endTag(null, "null");
789             return;
790         }
791 
792         out.startTag(null, "boolean-array");
793         if (name != null) {
794             out.attribute(null, "name", name);
795         }
796 
797         final int N = val.length;
798         out.attributeInt(null, "num", N);
799 
800         for (int i=0; i<N; i++) {
801             out.startTag(null, "item");
802             out.attributeBoolean(null, "value", val[i]);
803             out.endTag(null, "item");
804         }
805 
806         out.endTag(null, "boolean-array");
807     }
808 
809     @Deprecated
writeValueXml(Object v, String name, XmlSerializer out)810     public static final void writeValueXml(Object v, String name, XmlSerializer out)
811             throws XmlPullParserException, java.io.IOException {
812         writeValueXml(v, name, XmlUtils.makeTyped(out));
813     }
814 
815     /**
816      * Flatten an object's value into an XmlSerializer.  The value can later
817      * be read back with readThisValueXml().
818      *
819      * Currently supported value types are: null, String, Integer, Long,
820      * Float, Double Boolean, Map, List.
821      *
822      * @param v The object to be flattened.
823      * @param name Name attribute to include with this value's tag, or null
824      *             for none.
825      * @param out XmlSerializer to write the object into.
826      *
827      * @see #writeMapXml
828      * @see #writeListXml
829      * @see #readValueXml
830      */
writeValueXml(Object v, String name, TypedXmlSerializer out)831     public static final void writeValueXml(Object v, String name, TypedXmlSerializer out)
832             throws XmlPullParserException, java.io.IOException {
833         writeValueXml(v, name, out, null);
834     }
835 
836     /**
837      * Flatten an object's value into an XmlSerializer.  The value can later
838      * be read back with readThisValueXml().
839      *
840      * Currently supported value types are: null, String, Integer, Long,
841      * Float, Double Boolean, Map, List.
842      *
843      * @param v The object to be flattened.
844      * @param name Name attribute to include with this value's tag, or null
845      *             for none.
846      * @param out XmlSerializer to write the object into.
847      * @param callback Handler for Object types not recognized.
848      *
849      * @see #writeMapXml
850      * @see #writeListXml
851      * @see #readValueXml
852      */
writeValueXml(Object v, String name, TypedXmlSerializer out, WriteMapCallback callback)853     private static final void writeValueXml(Object v, String name, TypedXmlSerializer out,
854             WriteMapCallback callback)  throws XmlPullParserException, java.io.IOException {
855         if (v == null) {
856             out.startTag(null, "null");
857             if (name != null) {
858                 out.attribute(null, "name", name);
859             }
860             out.endTag(null, "null");
861             return;
862         } else if (v instanceof String) {
863             out.startTag(null, "string");
864             if (name != null) {
865                 out.attribute(null, "name", name);
866             }
867             out.text(v.toString());
868             out.endTag(null, "string");
869             return;
870         } else if (v instanceof Integer) {
871             out.startTag(null, "int");
872             if (name != null) {
873                 out.attribute(null, "name", name);
874             }
875             out.attributeInt(null, "value", (int) v);
876             out.endTag(null, "int");
877         } else if (v instanceof Long) {
878             out.startTag(null, "long");
879             if (name != null) {
880                 out.attribute(null, "name", name);
881             }
882             out.attributeLong(null, "value", (long) v);
883             out.endTag(null, "long");
884         } else if (v instanceof Float) {
885             out.startTag(null, "float");
886             if (name != null) {
887                 out.attribute(null, "name", name);
888             }
889             out.attributeFloat(null, "value", (float) v);
890             out.endTag(null, "float");
891         } else if (v instanceof Double) {
892             out.startTag(null, "double");
893             if (name != null) {
894                 out.attribute(null, "name", name);
895             }
896             out.attributeDouble(null, "value", (double) v);
897             out.endTag(null, "double");
898         } else if (v instanceof Boolean) {
899             out.startTag(null, "boolean");
900             if (name != null) {
901                 out.attribute(null, "name", name);
902             }
903             out.attributeBoolean(null, "value", (boolean) v);
904             out.endTag(null, "boolean");
905         } else if (v instanceof byte[]) {
906             writeByteArrayXml((byte[])v, name, out);
907             return;
908         } else if (v instanceof int[]) {
909             writeIntArrayXml((int[])v, name, out);
910             return;
911         } else if (v instanceof long[]) {
912             writeLongArrayXml((long[])v, name, out);
913             return;
914         } else if (v instanceof double[]) {
915             writeDoubleArrayXml((double[])v, name, out);
916             return;
917         } else if (v instanceof String[]) {
918             writeStringArrayXml((String[])v, name, out);
919             return;
920         } else if (v instanceof boolean[]) {
921             writeBooleanArrayXml((boolean[])v, name, out);
922             return;
923         } else if (v instanceof Map) {
924             writeMapXml((Map)v, name, out);
925             return;
926         } else if (v instanceof List) {
927             writeListXml((List) v, name, out);
928             return;
929         } else if (v instanceof Set) {
930             writeSetXml((Set) v, name, out);
931             return;
932         } else if (v instanceof CharSequence) {
933             // XXX This is to allow us to at least write something if
934             // we encounter styled text...  but it means we will drop all
935             // of the styling information. :(
936             out.startTag(null, "string");
937             if (name != null) {
938                 out.attribute(null, "name", name);
939             }
940             out.text(v.toString());
941             out.endTag(null, "string");
942             return;
943         } else if (callback != null) {
944             callback.writeUnknownObject(v, name, out);
945             return;
946         } else {
947             throw new RuntimeException("writeValueXml: unable to write value " + v);
948         }
949     }
950 
951     /**
952      * Read a HashMap from an InputStream containing XML.  The stream can
953      * previously have been written by writeMapXml().
954      *
955      * @param in The InputStream from which to read.
956      *
957      * @return HashMap The resulting map.
958      *
959      * @see #readListXml
960      * @see #readValueXml
961      * @see #readThisMapXml
962      * #see #writeMapXml
963      */
964     @SuppressWarnings("unchecked")
965     @UnsupportedAppUsage
readMapXml(InputStream in)966     public static final HashMap<String, ?> readMapXml(InputStream in)
967             throws XmlPullParserException, java.io.IOException {
968         TypedXmlPullParser parser = Xml.newFastPullParser();
969         parser.setInput(in, StandardCharsets.UTF_8.name());
970         return (HashMap<String, ?>) readValueXml(parser, new String[1]);
971     }
972 
973     /**
974      * Read an ArrayList from an InputStream containing XML.  The stream can
975      * previously have been written by writeListXml().
976      *
977      * @param in The InputStream from which to read.
978      *
979      * @return ArrayList The resulting list.
980      *
981      * @see #readMapXml
982      * @see #readValueXml
983      * @see #readThisListXml
984      * @see #writeListXml
985      */
readListXml(InputStream in)986     public static final ArrayList readListXml(InputStream in)
987             throws XmlPullParserException, java.io.IOException {
988         TypedXmlPullParser parser = Xml.newFastPullParser();
989         parser.setInput(in, StandardCharsets.UTF_8.name());
990         return (ArrayList)readValueXml(parser, new String[1]);
991     }
992 
993 
994     /**
995      * Read a HashSet from an InputStream containing XML. The stream can
996      * previously have been written by writeSetXml().
997      *
998      * @param in The InputStream from which to read.
999      *
1000      * @return HashSet The resulting set.
1001      *
1002      * @throws XmlPullParserException
1003      * @throws java.io.IOException
1004      *
1005      * @see #readValueXml
1006      * @see #readThisSetXml
1007      * @see #writeSetXml
1008      */
readSetXml(InputStream in)1009     public static final HashSet readSetXml(InputStream in)
1010             throws XmlPullParserException, java.io.IOException {
1011         TypedXmlPullParser parser = Xml.newFastPullParser();
1012         parser.setInput(in, StandardCharsets.UTF_8.name());
1013         return (HashSet) readValueXml(parser, new String[1]);
1014     }
1015 
1016     /**
1017      * Read a HashMap object from an XmlPullParser.  The XML data could
1018      * previously have been generated by writeMapXml().  The XmlPullParser
1019      * must be positioned <em>after</em> the tag that begins the map.
1020      *
1021      * @param parser The XmlPullParser from which to read the map data.
1022      * @param endTag Name of the tag that will end the map, usually "map".
1023      * @param name An array of one string, used to return the name attribute
1024      *             of the map's tag.
1025      *
1026      * @return HashMap The newly generated map.
1027      *
1028      * @see #readMapXml
1029      */
readThisMapXml(TypedXmlPullParser parser, String endTag, String[] name)1030     public static final HashMap<String, ?> readThisMapXml(TypedXmlPullParser parser, String endTag,
1031             String[] name) throws XmlPullParserException, java.io.IOException {
1032         return readThisMapXml(parser, endTag, name, null);
1033     }
1034 
1035     /**
1036      * Read a HashMap object from an XmlPullParser.  The XML data could
1037      * previously have been generated by writeMapXml().  The XmlPullParser
1038      * must be positioned <em>after</em> the tag that begins the map.
1039      *
1040      * @param parser The XmlPullParser from which to read the map data.
1041      * @param endTag Name of the tag that will end the map, usually "map".
1042      * @param name An array of one string, used to return the name attribute
1043      *             of the map's tag.
1044      *
1045      * @return HashMap The newly generated map.
1046      *
1047      * @see #readMapXml
1048      * @hide
1049      */
readThisMapXml(TypedXmlPullParser parser, String endTag, String[] name, ReadMapCallback callback)1050     public static final HashMap<String, ?> readThisMapXml(TypedXmlPullParser parser, String endTag,
1051             String[] name, ReadMapCallback callback)
1052             throws XmlPullParserException, java.io.IOException {
1053         HashMap<String, Object> map = new HashMap<String, Object>();
1054 
1055         int eventType = parser.getEventType();
1056         do {
1057             if (eventType == parser.START_TAG) {
1058                 Object val = readThisValueXml(parser, name, callback, false);
1059                 map.put(name[0], val);
1060             } else if (eventType == parser.END_TAG) {
1061                 if (parser.getName().equals(endTag)) {
1062                     return map;
1063                 }
1064                 throw new XmlPullParserException(
1065                     "Expected " + endTag + " end tag at: " + parser.getName());
1066             }
1067             eventType = parser.next();
1068         } while (eventType != parser.END_DOCUMENT);
1069 
1070         throw new XmlPullParserException(
1071             "Document ended before " + endTag + " end tag");
1072     }
1073 
1074     /**
1075      * Like {@link #readThisMapXml}, but returns an ArrayMap instead of HashMap.
1076      * @hide
1077      */
readThisArrayMapXml(TypedXmlPullParser parser, String endTag, String[] name, ReadMapCallback callback)1078     public static final ArrayMap<String, ?> readThisArrayMapXml(TypedXmlPullParser parser,
1079             String endTag, String[] name, ReadMapCallback callback)
1080             throws XmlPullParserException, java.io.IOException {
1081         ArrayMap<String, Object> map = new ArrayMap<>();
1082 
1083         int eventType = parser.getEventType();
1084         do {
1085             if (eventType == parser.START_TAG) {
1086                 Object val = readThisValueXml(parser, name, callback, true);
1087                 map.put(name[0], val);
1088             } else if (eventType == parser.END_TAG) {
1089                 if (parser.getName().equals(endTag)) {
1090                     return map;
1091                 }
1092                 throw new XmlPullParserException(
1093                     "Expected " + endTag + " end tag at: " + parser.getName());
1094             }
1095             eventType = parser.next();
1096         } while (eventType != parser.END_DOCUMENT);
1097 
1098         throw new XmlPullParserException(
1099             "Document ended before " + endTag + " end tag");
1100     }
1101 
1102     /**
1103      * Read an ArrayList object from an XmlPullParser.  The XML data could
1104      * previously have been generated by writeListXml().  The XmlPullParser
1105      * must be positioned <em>after</em> the tag that begins the list.
1106      *
1107      * @param parser The XmlPullParser from which to read the list data.
1108      * @param endTag Name of the tag that will end the list, usually "list".
1109      * @param name An array of one string, used to return the name attribute
1110      *             of the list's tag.
1111      *
1112      * @return HashMap The newly generated list.
1113      *
1114      * @see #readListXml
1115      */
readThisListXml(TypedXmlPullParser parser, String endTag, String[] name)1116     public static final ArrayList readThisListXml(TypedXmlPullParser parser, String endTag,
1117             String[] name) throws XmlPullParserException, java.io.IOException {
1118         return readThisListXml(parser, endTag, name, null, false);
1119     }
1120 
1121     /**
1122      * Read an ArrayList object from an XmlPullParser.  The XML data could
1123      * previously have been generated by writeListXml().  The XmlPullParser
1124      * must be positioned <em>after</em> the tag that begins the list.
1125      *
1126      * @param parser The XmlPullParser from which to read the list data.
1127      * @param endTag Name of the tag that will end the list, usually "list".
1128      * @param name An array of one string, used to return the name attribute
1129      *             of the list's tag.
1130      *
1131      * @return HashMap The newly generated list.
1132      *
1133      * @see #readListXml
1134      */
readThisListXml(TypedXmlPullParser parser, String endTag, String[] name, ReadMapCallback callback, boolean arrayMap)1135     private static final ArrayList readThisListXml(TypedXmlPullParser parser, String endTag,
1136             String[] name, ReadMapCallback callback, boolean arrayMap)
1137             throws XmlPullParserException, java.io.IOException {
1138         ArrayList list = new ArrayList();
1139 
1140         int eventType = parser.getEventType();
1141         do {
1142             if (eventType == parser.START_TAG) {
1143                 Object val = readThisValueXml(parser, name, callback, arrayMap);
1144                 list.add(val);
1145                 //System.out.println("Adding to list: " + val);
1146             } else if (eventType == parser.END_TAG) {
1147                 if (parser.getName().equals(endTag)) {
1148                     return list;
1149                 }
1150                 throw new XmlPullParserException(
1151                     "Expected " + endTag + " end tag at: " + parser.getName());
1152             }
1153             eventType = parser.next();
1154         } while (eventType != parser.END_DOCUMENT);
1155 
1156         throw new XmlPullParserException(
1157             "Document ended before " + endTag + " end tag");
1158     }
1159 
1160     /**
1161      * Read a HashSet object from an XmlPullParser. The XML data could previously
1162      * have been generated by writeSetXml(). The XmlPullParser must be positioned
1163      * <em>after</em> the tag that begins the set.
1164      *
1165      * @param parser The XmlPullParser from which to read the set data.
1166      * @param endTag Name of the tag that will end the set, usually "set".
1167      * @param name An array of one string, used to return the name attribute
1168      *             of the set's tag.
1169      *
1170      * @return HashSet The newly generated set.
1171      *
1172      * @throws XmlPullParserException
1173      * @throws java.io.IOException
1174      *
1175      * @see #readSetXml
1176      */
readThisSetXml(TypedXmlPullParser parser, String endTag, String[] name)1177     public static final HashSet readThisSetXml(TypedXmlPullParser parser, String endTag,
1178             String[] name) throws XmlPullParserException, java.io.IOException {
1179         return readThisSetXml(parser, endTag, name, null, false);
1180     }
1181 
1182     /**
1183      * Read a HashSet object from an XmlPullParser. The XML data could previously
1184      * have been generated by writeSetXml(). The XmlPullParser must be positioned
1185      * <em>after</em> the tag that begins the set.
1186      *
1187      * @param parser The XmlPullParser from which to read the set data.
1188      * @param endTag Name of the tag that will end the set, usually "set".
1189      * @param name An array of one string, used to return the name attribute
1190      *             of the set's tag.
1191      *
1192      * @return HashSet The newly generated set.
1193      *
1194      * @throws XmlPullParserException
1195      * @throws java.io.IOException
1196      *
1197      * @see #readSetXml
1198      * @hide
1199      */
readThisSetXml(TypedXmlPullParser parser, String endTag, String[] name, ReadMapCallback callback, boolean arrayMap)1200     private static final HashSet readThisSetXml(TypedXmlPullParser parser, String endTag,
1201             String[] name, ReadMapCallback callback, boolean arrayMap)
1202             throws XmlPullParserException, java.io.IOException {
1203         HashSet set = new HashSet();
1204 
1205         int eventType = parser.getEventType();
1206         do {
1207             if (eventType == parser.START_TAG) {
1208                 Object val = readThisValueXml(parser, name, callback, arrayMap);
1209                 set.add(val);
1210                 //System.out.println("Adding to set: " + val);
1211             } else if (eventType == parser.END_TAG) {
1212                 if (parser.getName().equals(endTag)) {
1213                     return set;
1214                 }
1215                 throw new XmlPullParserException(
1216                         "Expected " + endTag + " end tag at: " + parser.getName());
1217             }
1218             eventType = parser.next();
1219         } while (eventType != parser.END_DOCUMENT);
1220 
1221         throw new XmlPullParserException(
1222                 "Document ended before " + endTag + " end tag");
1223     }
1224 
1225     /**
1226      * Read a byte[] object from an XmlPullParser.  The XML data could
1227      * previously have been generated by writeByteArrayXml().  The XmlPullParser
1228      * must be positioned <em>after</em> the tag that begins the list.
1229      *
1230      * @param parser The XmlPullParser from which to read the list data.
1231      * @param endTag Name of the tag that will end the list, usually "list".
1232      * @param name An array of one string, used to return the name attribute
1233      *             of the list's tag.
1234      *
1235      * @return Returns a newly generated byte[].
1236      *
1237      * @see #writeByteArrayXml
1238      */
readThisByteArrayXml(TypedXmlPullParser parser, String endTag, String[] name)1239     public static final byte[] readThisByteArrayXml(TypedXmlPullParser parser,
1240             String endTag, String[] name)
1241             throws XmlPullParserException, java.io.IOException {
1242 
1243         int num = parser.getAttributeInt(null, "num");
1244 
1245         // 0 len byte array does not have a text in the XML tag. So, initialize to 0 len array.
1246         // For all other array lens, HexEncoding.decode() below overrides the array.
1247         byte[] array = new byte[0];
1248 
1249         int eventType = parser.getEventType();
1250         do {
1251             if (eventType == parser.TEXT) {
1252                 if (num > 0) {
1253                     String values = parser.getText();
1254                     if (values == null || values.length() != num * 2) {
1255                         throw new XmlPullParserException(
1256                                 "Invalid value found in byte-array: " + values);
1257                     }
1258                     array = HexEncoding.decode(values);
1259                 }
1260             } else if (eventType == parser.END_TAG) {
1261                 if (parser.getName().equals(endTag)) {
1262                     return array;
1263                 } else {
1264                     throw new XmlPullParserException(
1265                             "Expected " + endTag + " end tag at: "
1266                                     + parser.getName());
1267                 }
1268             }
1269             eventType = parser.next();
1270         } while (eventType != parser.END_DOCUMENT);
1271 
1272         throw new XmlPullParserException(
1273                 "Document ended before " + endTag + " end tag");
1274     }
1275 
1276     /**
1277      * Read an int[] object from an XmlPullParser.  The XML data could
1278      * previously have been generated by writeIntArrayXml().  The XmlPullParser
1279      * must be positioned <em>after</em> the tag that begins the list.
1280      *
1281      * @param parser The XmlPullParser from which to read the list data.
1282      * @param endTag Name of the tag that will end the list, usually "list".
1283      * @param name An array of one string, used to return the name attribute
1284      *             of the list's tag.
1285      *
1286      * @return Returns a newly generated int[].
1287      *
1288      * @see #readListXml
1289      */
readThisIntArrayXml(TypedXmlPullParser parser, String endTag, String[] name)1290     public static final int[] readThisIntArrayXml(TypedXmlPullParser parser,
1291             String endTag, String[] name)
1292             throws XmlPullParserException, java.io.IOException {
1293 
1294         int num = parser.getAttributeInt(null, "num");
1295         parser.next();
1296 
1297         int[] array = new int[num];
1298         int i = 0;
1299 
1300         int eventType = parser.getEventType();
1301         do {
1302             if (eventType == parser.START_TAG) {
1303                 if (parser.getName().equals("item")) {
1304                     array[i] = parser.getAttributeInt(null, "value");
1305                 } else {
1306                     throw new XmlPullParserException(
1307                             "Expected item tag at: " + parser.getName());
1308                 }
1309             } else if (eventType == parser.END_TAG) {
1310                 if (parser.getName().equals(endTag)) {
1311                     return array;
1312                 } else if (parser.getName().equals("item")) {
1313                     i++;
1314                 } else {
1315                     throw new XmlPullParserException(
1316                         "Expected " + endTag + " end tag at: "
1317                         + parser.getName());
1318                 }
1319             }
1320             eventType = parser.next();
1321         } while (eventType != parser.END_DOCUMENT);
1322 
1323         throw new XmlPullParserException(
1324             "Document ended before " + endTag + " end tag");
1325     }
1326 
1327     /**
1328      * Read a long[] object from an XmlPullParser.  The XML data could
1329      * previously have been generated by writeLongArrayXml().  The XmlPullParser
1330      * must be positioned <em>after</em> the tag that begins the list.
1331      *
1332      * @param parser The XmlPullParser from which to read the list data.
1333      * @param endTag Name of the tag that will end the list, usually "list".
1334      * @param name An array of one string, used to return the name attribute
1335      *             of the list's tag.
1336      *
1337      * @return Returns a newly generated long[].
1338      *
1339      * @see #readListXml
1340      */
readThisLongArrayXml(TypedXmlPullParser parser, String endTag, String[] name)1341     public static final long[] readThisLongArrayXml(TypedXmlPullParser parser,
1342             String endTag, String[] name)
1343             throws XmlPullParserException, java.io.IOException {
1344 
1345         int num = parser.getAttributeInt(null, "num");
1346         parser.next();
1347 
1348         long[] array = new long[num];
1349         int i = 0;
1350 
1351         int eventType = parser.getEventType();
1352         do {
1353             if (eventType == parser.START_TAG) {
1354                 if (parser.getName().equals("item")) {
1355                     array[i] = parser.getAttributeLong(null, "value");
1356                 } else {
1357                     throw new XmlPullParserException("Expected item tag at: " + parser.getName());
1358                 }
1359             } else if (eventType == parser.END_TAG) {
1360                 if (parser.getName().equals(endTag)) {
1361                     return array;
1362                 } else if (parser.getName().equals("item")) {
1363                     i++;
1364                 } else {
1365                     throw new XmlPullParserException("Expected " + endTag + " end tag at: " +
1366                             parser.getName());
1367                 }
1368             }
1369             eventType = parser.next();
1370         } while (eventType != parser.END_DOCUMENT);
1371 
1372         throw new XmlPullParserException("Document ended before " + endTag + " end tag");
1373     }
1374 
1375     /**
1376      * Read a double[] object from an XmlPullParser.  The XML data could
1377      * previously have been generated by writeDoubleArrayXml().  The XmlPullParser
1378      * must be positioned <em>after</em> the tag that begins the list.
1379      *
1380      * @param parser The XmlPullParser from which to read the list data.
1381      * @param endTag Name of the tag that will end the list, usually "double-array".
1382      * @param name An array of one string, used to return the name attribute
1383      *             of the list's tag.
1384      *
1385      * @return Returns a newly generated double[].
1386      *
1387      * @see #readListXml
1388      */
readThisDoubleArrayXml(TypedXmlPullParser parser, String endTag, String[] name)1389     public static final double[] readThisDoubleArrayXml(TypedXmlPullParser parser, String endTag,
1390             String[] name) throws XmlPullParserException, java.io.IOException {
1391 
1392         int num = parser.getAttributeInt(null, "num");
1393         parser.next();
1394 
1395         double[] array = new double[num];
1396         int i = 0;
1397 
1398         int eventType = parser.getEventType();
1399         do {
1400             if (eventType == parser.START_TAG) {
1401                 if (parser.getName().equals("item")) {
1402                     array[i] = parser.getAttributeDouble(null, "value");
1403                 } else {
1404                     throw new XmlPullParserException("Expected item tag at: " + parser.getName());
1405                 }
1406             } else if (eventType == parser.END_TAG) {
1407                 if (parser.getName().equals(endTag)) {
1408                     return array;
1409                 } else if (parser.getName().equals("item")) {
1410                     i++;
1411                 } else {
1412                     throw new XmlPullParserException("Expected " + endTag + " end tag at: " +
1413                             parser.getName());
1414                 }
1415             }
1416             eventType = parser.next();
1417         } while (eventType != parser.END_DOCUMENT);
1418 
1419         throw new XmlPullParserException("Document ended before " + endTag + " end tag");
1420     }
1421 
1422     /**
1423      * Read a String[] object from an XmlPullParser.  The XML data could
1424      * previously have been generated by writeStringArrayXml().  The XmlPullParser
1425      * must be positioned <em>after</em> the tag that begins the list.
1426      *
1427      * @param parser The XmlPullParser from which to read the list data.
1428      * @param endTag Name of the tag that will end the list, usually "string-array".
1429      * @param name An array of one string, used to return the name attribute
1430      *             of the list's tag.
1431      *
1432      * @return Returns a newly generated String[].
1433      *
1434      * @see #readListXml
1435      */
readThisStringArrayXml(TypedXmlPullParser parser, String endTag, String[] name)1436     public static final String[] readThisStringArrayXml(TypedXmlPullParser parser, String endTag,
1437             String[] name) throws XmlPullParserException, java.io.IOException {
1438 
1439         int num = parser.getAttributeInt(null, "num");
1440         parser.next();
1441 
1442         String[] array = new String[num];
1443         int i = 0;
1444 
1445         int eventType = parser.getEventType();
1446         do {
1447             if (eventType == parser.START_TAG) {
1448                 if (parser.getName().equals("item")) {
1449                     array[i] = parser.getAttributeValue(null, "value");
1450                 } else {
1451                     throw new XmlPullParserException("Expected item tag at: " + parser.getName());
1452                 }
1453             } else if (eventType == parser.END_TAG) {
1454                 if (parser.getName().equals(endTag)) {
1455                     return array;
1456                 } else if (parser.getName().equals("item")) {
1457                     i++;
1458                 } else {
1459                     throw new XmlPullParserException("Expected " + endTag + " end tag at: " +
1460                             parser.getName());
1461                 }
1462             }
1463             eventType = parser.next();
1464         } while (eventType != parser.END_DOCUMENT);
1465 
1466         throw new XmlPullParserException("Document ended before " + endTag + " end tag");
1467     }
1468 
1469     /**
1470      * Read a boolean[] object from an XmlPullParser.  The XML data could
1471      * previously have been generated by writeBooleanArrayXml().  The XmlPullParser
1472      * must be positioned <em>after</em> the tag that begins the list.
1473      *
1474      * @param parser The XmlPullParser from which to read the list data.
1475      * @param endTag Name of the tag that will end the list, usually "string-array".
1476      * @param name An array of one string, used to return the name attribute
1477      *             of the list's tag.
1478      *
1479      * @return Returns a newly generated boolean[].
1480      *
1481      * @see #readListXml
1482      */
readThisBooleanArrayXml(TypedXmlPullParser parser, String endTag, String[] name)1483     public static final boolean[] readThisBooleanArrayXml(TypedXmlPullParser parser, String endTag,
1484             String[] name) throws XmlPullParserException, java.io.IOException {
1485 
1486         int num = parser.getAttributeInt(null, "num");
1487         parser.next();
1488 
1489         boolean[] array = new boolean[num];
1490         int i = 0;
1491 
1492         int eventType = parser.getEventType();
1493         do {
1494             if (eventType == parser.START_TAG) {
1495                 if (parser.getName().equals("item")) {
1496                     array[i] = parser.getAttributeBoolean(null, "value");
1497                 } else {
1498                     throw new XmlPullParserException("Expected item tag at: " + parser.getName());
1499                 }
1500             } else if (eventType == parser.END_TAG) {
1501                 if (parser.getName().equals(endTag)) {
1502                     return array;
1503                 } else if (parser.getName().equals("item")) {
1504                     i++;
1505                 } else {
1506                     throw new XmlPullParserException("Expected " + endTag + " end tag at: " +
1507                             parser.getName());
1508                 }
1509             }
1510             eventType = parser.next();
1511         } while (eventType != parser.END_DOCUMENT);
1512 
1513         throw new XmlPullParserException("Document ended before " + endTag + " end tag");
1514     }
1515 
1516     /**
1517      * Read a flattened object from an XmlPullParser.  The XML data could
1518      * previously have been written with writeMapXml(), writeListXml(), or
1519      * writeValueXml().  The XmlPullParser must be positioned <em>at</em> the
1520      * tag that defines the value.
1521      *
1522      * @param parser The XmlPullParser from which to read the object.
1523      * @param name An array of one string, used to return the name attribute
1524      *             of the value's tag.
1525      *
1526      * @return Object The newly generated value object.
1527      *
1528      * @see #readMapXml
1529      * @see #readListXml
1530      * @see #writeValueXml
1531      */
readValueXml(TypedXmlPullParser parser, String[] name)1532     public static final Object readValueXml(TypedXmlPullParser parser, String[] name)
1533     throws XmlPullParserException, java.io.IOException
1534     {
1535         int eventType = parser.getEventType();
1536         do {
1537             if (eventType == parser.START_TAG) {
1538                 return readThisValueXml(parser, name, null, false);
1539             } else if (eventType == parser.END_TAG) {
1540                 throw new XmlPullParserException(
1541                     "Unexpected end tag at: " + parser.getName());
1542             } else if (eventType == parser.TEXT) {
1543                 throw new XmlPullParserException(
1544                     "Unexpected text: " + parser.getText());
1545             }
1546             eventType = parser.next();
1547         } while (eventType != parser.END_DOCUMENT);
1548 
1549         throw new XmlPullParserException(
1550             "Unexpected end of document");
1551     }
1552 
readThisValueXml(TypedXmlPullParser parser, String[] name, ReadMapCallback callback, boolean arrayMap)1553     private static final Object readThisValueXml(TypedXmlPullParser parser, String[] name,
1554             ReadMapCallback callback, boolean arrayMap)
1555             throws XmlPullParserException, java.io.IOException {
1556         final String valueName = parser.getAttributeValue(null, "name");
1557         final String tagName = parser.getName();
1558 
1559         //System.out.println("Reading this value tag: " + tagName + ", name=" + valueName);
1560 
1561         Object res;
1562 
1563         if (tagName.equals("null")) {
1564             res = null;
1565         } else if (tagName.equals("string")) {
1566             final StringBuilder value = new StringBuilder();
1567             int eventType;
1568             while ((eventType = parser.next()) != parser.END_DOCUMENT) {
1569                 if (eventType == parser.END_TAG) {
1570                     if (parser.getName().equals("string")) {
1571                         name[0] = valueName;
1572                         //System.out.println("Returning value for " + valueName + ": " + value);
1573                         return value.toString();
1574                     }
1575                     throw new XmlPullParserException(
1576                         "Unexpected end tag in <string>: " + parser.getName());
1577                 } else if (eventType == parser.TEXT) {
1578                     value.append(parser.getText());
1579                 } else if (eventType == parser.START_TAG) {
1580                     throw new XmlPullParserException(
1581                         "Unexpected start tag in <string>: " + parser.getName());
1582                 }
1583             }
1584             throw new XmlPullParserException(
1585                 "Unexpected end of document in <string>");
1586         } else if ((res = readThisPrimitiveValueXml(parser, tagName)) != null) {
1587             // all work already done by readThisPrimitiveValueXml
1588         } else if (tagName.equals("byte-array")) {
1589             res = readThisByteArrayXml(parser, "byte-array", name);
1590             name[0] = valueName;
1591             //System.out.println("Returning value for " + valueName + ": " + res);
1592             return res;
1593         } else if (tagName.equals("int-array")) {
1594             res = readThisIntArrayXml(parser, "int-array", name);
1595             name[0] = valueName;
1596             //System.out.println("Returning value for " + valueName + ": " + res);
1597             return res;
1598         } else if (tagName.equals("long-array")) {
1599             res = readThisLongArrayXml(parser, "long-array", name);
1600             name[0] = valueName;
1601             //System.out.println("Returning value for " + valueName + ": " + res);
1602             return res;
1603         } else if (tagName.equals("double-array")) {
1604             res = readThisDoubleArrayXml(parser, "double-array", name);
1605             name[0] = valueName;
1606             //System.out.println("Returning value for " + valueName + ": " + res);
1607             return res;
1608         } else if (tagName.equals("string-array")) {
1609             res = readThisStringArrayXml(parser, "string-array", name);
1610             name[0] = valueName;
1611             //System.out.println("Returning value for " + valueName + ": " + res);
1612             return res;
1613         } else if (tagName.equals("boolean-array")) {
1614             res = readThisBooleanArrayXml(parser, "boolean-array", name);
1615             name[0] = valueName;
1616             //System.out.println("Returning value for " + valueName + ": " + res);
1617             return res;
1618         } else if (tagName.equals("map")) {
1619             parser.next();
1620             res = arrayMap
1621                     ? readThisArrayMapXml(parser, "map", name, callback)
1622                     : readThisMapXml(parser, "map", name, callback);
1623             name[0] = valueName;
1624             //System.out.println("Returning value for " + valueName + ": " + res);
1625             return res;
1626         } else if (tagName.equals("list")) {
1627             parser.next();
1628             res = readThisListXml(parser, "list", name, callback, arrayMap);
1629             name[0] = valueName;
1630             //System.out.println("Returning value for " + valueName + ": " + res);
1631             return res;
1632         } else if (tagName.equals("set")) {
1633             parser.next();
1634             res = readThisSetXml(parser, "set", name, callback, arrayMap);
1635             name[0] = valueName;
1636             //System.out.println("Returning value for " + valueName + ": " + res);
1637             return res;
1638         } else if (callback != null) {
1639             res = callback.readThisUnknownObjectXml(parser, tagName);
1640             name[0] = valueName;
1641             return res;
1642         } else {
1643             throw new XmlPullParserException("Unknown tag: " + tagName);
1644         }
1645 
1646         // Skip through to end tag.
1647         int eventType;
1648         while ((eventType = parser.next()) != parser.END_DOCUMENT) {
1649             if (eventType == parser.END_TAG) {
1650                 if (parser.getName().equals(tagName)) {
1651                     name[0] = valueName;
1652                     //System.out.println("Returning value for " + valueName + ": " + res);
1653                     return res;
1654                 }
1655                 throw new XmlPullParserException(
1656                     "Unexpected end tag in <" + tagName + ">: " + parser.getName());
1657             } else if (eventType == parser.TEXT) {
1658                 throw new XmlPullParserException(
1659                 "Unexpected text in <" + tagName + ">: " + parser.getName());
1660             } else if (eventType == parser.START_TAG) {
1661                 throw new XmlPullParserException(
1662                     "Unexpected start tag in <" + tagName + ">: " + parser.getName());
1663             }
1664         }
1665         throw new XmlPullParserException(
1666             "Unexpected end of document in <" + tagName + ">");
1667     }
1668 
readThisPrimitiveValueXml(TypedXmlPullParser parser, String tagName)1669     private static final Object readThisPrimitiveValueXml(TypedXmlPullParser parser, String tagName)
1670             throws XmlPullParserException, java.io.IOException {
1671         if (tagName.equals("int")) {
1672             return parser.getAttributeInt(null, "value");
1673         } else if (tagName.equals("long")) {
1674             return parser.getAttributeLong(null, "value");
1675         } else if (tagName.equals("float")) {
1676             return parser.getAttributeFloat(null, "value");
1677         } else if (tagName.equals("double")) {
1678             return parser.getAttributeDouble(null, "value");
1679         } else if (tagName.equals("boolean")) {
1680             return parser.getAttributeBoolean(null, "value");
1681         } else {
1682             return null;
1683         }
1684     }
1685 
1686     @UnsupportedAppUsage
beginDocument(XmlPullParser parser, String firstElementName)1687     public static final void beginDocument(XmlPullParser parser, String firstElementName) throws XmlPullParserException, IOException
1688     {
1689         int type;
1690         while ((type=parser.next()) != parser.START_TAG
1691                    && type != parser.END_DOCUMENT) {
1692             ;
1693         }
1694 
1695         if (type != parser.START_TAG) {
1696             throw new XmlPullParserException("No start tag found");
1697         }
1698 
1699         if (!parser.getName().equals(firstElementName)) {
1700             throw new XmlPullParserException("Unexpected start tag: found " + parser.getName() +
1701                     ", expected " + firstElementName);
1702         }
1703     }
1704 
1705     @UnsupportedAppUsage
nextElement(XmlPullParser parser)1706     public static final void nextElement(XmlPullParser parser) throws XmlPullParserException, IOException
1707     {
1708         int type;
1709         while ((type=parser.next()) != parser.START_TAG
1710                    && type != parser.END_DOCUMENT) {
1711             ;
1712         }
1713     }
1714 
nextElementWithin(XmlPullParser parser, int outerDepth)1715     public static boolean nextElementWithin(XmlPullParser parser, int outerDepth)
1716             throws IOException, XmlPullParserException {
1717         for (;;) {
1718             int type = parser.next();
1719             if (type == XmlPullParser.END_DOCUMENT
1720                     || (type == XmlPullParser.END_TAG && parser.getDepth() == outerDepth)) {
1721                 return false;
1722             }
1723             if (type == XmlPullParser.START_TAG
1724                     && parser.getDepth() == outerDepth + 1) {
1725                 return true;
1726             }
1727         }
1728     }
1729 
1730     @SuppressWarnings("AndroidFrameworkEfficientXml")
readIntAttribute(XmlPullParser in, String name, int defaultValue)1731     public static int readIntAttribute(XmlPullParser in, String name, int defaultValue) {
1732         if (in instanceof TypedXmlPullParser) {
1733             return ((TypedXmlPullParser) in).getAttributeInt(null, name, defaultValue);
1734         }
1735         final String value = in.getAttributeValue(null, name);
1736         if (TextUtils.isEmpty(value)) {
1737             return defaultValue;
1738         }
1739         try {
1740             return Integer.parseInt(value);
1741         } catch (NumberFormatException e) {
1742             return defaultValue;
1743         }
1744     }
1745 
1746     @SuppressWarnings("AndroidFrameworkEfficientXml")
readIntAttribute(XmlPullParser in, String name)1747     public static int readIntAttribute(XmlPullParser in, String name) throws IOException {
1748         if (in instanceof TypedXmlPullParser) {
1749             try {
1750                 return ((TypedXmlPullParser) in).getAttributeInt(null, name);
1751             } catch (XmlPullParserException e) {
1752                 throw new ProtocolException(e.getMessage());
1753             }
1754         }
1755         final String value = in.getAttributeValue(null, name);
1756         try {
1757             return Integer.parseInt(value);
1758         } catch (NumberFormatException e) {
1759             throw new ProtocolException("problem parsing " + name + "=" + value + " as int");
1760         }
1761     }
1762 
1763     @SuppressWarnings("AndroidFrameworkEfficientXml")
writeIntAttribute(XmlSerializer out, String name, int value)1764     public static void writeIntAttribute(XmlSerializer out, String name, int value)
1765             throws IOException {
1766         if (out instanceof TypedXmlSerializer) {
1767             ((TypedXmlSerializer) out).attributeInt(null, name, value);
1768             return;
1769         }
1770         out.attribute(null, name, Integer.toString(value));
1771     }
1772 
1773     @SuppressWarnings("AndroidFrameworkEfficientXml")
readLongAttribute(XmlPullParser in, String name, long defaultValue)1774     public static long readLongAttribute(XmlPullParser in, String name, long defaultValue) {
1775         if (in instanceof TypedXmlPullParser) {
1776             return ((TypedXmlPullParser) in).getAttributeLong(null, name, defaultValue);
1777         }
1778         final String value = in.getAttributeValue(null, name);
1779         if (TextUtils.isEmpty(value)) {
1780             return defaultValue;
1781         }
1782         try {
1783             return Long.parseLong(value);
1784         } catch (NumberFormatException e) {
1785             return defaultValue;
1786         }
1787     }
1788 
1789     @SuppressWarnings("AndroidFrameworkEfficientXml")
readLongAttribute(XmlPullParser in, String name)1790     public static long readLongAttribute(XmlPullParser in, String name) throws IOException {
1791         if (in instanceof TypedXmlPullParser) {
1792             try {
1793                 return ((TypedXmlPullParser) in).getAttributeLong(null, name);
1794             } catch (XmlPullParserException e) {
1795                 throw new ProtocolException(e.getMessage());
1796             }
1797         }
1798         final String value = in.getAttributeValue(null, name);
1799         try {
1800             return Long.parseLong(value);
1801         } catch (NumberFormatException e) {
1802             throw new ProtocolException("problem parsing " + name + "=" + value + " as long");
1803         }
1804     }
1805 
1806     @SuppressWarnings("AndroidFrameworkEfficientXml")
writeLongAttribute(XmlSerializer out, String name, long value)1807     public static void writeLongAttribute(XmlSerializer out, String name, long value)
1808             throws IOException {
1809         if (out instanceof TypedXmlSerializer) {
1810             ((TypedXmlSerializer) out).attributeLong(null, name, value);
1811             return;
1812         }
1813         out.attribute(null, name, Long.toString(value));
1814     }
1815 
1816     @SuppressWarnings("AndroidFrameworkEfficientXml")
readFloatAttribute(XmlPullParser in, String name)1817     public static float readFloatAttribute(XmlPullParser in, String name) throws IOException {
1818         if (in instanceof TypedXmlPullParser) {
1819             try {
1820                 return ((TypedXmlPullParser) in).getAttributeFloat(null, name);
1821             } catch (XmlPullParserException e) {
1822                 throw new ProtocolException(e.getMessage());
1823             }
1824         }
1825         final String value = in.getAttributeValue(null, name);
1826         try {
1827             return Float.parseFloat(value);
1828         } catch (NumberFormatException e) {
1829             throw new ProtocolException("problem parsing " + name + "=" + value + " as long");
1830         }
1831     }
1832 
1833     @SuppressWarnings("AndroidFrameworkEfficientXml")
writeFloatAttribute(XmlSerializer out, String name, float value)1834     public static void writeFloatAttribute(XmlSerializer out, String name, float value)
1835             throws IOException {
1836         if (out instanceof TypedXmlSerializer) {
1837             ((TypedXmlSerializer) out).attributeFloat(null, name, value);
1838             return;
1839         }
1840         out.attribute(null, name, Float.toString(value));
1841     }
1842 
1843     @SuppressWarnings("AndroidFrameworkEfficientXml")
readBooleanAttribute(XmlPullParser in, String name)1844     public static boolean readBooleanAttribute(XmlPullParser in, String name) {
1845         return readBooleanAttribute(in, name, false);
1846     }
1847 
1848     @SuppressWarnings("AndroidFrameworkEfficientXml")
readBooleanAttribute(XmlPullParser in, String name, boolean defaultValue)1849     public static boolean readBooleanAttribute(XmlPullParser in, String name,
1850             boolean defaultValue) {
1851         if (in instanceof TypedXmlPullParser) {
1852             return ((TypedXmlPullParser) in).getAttributeBoolean(null, name, defaultValue);
1853         }
1854         final String value = in.getAttributeValue(null, name);
1855         if (TextUtils.isEmpty(value)) {
1856             return defaultValue;
1857         } else {
1858             return Boolean.parseBoolean(value);
1859         }
1860     }
1861 
1862     @SuppressWarnings("AndroidFrameworkEfficientXml")
writeBooleanAttribute(XmlSerializer out, String name, boolean value)1863     public static void writeBooleanAttribute(XmlSerializer out, String name, boolean value)
1864             throws IOException {
1865         if (out instanceof TypedXmlSerializer) {
1866             ((TypedXmlSerializer) out).attributeBoolean(null, name, value);
1867             return;
1868         }
1869         out.attribute(null, name, Boolean.toString(value));
1870     }
1871 
readUriAttribute(XmlPullParser in, String name)1872     public static Uri readUriAttribute(XmlPullParser in, String name) {
1873         final String value = in.getAttributeValue(null, name);
1874         return (value != null) ? Uri.parse(value) : null;
1875     }
1876 
writeUriAttribute(XmlSerializer out, String name, Uri value)1877     public static void writeUriAttribute(XmlSerializer out, String name, Uri value)
1878             throws IOException {
1879         if (value != null) {
1880             out.attribute(null, name, value.toString());
1881         }
1882     }
1883 
readStringAttribute(XmlPullParser in, String name)1884     public static String readStringAttribute(XmlPullParser in, String name) {
1885         return in.getAttributeValue(null, name);
1886     }
1887 
writeStringAttribute(XmlSerializer out, String name, CharSequence value)1888     public static void writeStringAttribute(XmlSerializer out, String name, CharSequence value)
1889             throws IOException {
1890         if (value != null) {
1891             out.attribute(null, name, value.toString());
1892         }
1893     }
1894 
1895     @SuppressWarnings("AndroidFrameworkEfficientXml")
readByteArrayAttribute(XmlPullParser in, String name)1896     public static byte[] readByteArrayAttribute(XmlPullParser in, String name) {
1897         if (in instanceof TypedXmlPullParser) {
1898             try {
1899                 return ((TypedXmlPullParser) in).getAttributeBytesBase64(null, name);
1900             } catch (XmlPullParserException e) {
1901                 return null;
1902             }
1903         }
1904         final String value = in.getAttributeValue(null, name);
1905         if (!TextUtils.isEmpty(value)) {
1906             return Base64.decode(value, Base64.DEFAULT);
1907         } else {
1908             return null;
1909         }
1910     }
1911 
1912     @SuppressWarnings("AndroidFrameworkEfficientXml")
writeByteArrayAttribute(XmlSerializer out, String name, byte[] value)1913     public static void writeByteArrayAttribute(XmlSerializer out, String name, byte[] value)
1914             throws IOException {
1915         if (value != null) {
1916             if (out instanceof TypedXmlSerializer) {
1917                 ((TypedXmlSerializer) out).attributeBytesBase64(null, name, value);
1918                 return;
1919             }
1920             out.attribute(null, name, Base64.encodeToString(value, Base64.DEFAULT));
1921         }
1922     }
1923 
readBitmapAttribute(XmlPullParser in, String name)1924     public static Bitmap readBitmapAttribute(XmlPullParser in, String name) {
1925         final byte[] value = readByteArrayAttribute(in, name);
1926         if (value != null) {
1927             return BitmapFactory.decodeByteArray(value, 0, value.length);
1928         } else {
1929             return null;
1930         }
1931     }
1932 
1933     @Deprecated
writeBitmapAttribute(XmlSerializer out, String name, Bitmap value)1934     public static void writeBitmapAttribute(XmlSerializer out, String name, Bitmap value)
1935             throws IOException {
1936         if (value != null) {
1937             final ByteArrayOutputStream os = new ByteArrayOutputStream();
1938             value.compress(CompressFormat.PNG, 90, os);
1939             writeByteArrayAttribute(out, name, os.toByteArray());
1940         }
1941     }
1942 
1943     /** @hide */
1944     public interface WriteMapCallback {
1945         /**
1946          * Called from writeMapXml when an Object type is not recognized. The implementer
1947          * must write out the entire element including start and end tags.
1948          *
1949          * @param v The object to be written out
1950          * @param name The mapping key for v. Must be written into the "name" attribute of the
1951          *             start tag.
1952          * @param out The XML output stream.
1953          * @throws XmlPullParserException on unrecognized Object type.
1954          * @throws IOException on XmlSerializer serialization errors.
1955          * @hide
1956          */
writeUnknownObject(Object v, String name, TypedXmlSerializer out)1957          public void writeUnknownObject(Object v, String name, TypedXmlSerializer out)
1958                  throws XmlPullParserException, IOException;
1959     }
1960 
1961     /** @hide */
1962     public interface ReadMapCallback {
1963         /**
1964          * Called from readThisMapXml when a START_TAG is not recognized. The input stream
1965          * is positioned within the start tag so that attributes can be read using in.getAttribute.
1966          *
1967          * @param in the XML input stream
1968          * @param tag the START_TAG that was not recognized.
1969          * @return the Object parsed from the stream which will be put into the map.
1970          * @throws XmlPullParserException if the START_TAG is not recognized.
1971          * @throws IOException on XmlPullParser serialization errors.
1972          * @hide
1973          */
readThisUnknownObjectXml(TypedXmlPullParser in, String tag)1974         public Object readThisUnknownObjectXml(TypedXmlPullParser in, String tag)
1975                 throws XmlPullParserException, IOException;
1976     }
1977 }
1978