1/*
2 * Copyright 2016, 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 */
16package com.android.managedprovisioning.common;
17
18import android.os.Parcel;
19import android.os.Parcelable;
20import android.os.PersistableBundle;
21import android.support.annotation.NonNull;
22import android.text.TextUtils;
23import java.util.ArrayList;
24import java.util.Arrays;
25import java.util.Collections;
26import java.util.List;
27import java.util.Objects;
28import java.util.Set;
29
30public abstract class PersistableBundlable implements Parcelable {
31    public abstract @NonNull PersistableBundle toPersistableBundle();
32
33    public static PersistableBundle getPersistableBundleFromParcel(Parcel parcel) {
34        return parcel.readParcelable(PersistableBundle.class.getClassLoader());
35    }
36
37    @Override
38    public boolean equals(Object object) {
39        return isPersistableBundlableEquals(this, object);
40    }
41
42    @Override
43    public int hashCode() {
44        // Concatenated sorted keys should be good enough as a hash
45        List<String> keys = new ArrayList(toPersistableBundle().keySet());
46        Collections.sort(keys);
47        return TextUtils.join(",", keys).hashCode();
48    }
49
50    @Override
51    public int describeContents() {
52        return 0;
53    }
54
55    @Override
56    public void writeToParcel(Parcel dest, int flags) {
57        dest.writeParcelable(toPersistableBundle(), flags);
58    }
59
60    private static boolean isPersistableBundlableEquals(PersistableBundlable pb1, Object obj) {
61        if (pb1 == obj) {
62            return true;
63        }
64        if (obj == null || pb1.getClass() != obj.getClass()) {
65            return false;
66        }
67
68        // obj has to be PersistableBundlable as it has the same class
69        PersistableBundlable pb2 = (PersistableBundlable) obj;
70        return isPersistableBundleEquals(pb1.toPersistableBundle(), pb2.toPersistableBundle());
71    }
72
73    /**
74     * Compares two {@link PersistableBundle} objects are equals.
75     */
76    private static boolean isPersistableBundleEquals(PersistableBundle obj1, PersistableBundle obj2) {
77        if (obj1 == obj2) {
78            return true;
79        }
80        if (obj1 == null || obj2 == null || obj1.size() != obj2.size()) {
81            return false;
82        }
83        Set<String> keys = obj1.keySet();
84        for (String key : keys) {
85            Object val1 = obj1.get(key);
86            Object val2 = obj2.get(key);
87            if (!isPersistableBundleSupportedValueEquals(val1, val2)) {
88                return false;
89            }
90        }
91        return true;
92    }
93
94    /**
95     * Compares two values which type is supported by {@link PersistableBundle}.
96     *
97     * <p>If the type isn't supported. The equality is done by {@link Object#equals(Object)}.
98     */
99    private static boolean isPersistableBundleSupportedValueEquals(Object val1, Object val2) {
100        if (val1 == val2) {
101            return true;
102        } else if (val1 == null || val2 == null || !val1.getClass().equals(val2.getClass())) {
103            return false;
104        } else if (val1 instanceof PersistableBundle) {
105            return isPersistableBundleEquals((PersistableBundle) val1, (PersistableBundle) val2);
106        } else if (val1 instanceof int[]) {
107            return Arrays.equals((int[]) val1, (int[]) val2);
108        } else if (val1 instanceof long[]) {
109            return Arrays.equals((long[]) val1, (long[]) val2);
110        } else if (val1 instanceof double[]) {
111            return Arrays.equals((double[]) val1, (double[]) val2);
112        } else if (val1 instanceof boolean[]) {
113            return Arrays.equals((boolean[]) val1, (boolean[]) val2);
114        } else if (val1 instanceof String[]) {
115            return Arrays.equals((String[]) val1, (String[]) val2);
116        } else {
117            return Objects.equals(val1, val2);
118        }
119    }
120
121}
122