1 /** 2 * Copyright (C) 2021 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.android.remoteprovisioner; 18 19 import android.content.Context; 20 import android.content.SharedPreferences; 21 import android.util.Log; 22 23 import java.time.Duration; 24 import java.util.Random; 25 26 /** 27 * SettingsManager makes use of SharedPreferences in order to store key/value pairs related to 28 * configuration settings that can be retrieved from the server. In the event that none have yet 29 * been retrieved, or for some reason a reset has occurred, there are reasonable default values. 30 */ 31 public class SettingsManager { 32 33 public static final int ID_UPPER_BOUND = 1000000; 34 public static final int EXTRA_SIGNED_KEYS_AVAILABLE_DEFAULT = 6; 35 // Check for expiring certs in the next 3 days 36 public static final int EXPIRING_BY_MS_DEFAULT = 1000 * 60 * 60 * 24 * 3; 37 public static final String URL_DEFAULT = "https://remoteprovisioning.googleapis.com/v1"; 38 39 private static final String KEY_EXPIRING_BY = "expiring_by"; 40 private static final String KEY_EXTRA_KEYS = "extra_keys"; 41 private static final String KEY_ID = "settings_id"; 42 private static final String KEY_FAILURE_COUNTER = "failure_counter"; 43 private static final String KEY_URL = "url"; 44 private static final String PREFERENCES_NAME = "com.android.remoteprovisioner.preferences"; 45 private static final String TAG = "RemoteProvisionerSettings"; 46 47 /** 48 * Generates a random ID for the use of gradual ramp up of remote provisioning. 49 */ generateAndSetId(Context context)50 public static void generateAndSetId(Context context) { 51 SharedPreferences sharedPref = 52 context.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE); 53 if (sharedPref.contains(KEY_ID)) { 54 // ID is already set, don't rotate it. 55 return; 56 } 57 Log.i(TAG, "Setting ID"); 58 Random rand = new Random(); 59 SharedPreferences.Editor editor = sharedPref.edit(); 60 editor.putInt(KEY_ID, rand.nextInt(ID_UPPER_BOUND)); 61 editor.apply(); 62 } 63 64 /** 65 * Fetches the generated ID. 66 */ getId(Context context)67 public static int getId(Context context) { 68 SharedPreferences sharedPref = 69 context.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE); 70 Random rand = new Random(); 71 return sharedPref.getInt(KEY_ID, rand.nextInt(ID_UPPER_BOUND) /* defaultValue */); 72 } 73 74 /** 75 * Sets the remote provisioning configuration values based on what was fetched from the server. 76 * The server is not guaranteed to have sent every available parameter in the config that 77 * was returned to the device, so the parameters should be checked for null values. 78 * 79 * @param extraKeys How many server signed remote provisioning key pairs that should be kept 80 * available in KeyStore. 81 * @param expiringBy How far in the future the app should check for expiring keys. 82 * @param url The base URL for the provisioning server. 83 * @return {@code true} if any settings were updated. 84 */ setDeviceConfig(Context context, int extraKeys, Duration expiringBy, String url)85 public static boolean setDeviceConfig(Context context, int extraKeys, 86 Duration expiringBy, String url) { 87 SharedPreferences sharedPref = 88 context.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE); 89 SharedPreferences.Editor editor = sharedPref.edit(); 90 boolean wereUpdatesMade = false; 91 if (extraKeys != GeekResponse.NO_EXTRA_KEY_UPDATE 92 && sharedPref.getInt(KEY_EXTRA_KEYS, -5) != extraKeys) { 93 editor.putInt(KEY_EXTRA_KEYS, extraKeys); 94 wereUpdatesMade = true; 95 } 96 if (expiringBy != null 97 && sharedPref.getLong(KEY_EXPIRING_BY, -1) != expiringBy.toMillis()) { 98 editor.putLong(KEY_EXPIRING_BY, expiringBy.toMillis()); 99 wereUpdatesMade = true; 100 } 101 if (url != null && !sharedPref.getString(KEY_URL, "").equals(url)) { 102 editor.putString(KEY_URL, url); 103 wereUpdatesMade = true; 104 } 105 if (wereUpdatesMade) { 106 editor.apply(); 107 } 108 return wereUpdatesMade; 109 } 110 111 /** 112 * Gets the setting for how many extra keys should be kept signed and available in KeyStore. 113 */ getExtraSignedKeysAvailable(Context context)114 public static int getExtraSignedKeysAvailable(Context context) { 115 SharedPreferences sharedPref = 116 context.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE); 117 return sharedPref.getInt(KEY_EXTRA_KEYS, EXTRA_SIGNED_KEYS_AVAILABLE_DEFAULT); 118 } 119 120 /** 121 * Gets the setting for how far into the future the provisioner should check for expiring keys. 122 */ getExpiringBy(Context context)123 public static Duration getExpiringBy(Context context) { 124 SharedPreferences sharedPref = 125 context.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE); 126 return Duration.ofMillis(sharedPref.getLong(KEY_EXPIRING_BY, EXPIRING_BY_MS_DEFAULT)); 127 } 128 129 /** 130 * Gets the setting for what base URL the provisioner should use to talk to provisioning 131 * servers. 132 */ getUrl(Context context)133 public static String getUrl(Context context) { 134 SharedPreferences sharedPref = 135 context.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE); 136 return sharedPref.getString(KEY_URL, URL_DEFAULT); 137 } 138 139 /** 140 * Increments the failure counter. This is intended to be used when reaching the server fails 141 * for any reason so that the app logic can decide if the preferences should be reset to 142 * defaults in the event that a bad push stored an incorrect URL string. 143 * 144 * @return the current failure counter after incrementing. 145 */ incrementFailureCounter(Context context)146 public static int incrementFailureCounter(Context context) { 147 SharedPreferences sharedPref = 148 context.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE); 149 SharedPreferences.Editor editor = sharedPref.edit(); 150 int failures = sharedPref.getInt(KEY_FAILURE_COUNTER, 0 /* defaultValue */); 151 editor.putInt(KEY_FAILURE_COUNTER, ++failures); 152 editor.apply(); 153 return failures; 154 } 155 156 /** 157 * Gets the current failure counter. 158 */ getFailureCounter(Context context)159 public static int getFailureCounter(Context context) { 160 SharedPreferences sharedPref = 161 context.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE); 162 return sharedPref.getInt(KEY_FAILURE_COUNTER, 0 /* defaultValue */); 163 } 164 165 /** 166 * Resets the failure counter to {@code 0}. 167 */ clearFailureCounter(Context context)168 public static void clearFailureCounter(Context context) { 169 SharedPreferences sharedPref = 170 context.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE); 171 if (sharedPref.getInt(KEY_FAILURE_COUNTER, 0) != 0) { 172 SharedPreferences.Editor editor = sharedPref.edit(); 173 editor.putInt(KEY_FAILURE_COUNTER, 0); 174 editor.apply(); 175 } 176 } 177 178 /** 179 * Clears all preferences, thus restoring the defaults. 180 */ clearPreferences(Context context)181 public static void clearPreferences(Context context) { 182 SharedPreferences sharedPref = 183 context.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE); 184 SharedPreferences.Editor editor = sharedPref.edit(); 185 editor.clear(); 186 editor.apply(); 187 } 188 } 189