1 /*
2  * Copyright (C) 2018 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 com.google.android.setupcompat.internal;
18 
19 import android.annotation.TargetApi;
20 import android.os.BaseBundle;
21 import android.os.Build.VERSION_CODES;
22 import android.os.Bundle;
23 import android.os.PersistableBundle;
24 import android.util.ArrayMap;
25 import com.google.android.setupcompat.util.Logger;
26 import java.util.ArrayList;
27 import java.util.Arrays;
28 import java.util.Collections;
29 import java.util.List;
30 
31 /** Contains utility methods related to {@link PersistableBundle}. */
32 @TargetApi(VERSION_CODES.LOLLIPOP_MR1)
33 public final class PersistableBundles {
34 
35   private static final Logger LOG = new Logger("PersistableBundles");
36 
37   /**
38    * Merges two or more {@link PersistableBundle}. Ensures no conflict of keys occurred during
39    * merge.
40    *
41    * @return Returns a new {@link PersistableBundle} that contains all the data from {@code
42    *     firstBundle}, {@code nextBundle} and {@code others}.
43    */
mergeBundles( PersistableBundle firstBundle, PersistableBundle nextBundle, PersistableBundle... others)44   public static PersistableBundle mergeBundles(
45       PersistableBundle firstBundle, PersistableBundle nextBundle, PersistableBundle... others) {
46     List<PersistableBundle> allBundles = new ArrayList<>();
47     allBundles.addAll(Arrays.asList(firstBundle, nextBundle));
48     Collections.addAll(allBundles, others);
49 
50     PersistableBundle result = new PersistableBundle();
51     for (PersistableBundle bundle : allBundles) {
52       for (String key : bundle.keySet()) {
53         Preconditions.checkArgument(
54             !result.containsKey(key),
55             String.format("Found duplicate key [%s] while attempting to merge bundles.", key));
56       }
57       result.putAll(bundle);
58     }
59 
60     return result;
61   }
62 
63   /** Returns a {@link Bundle} that contains all the values from {@code persistableBundle}. */
toBundle(PersistableBundle persistableBundle)64   public static Bundle toBundle(PersistableBundle persistableBundle) {
65     Bundle bundle = new Bundle();
66     bundle.putAll(persistableBundle);
67     return bundle;
68   }
69 
70   /**
71    * Returns a {@link PersistableBundle} that contains values from {@code bundle} that are supported
72    * by the logging API. Un-supported value types are dropped.
73    */
fromBundle(Bundle bundle)74   public static PersistableBundle fromBundle(Bundle bundle) {
75     PersistableBundle to = new PersistableBundle();
76     ArrayMap<String, Object> map = toMap(bundle);
77     for (String key : map.keySet()) {
78       Object value = map.get(key);
79       if (value instanceof Long) {
80         to.putLong(key, (Long) value);
81       } else if (value instanceof Integer) {
82         to.putInt(key, (Integer) value);
83       } else if (value instanceof Double) {
84         to.putDouble(key, (Double) value);
85       } else if (value instanceof Boolean) {
86         to.putBoolean(key, (Boolean) value);
87       } else if (value instanceof String) {
88         to.putString(key, (String) value);
89       } else {
90         throw new AssertionError(String.format("Missing put* for valid data type? = %s", value));
91       }
92     }
93     return to;
94   }
95 
96   /** Returns {@code true} if {@code left} contains same set of values as {@code right}. */
equals(PersistableBundle left, PersistableBundle right)97   public static boolean equals(PersistableBundle left, PersistableBundle right) {
98     return (left == right) || toMap(left).equals(toMap(right));
99   }
100 
101   /** Asserts that {@code persistableBundle} contains only supported data types. */
assertIsValid(PersistableBundle persistableBundle)102   public static PersistableBundle assertIsValid(PersistableBundle persistableBundle) {
103     Preconditions.checkNotNull(persistableBundle, "PersistableBundle cannot be null!");
104     for (String key : persistableBundle.keySet()) {
105       Object value = persistableBundle.get(key);
106       Preconditions.checkArgument(
107           isSupportedDataType(value),
108           String.format("Unknown/unsupported data type [%s] for key %s", value, key));
109     }
110     return persistableBundle;
111   }
112 
113   /**
114    * Returns a new {@link ArrayMap} that contains values from {@code bundle} that are supported by
115    * the logging API.
116    */
toMap(BaseBundle baseBundle)117   private static ArrayMap<String, Object> toMap(BaseBundle baseBundle) {
118     if (baseBundle == null || baseBundle.isEmpty()) {
119       return new ArrayMap<>(0);
120     }
121 
122     ArrayMap<String, Object> map = new ArrayMap<>(baseBundle.size());
123     for (String key : baseBundle.keySet()) {
124       Object value = baseBundle.get(key);
125       if (!isSupportedDataType(value)) {
126         LOG.w(String.format("Unknown/unsupported data type [%s] for key %s", value, key));
127         continue;
128       }
129       map.put(key, baseBundle.get(key));
130     }
131     return map;
132   }
133 
isSupportedDataType(Object value)134   private static boolean isSupportedDataType(Object value) {
135     return value instanceof Integer
136         || value instanceof Long
137         || value instanceof Double
138         || value instanceof Float
139         || value instanceof String
140         || value instanceof Boolean;
141   }
142 
PersistableBundles()143   private PersistableBundles() {
144     throw new AssertionError("Should not be instantiated");
145   }
146 }
147