/* * Copyright (C) 2023 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.devicelockcontroller.storage; import android.annotation.CurrentTimeMillisLong; import android.content.Context; import android.content.SharedPreferences; import android.os.Build; import androidx.annotation.Nullable; import androidx.annotation.WorkerThread; import com.android.devicelockcontroller.policy.ProvisionStateController.ProvisionState; import com.android.devicelockcontroller.util.LogUtil; import com.android.devicelockcontroller.util.ThreadAsserts; import java.util.Locale; import java.util.concurrent.Executors; /** * Stores per-user local parameters. * Unlike {@link GlobalParameters}, this class can be directly accessed. */ public final class UserParameters { private static final String FILENAME = "user-params"; private static final String TAG = "UserParameters"; private static final String KEY_PROVISION_STATE = "provision-state"; private static final String KEY_BOOT_TIME_MILLS = "boot-time-mills"; private static final String KEY_NEXT_CHECK_IN_TIME_MILLIS = "next-check-in-time-millis"; private static final String KEY_RESUME_PROVISION_TIME_MILLIS = "resume-provision-time-millis"; private static final String KEY_NEXT_PROVISION_FAILED_STEP_TIME_MILLIS = "next-provision-failed-step-time-millis"; private static final String KEY_RESET_DEVICE_TIME_MILLIS = "reset-device-time-millis"; private static final String KEY_DAYS_LEFT_UNTIL_RESET = "days-left-until-reset"; private static final String KEY_PROVISIONING_START_TIME_MILLIS = "provisioning-start-time-millis"; public static final String KEY_NEED_INITIAL_CHECK_IN = "need-initial-check-in"; public static final String KEY_NOTIFICATION_CHANNEL_ID_SUFFIX = "notification-channel-id-suffix"; public static final String KEY_SUW_TIMED_OUT = "suw-timed-out"; private UserParameters() { } private static SharedPreferences getSharedPreferences(Context context) { final Context deviceContext = context.createDeviceProtectedStorageContext(); return deviceContext.getSharedPreferences(FILENAME, Context.MODE_PRIVATE); } /** * Gets the current user state. */ @WorkerThread @ProvisionState public static int getProvisionState(Context context) { ThreadAsserts.assertWorkerThread("getProvisionState"); return getSharedPreferences(context).getInt(KEY_PROVISION_STATE, ProvisionState.UNPROVISIONED); } /** * Sets the current user state. */ public static void setProvisionState(Context context, @ProvisionState int state) { getSharedPreferences(context).edit().putInt(KEY_PROVISION_STATE, state).apply(); } /** Check if initial check-in is required. */ @WorkerThread public static boolean needInitialCheckIn(Context context) { ThreadAsserts.assertWorkerThread("needInitialCheckIn"); return getSharedPreferences(context).getBoolean(KEY_NEED_INITIAL_CHECK_IN, true); } /** Mark initial check-in has been scheduled. */ public static void initialCheckInScheduled(Context context) { getSharedPreferences(context).edit().putBoolean(KEY_NEED_INITIAL_CHECK_IN, false).apply(); } /** Get the device boot time */ @WorkerThread @CurrentTimeMillisLong public static long getBootTimeMillis(Context context) { ThreadAsserts.assertWorkerThread("getBootTimeMillis"); return getSharedPreferences(context).getLong(KEY_BOOT_TIME_MILLS, 0L); } /** Set the time when device boot */ public static void setBootTimeMillis(Context context, @CurrentTimeMillisLong long bootTime) { getSharedPreferences(context).edit().putLong(KEY_BOOT_TIME_MILLS, bootTime).apply(); } /** Get the time when next check in should happen */ @WorkerThread @CurrentTimeMillisLong public static long getNextCheckInTimeMillis(Context context) { ThreadAsserts.assertWorkerThread("getNextCheckInTimeMillis"); return getSharedPreferences(context).getLong(KEY_NEXT_CHECK_IN_TIME_MILLIS, 0L); } /** Set the time when next check in should happen */ public static void setNextCheckInTimeMillis(Context context, @CurrentTimeMillisLong long nextCheckInTime) { getSharedPreferences(context).edit().putLong(KEY_NEXT_CHECK_IN_TIME_MILLIS, nextCheckInTime).apply(); } /** Get the time when provision should resume */ @WorkerThread @CurrentTimeMillisLong public static long getResumeProvisionTimeMillis(Context context) { ThreadAsserts.assertWorkerThread("getResumeProvisionTimeMillis"); return getSharedPreferences(context).getLong(KEY_RESUME_PROVISION_TIME_MILLIS, 0L); } /** Set the time when provision should resume */ public static void setResumeProvisionTimeMillis(Context context, @CurrentTimeMillisLong long resumeProvisionTime) { getSharedPreferences(context).edit().putLong(KEY_RESUME_PROVISION_TIME_MILLIS, resumeProvisionTime).apply(); } /** Get the time when next provision failed step should happen */ @WorkerThread @CurrentTimeMillisLong public static long getNextProvisionFailedStepTimeMills(Context context) { ThreadAsserts.assertWorkerThread("getNextProvisionFailedStepTimeMills"); return getSharedPreferences(context).getLong(KEY_NEXT_PROVISION_FAILED_STEP_TIME_MILLIS, 0L); } /** Set the time when next provision failed step should happen */ public static void setNextProvisionFailedStepTimeMills(Context context, @CurrentTimeMillisLong long nextProvisionFailedStep) { getSharedPreferences(context).edit().putLong(KEY_NEXT_PROVISION_FAILED_STEP_TIME_MILLIS, nextProvisionFailedStep).apply(); } /** Get the time when device should factory reset */ @WorkerThread @CurrentTimeMillisLong public static long getResetDeviceTimeMillis(Context context) { ThreadAsserts.assertWorkerThread("getResetDeviceTimeMillis"); return getSharedPreferences(context).getLong(KEY_RESET_DEVICE_TIME_MILLIS, 0L); } /** Set the time when device should factory reset */ public static void setResetDeviceTimeMillis(Context context, @CurrentTimeMillisLong long resetDeviceTime) { getSharedPreferences(context).edit().putLong(KEY_RESET_DEVICE_TIME_MILLIS, resetDeviceTime).apply(); } /** Get the number of days before device should factory reset */ @WorkerThread public static int getDaysLeftUntilReset(Context context) { ThreadAsserts.assertWorkerThread("getDaysLeftUntilReset"); return getSharedPreferences(context).getInt(KEY_DAYS_LEFT_UNTIL_RESET, Integer.MAX_VALUE); } /** Set the number of days before device should factory reset */ public static void setDaysLeftUntilReset(Context context, int days) { getSharedPreferences(context).edit().putInt(KEY_DAYS_LEFT_UNTIL_RESET, days).apply(); } /** Get the provisioning start time */ public static long getProvisioningStartTimeMillis(Context context) { return getSharedPreferences(context).getLong(KEY_PROVISIONING_START_TIME_MILLIS, /* defValue = */-1L); } /** Set the provisioning start time */ public static void setProvisioningStartTimeMillis(Context context, @CurrentTimeMillisLong long provisioningStartTime) { getSharedPreferences(context).edit().putLong(KEY_PROVISIONING_START_TIME_MILLIS, provisioningStartTime).apply(); } /** Get the suffix used for the notification channel */ @WorkerThread public static String getNotificationChannelIdSuffix(Context context) { ThreadAsserts.assertWorkerThread("getNotificationChannelIdSuffix"); return getSharedPreferences(context).getString(KEY_NOTIFICATION_CHANNEL_ID_SUFFIX, /* defValue= */ ""); } /** Set the suffix used for the notification channel */ @WorkerThread public static void setNotificationChannelIdSuffix(Context context, @Nullable String notificationChannelSuffix) { ThreadAsserts.assertWorkerThread("setNotificationChannelIdSuffix"); getSharedPreferences(context).edit() .putString(KEY_NOTIFICATION_CHANNEL_ID_SUFFIX, notificationChannelSuffix).apply(); } /** Check if SUW timed out. */ @WorkerThread public static boolean isSetupWizardTimedOut(Context context) { ThreadAsserts.assertWorkerThread("isSetupWizardTimedOut"); return getSharedPreferences(context).getBoolean(KEY_SUW_TIMED_OUT, false); } /** Set the provisioning start time */ public static void setSetupWizardTimedOut(Context context) { getSharedPreferences(context).edit().putBoolean(KEY_SUW_TIMED_OUT, true).apply(); } /** * Clear all user parameters. */ @WorkerThread public static void clear(Context context) { ThreadAsserts.assertWorkerThread("clear"); if (!Build.isDebuggable()) { throw new SecurityException("Clear is not allowed in non-debuggable build!"); } // We want to keep the boot time in order to reschedule works/alarms when system clock // changes. long bootTime = UserParameters.getBootTimeMillis(context); getSharedPreferences(context).edit().clear().commit(); UserParameters.setBootTimeMillis(context, bootTime); } /** * Dump the current value of user parameters for the user associated with the input context. */ public static void dump(Context context) { Executors.newSingleThreadScheduledExecutor().submit(() -> { LogUtil.d(TAG, String.format(Locale.US, "Dumping UserParameters for user: %s ...\n" + "%s: %s\n" // user_state: + "%s: %s\n" // boot-time-mills: + "%s: %s\n" // next-check-in-time-millis: + "%s: %s\n" // resume-provision-time-millis: + "%s: %s\n" // next-provision-failed-step-time-millis: + "%s: %s\n" // reset-device-time-millis: + "%s: %s\n" // days-left-until-reset: + "%s: %s\n" // notification-channel-suffix: + "%s: %s\n", // suw-timed-out: context.getUser(), KEY_PROVISION_STATE, getProvisionState(context), KEY_BOOT_TIME_MILLS, getBootTimeMillis(context), KEY_NEXT_CHECK_IN_TIME_MILLIS, getNextCheckInTimeMillis(context), KEY_RESUME_PROVISION_TIME_MILLIS, getResumeProvisionTimeMillis(context), KEY_NEXT_PROVISION_FAILED_STEP_TIME_MILLIS, getNextProvisionFailedStepTimeMills(context), KEY_RESET_DEVICE_TIME_MILLIS, getResetDeviceTimeMillis(context), KEY_DAYS_LEFT_UNTIL_RESET, getDaysLeftUntilReset(context), KEY_NOTIFICATION_CHANNEL_ID_SUFFIX, getNotificationChannelIdSuffix(context), KEY_SUW_TIMED_OUT, isSetupWizardTimedOut(context) )); }); } }