1 /*
2  * Copyright (C) 2023 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.devicelockcontroller.storage;
18 
19 import android.annotation.CurrentTimeMillisLong;
20 import android.content.Context;
21 import android.content.SharedPreferences;
22 import android.os.Build;
23 
24 import androidx.annotation.Nullable;
25 import androidx.annotation.WorkerThread;
26 
27 import com.android.devicelockcontroller.policy.ProvisionStateController.ProvisionState;
28 import com.android.devicelockcontroller.util.LogUtil;
29 import com.android.devicelockcontroller.util.ThreadAsserts;
30 
31 import java.util.Locale;
32 import java.util.concurrent.Executors;
33 
34 /**
35  * Stores per-user local parameters.
36  * Unlike {@link GlobalParameters}, this class can be directly accessed.
37  */
38 public final class UserParameters {
39     private static final String FILENAME = "user-params";
40     private static final String TAG = "UserParameters";
41     private static final String KEY_PROVISION_STATE = "provision-state";
42     private static final String KEY_BOOT_TIME_MILLS = "boot-time-mills";
43     private static final String KEY_NEXT_CHECK_IN_TIME_MILLIS = "next-check-in-time-millis";
44     private static final String KEY_RESUME_PROVISION_TIME_MILLIS =
45             "resume-provision-time-millis";
46     private static final String KEY_NEXT_PROVISION_FAILED_STEP_TIME_MILLIS =
47             "next-provision-failed-step-time-millis";
48     private static final String KEY_RESET_DEVICE_TIME_MILLIS = "reset-device-time-millis";
49     private static final String KEY_DAYS_LEFT_UNTIL_RESET = "days-left-until-reset";
50     private static final String KEY_PROVISIONING_START_TIME_MILLIS =
51             "provisioning-start-time-millis";
52     public static final String KEY_NEED_INITIAL_CHECK_IN = "need-initial-check-in";
53     public static final String KEY_NOTIFICATION_CHANNEL_ID_SUFFIX =
54             "notification-channel-id-suffix";
55     public static final String KEY_SUW_TIMED_OUT = "suw-timed-out";
56 
UserParameters()57     private UserParameters() {
58     }
59 
getSharedPreferences(Context context)60     private static SharedPreferences getSharedPreferences(Context context) {
61         final Context deviceContext = context.createDeviceProtectedStorageContext();
62 
63         return deviceContext.getSharedPreferences(FILENAME, Context.MODE_PRIVATE);
64     }
65 
66     /**
67      * Gets the current user state.
68      */
69     @WorkerThread
70     @ProvisionState
getProvisionState(Context context)71     public static int getProvisionState(Context context) {
72         ThreadAsserts.assertWorkerThread("getProvisionState");
73         return getSharedPreferences(context).getInt(KEY_PROVISION_STATE,
74                 ProvisionState.UNPROVISIONED);
75     }
76 
77     /**
78      * Sets the current user state.
79      */
setProvisionState(Context context, @ProvisionState int state)80     public static void setProvisionState(Context context, @ProvisionState int state) {
81         getSharedPreferences(context).edit().putInt(KEY_PROVISION_STATE, state).apply();
82     }
83 
84     /** Check if initial check-in is required. */
85     @WorkerThread
needInitialCheckIn(Context context)86     public static boolean needInitialCheckIn(Context context) {
87         ThreadAsserts.assertWorkerThread("needInitialCheckIn");
88         return getSharedPreferences(context).getBoolean(KEY_NEED_INITIAL_CHECK_IN, true);
89     }
90 
91     /** Mark initial check-in has been scheduled. */
initialCheckInScheduled(Context context)92     public static void initialCheckInScheduled(Context context) {
93         getSharedPreferences(context).edit().putBoolean(KEY_NEED_INITIAL_CHECK_IN, false).apply();
94     }
95 
96     /** Get the device boot time */
97     @WorkerThread
98     @CurrentTimeMillisLong
getBootTimeMillis(Context context)99     public static long getBootTimeMillis(Context context) {
100         ThreadAsserts.assertWorkerThread("getBootTimeMillis");
101         return getSharedPreferences(context).getLong(KEY_BOOT_TIME_MILLS, 0L);
102     }
103 
104     /** Set the time when device boot */
setBootTimeMillis(Context context, @CurrentTimeMillisLong long bootTime)105     public static void setBootTimeMillis(Context context, @CurrentTimeMillisLong long bootTime) {
106         getSharedPreferences(context).edit().putLong(KEY_BOOT_TIME_MILLS, bootTime).apply();
107     }
108 
109     /** Get the time when next check in should happen */
110     @WorkerThread
111     @CurrentTimeMillisLong
getNextCheckInTimeMillis(Context context)112     public static long getNextCheckInTimeMillis(Context context) {
113         ThreadAsserts.assertWorkerThread("getNextCheckInTimeMillis");
114         return getSharedPreferences(context).getLong(KEY_NEXT_CHECK_IN_TIME_MILLIS, 0L);
115     }
116 
117     /** Set the time when next check in should happen */
setNextCheckInTimeMillis(Context context, @CurrentTimeMillisLong long nextCheckInTime)118     public static void setNextCheckInTimeMillis(Context context,
119             @CurrentTimeMillisLong long nextCheckInTime) {
120         getSharedPreferences(context).edit().putLong(KEY_NEXT_CHECK_IN_TIME_MILLIS,
121                 nextCheckInTime).apply();
122     }
123 
124     /** Get the time when provision should resume */
125     @WorkerThread
126     @CurrentTimeMillisLong
getResumeProvisionTimeMillis(Context context)127     public static long getResumeProvisionTimeMillis(Context context) {
128         ThreadAsserts.assertWorkerThread("getResumeProvisionTimeMillis");
129         return getSharedPreferences(context).getLong(KEY_RESUME_PROVISION_TIME_MILLIS, 0L);
130     }
131 
132     /** Set the time when provision should resume */
setResumeProvisionTimeMillis(Context context, @CurrentTimeMillisLong long resumeProvisionTime)133     public static void setResumeProvisionTimeMillis(Context context,
134             @CurrentTimeMillisLong long resumeProvisionTime) {
135         getSharedPreferences(context).edit().putLong(KEY_RESUME_PROVISION_TIME_MILLIS,
136                 resumeProvisionTime).apply();
137     }
138 
139     /** Get the time when next provision failed step should happen */
140     @WorkerThread
141     @CurrentTimeMillisLong
getNextProvisionFailedStepTimeMills(Context context)142     public static long getNextProvisionFailedStepTimeMills(Context context) {
143         ThreadAsserts.assertWorkerThread("getNextProvisionFailedStepTimeMills");
144         return getSharedPreferences(context).getLong(KEY_NEXT_PROVISION_FAILED_STEP_TIME_MILLIS,
145                 0L);
146     }
147 
148     /** Set the time when next provision failed step should happen */
setNextProvisionFailedStepTimeMills(Context context, @CurrentTimeMillisLong long nextProvisionFailedStep)149     public static void setNextProvisionFailedStepTimeMills(Context context,
150             @CurrentTimeMillisLong long nextProvisionFailedStep) {
151         getSharedPreferences(context).edit().putLong(KEY_NEXT_PROVISION_FAILED_STEP_TIME_MILLIS,
152                 nextProvisionFailedStep).apply();
153     }
154 
155     /** Get the time when device should factory reset */
156     @WorkerThread
157     @CurrentTimeMillisLong
getResetDeviceTimeMillis(Context context)158     public static long getResetDeviceTimeMillis(Context context) {
159         ThreadAsserts.assertWorkerThread("getResetDeviceTimeMillis");
160         return getSharedPreferences(context).getLong(KEY_RESET_DEVICE_TIME_MILLIS, 0L);
161     }
162 
163     /** Set the time when device should factory reset */
setResetDeviceTimeMillis(Context context, @CurrentTimeMillisLong long resetDeviceTime)164     public static void setResetDeviceTimeMillis(Context context,
165             @CurrentTimeMillisLong long resetDeviceTime) {
166         getSharedPreferences(context).edit().putLong(KEY_RESET_DEVICE_TIME_MILLIS,
167                 resetDeviceTime).apply();
168     }
169 
170     /** Get the number of days before device should factory reset */
171     @WorkerThread
getDaysLeftUntilReset(Context context)172     public static int getDaysLeftUntilReset(Context context) {
173         ThreadAsserts.assertWorkerThread("getDaysLeftUntilReset");
174         return getSharedPreferences(context).getInt(KEY_DAYS_LEFT_UNTIL_RESET, Integer.MAX_VALUE);
175     }
176 
177     /** Set the number of days before device should factory reset */
setDaysLeftUntilReset(Context context, int days)178     public static void setDaysLeftUntilReset(Context context, int days) {
179         getSharedPreferences(context).edit().putInt(KEY_DAYS_LEFT_UNTIL_RESET, days).apply();
180     }
181 
182     /** Get the provisioning start time */
getProvisioningStartTimeMillis(Context context)183     public static long getProvisioningStartTimeMillis(Context context) {
184         return getSharedPreferences(context).getLong(KEY_PROVISIONING_START_TIME_MILLIS,
185                 /* defValue = */-1L);
186     }
187 
188     /** Set the provisioning start time */
setProvisioningStartTimeMillis(Context context, @CurrentTimeMillisLong long provisioningStartTime)189     public static void setProvisioningStartTimeMillis(Context context,
190             @CurrentTimeMillisLong long provisioningStartTime) {
191         getSharedPreferences(context).edit().putLong(KEY_PROVISIONING_START_TIME_MILLIS,
192                 provisioningStartTime).apply();
193     }
194 
195     /** Get the suffix used for the notification channel */
196     @WorkerThread
getNotificationChannelIdSuffix(Context context)197     public static String getNotificationChannelIdSuffix(Context context) {
198         ThreadAsserts.assertWorkerThread("getNotificationChannelIdSuffix");
199         return getSharedPreferences(context).getString(KEY_NOTIFICATION_CHANNEL_ID_SUFFIX,
200                 /* defValue= */ "");
201     }
202 
203     /** Set the suffix used for the notification channel */
204     @WorkerThread
setNotificationChannelIdSuffix(Context context, @Nullable String notificationChannelSuffix)205     public static void setNotificationChannelIdSuffix(Context context,
206             @Nullable String notificationChannelSuffix) {
207         ThreadAsserts.assertWorkerThread("setNotificationChannelIdSuffix");
208         getSharedPreferences(context).edit()
209                 .putString(KEY_NOTIFICATION_CHANNEL_ID_SUFFIX, notificationChannelSuffix).apply();
210     }
211 
212     /** Check if SUW timed out. */
213     @WorkerThread
isSetupWizardTimedOut(Context context)214     public static boolean isSetupWizardTimedOut(Context context) {
215         ThreadAsserts.assertWorkerThread("isSetupWizardTimedOut");
216         return getSharedPreferences(context).getBoolean(KEY_SUW_TIMED_OUT, false);
217     }
218 
219     /** Set the provisioning start time */
setSetupWizardTimedOut(Context context)220     public static void setSetupWizardTimedOut(Context context) {
221         getSharedPreferences(context).edit().putBoolean(KEY_SUW_TIMED_OUT, true).apply();
222     }
223 
224     /**
225      * Clear all user parameters.
226      */
227     @WorkerThread
clear(Context context)228     public static void clear(Context context) {
229         ThreadAsserts.assertWorkerThread("clear");
230         if (!Build.isDebuggable()) {
231             throw new SecurityException("Clear is not allowed in non-debuggable build!");
232         }
233         // We want to keep the boot time in order to reschedule works/alarms when system clock
234         // changes.
235         long bootTime = UserParameters.getBootTimeMillis(context);
236         getSharedPreferences(context).edit().clear().commit();
237         UserParameters.setBootTimeMillis(context, bootTime);
238     }
239 
240     /**
241      * Dump the current value of user parameters for the user associated with the input context.
242      */
dump(Context context)243     public static void dump(Context context) {
244         Executors.newSingleThreadScheduledExecutor().submit(() -> {
245             LogUtil.d(TAG, String.format(Locale.US,
246                     "Dumping UserParameters for user: %s ...\n"
247                             + "%s: %s\n"    // user_state:
248                             + "%s: %s\n"    // boot-time-mills:
249                             + "%s: %s\n"    // next-check-in-time-millis:
250                             + "%s: %s\n"    // resume-provision-time-millis:
251                             + "%s: %s\n"    // next-provision-failed-step-time-millis:
252                             + "%s: %s\n"    // reset-device-time-millis:
253                             + "%s: %s\n"    // days-left-until-reset:
254                             + "%s: %s\n"    // notification-channel-suffix:
255                             + "%s: %s\n",   // suw-timed-out:
256                     context.getUser(),
257                     KEY_PROVISION_STATE, getProvisionState(context),
258                     KEY_BOOT_TIME_MILLS, getBootTimeMillis(context),
259                     KEY_NEXT_CHECK_IN_TIME_MILLIS, getNextCheckInTimeMillis(context),
260                     KEY_RESUME_PROVISION_TIME_MILLIS, getResumeProvisionTimeMillis(context),
261                     KEY_NEXT_PROVISION_FAILED_STEP_TIME_MILLIS,
262                     getNextProvisionFailedStepTimeMills(context),
263                     KEY_RESET_DEVICE_TIME_MILLIS, getResetDeviceTimeMillis(context),
264                     KEY_DAYS_LEFT_UNTIL_RESET, getDaysLeftUntilReset(context),
265                     KEY_NOTIFICATION_CHANNEL_ID_SUFFIX, getNotificationChannelIdSuffix(context),
266                     KEY_SUW_TIMED_OUT, isSetupWizardTimedOut(context)
267             ));
268         });
269     }
270 }
271