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.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 
     91     public KeyguardSimPukView(Context context) {
     92         this(context, null);
     93     }
     94 
     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 
    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 
    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 
    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 
    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
    215     protected int getPromptReasonStringRes(int reason) {
    216         // No message on SIM Puk
    217         return 0;
    218     }
    219 
    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
    245     public void resetState() {
    246         super.resetState();
    247         mStateMachine.reset();
    248     }
    249 
    250     @Override
    251     protected boolean shouldLockout(long deadline) {
    252         // SIM PUK doesn't have a timed lockout
    253         return false;
    254     }
    255 
    256     @Override
    257     protected int getPasswordTextViewId() {
    258         return R.id.pukEntry;
    259     }
    260 
    261     @Override
    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
    272     protected void onAttachedToWindow() {
    273         super.onAttachedToWindow();
    274         KeyguardUpdateMonitor.getInstance(mContext).registerCallback(mUpdateMonitorCallback);
    275         resetState();
    276     }
    277 
    278     @Override
    279     protected void onDetachedFromWindow() {
    280         super.onDetachedFromWindow();
    281         KeyguardUpdateMonitor.getInstance(mContext).removeCallback(mUpdateMonitorCallback);
    282     }
    283 
    284     @Override
    285     public void showUsabilityHint() {
    286     }
    287 
    288     @Override
    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 
    306         protected CheckSimPuk(String puk, String pin, int subId) {
    307             mPuk = puk;
    308             mPin = pin;
    309             mSubId = subId;
    310         }
    311 
    312         abstract void onSimLockChangedResponse(final int result, final int attemptsRemaining);
    313 
    314         @Override
    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 
    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 
    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 
    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 
    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 
    391     public boolean confirmPin() {
    392         return mPinText.equals(mPasswordEntry.getText());
    393     }
    394 
    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
    452     protected void verifyPasswordAndUnlock() {
    453         mStateMachine.next();
    454     }
    455 
    456     @Override
    457     public void startAppearAnimation() {
    458         // noop.
    459     }
    460 
    461     @Override
    462     public boolean startDisappearAnimation(Runnable finishRunnable) {
    463         return false;
    464     }
    465 
    466     @Override
    467     public CharSequence getTitle() {
    468         return getContext().getString(
    469                 com.android.internal.R.string.keyguard_accessibility_sim_puk_unlock);
    470     }
    471 }
    472 
    473 
    474