1 /*
2  * Copyright (C) 2012 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.Context;
25 import android.content.res.ColorStateList;
26 import android.content.res.Resources;
27 import android.content.res.TypedArray;
28 import android.graphics.Color;
29 import android.telephony.PinResult;
30 import android.telephony.SubscriptionInfo;
31 import android.telephony.SubscriptionManager;
32 import android.telephony.TelephonyManager;
33 import android.util.AttributeSet;
34 import android.util.Log;
35 import android.view.View;
36 import android.view.WindowManager;
37 import android.widget.ImageView;
38 
39 import com.android.internal.telephony.IccCardConstants;
40 import com.android.internal.telephony.IccCardConstants.State;
41 import com.android.systemui.R;
42 
43 
44 /**
45  * Displays a PIN pad for entering a PUK (Pin Unlock Kode) provided by a carrier.
46  */
47 public class KeyguardSimPukView extends KeyguardPinBasedInputView {
48     private static final String LOG_TAG = "KeyguardSimPukView";
49     private static final boolean DEBUG = KeyguardConstants.DEBUG;
50     public static final String TAG = "KeyguardSimPukView";
51 
52     private ProgressDialog mSimUnlockProgressDialog = null;
53     private CheckSimPuk mCheckSimPukThread;
54 
55     // Below flag is set to true during power-up or when a new SIM card inserted on device.
56     // When this is true and when SIM card is PUK locked state, on PIN lock screen, message would
57     // be displayed to inform user about the number of remaining PUK attempts left.
58     private boolean mShowDefaultMessage = true;
59     private int mRemainingAttempts = -1;
60     private String mPukText;
61     private String mPinText;
62     private StateMachine mStateMachine = new StateMachine();
63     private AlertDialog mRemainingAttemptsDialog;
64     private int mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
65     private ImageView mSimImageView;
66 
67     KeyguardUpdateMonitorCallback mUpdateMonitorCallback = new KeyguardUpdateMonitorCallback() {
68         @Override
69         public void onSimStateChanged(int subId, int slotId, State simState) {
70             if (DEBUG) Log.v(TAG, "onSimStateChanged(subId=" + subId + ",state=" + simState + ")");
71             switch(simState) {
72                 // If the SIM is unlocked via a key sequence through the emergency dialer, it will
73                 // move into the READY state and the PUK lock keyguard should be removed.
74                 case READY: {
75                     mRemainingAttempts = -1;
76                     mShowDefaultMessage = true;
77                     // mCallback can be null if onSimStateChanged callback is called when keyguard
78                     // isn't active.
79                     if (mCallback != null) {
80                         mCallback.dismiss(true, KeyguardUpdateMonitor.getCurrentUser());
81                     }
82                     break;
83                 }
84                 default:
85                     resetState();
86             }
87         }
88     };
89 
KeyguardSimPukView(Context context)90     public KeyguardSimPukView(Context context) {
91         this(context, null);
92     }
93 
KeyguardSimPukView(Context context, AttributeSet attrs)94     public KeyguardSimPukView(Context context, AttributeSet attrs) {
95         super(context, attrs);
96     }
97 
98     private class StateMachine {
99         final int ENTER_PUK = 0;
100         final int ENTER_PIN = 1;
101         final int CONFIRM_PIN = 2;
102         final int DONE = 3;
103         private int state = ENTER_PUK;
104 
next()105         public void next() {
106             int msg = 0;
107             if (state == ENTER_PUK) {
108                 if (checkPuk()) {
109                     state = ENTER_PIN;
110                     msg = R.string.kg_puk_enter_pin_hint;
111                 } else {
112                     msg = R.string.kg_invalid_sim_puk_hint;
113                 }
114             } else if (state == ENTER_PIN) {
115                 if (checkPin()) {
116                     state = CONFIRM_PIN;
117                     msg = R.string.kg_enter_confirm_pin_hint;
118                 } else {
119                     msg = R.string.kg_invalid_sim_pin_hint;
120                 }
121             } else if (state == CONFIRM_PIN) {
122                 if (confirmPin()) {
123                     state = DONE;
124                     msg = R.string.keyguard_sim_unlock_progress_dialog_message;
125                     updateSim();
126                 } else {
127                     state = ENTER_PIN; // try again?
128                     msg = R.string.kg_invalid_confirm_pin_hint;
129                 }
130             }
131             resetPasswordText(true /* animate */, true /* announce */);
132             if (msg != 0) {
133                 mSecurityMessageDisplay.setMessage(msg);
134             }
135         }
136 
137 
reset()138         void reset() {
139             mPinText="";
140             mPukText="";
141             state = ENTER_PUK;
142             handleSubInfoChangeIfNeeded();
143             if (mShowDefaultMessage) {
144                 showDefaultMessage();
145             }
146             boolean isEsimLocked = KeyguardEsimArea.isEsimLocked(mContext, mSubId);
147 
148             KeyguardEsimArea esimButton = findViewById(R.id.keyguard_esim_area);
149             esimButton.setVisibility(isEsimLocked ? View.VISIBLE : View.GONE);
150             mPasswordEntry.requestFocus();
151         }
152 
153 
154     }
155 
showDefaultMessage()156     private void showDefaultMessage() {
157         if (mRemainingAttempts >= 0) {
158             mSecurityMessageDisplay.setMessage(getPukPasswordErrorMessage(
159                     mRemainingAttempts, true));
160             return;
161         }
162 
163         boolean isEsimLocked = KeyguardEsimArea.isEsimLocked(mContext, mSubId);
164         int count = TelephonyManager.getDefault().getSimCount();
165         Resources rez = getResources();
166         String msg;
167         TypedArray array = mContext.obtainStyledAttributes(new int[] { R.attr.wallpaperTextColor });
168         int color = array.getColor(0, Color.WHITE);
169         array.recycle();
170         if (count < 2) {
171             msg = rez.getString(R.string.kg_puk_enter_puk_hint);
172         } else {
173             SubscriptionInfo info = KeyguardUpdateMonitor.getInstance(mContext).
174                     getSubscriptionInfoForSubId(mSubId);
175             CharSequence displayName = info != null ? info.getDisplayName() : "";
176             msg = rez.getString(R.string.kg_puk_enter_puk_hint_multi, displayName);
177             if (info != null) {
178                 color = info.getIconTint();
179             }
180         }
181         if (isEsimLocked) {
182             msg = rez.getString(R.string.kg_sim_lock_esim_instructions, msg);
183         }
184         if (mSecurityMessageDisplay != null) {
185             mSecurityMessageDisplay.setMessage(msg);
186         }
187         mSimImageView.setImageTintList(ColorStateList.valueOf(color));
188 
189         // Sending empty PUK here to query the number of remaining PIN attempts
190         new CheckSimPuk("", "", mSubId) {
191             void onSimLockChangedResponse(final PinResult result) {
192                 if (result == null) Log.e(LOG_TAG, "onSimCheckResponse, pin result is NULL");
193                 else {
194                     Log.d(LOG_TAG, "onSimCheckResponse " + " empty One result "
195                             + result.toString());
196                     if (result.getAttemptsRemaining() >= 0) {
197                         mRemainingAttempts = result.getAttemptsRemaining();
198                         mSecurityMessageDisplay.setMessage(
199                                 getPukPasswordErrorMessage(result.getAttemptsRemaining(), true));
200                     }
201                 }
202             }
203         }.start();
204     }
205 
handleSubInfoChangeIfNeeded()206     private void handleSubInfoChangeIfNeeded() {
207         KeyguardUpdateMonitor monitor = KeyguardUpdateMonitor.getInstance(mContext);
208         int subId = monitor.getNextSubIdForState(IccCardConstants.State.PUK_REQUIRED);
209         if (subId != mSubId && SubscriptionManager.isValidSubscriptionId(subId)) {
210             mSubId = subId;
211             mShowDefaultMessage = true;
212             mRemainingAttempts = -1;
213         }
214     }
215 
216     @Override
getPromptReasonStringRes(int reason)217     protected int getPromptReasonStringRes(int reason) {
218         // No message on SIM Puk
219         return 0;
220     }
221 
getPukPasswordErrorMessage(int attemptsRemaining, boolean isDefault)222     private String getPukPasswordErrorMessage(int attemptsRemaining, boolean isDefault) {
223         String displayMessage;
224 
225         if (attemptsRemaining == 0) {
226             displayMessage = getContext().getString(R.string.kg_password_wrong_puk_code_dead);
227         } else if (attemptsRemaining > 0) {
228             int msgId = isDefault ? R.plurals.kg_password_default_puk_message :
229                     R.plurals.kg_password_wrong_puk_code;
230             displayMessage = getContext().getResources()
231                     .getQuantityString(msgId, attemptsRemaining, attemptsRemaining);
232         } else {
233             int msgId = isDefault ? R.string.kg_puk_enter_puk_hint :
234                     R.string.kg_password_puk_failed;
235             displayMessage = getContext().getString(msgId);
236         }
237         if (KeyguardEsimArea.isEsimLocked(mContext, mSubId)) {
238             displayMessage = getResources()
239                     .getString(R.string.kg_sim_lock_esim_instructions, displayMessage);
240         }
241         if (DEBUG) Log.d(LOG_TAG, "getPukPasswordErrorMessage:"
242                 + " attemptsRemaining=" + attemptsRemaining + " displayMessage=" + displayMessage);
243         return displayMessage;
244     }
245 
246     @Override
resetState()247     public void resetState() {
248         super.resetState();
249         mStateMachine.reset();
250     }
251 
252     @Override
shouldLockout(long deadline)253     protected boolean shouldLockout(long deadline) {
254         // SIM PUK doesn't have a timed lockout
255         return false;
256     }
257 
258     @Override
getPasswordTextViewId()259     protected int getPasswordTextViewId() {
260         return R.id.pukEntry;
261     }
262 
263     @Override
onFinishInflate()264     protected void onFinishInflate() {
265         super.onFinishInflate();
266 
267         if (mEcaView instanceof EmergencyCarrierArea) {
268             ((EmergencyCarrierArea) mEcaView).setCarrierTextVisible(true);
269         }
270         mSimImageView = findViewById(R.id.keyguard_sim);
271     }
272 
273     @Override
onAttachedToWindow()274     protected void onAttachedToWindow() {
275         super.onAttachedToWindow();
276         KeyguardUpdateMonitor.getInstance(mContext).registerCallback(mUpdateMonitorCallback);
277         resetState();
278     }
279 
280     @Override
onDetachedFromWindow()281     protected void onDetachedFromWindow() {
282         super.onDetachedFromWindow();
283         KeyguardUpdateMonitor.getInstance(mContext).removeCallback(mUpdateMonitorCallback);
284     }
285 
286     @Override
showUsabilityHint()287     public void showUsabilityHint() {
288     }
289 
290     @Override
onPause()291     public void onPause() {
292         // dismiss the dialog.
293         if (mSimUnlockProgressDialog != null) {
294             mSimUnlockProgressDialog.dismiss();
295             mSimUnlockProgressDialog = null;
296         }
297     }
298 
299     /**
300      * Since the IPC can block, we want to run the request in a separate thread
301      * with a callback.
302      */
303     private abstract class CheckSimPuk extends Thread {
304 
305         private final String mPin, mPuk;
306         private final int mSubId;
307 
CheckSimPuk(String puk, String pin, int subId)308         protected CheckSimPuk(String puk, String pin, int subId) {
309             mPuk = puk;
310             mPin = pin;
311             mSubId = subId;
312         }
313 
onSimLockChangedResponse(@onNull PinResult result)314         abstract void onSimLockChangedResponse(@NonNull PinResult result);
315 
316         @Override
run()317         public void run() {
318             if (DEBUG) Log.v(TAG, "call supplyPukReportResult()");
319             TelephonyManager telephonyManager =
320                     ((TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE))
321                             .createForSubscriptionId(mSubId);
322             final PinResult result = telephonyManager.supplyPukReportPinResult(mPuk, mPin);
323             if (result == null) {
324                 Log.e(TAG, "Error result for supplyPukReportResult.");
325                 post(new Runnable() {
326                     @Override
327                     public void run() {
328                         onSimLockChangedResponse(PinResult.getDefaultFailedResult());
329                     }
330                 });
331             } else {
332                 if (DEBUG) {
333                     Log.v(TAG, "supplyPukReportResult returned: " + result.toString());
334                 }
335                 post(new Runnable() {
336                     @Override
337                     public void run() {
338                         onSimLockChangedResponse(result);
339                     }
340                 });
341             }
342         }
343     }
344 
getSimUnlockProgressDialog()345     private Dialog getSimUnlockProgressDialog() {
346         if (mSimUnlockProgressDialog == null) {
347             mSimUnlockProgressDialog = new ProgressDialog(mContext);
348             mSimUnlockProgressDialog.setMessage(
349                     mContext.getString(R.string.kg_sim_unlock_progress_dialog_message));
350             mSimUnlockProgressDialog.setIndeterminate(true);
351             mSimUnlockProgressDialog.setCancelable(false);
352             if (!(mContext instanceof Activity)) {
353                 mSimUnlockProgressDialog.getWindow().setType(
354                         WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
355             }
356         }
357         return mSimUnlockProgressDialog;
358     }
359 
getPukRemainingAttemptsDialog(int remaining)360     private Dialog getPukRemainingAttemptsDialog(int remaining) {
361         String msg = getPukPasswordErrorMessage(remaining, false);
362         if (mRemainingAttemptsDialog == null) {
363             AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
364             builder.setMessage(msg);
365             builder.setCancelable(false);
366             builder.setNeutralButton(R.string.ok, null);
367             mRemainingAttemptsDialog = builder.create();
368             mRemainingAttemptsDialog.getWindow().setType(
369                     WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
370         } else {
371             mRemainingAttemptsDialog.setMessage(msg);
372         }
373         return mRemainingAttemptsDialog;
374     }
375 
checkPuk()376     private boolean checkPuk() {
377         // make sure the puk is at least 8 digits long.
378         if (mPasswordEntry.getText().length() == 8) {
379             mPukText = mPasswordEntry.getText();
380             return true;
381         }
382         return false;
383     }
384 
checkPin()385     private boolean checkPin() {
386         // make sure the PIN is between 4 and 8 digits
387         int length = mPasswordEntry.getText().length();
388         if (length >= 4 && length <= 8) {
389             mPinText = mPasswordEntry.getText();
390             return true;
391         }
392         return false;
393     }
394 
confirmPin()395     public boolean confirmPin() {
396         return mPinText.equals(mPasswordEntry.getText());
397     }
398 
updateSim()399     private void updateSim() {
400         getSimUnlockProgressDialog().show();
401 
402         if (mCheckSimPukThread == null) {
403             mCheckSimPukThread = new CheckSimPuk(mPukText, mPinText, mSubId) {
404                 @Override
405                 void onSimLockChangedResponse(final PinResult result) {
406                     post(new Runnable() {
407                         @Override
408                         public void run() {
409                             if (mSimUnlockProgressDialog != null) {
410                                 mSimUnlockProgressDialog.hide();
411                             }
412                             resetPasswordText(true /* animate */,
413                                     /* announce */
414                                     result.getType() != PinResult.PIN_RESULT_TYPE_SUCCESS);
415                             if (result.getType() == PinResult.PIN_RESULT_TYPE_SUCCESS) {
416                                 KeyguardUpdateMonitor.getInstance(getContext())
417                                         .reportSimUnlocked(mSubId);
418                                 mRemainingAttempts = -1;
419                                 mShowDefaultMessage = true;
420                                 if (mCallback != null) {
421                                     mCallback.dismiss(true,
422                                             KeyguardUpdateMonitor.getCurrentUser());
423                                 }
424                             } else {
425                                 mShowDefaultMessage = false;
426                                 if (result.getType() == PinResult.PIN_RESULT_TYPE_INCORRECT) {
427                                     // show message
428                                     mSecurityMessageDisplay.setMessage(getPukPasswordErrorMessage(
429                                             result.getAttemptsRemaining(), false));
430                                     if (result.getAttemptsRemaining() <= 2) {
431                                         // this is getting critical - show dialog
432                                         getPukRemainingAttemptsDialog(
433                                                 result.getAttemptsRemaining()).show();
434                                     } else {
435                                         // show message
436                                         mSecurityMessageDisplay.setMessage(
437                                                 getPukPasswordErrorMessage(
438                                                         result.getAttemptsRemaining(), false));
439                                     }
440                                 } else {
441                                     mSecurityMessageDisplay.setMessage(getContext().getString(
442                                             R.string.kg_password_puk_failed));
443                                 }
444                                 if (DEBUG) Log.d(LOG_TAG, "verifyPasswordAndUnlock "
445                                         + " UpdateSim.onSimCheckResponse: "
446                                         + " attemptsRemaining=" + result.getAttemptsRemaining());
447                                 mStateMachine.reset();
448                             }
449                             mCheckSimPukThread = null;
450                         }
451                     });
452                 }
453             };
454             mCheckSimPukThread.start();
455         }
456     }
457 
458     @Override
verifyPasswordAndUnlock()459     protected void verifyPasswordAndUnlock() {
460         mStateMachine.next();
461     }
462 
463     @Override
startAppearAnimation()464     public void startAppearAnimation() {
465         // noop.
466     }
467 
468     @Override
startDisappearAnimation(Runnable finishRunnable)469     public boolean startDisappearAnimation(Runnable finishRunnable) {
470         return false;
471     }
472 
473     @Override
getTitle()474     public CharSequence getTitle() {
475         return getContext().getString(
476                 com.android.internal.R.string.keyguard_accessibility_sim_puk_unlock);
477     }
478 }
479 
480 
481