BaseBundle.java revision 021b57ab8df0927aa1f78a2f3bb01d5e70594b1a
1/*
2 * Copyright (C) 2014 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
17package android.os;
18
19import android.annotation.NonNull;
20import android.annotation.Nullable;
21import android.util.ArrayMap;
22import android.util.Log;
23import android.util.MathUtils;
24import android.util.Slog;
25import android.util.SparseArray;
26
27import com.android.internal.annotations.VisibleForTesting;
28import com.android.internal.util.IndentingPrintWriter;
29
30import java.io.Serializable;
31import java.util.ArrayList;
32import java.util.Set;
33
34/**
35 * A mapping from String keys to values of various types. In most cases, you
36 * should work directly with either the {@link Bundle} or
37 * {@link PersistableBundle} subclass.
38 */
39public class BaseBundle {
40    private static final String TAG = "Bundle";
41    static final boolean DEBUG = false;
42
43    // Keep in sync with frameworks/native/libs/binder/PersistableBundle.cpp.
44    static final int BUNDLE_MAGIC = 0x4C444E42; // 'B' 'N' 'D' 'L'
45
46    /**
47     * Flag indicating that this Bundle is okay to "defuse." That is, it's okay
48     * for system processes to ignore any {@link BadParcelableException}
49     * encountered when unparceling it, leaving an empty bundle in its place.
50     * <p>
51     * This should <em>only</em> be set when the Bundle reaches its final
52     * destination, otherwise a system process may clobber contents that were
53     * destined for an app that could have unparceled them.
54     */
55    static final int FLAG_DEFUSABLE = 1 << 0;
56
57    private static final boolean LOG_DEFUSABLE = false;
58
59    private static volatile boolean sShouldDefuse = false;
60
61    /**
62     * Set global variable indicating that any Bundles parsed in this process
63     * should be "defused." That is, any {@link BadParcelableException}
64     * encountered will be suppressed and logged, leaving an empty Bundle
65     * instead of crashing.
66     *
67     * @hide
68     */
69    public static void setShouldDefuse(boolean shouldDefuse) {
70        sShouldDefuse = shouldDefuse;
71    }
72
73    // A parcel cannot be obtained during compile-time initialization. Put the
74    // empty parcel into an inner class that can be initialized separately. This
75    // allows to initialize BaseBundle, and classes depending on it.
76    /** {@hide} */
77    static final class NoImagePreloadHolder {
78        public static final Parcel EMPTY_PARCEL = Parcel.obtain();
79    }
80
81    // Invariant - exactly one of mMap / mParcelledData will be null
82    // (except inside a call to unparcel)
83
84    ArrayMap<String, Object> mMap = null;
85
86    /*
87     * If mParcelledData is non-null, then mMap will be null and the
88     * data are stored as a Parcel containing a Bundle.  When the data
89     * are unparcelled, mParcelledData willbe set to null.
90     */
91    Parcel mParcelledData = null;
92
93    /**
94     * The ClassLoader used when unparcelling data from mParcelledData.
95     */
96    private ClassLoader mClassLoader;
97
98    /** {@hide} */
99    @VisibleForTesting
100    public int mFlags;
101
102    /**
103     * Constructs a new, empty Bundle that uses a specific ClassLoader for
104     * instantiating Parcelable and Serializable objects.
105     *
106     * @param loader An explicit ClassLoader to use when instantiating objects
107     * inside of the Bundle.
108     * @param capacity Initial size of the ArrayMap.
109     */
110    BaseBundle(@Nullable ClassLoader loader, int capacity) {
111        mMap = capacity > 0 ?
112                new ArrayMap<String, Object>(capacity) : new ArrayMap<String, Object>();
113        mClassLoader = loader == null ? getClass().getClassLoader() : loader;
114    }
115
116    /**
117     * Constructs a new, empty Bundle.
118     */
119    BaseBundle() {
120        this((ClassLoader) null, 0);
121    }
122
123    /**
124     * Constructs a Bundle whose data is stored as a Parcel.  The data
125     * will be unparcelled on first contact, using the assigned ClassLoader.
126     *
127     * @param parcelledData a Parcel containing a Bundle
128     */
129    BaseBundle(Parcel parcelledData) {
130        readFromParcelInner(parcelledData);
131    }
132
133    BaseBundle(Parcel parcelledData, int length) {
134        readFromParcelInner(parcelledData, length);
135    }
136
137    /**
138     * Constructs a new, empty Bundle that uses a specific ClassLoader for
139     * instantiating Parcelable and Serializable objects.
140     *
141     * @param loader An explicit ClassLoader to use when instantiating objects
142     * inside of the Bundle.
143     */
144    BaseBundle(ClassLoader loader) {
145        this(loader, 0);
146    }
147
148    /**
149     * Constructs a new, empty Bundle sized to hold the given number of
150     * elements. The Bundle will grow as needed.
151     *
152     * @param capacity the initial capacity of the Bundle
153     */
154    BaseBundle(int capacity) {
155        this((ClassLoader) null, capacity);
156    }
157
158    /**
159     * Constructs a Bundle containing a copy of the mappings from the given
160     * Bundle.
161     *
162     * @param b a Bundle to be copied.
163     */
164    BaseBundle(BaseBundle b) {
165        copyInternal(b, false);
166    }
167
168    /**
169     * Special constructor that does not initialize the bundle.
170     */
171    BaseBundle(boolean doInit) {
172    }
173
174    /**
175     * TODO: optimize this later (getting just the value part of a Bundle
176     * with a single pair) once Bundle.forPair() above is implemented
177     * with a special single-value Map implementation/serialization.
178     *
179     * Note: value in single-pair Bundle may be null.
180     *
181     * @hide
182     */
183    public String getPairValue() {
184        unparcel();
185        int size = mMap.size();
186        if (size > 1) {
187            Log.w(TAG, "getPairValue() used on Bundle with multiple pairs.");
188        }
189        if (size == 0) {
190            return null;
191        }
192        Object o = mMap.valueAt(0);
193        try {
194            return (String) o;
195        } catch (ClassCastException e) {
196            typeWarning("getPairValue()", o, "String", e);
197            return null;
198        }
199    }
200
201    /**
202     * Changes the ClassLoader this Bundle uses when instantiating objects.
203     *
204     * @param loader An explicit ClassLoader to use when instantiating objects
205     * inside of the Bundle.
206     */
207    void setClassLoader(ClassLoader loader) {
208        mClassLoader = loader;
209    }
210
211    /**
212     * Return the ClassLoader currently associated with this Bundle.
213     */
214    ClassLoader getClassLoader() {
215        return mClassLoader;
216    }
217
218    /**
219     * If the underlying data are stored as a Parcel, unparcel them
220     * using the currently assigned class loader.
221     */
222    /* package */ void unparcel() {
223        synchronized (this) {
224            final Parcel source = mParcelledData;
225            if (source != null) {
226                initializeFromParcelLocked(source, /*recycleParcel=*/ true);
227            } else {
228                if (DEBUG) {
229                    Log.d(TAG, "unparcel "
230                            + Integer.toHexString(System.identityHashCode(this))
231                            + ": no parcelled data");
232                }
233            }
234        }
235    }
236
237    private void initializeFromParcelLocked(@NonNull Parcel parcelledData, boolean recycleParcel) {
238        if (LOG_DEFUSABLE && sShouldDefuse && (mFlags & FLAG_DEFUSABLE) == 0) {
239            Slog.wtf(TAG, "Attempting to unparcel a Bundle while in transit; this may "
240                    + "clobber all data inside!", new Throwable());
241        }
242
243        if (isEmptyParcel(parcelledData)) {
244            if (DEBUG) {
245                Log.d(TAG, "unparcel "
246                        + Integer.toHexString(System.identityHashCode(this)) + ": empty");
247            }
248            if (mMap == null) {
249                mMap = new ArrayMap<>(1);
250            } else {
251                mMap.erase();
252            }
253            mParcelledData = null;
254            return;
255        }
256
257        final int count = parcelledData.readInt();
258        if (DEBUG) {
259            Log.d(TAG, "unparcel " + Integer.toHexString(System.identityHashCode(this))
260                    + ": reading " + count + " maps");
261        }
262        if (count < 0) {
263            return;
264        }
265        ArrayMap<String, Object> map = mMap;
266        if (map == null) {
267            map = new ArrayMap<>(count);
268        } else {
269            map.erase();
270            map.ensureCapacity(count);
271        }
272        try {
273            parcelledData.readArrayMapInternal(map, count, mClassLoader);
274        } catch (BadParcelableException e) {
275            if (sShouldDefuse) {
276                Log.w(TAG, "Failed to parse Bundle, but defusing quietly", e);
277                map.erase();
278            } else {
279                throw e;
280            }
281        } finally {
282            mMap = map;
283            if (recycleParcel) {
284                recycleParcel(parcelledData);
285            }
286            mParcelledData = null;
287        }
288        if (DEBUG) {
289            Log.d(TAG, "unparcel " + Integer.toHexString(System.identityHashCode(this))
290                    + " final map: " + mMap);
291        }
292    }
293
294    /**
295     * @hide
296     */
297    public boolean isParcelled() {
298        return mParcelledData != null;
299    }
300
301    /**
302     * @hide
303     */
304    public boolean isEmptyParcel() {
305        return isEmptyParcel(mParcelledData);
306    }
307
308    /**
309     * @hide
310     */
311    private static boolean isEmptyParcel(Parcel p) {
312        return p == NoImagePreloadHolder.EMPTY_PARCEL;
313    }
314
315    private static void recycleParcel(Parcel p) {
316        if (p != null && !isEmptyParcel(p)) {
317            p.recycle();
318        }
319    }
320
321    /** @hide */
322    ArrayMap<String, Object> getMap() {
323        unparcel();
324        return mMap;
325    }
326
327    /**
328     * Returns the number of mappings contained in this Bundle.
329     *
330     * @return the number of mappings as an int.
331     */
332    public int size() {
333        unparcel();
334        return mMap.size();
335    }
336
337    /**
338     * Returns true if the mapping of this Bundle is empty, false otherwise.
339     */
340    public boolean isEmpty() {
341        unparcel();
342        return mMap.isEmpty();
343    }
344
345    /**
346     * @hide this should probably be the implementation of isEmpty().  To do that we
347     * need to ensure we always use the special empty parcel form when the bundle is
348     * empty.  (This may already be the case, but to be safe we'll do this later when
349     * we aren't trying to stabilize.)
350     */
351    public boolean maybeIsEmpty() {
352        if (isParcelled()) {
353            return isEmptyParcel();
354        } else {
355            return isEmpty();
356        }
357    }
358
359    /**
360     * Does a loose equality check between two given {@link BaseBundle} objects.
361     * Returns {@code true} if both are {@code null}, or if both are equal as per
362     * {@link #kindofEquals(BaseBundle)}
363     *
364     * @param a A {@link BaseBundle} object
365     * @param b Another {@link BaseBundle} to compare with a
366     * @return {@code true} if both are the same, {@code false} otherwise
367     *
368     * @see #kindofEquals(BaseBundle)
369     *
370     * @hide
371     */
372    public static boolean kindofEquals(BaseBundle a, BaseBundle b) {
373        return (a == b) || (a != null && a.kindofEquals(b));
374    }
375
376    /**
377     * @hide This kind-of does an equality comparison.  Kind-of.
378     */
379    public boolean kindofEquals(BaseBundle other) {
380        if (other == null) {
381            return false;
382        }
383        if (isParcelled() != other.isParcelled()) {
384            // Big kind-of here!
385            return false;
386        } else if (isParcelled()) {
387            return mParcelledData.compareData(other.mParcelledData) == 0;
388        } else {
389            return mMap.equals(other.mMap);
390        }
391    }
392
393    /**
394     * Removes all elements from the mapping of this Bundle.
395     */
396    public void clear() {
397        unparcel();
398        mMap.clear();
399    }
400
401    void copyInternal(BaseBundle from, boolean deep) {
402        synchronized (from) {
403            if (from.mParcelledData != null) {
404                if (from.isEmptyParcel()) {
405                    mParcelledData = NoImagePreloadHolder.EMPTY_PARCEL;
406                } else {
407                    mParcelledData = Parcel.obtain();
408                    mParcelledData.appendFrom(from.mParcelledData, 0,
409                            from.mParcelledData.dataSize());
410                    mParcelledData.setDataPosition(0);
411                }
412            } else {
413                mParcelledData = null;
414            }
415
416            if (from.mMap != null) {
417                if (!deep) {
418                    mMap = new ArrayMap<>(from.mMap);
419                } else {
420                    final ArrayMap<String, Object> fromMap = from.mMap;
421                    final int N = fromMap.size();
422                    mMap = new ArrayMap<>(N);
423                    for (int i = 0; i < N; i++) {
424                        mMap.append(fromMap.keyAt(i), deepCopyValue(fromMap.valueAt(i)));
425                    }
426                }
427            } else {
428                mMap = null;
429            }
430
431            mClassLoader = from.mClassLoader;
432        }
433    }
434
435    Object deepCopyValue(Object value) {
436        if (value == null) {
437            return null;
438        }
439        if (value instanceof Bundle) {
440            return ((Bundle)value).deepCopy();
441        } else if (value instanceof PersistableBundle) {
442            return ((PersistableBundle)value).deepCopy();
443        } else if (value instanceof ArrayList) {
444            return deepcopyArrayList((ArrayList) value);
445        } else if (value.getClass().isArray()) {
446            if (value instanceof int[]) {
447                return ((int[])value).clone();
448            } else if (value instanceof long[]) {
449                return ((long[])value).clone();
450            } else if (value instanceof float[]) {
451                return ((float[])value).clone();
452            } else if (value instanceof double[]) {
453                return ((double[])value).clone();
454            } else if (value instanceof Object[]) {
455                return ((Object[])value).clone();
456            } else if (value instanceof byte[]) {
457                return ((byte[])value).clone();
458            } else if (value instanceof short[]) {
459                return ((short[])value).clone();
460            } else if (value instanceof char[]) {
461                return ((char[]) value).clone();
462            }
463        }
464        return value;
465    }
466
467    ArrayList deepcopyArrayList(ArrayList from) {
468        final int N = from.size();
469        ArrayList out = new ArrayList(N);
470        for (int i=0; i<N; i++) {
471            out.add(deepCopyValue(from.get(i)));
472        }
473        return out;
474    }
475
476    /**
477     * Returns true if the given key is contained in the mapping
478     * of this Bundle.
479     *
480     * @param key a String key
481     * @return true if the key is part of the mapping, false otherwise
482     */
483    public boolean containsKey(String key) {
484        unparcel();
485        return mMap.containsKey(key);
486    }
487
488    /**
489     * Returns the entry with the given key as an object.
490     *
491     * @param key a String key
492     * @return an Object, or null
493     */
494    @Nullable
495    public Object get(String key) {
496        unparcel();
497        return mMap.get(key);
498    }
499
500    /**
501     * Removes any entry with the given key from the mapping of this Bundle.
502     *
503     * @param key a String key
504     */
505    public void remove(String key) {
506        unparcel();
507        mMap.remove(key);
508    }
509
510    /**
511     * Inserts all mappings from the given PersistableBundle into this BaseBundle.
512     *
513     * @param bundle a PersistableBundle
514     */
515    public void putAll(PersistableBundle bundle) {
516        unparcel();
517        bundle.unparcel();
518        mMap.putAll(bundle.mMap);
519    }
520
521    /**
522     * Inserts all mappings from the given Map into this BaseBundle.
523     *
524     * @param map a Map
525     */
526    void putAll(ArrayMap map) {
527        unparcel();
528        mMap.putAll(map);
529    }
530
531    /**
532     * Returns a Set containing the Strings used as keys in this Bundle.
533     *
534     * @return a Set of String keys
535     */
536    public Set<String> keySet() {
537        unparcel();
538        return mMap.keySet();
539    }
540
541    /**
542     * Inserts a Boolean value into the mapping of this Bundle, replacing
543     * any existing value for the given key.  Either key or value may be null.
544     *
545     * @param key a String, or null
546     * @param value a boolean
547     */
548    public void putBoolean(@Nullable String key, boolean value) {
549        unparcel();
550        mMap.put(key, value);
551    }
552
553    /**
554     * Inserts a byte value into the mapping of this Bundle, replacing
555     * any existing value for the given key.
556     *
557     * @param key a String, or null
558     * @param value a byte
559     */
560    void putByte(@Nullable String key, byte value) {
561        unparcel();
562        mMap.put(key, value);
563    }
564
565    /**
566     * Inserts a char value into the mapping of this Bundle, replacing
567     * any existing value for the given key.
568     *
569     * @param key a String, or null
570     * @param value a char
571     */
572    void putChar(@Nullable String key, char value) {
573        unparcel();
574        mMap.put(key, value);
575    }
576
577    /**
578     * Inserts a short value into the mapping of this Bundle, replacing
579     * any existing value for the given key.
580     *
581     * @param key a String, or null
582     * @param value a short
583     */
584    void putShort(@Nullable String key, short value) {
585        unparcel();
586        mMap.put(key, value);
587    }
588
589    /**
590     * Inserts an int value into the mapping of this Bundle, replacing
591     * any existing value for the given key.
592     *
593     * @param key a String, or null
594     * @param value an int
595     */
596    public void putInt(@Nullable String key, int value) {
597        unparcel();
598        mMap.put(key, value);
599    }
600
601    /**
602     * Inserts a long value into the mapping of this Bundle, replacing
603     * any existing value for the given key.
604     *
605     * @param key a String, or null
606     * @param value a long
607     */
608    public void putLong(@Nullable String key, long value) {
609        unparcel();
610        mMap.put(key, value);
611    }
612
613    /**
614     * Inserts a float value into the mapping of this Bundle, replacing
615     * any existing value for the given key.
616     *
617     * @param key a String, or null
618     * @param value a float
619     */
620    void putFloat(@Nullable String key, float value) {
621        unparcel();
622        mMap.put(key, value);
623    }
624
625    /**
626     * Inserts a double value into the mapping of this Bundle, replacing
627     * any existing value for the given key.
628     *
629     * @param key a String, or null
630     * @param value a double
631     */
632    public void putDouble(@Nullable String key, double value) {
633        unparcel();
634        mMap.put(key, value);
635    }
636
637    /**
638     * Inserts a String value into the mapping of this Bundle, replacing
639     * any existing value for the given key.  Either key or value may be null.
640     *
641     * @param key a String, or null
642     * @param value a String, or null
643     */
644    public void putString(@Nullable String key, @Nullable String value) {
645        unparcel();
646        mMap.put(key, value);
647    }
648
649    /**
650     * Inserts a CharSequence value into the mapping of this Bundle, replacing
651     * any existing value for the given key.  Either key or value may be null.
652     *
653     * @param key a String, or null
654     * @param value a CharSequence, or null
655     */
656    void putCharSequence(@Nullable String key, @Nullable CharSequence value) {
657        unparcel();
658        mMap.put(key, value);
659    }
660
661    /**
662     * Inserts an ArrayList<Integer> value into the mapping of this Bundle, replacing
663     * any existing value for the given key.  Either key or value may be null.
664     *
665     * @param key a String, or null
666     * @param value an ArrayList<Integer> object, or null
667     */
668    void putIntegerArrayList(@Nullable String key, @Nullable ArrayList<Integer> value) {
669        unparcel();
670        mMap.put(key, value);
671    }
672
673    /**
674     * Inserts an ArrayList<String> value into the mapping of this Bundle, replacing
675     * any existing value for the given key.  Either key or value may be null.
676     *
677     * @param key a String, or null
678     * @param value an ArrayList<String> object, or null
679     */
680    void putStringArrayList(@Nullable String key, @Nullable ArrayList<String> value) {
681        unparcel();
682        mMap.put(key, value);
683    }
684
685    /**
686     * Inserts an ArrayList<CharSequence> value into the mapping of this Bundle, replacing
687     * any existing value for the given key.  Either key or value may be null.
688     *
689     * @param key a String, or null
690     * @param value an ArrayList<CharSequence> object, or null
691     */
692    void putCharSequenceArrayList(@Nullable String key, @Nullable ArrayList<CharSequence> value) {
693        unparcel();
694        mMap.put(key, value);
695    }
696
697    /**
698     * Inserts a Serializable value into the mapping of this Bundle, replacing
699     * any existing value for the given key.  Either key or value may be null.
700     *
701     * @param key a String, or null
702     * @param value a Serializable object, or null
703     */
704    void putSerializable(@Nullable String key, @Nullable Serializable value) {
705        unparcel();
706        mMap.put(key, value);
707    }
708
709    /**
710     * Inserts a boolean array value into the mapping of this Bundle, replacing
711     * any existing value for the given key.  Either key or value may be null.
712     *
713     * @param key a String, or null
714     * @param value a boolean array object, or null
715     */
716    public void putBooleanArray(@Nullable String key, @Nullable boolean[] value) {
717        unparcel();
718        mMap.put(key, value);
719    }
720
721    /**
722     * Inserts a byte array value into the mapping of this Bundle, replacing
723     * any existing value for the given key.  Either key or value may be null.
724     *
725     * @param key a String, or null
726     * @param value a byte array object, or null
727     */
728    void putByteArray(@Nullable String key, @Nullable byte[] value) {
729        unparcel();
730        mMap.put(key, value);
731    }
732
733    /**
734     * Inserts a short array value into the mapping of this Bundle, replacing
735     * any existing value for the given key.  Either key or value may be null.
736     *
737     * @param key a String, or null
738     * @param value a short array object, or null
739     */
740    void putShortArray(@Nullable String key, @Nullable short[] value) {
741        unparcel();
742        mMap.put(key, value);
743    }
744
745    /**
746     * Inserts a char array value into the mapping of this Bundle, replacing
747     * any existing value for the given key.  Either key or value may be null.
748     *
749     * @param key a String, or null
750     * @param value a char array object, or null
751     */
752    void putCharArray(@Nullable String key, @Nullable char[] value) {
753        unparcel();
754        mMap.put(key, value);
755    }
756
757    /**
758     * Inserts an int array value into the mapping of this Bundle, replacing
759     * any existing value for the given key.  Either key or value may be null.
760     *
761     * @param key a String, or null
762     * @param value an int array object, or null
763     */
764    public void putIntArray(@Nullable String key, @Nullable int[] value) {
765        unparcel();
766        mMap.put(key, value);
767    }
768
769    /**
770     * Inserts a long array value into the mapping of this Bundle, replacing
771     * any existing value for the given key.  Either key or value may be null.
772     *
773     * @param key a String, or null
774     * @param value a long array object, or null
775     */
776    public void putLongArray(@Nullable String key, @Nullable long[] value) {
777        unparcel();
778        mMap.put(key, value);
779    }
780
781    /**
782     * Inserts a float array value into the mapping of this Bundle, replacing
783     * any existing value for the given key.  Either key or value may be null.
784     *
785     * @param key a String, or null
786     * @param value a float array object, or null
787     */
788    void putFloatArray(@Nullable String key, @Nullable float[] value) {
789        unparcel();
790        mMap.put(key, value);
791    }
792
793    /**
794     * Inserts a double array value into the mapping of this Bundle, replacing
795     * any existing value for the given key.  Either key or value may be null.
796     *
797     * @param key a String, or null
798     * @param value a double array object, or null
799     */
800    public void putDoubleArray(@Nullable String key, @Nullable double[] value) {
801        unparcel();
802        mMap.put(key, value);
803    }
804
805    /**
806     * Inserts a String array value into the mapping of this Bundle, replacing
807     * any existing value for the given key.  Either key or value may be null.
808     *
809     * @param key a String, or null
810     * @param value a String array object, or null
811     */
812    public void putStringArray(@Nullable String key, @Nullable String[] value) {
813        unparcel();
814        mMap.put(key, value);
815    }
816
817    /**
818     * Inserts a CharSequence array value into the mapping of this Bundle, replacing
819     * any existing value for the given key.  Either key or value may be null.
820     *
821     * @param key a String, or null
822     * @param value a CharSequence array object, or null
823     */
824    void putCharSequenceArray(@Nullable String key, @Nullable CharSequence[] value) {
825        unparcel();
826        mMap.put(key, value);
827    }
828
829    /**
830     * Returns the value associated with the given key, or false if
831     * no mapping of the desired type exists for the given key.
832     *
833     * @param key a String
834     * @return a boolean value
835     */
836    public boolean getBoolean(String key) {
837        unparcel();
838        if (DEBUG) Log.d(TAG, "Getting boolean in "
839                + Integer.toHexString(System.identityHashCode(this)));
840        return getBoolean(key, false);
841    }
842
843    // Log a message if the value was non-null but not of the expected type
844    void typeWarning(String key, Object value, String className,
845            Object defaultValue, ClassCastException e) {
846        StringBuilder sb = new StringBuilder();
847        sb.append("Key ");
848        sb.append(key);
849        sb.append(" expected ");
850        sb.append(className);
851        sb.append(" but value was a ");
852        sb.append(value.getClass().getName());
853        sb.append(".  The default value ");
854        sb.append(defaultValue);
855        sb.append(" was returned.");
856        Log.w(TAG, sb.toString());
857        Log.w(TAG, "Attempt to cast generated internal exception:", e);
858    }
859
860    void typeWarning(String key, Object value, String className,
861            ClassCastException e) {
862        typeWarning(key, value, className, "<null>", e);
863    }
864
865    /**
866     * Returns the value associated with the given key, or defaultValue if
867     * no mapping of the desired type exists for the given key.
868     *
869     * @param key a String
870     * @param defaultValue Value to return if key does not exist
871     * @return a boolean value
872     */
873    public boolean getBoolean(String key, boolean defaultValue) {
874        unparcel();
875        Object o = mMap.get(key);
876        if (o == null) {
877            return defaultValue;
878        }
879        try {
880            return (Boolean) o;
881        } catch (ClassCastException e) {
882            typeWarning(key, o, "Boolean", defaultValue, e);
883            return defaultValue;
884        }
885    }
886
887    /**
888     * Returns the value associated with the given key, or (byte) 0 if
889     * no mapping of the desired type exists for the given key.
890     *
891     * @param key a String
892     * @return a byte value
893     */
894    byte getByte(String key) {
895        unparcel();
896        return getByte(key, (byte) 0);
897    }
898
899    /**
900     * Returns the value associated with the given key, or defaultValue if
901     * no mapping of the desired type exists for the given key.
902     *
903     * @param key a String
904     * @param defaultValue Value to return if key does not exist
905     * @return a byte value
906     */
907    Byte getByte(String key, byte defaultValue) {
908        unparcel();
909        Object o = mMap.get(key);
910        if (o == null) {
911            return defaultValue;
912        }
913        try {
914            return (Byte) o;
915        } catch (ClassCastException e) {
916            typeWarning(key, o, "Byte", defaultValue, e);
917            return defaultValue;
918        }
919    }
920
921    /**
922     * Returns the value associated with the given key, or (char) 0 if
923     * no mapping of the desired type exists for the given key.
924     *
925     * @param key a String
926     * @return a char value
927     */
928    char getChar(String key) {
929        unparcel();
930        return getChar(key, (char) 0);
931    }
932
933    /**
934     * Returns the value associated with the given key, or defaultValue if
935     * no mapping of the desired type exists for the given key.
936     *
937     * @param key a String
938     * @param defaultValue Value to return if key does not exist
939     * @return a char value
940     */
941    char getChar(String key, char defaultValue) {
942        unparcel();
943        Object o = mMap.get(key);
944        if (o == null) {
945            return defaultValue;
946        }
947        try {
948            return (Character) o;
949        } catch (ClassCastException e) {
950            typeWarning(key, o, "Character", defaultValue, e);
951            return defaultValue;
952        }
953    }
954
955    /**
956     * Returns the value associated with the given key, or (short) 0 if
957     * no mapping of the desired type exists for the given key.
958     *
959     * @param key a String
960     * @return a short value
961     */
962    short getShort(String key) {
963        unparcel();
964        return getShort(key, (short) 0);
965    }
966
967    /**
968     * Returns the value associated with the given key, or defaultValue if
969     * no mapping of the desired type exists for the given key.
970     *
971     * @param key a String
972     * @param defaultValue Value to return if key does not exist
973     * @return a short value
974     */
975    short getShort(String key, short defaultValue) {
976        unparcel();
977        Object o = mMap.get(key);
978        if (o == null) {
979            return defaultValue;
980        }
981        try {
982            return (Short) o;
983        } catch (ClassCastException e) {
984            typeWarning(key, o, "Short", defaultValue, e);
985            return defaultValue;
986        }
987    }
988
989    /**
990     * Returns the value associated with the given key, or 0 if
991     * no mapping of the desired type exists for the given key.
992     *
993     * @param key a String
994     * @return an int value
995     */
996    public int getInt(String key) {
997        unparcel();
998        return getInt(key, 0);
999    }
1000
1001    /**
1002     * Returns the value associated with the given key, or defaultValue if
1003     * no mapping of the desired type exists for the given key.
1004     *
1005     * @param key a String
1006     * @param defaultValue Value to return if key does not exist
1007     * @return an int value
1008     */
1009   public int getInt(String key, int defaultValue) {
1010        unparcel();
1011        Object o = mMap.get(key);
1012        if (o == null) {
1013            return defaultValue;
1014        }
1015        try {
1016            return (Integer) o;
1017        } catch (ClassCastException e) {
1018            typeWarning(key, o, "Integer", defaultValue, e);
1019            return defaultValue;
1020        }
1021    }
1022
1023    /**
1024     * Returns the value associated with the given key, or 0L if
1025     * no mapping of the desired type exists for the given key.
1026     *
1027     * @param key a String
1028     * @return a long value
1029     */
1030    public long getLong(String key) {
1031        unparcel();
1032        return getLong(key, 0L);
1033    }
1034
1035    /**
1036     * Returns the value associated with the given key, or defaultValue if
1037     * no mapping of the desired type exists for the given key.
1038     *
1039     * @param key a String
1040     * @param defaultValue Value to return if key does not exist
1041     * @return a long value
1042     */
1043    public long getLong(String key, long defaultValue) {
1044        unparcel();
1045        Object o = mMap.get(key);
1046        if (o == null) {
1047            return defaultValue;
1048        }
1049        try {
1050            return (Long) o;
1051        } catch (ClassCastException e) {
1052            typeWarning(key, o, "Long", defaultValue, e);
1053            return defaultValue;
1054        }
1055    }
1056
1057    /**
1058     * Returns the value associated with the given key, or 0.0f if
1059     * no mapping of the desired type exists for the given key.
1060     *
1061     * @param key a String
1062     * @return a float value
1063     */
1064    float getFloat(String key) {
1065        unparcel();
1066        return getFloat(key, 0.0f);
1067    }
1068
1069    /**
1070     * Returns the value associated with the given key, or defaultValue if
1071     * no mapping of the desired type exists for the given key.
1072     *
1073     * @param key a String
1074     * @param defaultValue Value to return if key does not exist
1075     * @return a float value
1076     */
1077    float getFloat(String key, float defaultValue) {
1078        unparcel();
1079        Object o = mMap.get(key);
1080        if (o == null) {
1081            return defaultValue;
1082        }
1083        try {
1084            return (Float) o;
1085        } catch (ClassCastException e) {
1086            typeWarning(key, o, "Float", defaultValue, e);
1087            return defaultValue;
1088        }
1089    }
1090
1091    /**
1092     * Returns the value associated with the given key, or 0.0 if
1093     * no mapping of the desired type exists for the given key.
1094     *
1095     * @param key a String
1096     * @return a double value
1097     */
1098    public double getDouble(String key) {
1099        unparcel();
1100        return getDouble(key, 0.0);
1101    }
1102
1103    /**
1104     * Returns the value associated with the given key, or defaultValue if
1105     * no mapping of the desired type exists for the given key.
1106     *
1107     * @param key a String
1108     * @param defaultValue Value to return if key does not exist
1109     * @return a double value
1110     */
1111    public double getDouble(String key, double defaultValue) {
1112        unparcel();
1113        Object o = mMap.get(key);
1114        if (o == null) {
1115            return defaultValue;
1116        }
1117        try {
1118            return (Double) o;
1119        } catch (ClassCastException e) {
1120            typeWarning(key, o, "Double", defaultValue, e);
1121            return defaultValue;
1122        }
1123    }
1124
1125    /**
1126     * Returns the value associated with the given key, or null if
1127     * no mapping of the desired type exists for the given key or a null
1128     * value is explicitly associated with the key.
1129     *
1130     * @param key a String, or null
1131     * @return a String value, or null
1132     */
1133    @Nullable
1134    public String getString(@Nullable String key) {
1135        unparcel();
1136        final Object o = mMap.get(key);
1137        try {
1138            return (String) o;
1139        } catch (ClassCastException e) {
1140            typeWarning(key, o, "String", e);
1141            return null;
1142        }
1143    }
1144
1145    /**
1146     * Returns the value associated with the given key, or defaultValue if
1147     * no mapping of the desired type exists for the given key or if a null
1148     * value is explicitly associated with the given key.
1149     *
1150     * @param key a String, or null
1151     * @param defaultValue Value to return if key does not exist or if a null
1152     *     value is associated with the given key.
1153     * @return the String value associated with the given key, or defaultValue
1154     *     if no valid String object is currently mapped to that key.
1155     */
1156    public String getString(@Nullable String key, String defaultValue) {
1157        final String s = getString(key);
1158        return (s == null) ? defaultValue : s;
1159    }
1160
1161    /**
1162     * Returns the value associated with the given key, or null if
1163     * no mapping of the desired type exists for the given key or a null
1164     * value is explicitly associated with the key.
1165     *
1166     * @param key a String, or null
1167     * @return a CharSequence value, or null
1168     */
1169    @Nullable
1170    CharSequence getCharSequence(@Nullable String key) {
1171        unparcel();
1172        final Object o = mMap.get(key);
1173        try {
1174            return (CharSequence) o;
1175        } catch (ClassCastException e) {
1176            typeWarning(key, o, "CharSequence", e);
1177            return null;
1178        }
1179    }
1180
1181    /**
1182     * Returns the value associated with the given key, or defaultValue if
1183     * no mapping of the desired type exists for the given key or if a null
1184     * value is explicitly associated with the given key.
1185     *
1186     * @param key a String, or null
1187     * @param defaultValue Value to return if key does not exist or if a null
1188     *     value is associated with the given key.
1189     * @return the CharSequence value associated with the given key, or defaultValue
1190     *     if no valid CharSequence object is currently mapped to that key.
1191     */
1192    CharSequence getCharSequence(@Nullable String key, CharSequence defaultValue) {
1193        final CharSequence cs = getCharSequence(key);
1194        return (cs == null) ? defaultValue : cs;
1195    }
1196
1197    /**
1198     * Returns the value associated with the given key, or null if
1199     * no mapping of the desired type exists for the given key or a null
1200     * value is explicitly associated with the key.
1201     *
1202     * @param key a String, or null
1203     * @return a Serializable value, or null
1204     */
1205    @Nullable
1206    Serializable getSerializable(@Nullable String key) {
1207        unparcel();
1208        Object o = mMap.get(key);
1209        if (o == null) {
1210            return null;
1211        }
1212        try {
1213            return (Serializable) o;
1214        } catch (ClassCastException e) {
1215            typeWarning(key, o, "Serializable", e);
1216            return null;
1217        }
1218    }
1219
1220    /**
1221     * Returns the value associated with the given key, or null if
1222     * no mapping of the desired type exists for the given key or a null
1223     * value is explicitly associated with the key.
1224     *
1225     * @param key a String, or null
1226     * @return an ArrayList<String> value, or null
1227     */
1228    @Nullable
1229    ArrayList<Integer> getIntegerArrayList(@Nullable String key) {
1230        unparcel();
1231        Object o = mMap.get(key);
1232        if (o == null) {
1233            return null;
1234        }
1235        try {
1236            return (ArrayList<Integer>) o;
1237        } catch (ClassCastException e) {
1238            typeWarning(key, o, "ArrayList<Integer>", e);
1239            return null;
1240        }
1241    }
1242
1243    /**
1244     * Returns the value associated with the given key, or null if
1245     * no mapping of the desired type exists for the given key or a null
1246     * value is explicitly associated with the key.
1247     *
1248     * @param key a String, or null
1249     * @return an ArrayList<String> value, or null
1250     */
1251    @Nullable
1252    ArrayList<String> getStringArrayList(@Nullable String key) {
1253        unparcel();
1254        Object o = mMap.get(key);
1255        if (o == null) {
1256            return null;
1257        }
1258        try {
1259            return (ArrayList<String>) o;
1260        } catch (ClassCastException e) {
1261            typeWarning(key, o, "ArrayList<String>", e);
1262            return null;
1263        }
1264    }
1265
1266    /**
1267     * Returns the value associated with the given key, or null if
1268     * no mapping of the desired type exists for the given key or a null
1269     * value is explicitly associated with the key.
1270     *
1271     * @param key a String, or null
1272     * @return an ArrayList<CharSequence> value, or null
1273     */
1274    @Nullable
1275    ArrayList<CharSequence> getCharSequenceArrayList(@Nullable String key) {
1276        unparcel();
1277        Object o = mMap.get(key);
1278        if (o == null) {
1279            return null;
1280        }
1281        try {
1282            return (ArrayList<CharSequence>) o;
1283        } catch (ClassCastException e) {
1284            typeWarning(key, o, "ArrayList<CharSequence>", e);
1285            return null;
1286        }
1287    }
1288
1289    /**
1290     * Returns the value associated with the given key, or null if
1291     * no mapping of the desired type exists for the given key or a null
1292     * value is explicitly associated with the key.
1293     *
1294     * @param key a String, or null
1295     * @return a boolean[] value, or null
1296     */
1297    @Nullable
1298    public boolean[] getBooleanArray(@Nullable String key) {
1299        unparcel();
1300        Object o = mMap.get(key);
1301        if (o == null) {
1302            return null;
1303        }
1304        try {
1305            return (boolean[]) o;
1306        } catch (ClassCastException e) {
1307            typeWarning(key, o, "byte[]", e);
1308            return null;
1309        }
1310    }
1311
1312    /**
1313     * Returns the value associated with the given key, or null if
1314     * no mapping of the desired type exists for the given key or a null
1315     * value is explicitly associated with the key.
1316     *
1317     * @param key a String, or null
1318     * @return a byte[] value, or null
1319     */
1320    @Nullable
1321    byte[] getByteArray(@Nullable String key) {
1322        unparcel();
1323        Object o = mMap.get(key);
1324        if (o == null) {
1325            return null;
1326        }
1327        try {
1328            return (byte[]) o;
1329        } catch (ClassCastException e) {
1330            typeWarning(key, o, "byte[]", e);
1331            return null;
1332        }
1333    }
1334
1335    /**
1336     * Returns the value associated with the given key, or null if
1337     * no mapping of the desired type exists for the given key or a null
1338     * value is explicitly associated with the key.
1339     *
1340     * @param key a String, or null
1341     * @return a short[] value, or null
1342     */
1343    @Nullable
1344    short[] getShortArray(@Nullable String key) {
1345        unparcel();
1346        Object o = mMap.get(key);
1347        if (o == null) {
1348            return null;
1349        }
1350        try {
1351            return (short[]) o;
1352        } catch (ClassCastException e) {
1353            typeWarning(key, o, "short[]", e);
1354            return null;
1355        }
1356    }
1357
1358    /**
1359     * Returns the value associated with the given key, or null if
1360     * no mapping of the desired type exists for the given key or a null
1361     * value is explicitly associated with the key.
1362     *
1363     * @param key a String, or null
1364     * @return a char[] value, or null
1365     */
1366    @Nullable
1367    char[] getCharArray(@Nullable String key) {
1368        unparcel();
1369        Object o = mMap.get(key);
1370        if (o == null) {
1371            return null;
1372        }
1373        try {
1374            return (char[]) o;
1375        } catch (ClassCastException e) {
1376            typeWarning(key, o, "char[]", e);
1377            return null;
1378        }
1379    }
1380
1381    /**
1382     * Returns the value associated with the given key, or null if
1383     * no mapping of the desired type exists for the given key or a null
1384     * value is explicitly associated with the key.
1385     *
1386     * @param key a String, or null
1387     * @return an int[] value, or null
1388     */
1389    @Nullable
1390    public int[] getIntArray(@Nullable String key) {
1391        unparcel();
1392        Object o = mMap.get(key);
1393        if (o == null) {
1394            return null;
1395        }
1396        try {
1397            return (int[]) o;
1398        } catch (ClassCastException e) {
1399            typeWarning(key, o, "int[]", e);
1400            return null;
1401        }
1402    }
1403
1404    /**
1405     * Returns the value associated with the given key, or null if
1406     * no mapping of the desired type exists for the given key or a null
1407     * value is explicitly associated with the key.
1408     *
1409     * @param key a String, or null
1410     * @return a long[] value, or null
1411     */
1412    @Nullable
1413    public long[] getLongArray(@Nullable String key) {
1414        unparcel();
1415        Object o = mMap.get(key);
1416        if (o == null) {
1417            return null;
1418        }
1419        try {
1420            return (long[]) o;
1421        } catch (ClassCastException e) {
1422            typeWarning(key, o, "long[]", e);
1423            return null;
1424        }
1425    }
1426
1427    /**
1428     * Returns the value associated with the given key, or null if
1429     * no mapping of the desired type exists for the given key or a null
1430     * value is explicitly associated with the key.
1431     *
1432     * @param key a String, or null
1433     * @return a float[] value, or null
1434     */
1435    @Nullable
1436    float[] getFloatArray(@Nullable String key) {
1437        unparcel();
1438        Object o = mMap.get(key);
1439        if (o == null) {
1440            return null;
1441        }
1442        try {
1443            return (float[]) o;
1444        } catch (ClassCastException e) {
1445            typeWarning(key, o, "float[]", e);
1446            return null;
1447        }
1448    }
1449
1450    /**
1451     * Returns the value associated with the given key, or null if
1452     * no mapping of the desired type exists for the given key or a null
1453     * value is explicitly associated with the key.
1454     *
1455     * @param key a String, or null
1456     * @return a double[] value, or null
1457     */
1458    @Nullable
1459    public double[] getDoubleArray(@Nullable String key) {
1460        unparcel();
1461        Object o = mMap.get(key);
1462        if (o == null) {
1463            return null;
1464        }
1465        try {
1466            return (double[]) o;
1467        } catch (ClassCastException e) {
1468            typeWarning(key, o, "double[]", e);
1469            return null;
1470        }
1471    }
1472
1473    /**
1474     * Returns the value associated with the given key, or null if
1475     * no mapping of the desired type exists for the given key or a null
1476     * value is explicitly associated with the key.
1477     *
1478     * @param key a String, or null
1479     * @return a String[] value, or null
1480     */
1481    @Nullable
1482    public String[] getStringArray(@Nullable String key) {
1483        unparcel();
1484        Object o = mMap.get(key);
1485        if (o == null) {
1486            return null;
1487        }
1488        try {
1489            return (String[]) o;
1490        } catch (ClassCastException e) {
1491            typeWarning(key, o, "String[]", e);
1492            return null;
1493        }
1494    }
1495
1496    /**
1497     * Returns the value associated with the given key, or null if
1498     * no mapping of the desired type exists for the given key or a null
1499     * value is explicitly associated with the key.
1500     *
1501     * @param key a String, or null
1502     * @return a CharSequence[] value, or null
1503     */
1504    @Nullable
1505    CharSequence[] getCharSequenceArray(@Nullable String key) {
1506        unparcel();
1507        Object o = mMap.get(key);
1508        if (o == null) {
1509            return null;
1510        }
1511        try {
1512            return (CharSequence[]) o;
1513        } catch (ClassCastException e) {
1514            typeWarning(key, o, "CharSequence[]", e);
1515            return null;
1516        }
1517    }
1518
1519    /**
1520     * Writes the Bundle contents to a Parcel, typically in order for
1521     * it to be passed through an IBinder connection.
1522     * @param parcel The parcel to copy this bundle to.
1523     */
1524    void writeToParcelInner(Parcel parcel, int flags) {
1525        // If the parcel has a read-write helper, we can't just copy the blob, so unparcel it first.
1526        if (parcel.hasReadWriteHelper()) {
1527            unparcel();
1528        }
1529        // Keep implementation in sync with writeToParcel() in
1530        // frameworks/native/libs/binder/PersistableBundle.cpp.
1531        final ArrayMap<String, Object> map;
1532        synchronized (this) {
1533            // unparcel() can race with this method and cause the parcel to recycle
1534            // at the wrong time. So synchronize access the mParcelledData's content.
1535            if (mParcelledData != null) {
1536                if (mParcelledData == NoImagePreloadHolder.EMPTY_PARCEL) {
1537                    parcel.writeInt(0);
1538                } else {
1539                    int length = mParcelledData.dataSize();
1540                    parcel.writeInt(length);
1541                    parcel.writeInt(BUNDLE_MAGIC);
1542                    parcel.appendFrom(mParcelledData, 0, length);
1543                }
1544                return;
1545            }
1546            map = mMap;
1547        }
1548
1549        // Special case for empty bundles.
1550        if (map == null || map.size() <= 0) {
1551            parcel.writeInt(0);
1552            return;
1553        }
1554        int lengthPos = parcel.dataPosition();
1555        parcel.writeInt(-1); // dummy, will hold length
1556        parcel.writeInt(BUNDLE_MAGIC);
1557
1558        int startPos = parcel.dataPosition();
1559        parcel.writeArrayMapInternal(map);
1560        int endPos = parcel.dataPosition();
1561
1562        // Backpatch length
1563        parcel.setDataPosition(lengthPos);
1564        int length = endPos - startPos;
1565        parcel.writeInt(length);
1566        parcel.setDataPosition(endPos);
1567    }
1568
1569    /**
1570     * Reads the Parcel contents into this Bundle, typically in order for
1571     * it to be passed through an IBinder connection.
1572     * @param parcel The parcel to overwrite this bundle from.
1573     */
1574    void readFromParcelInner(Parcel parcel) {
1575        // Keep implementation in sync with readFromParcel() in
1576        // frameworks/native/libs/binder/PersistableBundle.cpp.
1577        int length = parcel.readInt();
1578        readFromParcelInner(parcel, length);
1579    }
1580
1581    private void readFromParcelInner(Parcel parcel, int length) {
1582        if (length < 0) {
1583            throw new RuntimeException("Bad length in parcel: " + length);
1584
1585        } else if (length == 0) {
1586            // Empty Bundle or end of data.
1587            mParcelledData = NoImagePreloadHolder.EMPTY_PARCEL;
1588            return;
1589        }
1590
1591        final int magic = parcel.readInt();
1592        if (magic != BUNDLE_MAGIC) {
1593            throw new IllegalStateException("Bad magic number for Bundle: 0x"
1594                    + Integer.toHexString(magic));
1595        }
1596
1597        if (parcel.hasReadWriteHelper()) {
1598            // If the parcel has a read-write helper, then we can't lazily-unparcel it, so just
1599            // unparcel right away.
1600            synchronized (this) {
1601                initializeFromParcelLocked(parcel, /*recycleParcel=*/ false);
1602            }
1603            return;
1604        }
1605
1606        // Advance within this Parcel
1607        int offset = parcel.dataPosition();
1608        parcel.setDataPosition(MathUtils.addOrThrow(offset, length));
1609
1610        Parcel p = Parcel.obtain();
1611        p.setDataPosition(0);
1612        p.appendFrom(parcel, offset, length);
1613        p.adoptClassCookies(parcel);
1614        if (DEBUG) Log.d(TAG, "Retrieving "  + Integer.toHexString(System.identityHashCode(this))
1615                + ": " + length + " bundle bytes starting at " + offset);
1616        p.setDataPosition(0);
1617
1618        mParcelledData = p;
1619    }
1620
1621    /** {@hide} */
1622    public static void dumpStats(IndentingPrintWriter pw, String key, Object value) {
1623        final Parcel tmp = Parcel.obtain();
1624        tmp.writeValue(value);
1625        final int size = tmp.dataPosition();
1626        tmp.recycle();
1627
1628        // We only really care about logging large values
1629        if (size > 1024) {
1630            pw.println(key + " [size=" + size + "]");
1631            if (value instanceof BaseBundle) {
1632                dumpStats(pw, (BaseBundle) value);
1633            } else if (value instanceof SparseArray) {
1634                dumpStats(pw, (SparseArray) value);
1635            }
1636        }
1637    }
1638
1639    /** {@hide} */
1640    public static void dumpStats(IndentingPrintWriter pw, SparseArray array) {
1641        pw.increaseIndent();
1642        if (array == null) {
1643            pw.println("[null]");
1644            return;
1645        }
1646        for (int i = 0; i < array.size(); i++) {
1647            dumpStats(pw, "0x" + Integer.toHexString(array.keyAt(i)), array.valueAt(i));
1648        }
1649        pw.decreaseIndent();
1650    }
1651
1652    /** {@hide} */
1653    public static void dumpStats(IndentingPrintWriter pw, BaseBundle bundle) {
1654        pw.increaseIndent();
1655        if (bundle == null) {
1656            pw.println("[null]");
1657            return;
1658        }
1659        final ArrayMap<String, Object> map = bundle.getMap();
1660        for (int i = 0; i < map.size(); i++) {
1661            dumpStats(pw, map.keyAt(i), map.valueAt(i));
1662        }
1663        pw.decreaseIndent();
1664    }
1665}
1666