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.keyguard;
18 
19 import android.annotation.NonNull;
20 import android.app.Activity;
21 import android.app.AlertDialog;
22 import android.app.Dialog;
23 import android.app.ProgressDialog;
24 import android.content.res.ColorStateList;
25 import android.content.res.Resources;
26 import android.content.res.TypedArray;
27 import android.graphics.Color;
28 import android.telephony.PinResult;
29 import android.telephony.SubscriptionInfo;
30 import android.telephony.SubscriptionManager;
31 import android.telephony.TelephonyManager;
32 import android.util.Log;
33 import android.view.WindowManager;
34 import android.widget.ImageView;
35 
36 import com.android.internal.util.LatencyTracker;
37 import com.android.internal.widget.LockPatternUtils;
38 import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
39 import com.android.systemui.R;
40 import com.android.systemui.classifier.FalsingCollector;
41 import com.android.systemui.flags.FeatureFlags;
42 
43 public class KeyguardSimPukViewController
44         extends KeyguardPinBasedInputViewController<KeyguardSimPukView> {
45     private static final boolean DEBUG = KeyguardConstants.DEBUG;
46     public static final String TAG = "KeyguardSimPukView";
47 
48     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
49     private final TelephonyManager mTelephonyManager;
50 
51     private String mPukText;
52     private String mPinText;
53     private int mRemainingAttempts;
54     // Below flag is set to true during power-up or when a new SIM card inserted on device.
55     // When this is true and when SIM card is PUK locked state, on PIN lock screen, message would
56     // be displayed to inform user about the number of remaining PUK attempts left.
57     private boolean mShowDefaultMessage;
58     private StateMachine mStateMachine = new StateMachine();
59     private int mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
60     private CheckSimPuk mCheckSimPukThread;
61     private ProgressDialog mSimUnlockProgressDialog;
62 
63     KeyguardUpdateMonitorCallback mUpdateMonitorCallback = new KeyguardUpdateMonitorCallback() {
64         @Override
65         public void onSimStateChanged(int subId, int slotId, int simState) {
66             if (DEBUG) Log.v(TAG, "onSimStateChanged(subId=" + subId + ",state=" + simState + ")");
67             // If the SIM is unlocked via a key sequence through the emergency dialer, it will
68             // move into the READY state and the PUK lock keyguard should be removed.
69             if (simState == TelephonyManager.SIM_STATE_READY) {
70                 mRemainingAttempts = -1;
71                 mShowDefaultMessage = true;
72                 getKeyguardSecurityCallback().dismiss(true, KeyguardUpdateMonitor.getCurrentUser(),
73                         SecurityMode.SimPuk);
74             } else {
75                 resetState();
76             }
77         }
78     };
79     private ImageView mSimImageView;
80     private AlertDialog mRemainingAttemptsDialog;
81 
KeyguardSimPukViewController(KeyguardSimPukView view, KeyguardUpdateMonitor keyguardUpdateMonitor, SecurityMode securityMode, LockPatternUtils lockPatternUtils, KeyguardSecurityCallback keyguardSecurityCallback, KeyguardMessageAreaController.Factory messageAreaControllerFactory, LatencyTracker latencyTracker, LiftToActivateListener liftToActivateListener, TelephonyManager telephonyManager, FalsingCollector falsingCollector, EmergencyButtonController emergencyButtonController, FeatureFlags featureFlags)82     protected KeyguardSimPukViewController(KeyguardSimPukView view,
83             KeyguardUpdateMonitor keyguardUpdateMonitor,
84             SecurityMode securityMode, LockPatternUtils lockPatternUtils,
85             KeyguardSecurityCallback keyguardSecurityCallback,
86             KeyguardMessageAreaController.Factory messageAreaControllerFactory,
87             LatencyTracker latencyTracker, LiftToActivateListener liftToActivateListener,
88             TelephonyManager telephonyManager, FalsingCollector falsingCollector,
89             EmergencyButtonController emergencyButtonController, FeatureFlags featureFlags) {
90         super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
91                 messageAreaControllerFactory, latencyTracker, liftToActivateListener,
92                 emergencyButtonController, falsingCollector, featureFlags);
93         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
94         mTelephonyManager = telephonyManager;
95         mSimImageView = mView.findViewById(R.id.keyguard_sim);
96     }
97 
98     @Override
onViewAttached()99     protected void onViewAttached() {
100         super.onViewAttached();
101         mKeyguardUpdateMonitor.registerCallback(mUpdateMonitorCallback);
102     }
103 
104     @Override
onViewDetached()105     protected void onViewDetached() {
106         super.onViewDetached();
107         mKeyguardUpdateMonitor.removeCallback(mUpdateMonitorCallback);
108     }
109 
110     @Override
onResume(int reason)111     public void onResume(int reason) {
112         super.onResume(reason);
113         if (mShowDefaultMessage) {
114             showDefaultMessage();
115         }
116     }
117 
118     @Override
resetState()119     void resetState() {
120         super.resetState();
121         mStateMachine.reset();
122     }
123 
124     @Override
verifyPasswordAndUnlock()125     protected void verifyPasswordAndUnlock() {
126         mStateMachine.next();
127     }
128 
129     private class StateMachine {
130         static final int ENTER_PUK = 0;
131         static final int ENTER_PIN = 1;
132         static final int CONFIRM_PIN = 2;
133         static final int DONE = 3;
134 
135         private int mState = ENTER_PUK;
136 
next()137         public void next() {
138             int msg = 0;
139             if (mState == ENTER_PUK) {
140                 if (checkPuk()) {
141                     mState = ENTER_PIN;
142                     msg = com.android.systemui.R.string.kg_puk_enter_pin_hint;
143                 } else {
144                     msg = com.android.systemui.R.string.kg_invalid_sim_puk_hint;
145                 }
146             } else if (mState == ENTER_PIN) {
147                 if (checkPin()) {
148                     mState = CONFIRM_PIN;
149                     msg = com.android.systemui.R.string.kg_enter_confirm_pin_hint;
150                 } else {
151                     msg = com.android.systemui.R.string.kg_invalid_sim_pin_hint;
152                 }
153             } else if (mState == CONFIRM_PIN) {
154                 if (confirmPin()) {
155                     mState = DONE;
156                     msg = com.android.systemui.R.string.keyguard_sim_unlock_progress_dialog_message;
157                     updateSim();
158                 } else {
159                     mState = ENTER_PIN; // try again?
160                     msg = com.android.systemui.R.string.kg_invalid_confirm_pin_hint;
161                 }
162             }
163             mView.resetPasswordText(true /* animate */, true /* announce */);
164             if (msg != 0) {
165                 mMessageAreaController.setMessage(msg);
166             }
167         }
168 
169 
reset()170         void reset() {
171             mPinText = "";
172             mPukText = "";
173             mState = ENTER_PUK;
174             handleSubInfoChangeIfNeeded();
175             if (mShowDefaultMessage) {
176                 showDefaultMessage();
177             }
178 
179             mView.setESimLocked(KeyguardEsimArea.isEsimLocked(mView.getContext(), mSubId), mSubId);
180 
181             mPasswordEntry.requestFocus();
182         }
183     }
184 
showDefaultMessage()185     private void showDefaultMessage() {
186         if (mRemainingAttempts >= 0) {
187             mMessageAreaController.setMessage(mView.getPukPasswordErrorMessage(
188                     mRemainingAttempts, true,
189                     KeyguardEsimArea.isEsimLocked(mView.getContext(), mSubId)));
190             return;
191         }
192 
193         boolean isEsimLocked = KeyguardEsimArea.isEsimLocked(mView.getContext(), mSubId);
194         int count = 1;
195         if (mTelephonyManager != null) {
196             count = mTelephonyManager.getActiveModemCount();
197         }
198         Resources rez = mView.getResources();
199         String msg;
200         TypedArray array = mView.getContext().obtainStyledAttributes(
201                 new int[] { android.R.attr.textColor });
202         int color = array.getColor(0, Color.WHITE);
203         array.recycle();
204         if (count < 2) {
205             msg = rez.getString(R.string.kg_puk_enter_puk_hint);
206         } else {
207             SubscriptionInfo info = mKeyguardUpdateMonitor.getSubscriptionInfoForSubId(mSubId);
208             CharSequence displayName = info != null ? info.getDisplayName() : "";
209             msg = rez.getString(R.string.kg_puk_enter_puk_hint_multi, displayName);
210             if (info != null) {
211                 color = info.getIconTint();
212             }
213         }
214         if (isEsimLocked) {
215             msg = rez.getString(R.string.kg_sim_lock_esim_instructions, msg);
216         }
217         mMessageAreaController.setMessage(msg);
218         mSimImageView.setImageTintList(ColorStateList.valueOf(color));
219 
220         // Sending empty PUK here to query the number of remaining PIN attempts
221         new CheckSimPuk("", "", mSubId) {
222             void onSimLockChangedResponse(final PinResult result) {
223                 if (result == null) Log.e(TAG, "onSimCheckResponse, pin result is NULL");
224                 else {
225                     Log.d(TAG, "onSimCheckResponse " + " empty One result "
226                             + result.toString());
227                     if (result.getAttemptsRemaining() >= 0) {
228                         mRemainingAttempts = result.getAttemptsRemaining();
229                         mMessageAreaController.setMessage(
230                                 mView.getPukPasswordErrorMessage(
231                                         result.getAttemptsRemaining(), true,
232                                         KeyguardEsimArea.isEsimLocked(mView.getContext(), mSubId)));
233                     }
234                 }
235             }
236         }.start();
237     }
238 
checkPuk()239     private boolean checkPuk() {
240         // make sure the puk is at least 8 digits long.
241         if (mPasswordEntry.getText().length() == 8) {
242             mPukText = mPasswordEntry.getText();
243             return true;
244         }
245         return false;
246     }
247 
checkPin()248     private boolean checkPin() {
249         // make sure the PIN is between 4 and 8 digits
250         int length = mPasswordEntry.getText().length();
251         if (length >= 4 && length <= 8) {
252             mPinText = mPasswordEntry.getText();
253             return true;
254         }
255         return false;
256     }
257 
confirmPin()258     public boolean confirmPin() {
259         return mPinText.equals(mPasswordEntry.getText());
260     }
261 
updateSim()262     private void updateSim() {
263         getSimUnlockProgressDialog().show();
264 
265         if (mCheckSimPukThread == null) {
266             mCheckSimPukThread = new CheckSimPuk(mPukText, mPinText, mSubId) {
267                 @Override
268                 void onSimLockChangedResponse(final PinResult result) {
269                     mView.post(() -> {
270                         if (mSimUnlockProgressDialog != null) {
271                             mSimUnlockProgressDialog.hide();
272                         }
273                         mView.resetPasswordText(true /* animate */,
274                                 /* announce */
275                                 result.getResult() != PinResult.PIN_RESULT_TYPE_SUCCESS);
276                         if (result.getResult() == PinResult.PIN_RESULT_TYPE_SUCCESS) {
277                             mKeyguardUpdateMonitor.reportSimUnlocked(mSubId);
278                             mRemainingAttempts = -1;
279                             mShowDefaultMessage = true;
280 
281                             getKeyguardSecurityCallback().dismiss(
282                                     true, KeyguardUpdateMonitor.getCurrentUser(),
283                                     SecurityMode.SimPuk);
284                         } else {
285                             mShowDefaultMessage = false;
286                             if (result.getResult() == PinResult.PIN_RESULT_TYPE_INCORRECT) {
287                                 // show message
288                                 mMessageAreaController.setMessage(mView.getPukPasswordErrorMessage(
289                                         result.getAttemptsRemaining(), false,
290                                         KeyguardEsimArea.isEsimLocked(mView.getContext(), mSubId)));
291                                 if (result.getAttemptsRemaining() <= 2) {
292                                     // this is getting critical - show dialog
293                                     getPukRemainingAttemptsDialog(
294                                             result.getAttemptsRemaining()).show();
295                                 } else {
296                                     // show message
297                                     mMessageAreaController.setMessage(
298                                             mView.getPukPasswordErrorMessage(
299                                                     result.getAttemptsRemaining(), false,
300                                                     KeyguardEsimArea.isEsimLocked(
301                                                             mView.getContext(), mSubId)));
302                                 }
303                             } else {
304                                 mMessageAreaController.setMessage(mView.getResources().getString(
305                                         R.string.kg_password_puk_failed));
306                             }
307                             if (DEBUG) {
308                                 Log.d(TAG, "verifyPasswordAndUnlock "
309                                         + " UpdateSim.onSimCheckResponse: "
310                                         + " attemptsRemaining=" + result.getAttemptsRemaining());
311                             }
312                         }
313                         mStateMachine.reset();
314                         mCheckSimPukThread = null;
315                     });
316                 }
317             };
318             mCheckSimPukThread.start();
319         }
320     }
321 
322     @Override
shouldLockout(long deadline)323     protected boolean shouldLockout(long deadline) {
324         // SIM PUK doesn't have a timed lockout
325         return false;
326     }
327 
getSimUnlockProgressDialog()328     private Dialog getSimUnlockProgressDialog() {
329         if (mSimUnlockProgressDialog == null) {
330             mSimUnlockProgressDialog = new ProgressDialog(mView.getContext());
331             mSimUnlockProgressDialog.setMessage(
332                     mView.getResources().getString(R.string.kg_sim_unlock_progress_dialog_message));
333             mSimUnlockProgressDialog.setIndeterminate(true);
334             mSimUnlockProgressDialog.setCancelable(false);
335             if (!(mView.getContext() instanceof Activity)) {
336                 mSimUnlockProgressDialog.getWindow().setType(
337                         WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
338             }
339         }
340         return mSimUnlockProgressDialog;
341     }
342 
handleSubInfoChangeIfNeeded()343     private void handleSubInfoChangeIfNeeded() {
344         int subId = mKeyguardUpdateMonitor.getNextSubIdForState(
345                 TelephonyManager.SIM_STATE_PUK_REQUIRED);
346         if (subId != mSubId && SubscriptionManager.isValidSubscriptionId(subId)) {
347             mSubId = subId;
348             mShowDefaultMessage = true;
349             mRemainingAttempts = -1;
350         }
351     }
352 
353 
getPukRemainingAttemptsDialog(int remaining)354     private Dialog getPukRemainingAttemptsDialog(int remaining) {
355         String msg = mView.getPukPasswordErrorMessage(remaining, false,
356                 KeyguardEsimArea.isEsimLocked(mView.getContext(), mSubId));
357         if (mRemainingAttemptsDialog == null) {
358             AlertDialog.Builder builder = new AlertDialog.Builder(mView.getContext());
359             builder.setMessage(msg);
360             builder.setCancelable(false);
361             builder.setNeutralButton(R.string.ok, null);
362             mRemainingAttemptsDialog = builder.create();
363             mRemainingAttemptsDialog.getWindow().setType(
364                     WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
365         } else {
366             mRemainingAttemptsDialog.setMessage(msg);
367         }
368         return mRemainingAttemptsDialog;
369     }
370 
371     @Override
onPause()372     public void onPause() {
373         // dismiss the dialog.
374         if (mSimUnlockProgressDialog != null) {
375             mSimUnlockProgressDialog.dismiss();
376             mSimUnlockProgressDialog = null;
377         }
378     }
379 
380     /**
381      * Since the IPC can block, we want to run the request in a separate thread
382      * with a callback.
383      */
384     private abstract class CheckSimPuk extends Thread {
385 
386         private final String mPin, mPuk;
387         private final int mSubId;
388 
CheckSimPuk(String puk, String pin, int subId)389         protected CheckSimPuk(String puk, String pin, int subId) {
390             mPuk = puk;
391             mPin = pin;
392             mSubId = subId;
393         }
394 
onSimLockChangedResponse(@onNull PinResult result)395         abstract void onSimLockChangedResponse(@NonNull PinResult result);
396 
397         @Override
run()398         public void run() {
399             if (DEBUG) {
400                 Log.v(TAG, "call supplyIccLockPuk(subid=" + mSubId + ")");
401             }
402             TelephonyManager telephonyManager = mTelephonyManager.createForSubscriptionId(mSubId);
403             final PinResult result = telephonyManager.supplyIccLockPuk(mPuk, mPin);
404             if (DEBUG) {
405                 Log.v(TAG, "supplyIccLockPuk returned: " + result.toString());
406             }
407             mView.post(() -> onSimLockChangedResponse(result));
408         }
409     }
410 
411 }
412