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