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