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.AlertDialog;
     20 import android.app.AlertDialog.Builder;
     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.Configuration;
     26 import android.content.res.Resources;
     27 import android.content.res.TypedArray;
     28 import android.graphics.Color;
     29 import android.os.RemoteException;
     30 import android.os.ServiceManager;
     31 import android.telephony.SubscriptionInfo;
     32 import android.telephony.SubscriptionManager;
     33 import android.telephony.TelephonyManager;
     34 import android.util.AttributeSet;
     35 import android.util.Log;
     36 import android.view.View;
     37 import android.view.WindowManager;
     38 import android.widget.ImageView;
     39 
     40 import com.android.internal.telephony.ITelephony;
     41 import com.android.internal.telephony.IccCardConstants;
     42 import com.android.internal.telephony.IccCardConstants.State;
     43 import com.android.internal.telephony.PhoneConstants;
     44 
     45 /**
     46  * Displays a PIN pad for unlocking.
     47  */
     48 public class KeyguardSimPinView extends KeyguardPinBasedInputView {
     49     private static final String LOG_TAG = "KeyguardSimPinView";
     50     private static final boolean DEBUG = KeyguardConstants.DEBUG_SIM_STATES;
     51     public static final String TAG = "KeyguardSimPinView";
     52 
     53     private ProgressDialog mSimUnlockProgressDialog = null;
     54     private CheckSimPin mCheckSimPinThread;
     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 PIN locked state, on PIN lock screen, message would
     58     // be displayed to inform user about the number of remaining PIN attempts left.
     59     private boolean mShowDefaultMessage = true;
     60     private int mRemainingAttempts = -1;
     61     private AlertDialog mRemainingAttemptsDialog;
     62     private int mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
     63     private ImageView mSimImageView;
     64 
     65     KeyguardUpdateMonitorCallback mUpdateMonitorCallback = new KeyguardUpdateMonitorCallback() {
     66         @Override
     67         public void onSimStateChanged(int subId, int slotId, State simState) {
     68             if (DEBUG) Log.v(TAG, "onSimStateChanged(subId=" + subId + ",state=" + simState + ")");
     69             switch(simState) {
     70                 case READY: {
     71                     mRemainingAttempts = -1;
     72                     resetState();
     73                     break;
     74                 }
     75                 default:
     76                     resetState();
     77             }
     78         }
     79     };
     80 
     81     public KeyguardSimPinView(Context context) {
     82         this(context, null);
     83     }
     84 
     85     public KeyguardSimPinView(Context context, AttributeSet attrs) {
     86         super(context, attrs);
     87     }
     88 
     89     @Override
     90     public void resetState() {
     91         super.resetState();
     92         if (DEBUG) Log.v(TAG, "Resetting state");
     93         handleSubInfoChangeIfNeeded();
     94         if (mShowDefaultMessage) {
     95             showDefaultMessage();
     96         }
     97         boolean isEsimLocked = KeyguardEsimArea.isEsimLocked(mContext, mSubId);
     98 
     99         KeyguardEsimArea esimButton = findViewById(R.id.keyguard_esim_area);
    100         esimButton.setVisibility(isEsimLocked ? View.VISIBLE : View.GONE);
    101     }
    102 
    103     private void setLockedSimMessage() {
    104         boolean isEsimLocked = KeyguardEsimArea.isEsimLocked(mContext, mSubId);
    105         int count = TelephonyManager.getDefault().getSimCount();
    106         Resources rez = getResources();
    107         String msg;
    108         TypedArray array = mContext.obtainStyledAttributes(new int[] { R.attr.wallpaperTextColor });
    109         int color = array.getColor(0, Color.WHITE);
    110         array.recycle();
    111         if (count < 2) {
    112             msg = rez.getString(R.string.kg_sim_pin_instructions);
    113         } else {
    114             SubscriptionInfo info = KeyguardUpdateMonitor.getInstance(mContext).
    115                     getSubscriptionInfoForSubId(mSubId);
    116             CharSequence displayName = info != null ? info.getDisplayName() : ""; // don't crash
    117             msg = rez.getString(R.string.kg_sim_pin_instructions_multi, displayName);
    118             if (info != null) {
    119                 color = info.getIconTint();
    120             }
    121         }
    122         if (isEsimLocked) {
    123             msg = rez.getString(R.string.kg_sim_lock_esim_instructions, msg);
    124         }
    125 
    126         if (mSecurityMessageDisplay != null) {
    127             mSecurityMessageDisplay.setMessage(msg);
    128         }
    129         mSimImageView.setImageTintList(ColorStateList.valueOf(color));
    130     }
    131 
    132     private void showDefaultMessage() {
    133         setLockedSimMessage();
    134         if (mRemainingAttempts >= 0) {
    135             return;
    136         }
    137 
    138         // Sending empty PIN here to query the number of remaining PIN attempts
    139         new CheckSimPin("", mSubId) {
    140             void onSimCheckResponse(final int result, final int attemptsRemaining) {
    141                 Log.d(LOG_TAG, "onSimCheckResponse " + " dummy One result" + result +
    142                         " attemptsRemaining=" + attemptsRemaining);
    143                 if (attemptsRemaining >= 0) {
    144                     mRemainingAttempts = attemptsRemaining;
    145                     setLockedSimMessage();
    146                 }
    147             }
    148         }.start();
    149     }
    150 
    151     private void handleSubInfoChangeIfNeeded() {
    152         KeyguardUpdateMonitor monitor = KeyguardUpdateMonitor.getInstance(mContext);
    153         int subId = monitor.getNextSubIdForState(IccCardConstants.State.PIN_REQUIRED);
    154         if (subId != mSubId && SubscriptionManager.isValidSubscriptionId(subId)) {
    155             mSubId = subId;
    156             mShowDefaultMessage = true;
    157             mRemainingAttempts = -1;
    158         }
    159     }
    160 
    161     @Override
    162     protected void onConfigurationChanged(Configuration newConfig) {
    163         super.onConfigurationChanged(newConfig);
    164         resetState();
    165     }
    166 
    167     @Override
    168     protected int getPromptReasonStringRes(int reason) {
    169         // No message on SIM Pin
    170         return 0;
    171     }
    172 
    173     private String getPinPasswordErrorMessage(int attemptsRemaining, boolean isDefault) {
    174         String displayMessage;
    175         int msgId;
    176         if (attemptsRemaining == 0) {
    177             displayMessage = getContext().getString(R.string.kg_password_wrong_pin_code_pukked);
    178         } else if (attemptsRemaining > 0) {
    179             msgId = isDefault ? R.plurals.kg_password_default_pin_message :
    180                      R.plurals.kg_password_wrong_pin_code;
    181             displayMessage = getContext().getResources()
    182                     .getQuantityString(msgId, attemptsRemaining, attemptsRemaining);
    183         } else {
    184             msgId = isDefault ? R.string.kg_sim_pin_instructions : R.string.kg_password_pin_failed;
    185             displayMessage = getContext().getString(msgId);
    186         }
    187         if (KeyguardEsimArea.isEsimLocked(mContext, mSubId)) {
    188             displayMessage = getResources()
    189                     .getString(R.string.kg_sim_lock_esim_instructions, displayMessage);
    190         }
    191         if (DEBUG) Log.d(LOG_TAG, "getPinPasswordErrorMessage:"
    192                 + " attemptsRemaining=" + attemptsRemaining + " displayMessage=" + displayMessage);
    193         return displayMessage;
    194     }
    195 
    196     @Override
    197     protected boolean shouldLockout(long deadline) {
    198         // SIM PIN doesn't have a timed lockout
    199         return false;
    200     }
    201 
    202     @Override
    203     protected int getPasswordTextViewId() {
    204         return R.id.simPinEntry;
    205     }
    206 
    207     @Override
    208     protected void onFinishInflate() {
    209         super.onFinishInflate();
    210 
    211         if (mEcaView instanceof EmergencyCarrierArea) {
    212             ((EmergencyCarrierArea) mEcaView).setCarrierTextVisible(true);
    213         }
    214         mSimImageView = findViewById(R.id.keyguard_sim);
    215     }
    216 
    217     @Override
    218     public void showUsabilityHint() {
    219 
    220     }
    221 
    222     @Override
    223     public void onResume(int reason) {
    224         super.onResume(reason);
    225         KeyguardUpdateMonitor.getInstance(mContext).registerCallback(mUpdateMonitorCallback);
    226         resetState();
    227     }
    228 
    229     @Override
    230     public void onPause() {
    231         // dismiss the dialog.
    232         if (mSimUnlockProgressDialog != null) {
    233             mSimUnlockProgressDialog.dismiss();
    234             mSimUnlockProgressDialog = null;
    235         }
    236         KeyguardUpdateMonitor.getInstance(mContext).removeCallback(mUpdateMonitorCallback);
    237     }
    238 
    239     /**
    240      * Since the IPC can block, we want to run the request in a separate thread
    241      * with a callback.
    242      */
    243     private abstract class CheckSimPin extends Thread {
    244         private final String mPin;
    245         private int mSubId;
    246 
    247         protected CheckSimPin(String pin, int subId) {
    248             mPin = pin;
    249             mSubId = subId;
    250         }
    251 
    252         abstract void onSimCheckResponse(final int result, final int attemptsRemaining);
    253 
    254         @Override
    255         public void run() {
    256             try {
    257                 if (DEBUG) {
    258                     Log.v(TAG, "call supplyPinReportResultForSubscriber(subid=" + mSubId + ")");
    259                 }
    260                 final int[] result = ITelephony.Stub.asInterface(ServiceManager
    261                         .checkService("phone")).supplyPinReportResultForSubscriber(mSubId, mPin);
    262                 if (DEBUG) {
    263                     Log.v(TAG, "supplyPinReportResult returned: " + result[0] + " " + result[1]);
    264                 }
    265                 post(new Runnable() {
    266                     @Override
    267                     public void run() {
    268                         onSimCheckResponse(result[0], result[1]);
    269                     }
    270                 });
    271             } catch (RemoteException e) {
    272                 Log.e(TAG, "RemoteException for supplyPinReportResult:", e);
    273                 post(new Runnable() {
    274                     @Override
    275                     public void run() {
    276                         onSimCheckResponse(PhoneConstants.PIN_GENERAL_FAILURE, -1);
    277                     }
    278                 });
    279             }
    280         }
    281     }
    282 
    283     private Dialog getSimUnlockProgressDialog() {
    284         if (mSimUnlockProgressDialog == null) {
    285             mSimUnlockProgressDialog = new ProgressDialog(mContext);
    286             mSimUnlockProgressDialog.setMessage(
    287                     mContext.getString(R.string.kg_sim_unlock_progress_dialog_message));
    288             mSimUnlockProgressDialog.setIndeterminate(true);
    289             mSimUnlockProgressDialog.setCancelable(false);
    290             mSimUnlockProgressDialog.getWindow().setType(
    291                     WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
    292         }
    293         return mSimUnlockProgressDialog;
    294     }
    295 
    296     private Dialog getSimRemainingAttemptsDialog(int remaining) {
    297         String msg = getPinPasswordErrorMessage(remaining, false);
    298         if (mRemainingAttemptsDialog == null) {
    299             Builder builder = new AlertDialog.Builder(mContext);
    300             builder.setMessage(msg);
    301             builder.setCancelable(false);
    302             builder.setNeutralButton(R.string.ok, null);
    303             mRemainingAttemptsDialog = builder.create();
    304             mRemainingAttemptsDialog.getWindow().setType(
    305                     WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
    306         } else {
    307             mRemainingAttemptsDialog.setMessage(msg);
    308         }
    309         return mRemainingAttemptsDialog;
    310     }
    311 
    312     @Override
    313     protected void verifyPasswordAndUnlock() {
    314         String entry = mPasswordEntry.getText();
    315 
    316         if (entry.length() < 4) {
    317             // otherwise, display a message to the user, and don't submit.
    318             mSecurityMessageDisplay.setMessage(R.string.kg_invalid_sim_pin_hint);
    319             resetPasswordText(true /* animate */, true /* announce */);
    320             mCallback.userActivity();
    321             return;
    322         }
    323 
    324         getSimUnlockProgressDialog().show();
    325 
    326         if (mCheckSimPinThread == null) {
    327             mCheckSimPinThread = new CheckSimPin(mPasswordEntry.getText(), mSubId) {
    328                 @Override
    329                 void onSimCheckResponse(final int result, final int attemptsRemaining) {
    330                     post(new Runnable() {
    331                         @Override
    332                         public void run() {
    333                             mRemainingAttempts = attemptsRemaining;
    334                             if (mSimUnlockProgressDialog != null) {
    335                                 mSimUnlockProgressDialog.hide();
    336                             }
    337                             resetPasswordText(true /* animate */,
    338                                     result != PhoneConstants.PIN_RESULT_SUCCESS /* announce */);
    339                             if (result == PhoneConstants.PIN_RESULT_SUCCESS) {
    340                                 KeyguardUpdateMonitor.getInstance(getContext())
    341                                         .reportSimUnlocked(mSubId);
    342                                 mRemainingAttempts = -1;
    343                                 mShowDefaultMessage = true;
    344                                 if (mCallback != null) {
    345                                     mCallback.dismiss(true, KeyguardUpdateMonitor.getCurrentUser());
    346                                 }
    347                             } else {
    348                                 mShowDefaultMessage = false;
    349                                 if (result == PhoneConstants.PIN_PASSWORD_INCORRECT) {
    350                                     if (attemptsRemaining <= 2) {
    351                                         // this is getting critical - show dialog
    352                                         getSimRemainingAttemptsDialog(attemptsRemaining).show();
    353                                     } else {
    354                                         // show message
    355                                         mSecurityMessageDisplay.setMessage(
    356                                                 getPinPasswordErrorMessage(attemptsRemaining, false));
    357                                     }
    358                                 } else {
    359                                     // "PIN operation failed!" - no idea what this was and no way to
    360                                     // find out. :/
    361                                     mSecurityMessageDisplay.setMessage(getContext().getString(
    362                                             R.string.kg_password_pin_failed));
    363                                 }
    364                                 if (DEBUG) Log.d(LOG_TAG, "verifyPasswordAndUnlock "
    365                                         + " CheckSimPin.onSimCheckResponse: " + result
    366                                         + " attemptsRemaining=" + attemptsRemaining);
    367                             }
    368                             mCallback.userActivity();
    369                             mCheckSimPinThread = null;
    370                         }
    371                     });
    372                 }
    373             };
    374             mCheckSimPinThread.start();
    375         }
    376     }
    377 
    378     @Override
    379     public void startAppearAnimation() {
    380         // noop.
    381     }
    382 
    383     @Override
    384     public boolean startDisappearAnimation(Runnable finishRunnable) {
    385         return false;
    386     }
    387 
    388     @Override
    389     public CharSequence getTitle() {
    390         return getContext().getString(
    391                 com.android.internal.R.string.keyguard_accessibility_sim_pin_unlock);
    392     }
    393 }
    394 
    395