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