blob: 5808475c32c8281c1b6f50915967293afcc9ed18 [file] [log] [blame]
/**
* 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/v1";
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();
}
}