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