• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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