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