1 /*
2  * Copyright (C) 2020 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.car.settings.security;
18 
19 import android.content.Context;
20 import android.os.CountDownTimer;
21 import android.os.SystemClock;
22 
23 import androidx.annotation.Nullable;
24 
25 import com.android.car.settings.R;
26 import com.android.internal.annotations.VisibleForTesting;
27 import com.android.internal.widget.LockPatternUtils;
28 
29 /** Common lockout handling code. */
30 public class ConfirmLockLockoutHelper {
31 
32     private static ConfirmLockLockoutHelper sInstance;
33 
34     private final Context mContext;
35     private final int mUserId;
36     private final LockPatternUtils mLockPatternUtils;
37     private ConfirmLockUIController mUiController;
38     private CountDownTimer mCountDownTimer;
39 
40     /** Return an instance of {@link ConfirmLockLockoutHelper}. */
getInstance(Context context, int userId)41     public static ConfirmLockLockoutHelper getInstance(Context context, int userId) {
42         if (sInstance == null) {
43             sInstance = new ConfirmLockLockoutHelper(context, userId,
44                     new LockPatternUtils(context));
45         }
46         return sInstance;
47     }
48 
49     @VisibleForTesting
ConfirmLockLockoutHelper(Context context, int userId, LockPatternUtils lockPatternUtils)50     ConfirmLockLockoutHelper(Context context, int userId,
51             LockPatternUtils lockPatternUtils) {
52         mContext = context;
53         mUserId = userId;
54         mLockPatternUtils = lockPatternUtils;
55     }
56 
57     /** Sets the UI controller. */
setConfirmLockUIController(ConfirmLockUIController uiController)58     public void setConfirmLockUIController(ConfirmLockUIController uiController) {
59         mUiController = uiController;
60     }
61 
62     /** Gets the lock pattern utils used by this helper. */
getLockPatternUtils()63     public LockPatternUtils getLockPatternUtils() {
64         return mLockPatternUtils;
65     }
66 
67     /** Handles when the lock check is completed but returns a timeout. */
onCheckCompletedWithTimeout(int timeoutMs)68     public void onCheckCompletedWithTimeout(int timeoutMs) {
69         if (timeoutMs <= 0) {
70             return;
71         }
72 
73         long deadline = mLockPatternUtils.setLockoutAttemptDeadline(mUserId, timeoutMs);
74         handleAttemptLockout(deadline);
75     }
76 
77     /** To be called when the UI is resumed to reset the timeout countdown if necessary. */
onResumeUI()78     public void onResumeUI() {
79         if (isLockedOut()) {
80             handleAttemptLockout(mLockPatternUtils.getLockoutAttemptDeadline(mUserId));
81         } else {
82             mUiController.refreshUI(isLockedOut());
83         }
84     }
85 
86     /** To be called when the UI is paused to cancel the ongoing countdown timer. */
onPauseUI()87     public void onPauseUI() {
88         if (mCountDownTimer != null) {
89             mCountDownTimer.cancel();
90             mUiController.setErrorText("");
91         }
92     }
93 
94     @VisibleForTesting
95     @Nullable
getCountDownTimer()96     CountDownTimer getCountDownTimer() {
97         return mCountDownTimer;
98     }
99 
handleAttemptLockout(long deadline)100     private void handleAttemptLockout(long deadline) {
101         long elapsedRealtime = SystemClock.elapsedRealtime();
102         mUiController.refreshUI(isLockedOut());
103         mCountDownTimer = newCountDownTimer(deadline - elapsedRealtime).start();
104     }
105 
isLockedOut()106     private boolean isLockedOut() {
107         return mLockPatternUtils.getLockoutAttemptDeadline(mUserId) != 0;
108     }
109 
newCountDownTimer(long countDownMillis)110     private CountDownTimer newCountDownTimer(long countDownMillis) {
111         return new CountDownTimer(countDownMillis,
112                 LockPatternUtils.FAILED_ATTEMPT_COUNTDOWN_INTERVAL_MS) {
113             @Override
114             public void onTick(long millisUntilFinished) {
115                 int secondsCountdown = (int) (millisUntilFinished / 1000);
116                 mUiController.setErrorText(
117                         mContext.getString(
118                                 R.string.lockpattern_too_many_failed_confirmation_attempts,
119                                 secondsCountdown));
120             }
121 
122             @Override
123             public void onFinish() {
124                 mUiController.refreshUI(/* isLockedOut= */ false);
125                 mUiController.setErrorText("");
126             }
127         };
128     }
129 
130     /** Interface for controlling the associated lock UI. */
131     public interface ConfirmLockUIController {
132         /** Sets the error text with the given string. */
133         void setErrorText(String text);
134         /** Refreshes the UI based on the locked out state. */
135         void refreshUI(boolean isLockedOut);
136     }
137 }
138