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