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