1 /* 2 * Copyright 2020 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.app.appsearch.util; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.os.Bundle; 22 import android.os.Parcel; 23 import android.util.SparseArray; 24 25 import java.util.ArrayList; 26 import java.util.Arrays; 27 28 /** 29 * Utilities for working with {@link android.os.Bundle}. 30 * 31 * @hide 32 */ 33 public final class BundleUtil { BundleUtil()34 private BundleUtil() {} 35 36 /** 37 * Deeply checks two bundles are equal or not. 38 * 39 * <p>Two bundles will be considered equal if they contain the same keys, and each value is also 40 * equal. Bundle values are compared using deepEquals. 41 */ 42 @SuppressWarnings("deprecation") deepEquals(@ullable Bundle one, @Nullable Bundle two)43 public static boolean deepEquals(@Nullable Bundle one, @Nullable Bundle two) { 44 if (one == null && two == null) { 45 return true; 46 } 47 if (one == null || two == null) { 48 return false; 49 } 50 if (one.size() != two.size()) { 51 return false; 52 } 53 if (!one.keySet().equals(two.keySet())) { 54 return false; 55 } 56 // Bundle inherit its equals() from Object.java, which only compare their memory address. 57 // We should iterate all keys and check their presents and values in both bundle. 58 for (String key : one.keySet()) { 59 if (!bundleValueEquals(one.get(key), two.get(key))) { 60 return false; 61 } 62 } 63 return true; 64 } 65 66 /** 67 * Deeply checks whether two values in a Bundle are equal or not. 68 * 69 * <p>Values of type Bundle are compared using {@link #deepEquals}. 70 */ bundleValueEquals(@ullable Object one, @Nullable Object two)71 private static boolean bundleValueEquals(@Nullable Object one, @Nullable Object two) { 72 if (one == null && two == null) { 73 return true; 74 } 75 if (one == null || two == null) { 76 return false; 77 } 78 if (one.equals(two)) { 79 return true; 80 } 81 if (one instanceof Bundle && two instanceof Bundle) { 82 return deepEquals((Bundle) one, (Bundle) two); 83 } else if (one instanceof int[] && two instanceof int[]) { 84 return Arrays.equals((int[]) one, (int[]) two); 85 } else if (one instanceof byte[] && two instanceof byte[]) { 86 return Arrays.equals((byte[]) one, (byte[]) two); 87 } else if (one instanceof char[] && two instanceof char[]) { 88 return Arrays.equals((char[]) one, (char[]) two); 89 } else if (one instanceof long[] && two instanceof long[]) { 90 return Arrays.equals((long[]) one, (long[]) two); 91 } else if (one instanceof float[] && two instanceof float[]) { 92 return Arrays.equals((float[]) one, (float[]) two); 93 } else if (one instanceof short[] && two instanceof short[]) { 94 return Arrays.equals((short[]) one, (short[]) two); 95 } else if (one instanceof double[] && two instanceof double[]) { 96 return Arrays.equals((double[]) one, (double[]) two); 97 } else if (one instanceof boolean[] && two instanceof boolean[]) { 98 return Arrays.equals((boolean[]) one, (boolean[]) two); 99 } else if (one instanceof Object[] && two instanceof Object[]) { 100 Object[] arrayOne = (Object[]) one; 101 Object[] arrayTwo = (Object[]) two; 102 if (arrayOne.length != arrayTwo.length) { 103 return false; 104 } 105 if (Arrays.equals(arrayOne, arrayTwo)) { 106 return true; 107 } 108 for (int i = 0; i < arrayOne.length; i++) { 109 if (!bundleValueEquals(arrayOne[i], arrayTwo[i])) { 110 return false; 111 } 112 } 113 return true; 114 } else if (one instanceof ArrayList && two instanceof ArrayList) { 115 ArrayList<?> listOne = (ArrayList<?>) one; 116 ArrayList<?> listTwo = (ArrayList<?>) two; 117 if (listOne.size() != listTwo.size()) { 118 return false; 119 } 120 for (int i = 0; i < listOne.size(); i++) { 121 if (!bundleValueEquals(listOne.get(i), listTwo.get(i))) { 122 return false; 123 } 124 } 125 return true; 126 } else if (one instanceof SparseArray && two instanceof SparseArray) { 127 SparseArray<?> arrayOne = (SparseArray<?>) one; 128 SparseArray<?> arrayTwo = (SparseArray<?>) two; 129 if (arrayOne.size() != arrayTwo.size()) { 130 return false; 131 } 132 for (int i = 0; i < arrayOne.size(); i++) { 133 if (arrayOne.keyAt(i) != arrayTwo.keyAt(i) 134 || !bundleValueEquals(arrayOne.valueAt(i), arrayTwo.valueAt(i))) { 135 return false; 136 } 137 } 138 return true; 139 } 140 return false; 141 } 142 143 /** 144 * Calculates the hash code for a bundle. 145 * 146 * <p>The hash code is only effected by the contents in the bundle. Bundles will get consistent 147 * hash code if they have same contents. 148 */ 149 @SuppressWarnings("deprecation") deepHashCode(@ullable Bundle bundle)150 public static int deepHashCode(@Nullable Bundle bundle) { 151 if (bundle == null) { 152 return 0; 153 } 154 int[] hashCodes = new int[bundle.size() + 1]; 155 int hashCodeIdx = 0; 156 // Bundle inherit its hashCode() from Object.java, which only relative to their memory 157 // address. Bundle doesn't have an order, so we should iterate all keys and combine 158 // their value's hashcode into an array. And use the hashcode of the array to be 159 // the hashcode of the bundle. 160 // Because bundle.keySet() doesn't guarantee any particular order, we need to sort the keys 161 // in case the iteration order varies from run to run. 162 String[] keys = bundle.keySet().toArray(new String[0]); 163 Arrays.sort(keys); 164 // Hash the keys so we can detect key-only differences 165 hashCodes[hashCodeIdx++] = Arrays.hashCode(keys); 166 for (int keyIdx = 0; keyIdx < keys.length; keyIdx++) { 167 Object value = bundle.get(keys[keyIdx]); 168 if (value instanceof Bundle) { 169 hashCodes[hashCodeIdx++] = deepHashCode((Bundle) value); 170 } else if (value instanceof int[]) { 171 hashCodes[hashCodeIdx++] = Arrays.hashCode((int[]) value); 172 } else if (value instanceof byte[]) { 173 hashCodes[hashCodeIdx++] = Arrays.hashCode((byte[]) value); 174 } else if (value instanceof char[]) { 175 hashCodes[hashCodeIdx++] = Arrays.hashCode((char[]) value); 176 } else if (value instanceof long[]) { 177 hashCodes[hashCodeIdx++] = Arrays.hashCode((long[]) value); 178 } else if (value instanceof float[]) { 179 hashCodes[hashCodeIdx++] = Arrays.hashCode((float[]) value); 180 } else if (value instanceof short[]) { 181 hashCodes[hashCodeIdx++] = Arrays.hashCode((short[]) value); 182 } else if (value instanceof double[]) { 183 hashCodes[hashCodeIdx++] = Arrays.hashCode((double[]) value); 184 } else if (value instanceof boolean[]) { 185 hashCodes[hashCodeIdx++] = Arrays.hashCode((boolean[]) value); 186 } else if (value instanceof String[]) { 187 // Optimization to avoid Object[] handler creating an inner array for common cases 188 hashCodes[hashCodeIdx++] = Arrays.hashCode((String[]) value); 189 } else if (value instanceof Object[]) { 190 Object[] array = (Object[]) value; 191 int[] innerHashCodes = new int[array.length]; 192 for (int j = 0; j < array.length; j++) { 193 if (array[j] instanceof Bundle) { 194 innerHashCodes[j] = deepHashCode((Bundle) array[j]); 195 } else if (array[j] != null) { 196 innerHashCodes[j] = array[j].hashCode(); 197 } 198 } 199 hashCodes[hashCodeIdx++] = Arrays.hashCode(innerHashCodes); 200 } else if (value instanceof ArrayList) { 201 ArrayList<?> list = (ArrayList<?>) value; 202 int[] innerHashCodes = new int[list.size()]; 203 for (int j = 0; j < innerHashCodes.length; j++) { 204 Object item = list.get(j); 205 if (item instanceof Bundle) { 206 innerHashCodes[j] = deepHashCode((Bundle) item); 207 } else if (item != null) { 208 innerHashCodes[j] = item.hashCode(); 209 } 210 } 211 hashCodes[hashCodeIdx++] = Arrays.hashCode(innerHashCodes); 212 } else if (value instanceof SparseArray) { 213 SparseArray<?> array = (SparseArray<?>) value; 214 int[] innerHashCodes = new int[array.size() * 2]; 215 for (int j = 0; j < array.size(); j++) { 216 innerHashCodes[j * 2] = array.keyAt(j); 217 Object item = array.valueAt(j); 218 if (item instanceof Bundle) { 219 innerHashCodes[j * 2 + 1] = deepHashCode((Bundle) item); 220 } else if (item != null) { 221 innerHashCodes[j * 2 + 1] = item.hashCode(); 222 } 223 } 224 hashCodes[hashCodeIdx++] = Arrays.hashCode(innerHashCodes); 225 } else { 226 hashCodes[hashCodeIdx++] = value.hashCode(); 227 } 228 } 229 return Arrays.hashCode(hashCodes); 230 } 231 232 /** 233 * Deeply clones a Bundle. 234 * 235 * <p>Values which are Bundles, Lists or Arrays are deeply copied themselves. 236 */ 237 @NonNull deepCopy(@onNull Bundle bundle)238 public static Bundle deepCopy(@NonNull Bundle bundle) { 239 // Write bundle to bytes 240 Parcel parcel = Parcel.obtain(); 241 try { 242 parcel.writeBundle(bundle); 243 byte[] serializedMessage = parcel.marshall(); 244 245 // Read bundle from bytes 246 parcel.unmarshall(serializedMessage, 0, serializedMessage.length); 247 parcel.setDataPosition(0); 248 return parcel.readBundle(); 249 } finally { 250 parcel.recycle(); 251 } 252 } 253 } 254