| /** |
| * Copyright (C) 2021 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.remoteprovisioner; |
| |
| import android.content.Context; |
| import android.content.SharedPreferences; |
| import android.util.Log; |
| |
| import java.time.Duration; |
| import java.util.Random; |
| |
| /** |
| * SettingsManager makes use of SharedPreferences in order to store key/value pairs related to |
| * configuration settings that can be retrieved from the server. In the event that none have yet |
| * been retrieved, or for some reason a reset has occurred, there are reasonable default values. |
| */ |
| public class SettingsManager { |
| |
| public static final int ID_UPPER_BOUND = 1000000; |
| public static final int EXTRA_SIGNED_KEYS_AVAILABLE_DEFAULT = 6; |
| // Check for expiring certs in the next 3 days |
| public static final int EXPIRING_BY_MS_DEFAULT = 1000 * 60 * 60 * 24 * 3; |
| public static final String URL_DEFAULT = "https://remoteprovisioning.googleapis.com/v1alpha1"; |
| |
| private static final String KEY_EXPIRING_BY = "expiring_by"; |
| private static final String KEY_EXTRA_KEYS = "extra_keys"; |
| private static final String KEY_ID = "settings_id"; |
| private static final String KEY_FAILURE_COUNTER = "failure_counter"; |
| private static final String KEY_URL = "url"; |
| private static final String PREFERENCES_NAME = "com.android.remoteprovisioner.preferences"; |
| private static final String TAG = "RemoteProvisionerSettings"; |
| |
| /** |
| * Generates a random ID for the use of gradual ramp up of remote provisioning. |
| */ |
| public static void generateAndSetId(Context context) { |
| SharedPreferences sharedPref = |
| context.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE); |
| if (sharedPref.contains(KEY_ID)) { |
| // ID is already set, don't rotate it. |
| return; |
| } |
| Log.i(TAG, "Setting ID"); |
| Random rand = new Random(); |
| SharedPreferences.Editor editor = sharedPref.edit(); |
| editor.putInt(KEY_ID, rand.nextInt(ID_UPPER_BOUND)); |
| editor.apply(); |
| } |
| |
| /** |
| * Fetches the generated ID. |
| */ |
| public static int getId(Context context) { |
| SharedPreferences sharedPref = |
| context.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE); |
| Random rand = new Random(); |
| return sharedPref.getInt(KEY_ID, rand.nextInt(ID_UPPER_BOUND) /* defaultValue */); |
| } |
| |
| /** |
| * Sets the remote provisioning configuration values based on what was fetched from the server. |
| * The server is not guaranteed to have sent every available parameter in the config that |
| * was returned to the device, so the parameters should be checked for null values. |
| * |
| * @param extraKeys How many server signed remote provisioning key pairs that should be kept |
| * available in KeyStore. |
| * @param expiringBy How far in the future the app should check for expiring keys. |
| * @param url The base URL for the provisioning server. |
| * @return {@code true} if any settings were updated. |
| */ |
| public static boolean setDeviceConfig(Context context, int extraKeys, |
| Duration expiringBy, String url) { |
| SharedPreferences sharedPref = |
| context.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE); |
| SharedPreferences.Editor editor = sharedPref.edit(); |
| boolean wereUpdatesMade = false; |
| if (extraKeys != GeekResponse.NO_EXTRA_KEY_UPDATE |
| && sharedPref.getInt(KEY_EXTRA_KEYS, -5) != extraKeys) { |
| editor.putInt(KEY_EXTRA_KEYS, extraKeys); |
| wereUpdatesMade = true; |
| } |
| if (expiringBy != null |
| && sharedPref.getLong(KEY_EXPIRING_BY, -1) != expiringBy.toMillis()) { |
| editor.putLong(KEY_EXPIRING_BY, expiringBy.toMillis()); |
| wereUpdatesMade = true; |
| } |
| if (url != null && !sharedPref.getString(KEY_URL, "").equals(url)) { |
| editor.putString(KEY_URL, url); |
| wereUpdatesMade = true; |
| } |
| if (wereUpdatesMade) { |
| editor.apply(); |
| } |
| return wereUpdatesMade; |
| } |
| |
| /** |
| * Gets the setting for how many extra keys should be kept signed and available in KeyStore. |
| */ |
| public static int getExtraSignedKeysAvailable(Context context) { |
| SharedPreferences sharedPref = |
| context.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE); |
| return sharedPref.getInt(KEY_EXTRA_KEYS, EXTRA_SIGNED_KEYS_AVAILABLE_DEFAULT); |
| } |
| |
| /** |
| * Gets the setting for how far into the future the provisioner should check for expiring keys. |
| */ |
| public static Duration getExpiringBy(Context context) { |
| SharedPreferences sharedPref = |
| context.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE); |
| return Duration.ofMillis(sharedPref.getLong(KEY_EXPIRING_BY, EXPIRING_BY_MS_DEFAULT)); |
| } |
| |
| /** |
| * Gets the setting for what base URL the provisioner should use to talk to provisioning |
| * servers. |
| */ |
| public static String getUrl(Context context) { |
| SharedPreferences sharedPref = |
| context.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE); |
| return sharedPref.getString(KEY_URL, URL_DEFAULT); |
| } |
| |
| /** |
| * Increments the failure counter. This is intended to be used when reaching the server fails |
| * for any reason so that the app logic can decide if the preferences should be reset to |
| * defaults in the event that a bad push stored an incorrect URL string. |
| * |
| * @return the current failure counter after incrementing. |
| */ |
| public static int incrementFailureCounter(Context context) { |
| SharedPreferences sharedPref = |
| context.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE); |
| SharedPreferences.Editor editor = sharedPref.edit(); |
| int failures = sharedPref.getInt(KEY_FAILURE_COUNTER, 0 /* defaultValue */); |
| editor.putInt(KEY_FAILURE_COUNTER, ++failures); |
| editor.apply(); |
| return failures; |
| } |
| |
| /** |
| * Gets the current failure counter. |
| */ |
| public static int getFailureCounter(Context context) { |
| SharedPreferences sharedPref = |
| context.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE); |
| return sharedPref.getInt(KEY_FAILURE_COUNTER, 0 /* defaultValue */); |
| } |
| |
| /** |
| * Resets the failure counter to {@code 0}. |
| */ |
| public static void clearFailureCounter(Context context) { |
| SharedPreferences sharedPref = |
| context.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE); |
| if (sharedPref.getInt(KEY_FAILURE_COUNTER, 0) != 0) { |
| SharedPreferences.Editor editor = sharedPref.edit(); |
| editor.putInt(KEY_FAILURE_COUNTER, 0); |
| editor.apply(); |
| } |
| } |
| |
| /** |
| * Clears all preferences, thus restoring the defaults. |
| */ |
| public static void clearPreferences(Context context) { |
| SharedPreferences sharedPref = |
| context.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE); |
| SharedPreferences.Editor editor = sharedPref.edit(); |
| editor.clear(); |
| editor.apply(); |
| } |
| } |