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