/* * Copyright 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.app.appsearch.util; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.appsearch.safeparcel.SafeParcelable; import android.app.appsearch.safeparcel.SafeParcelableSerializer; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; import android.util.SparseArray; import java.util.ArrayList; import java.util.Arrays; import java.util.List; /** * Utilities for working with {@link android.os.Bundle}. * * @hide */ public final class BundleUtil { private BundleUtil() {} /** * Deeply checks two bundles are equal or not. * *
Two bundles will be considered equal if they contain the same keys, and each value is also * equal. Bundle values are compared using deepEquals. */ @SuppressWarnings("deprecation") public static boolean deepEquals(@Nullable Bundle one, @Nullable Bundle two) { if (one == null && two == null) { return true; } if (one == null || two == null) { return false; } if (one.size() != two.size()) { return false; } if (!one.keySet().equals(two.keySet())) { return false; } // Bundle inherit its equals() from Object.java, which only compare their memory address. // We should iterate all keys and check their presents and values in both bundle. for (String key : one.keySet()) { if (!bundleValueEquals(one.get(key), two.get(key))) { return false; } } return true; } /** * Deeply checks whether two values in a Bundle are equal or not. * *
Values of type Bundle are compared using {@link #deepEquals}. */ public static boolean bundleValueEquals(@Nullable Object one, @Nullable Object two) { if (one == null && two == null) { return true; } if (one == null || two == null) { return false; } if (one.equals(two)) { return true; } if (one instanceof Bundle && two instanceof Bundle) { return deepEquals((Bundle) one, (Bundle) two); } else if (one instanceof int[] && two instanceof int[]) { return Arrays.equals((int[]) one, (int[]) two); } else if (one instanceof byte[] && two instanceof byte[]) { return Arrays.equals((byte[]) one, (byte[]) two); } else if (one instanceof char[] && two instanceof char[]) { return Arrays.equals((char[]) one, (char[]) two); } else if (one instanceof long[] && two instanceof long[]) { return Arrays.equals((long[]) one, (long[]) two); } else if (one instanceof float[] && two instanceof float[]) { return Arrays.equals((float[]) one, (float[]) two); } else if (one instanceof short[] && two instanceof short[]) { return Arrays.equals((short[]) one, (short[]) two); } else if (one instanceof double[] && two instanceof double[]) { return Arrays.equals((double[]) one, (double[]) two); } else if (one instanceof boolean[] && two instanceof boolean[]) { return Arrays.equals((boolean[]) one, (boolean[]) two); } else if (one instanceof Object[] && two instanceof Object[]) { Object[] arrayOne = (Object[]) one; Object[] arrayTwo = (Object[]) two; if (arrayOne.length != arrayTwo.length) { return false; } if (Arrays.equals(arrayOne, arrayTwo)) { return true; } for (int i = 0; i < arrayOne.length; i++) { if (!bundleValueEquals(arrayOne[i], arrayTwo[i])) { return false; } } return true; } else if (one instanceof ArrayList && two instanceof ArrayList) { ArrayList> listOne = (ArrayList>) one; ArrayList> listTwo = (ArrayList>) two; if (listOne.size() != listTwo.size()) { return false; } for (int i = 0; i < listOne.size(); i++) { if (!bundleValueEquals(listOne.get(i), listTwo.get(i))) { return false; } } return true; } else if (one instanceof SparseArray && two instanceof SparseArray) { SparseArray> arrayOne = (SparseArray>) one; SparseArray> arrayTwo = (SparseArray>) two; if (arrayOne.size() != arrayTwo.size()) { return false; } for (int i = 0; i < arrayOne.size(); i++) { if (arrayOne.keyAt(i) != arrayTwo.keyAt(i) || !bundleValueEquals(arrayOne.valueAt(i), arrayTwo.valueAt(i))) { return false; } } return true; } return false; } /** * Calculates the hash code for a bundle. * *
The hash code is only effected by the contents in the bundle. Bundles will get consistent * hash code if they have same contents. */ @SuppressWarnings("deprecation") public static int deepHashCode(@Nullable Bundle bundle) { if (bundle == null) { return 0; } int[] hashCodes = new int[bundle.size() + 1]; int hashCodeIdx = 0; // Bundle inherit its hashCode() from Object.java, which only relative to their memory // address. Bundle doesn't have an order, so we should iterate all keys and combine // their value's hashcode into an array. And use the hashcode of the array to be // the hashcode of the bundle. // Because bundle.keySet() doesn't guarantee any particular order, we need to sort the keys // in case the iteration order varies from run to run. String[] keys = bundle.keySet().toArray(new String[0]); Arrays.sort(keys); // Hash the keys so we can detect key-only differences hashCodes[hashCodeIdx++] = Arrays.hashCode(keys); for (int keyIdx = 0; keyIdx < keys.length; keyIdx++) { Object value = bundle.get(keys[keyIdx]); if (value instanceof Bundle) { hashCodes[hashCodeIdx++] = deepHashCode((Bundle) value); } else if (value instanceof int[]) { hashCodes[hashCodeIdx++] = Arrays.hashCode((int[]) value); } else if (value instanceof byte[]) { hashCodes[hashCodeIdx++] = Arrays.hashCode((byte[]) value); } else if (value instanceof char[]) { hashCodes[hashCodeIdx++] = Arrays.hashCode((char[]) value); } else if (value instanceof long[]) { hashCodes[hashCodeIdx++] = Arrays.hashCode((long[]) value); } else if (value instanceof float[]) { hashCodes[hashCodeIdx++] = Arrays.hashCode((float[]) value); } else if (value instanceof short[]) { hashCodes[hashCodeIdx++] = Arrays.hashCode((short[]) value); } else if (value instanceof double[]) { hashCodes[hashCodeIdx++] = Arrays.hashCode((double[]) value); } else if (value instanceof boolean[]) { hashCodes[hashCodeIdx++] = Arrays.hashCode((boolean[]) value); } else if (value instanceof String[]) { // Optimization to avoid Object[] handler creating an inner array for common cases hashCodes[hashCodeIdx++] = Arrays.hashCode((String[]) value); } else if (value instanceof Object[]) { Object[] array = (Object[]) value; int[] innerHashCodes = new int[array.length]; for (int j = 0; j < array.length; j++) { if (array[j] instanceof Bundle) { innerHashCodes[j] = deepHashCode((Bundle) array[j]); } else if (array[j] != null) { innerHashCodes[j] = array[j].hashCode(); } } hashCodes[hashCodeIdx++] = Arrays.hashCode(innerHashCodes); } else if (value instanceof ArrayList) { ArrayList> list = (ArrayList>) value; int[] innerHashCodes = new int[list.size()]; for (int j = 0; j < innerHashCodes.length; j++) { Object item = list.get(j); if (item instanceof Bundle) { innerHashCodes[j] = deepHashCode((Bundle) item); } else if (item != null) { innerHashCodes[j] = item.hashCode(); } } hashCodes[hashCodeIdx++] = Arrays.hashCode(innerHashCodes); } else if (value instanceof SparseArray) { SparseArray> array = (SparseArray>) value; int[] innerHashCodes = new int[array.size() * 2]; for (int j = 0; j < array.size(); j++) { innerHashCodes[j * 2] = array.keyAt(j); Object item = array.valueAt(j); if (item instanceof Bundle) { innerHashCodes[j * 2 + 1] = deepHashCode((Bundle) item); } else if (item != null) { innerHashCodes[j * 2 + 1] = item.hashCode(); } } hashCodes[hashCodeIdx++] = Arrays.hashCode(innerHashCodes); } else if (value != null) { hashCodes[hashCodeIdx++] = value.hashCode(); } else { hashCodes[hashCodeIdx++] = 0; } } return Arrays.hashCode(hashCodes); } /** * Deeply clones a Bundle. * *
Values which are Bundles, Lists or Arrays are deeply copied themselves. */ @NonNull public static Bundle deepCopy(@NonNull Bundle bundle) { // Write bundle to bytes Parcel parcel = Parcel.obtain(); try { parcel.writeBundle(bundle); byte[] serializedMessage = parcel.marshall(); // Read bundle from bytes parcel.unmarshall(serializedMessage, 0, serializedMessage.length); parcel.setDataPosition(0); return parcel.readBundle(BundleUtil.class.getClassLoader()); } finally { parcel.recycle(); } } /** * Helper function to serialize a {@link SafeParcelable} in a Bundle. * *
Note: {@link Bundle#putParcelableArrayList(String, java.util.ArrayList)} does not preserve * SafeParcelable semantics. Use this method instead.Failure to heed this advice will inevitably * result in unanticipated runtime crash loops. * *
See http://shortn/_vLTA1IpXK1 * *
See http://shortn/_b2QcRB4tpG
*
* @param parcel the {@link SafeParcelable} to write to the bundle.
* @param bundle the bundle containing the key to deserialize from.
* @param key the name of the key mapping to the serialized object.
*/
public static