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