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