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.annotation.NonNull; 20 import android.app.Activity; 21 import android.app.AlertDialog; 22 import android.app.Dialog; 23 import android.app.ProgressDialog; 24 import android.content.Context; 25 import android.content.res.ColorStateList; 26 import android.content.res.Resources; 27 import android.content.res.TypedArray; 28 import android.graphics.Color; 29 import android.telephony.PinResult; 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.IccCardConstants; 40 import com.android.internal.telephony.IccCardConstants.State; 41 import com.android.systemui.R; 42 43 44 /** 45 * Displays a PIN pad for entering a PUK (Pin Unlock Kode) provided by a carrier. 46 */ 47 public class KeyguardSimPukView extends KeyguardPinBasedInputView { 48 private static final String LOG_TAG = "KeyguardSimPukView"; 49 private static final boolean DEBUG = KeyguardConstants.DEBUG; 50 public static final String TAG = "KeyguardSimPukView"; 51 52 private ProgressDialog mSimUnlockProgressDialog = null; 53 private CheckSimPuk mCheckSimPukThread; 54 55 // Below flag is set to true during power-up or when a new SIM card inserted on device. 56 // When this is true and when SIM card is PUK locked state, on PIN lock screen, message would 57 // be displayed to inform user about the number of remaining PUK attempts left. 58 private boolean mShowDefaultMessage = true; 59 private int mRemainingAttempts = -1; 60 private String mPukText; 61 private String mPinText; 62 private StateMachine mStateMachine = new StateMachine(); 63 private AlertDialog mRemainingAttemptsDialog; 64 private int mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; 65 private ImageView mSimImageView; 66 67 KeyguardUpdateMonitorCallback mUpdateMonitorCallback = new KeyguardUpdateMonitorCallback() { 68 @Override 69 public void onSimStateChanged(int subId, int slotId, State simState) { 70 if (DEBUG) Log.v(TAG, "onSimStateChanged(subId=" + subId + ",state=" + simState + ")"); 71 switch(simState) { 72 // If the SIM is unlocked via a key sequence through the emergency dialer, it will 73 // move into the READY state and the PUK lock keyguard should be removed. 74 case READY: { 75 mRemainingAttempts = -1; 76 mShowDefaultMessage = true; 77 // mCallback can be null if onSimStateChanged callback is called when keyguard 78 // isn't active. 79 if (mCallback != null) { 80 mCallback.dismiss(true, KeyguardUpdateMonitor.getCurrentUser()); 81 } 82 break; 83 } 84 default: 85 resetState(); 86 } 87 } 88 }; 89 KeyguardSimPukView(Context context)90 public KeyguardSimPukView(Context context) { 91 this(context, null); 92 } 93 KeyguardSimPukView(Context context, AttributeSet attrs)94 public KeyguardSimPukView(Context context, AttributeSet attrs) { 95 super(context, attrs); 96 } 97 98 private class StateMachine { 99 final int ENTER_PUK = 0; 100 final int ENTER_PIN = 1; 101 final int CONFIRM_PIN = 2; 102 final int DONE = 3; 103 private int state = ENTER_PUK; 104 next()105 public void next() { 106 int msg = 0; 107 if (state == ENTER_PUK) { 108 if (checkPuk()) { 109 state = ENTER_PIN; 110 msg = R.string.kg_puk_enter_pin_hint; 111 } else { 112 msg = R.string.kg_invalid_sim_puk_hint; 113 } 114 } else if (state == ENTER_PIN) { 115 if (checkPin()) { 116 state = CONFIRM_PIN; 117 msg = R.string.kg_enter_confirm_pin_hint; 118 } else { 119 msg = R.string.kg_invalid_sim_pin_hint; 120 } 121 } else if (state == CONFIRM_PIN) { 122 if (confirmPin()) { 123 state = DONE; 124 msg = R.string.keyguard_sim_unlock_progress_dialog_message; 125 updateSim(); 126 } else { 127 state = ENTER_PIN; // try again? 128 msg = R.string.kg_invalid_confirm_pin_hint; 129 } 130 } 131 resetPasswordText(true /* animate */, true /* announce */); 132 if (msg != 0) { 133 mSecurityMessageDisplay.setMessage(msg); 134 } 135 } 136 137 reset()138 void reset() { 139 mPinText=""; 140 mPukText=""; 141 state = ENTER_PUK; 142 handleSubInfoChangeIfNeeded(); 143 if (mShowDefaultMessage) { 144 showDefaultMessage(); 145 } 146 boolean isEsimLocked = KeyguardEsimArea.isEsimLocked(mContext, mSubId); 147 148 KeyguardEsimArea esimButton = findViewById(R.id.keyguard_esim_area); 149 esimButton.setVisibility(isEsimLocked ? View.VISIBLE : View.GONE); 150 mPasswordEntry.requestFocus(); 151 } 152 153 154 } 155 showDefaultMessage()156 private void showDefaultMessage() { 157 if (mRemainingAttempts >= 0) { 158 mSecurityMessageDisplay.setMessage(getPukPasswordErrorMessage( 159 mRemainingAttempts, true)); 160 return; 161 } 162 163 boolean isEsimLocked = KeyguardEsimArea.isEsimLocked(mContext, mSubId); 164 int count = TelephonyManager.getDefault().getSimCount(); 165 Resources rez = getResources(); 166 String msg; 167 TypedArray array = mContext.obtainStyledAttributes(new int[] { R.attr.wallpaperTextColor }); 168 int color = array.getColor(0, Color.WHITE); 169 array.recycle(); 170 if (count < 2) { 171 msg = rez.getString(R.string.kg_puk_enter_puk_hint); 172 } else { 173 SubscriptionInfo info = KeyguardUpdateMonitor.getInstance(mContext). 174 getSubscriptionInfoForSubId(mSubId); 175 CharSequence displayName = info != null ? info.getDisplayName() : ""; 176 msg = rez.getString(R.string.kg_puk_enter_puk_hint_multi, displayName); 177 if (info != null) { 178 color = info.getIconTint(); 179 } 180 } 181 if (isEsimLocked) { 182 msg = rez.getString(R.string.kg_sim_lock_esim_instructions, msg); 183 } 184 if (mSecurityMessageDisplay != null) { 185 mSecurityMessageDisplay.setMessage(msg); 186 } 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 PinResult result) { 192 if (result == null) Log.e(LOG_TAG, "onSimCheckResponse, pin result is NULL"); 193 else { 194 Log.d(LOG_TAG, "onSimCheckResponse " + " empty One result " 195 + result.toString()); 196 if (result.getAttemptsRemaining() >= 0) { 197 mRemainingAttempts = result.getAttemptsRemaining(); 198 mSecurityMessageDisplay.setMessage( 199 getPukPasswordErrorMessage(result.getAttemptsRemaining(), true)); 200 } 201 } 202 } 203 }.start(); 204 } 205 handleSubInfoChangeIfNeeded()206 private void handleSubInfoChangeIfNeeded() { 207 KeyguardUpdateMonitor monitor = KeyguardUpdateMonitor.getInstance(mContext); 208 int subId = monitor.getNextSubIdForState(IccCardConstants.State.PUK_REQUIRED); 209 if (subId != mSubId && SubscriptionManager.isValidSubscriptionId(subId)) { 210 mSubId = subId; 211 mShowDefaultMessage = true; 212 mRemainingAttempts = -1; 213 } 214 } 215 216 @Override getPromptReasonStringRes(int reason)217 protected int getPromptReasonStringRes(int reason) { 218 // No message on SIM Puk 219 return 0; 220 } 221 getPukPasswordErrorMessage(int attemptsRemaining, boolean isDefault)222 private String getPukPasswordErrorMessage(int attemptsRemaining, boolean isDefault) { 223 String displayMessage; 224 225 if (attemptsRemaining == 0) { 226 displayMessage = getContext().getString(R.string.kg_password_wrong_puk_code_dead); 227 } else if (attemptsRemaining > 0) { 228 int msgId = isDefault ? R.plurals.kg_password_default_puk_message : 229 R.plurals.kg_password_wrong_puk_code; 230 displayMessage = getContext().getResources() 231 .getQuantityString(msgId, attemptsRemaining, attemptsRemaining); 232 } else { 233 int msgId = isDefault ? R.string.kg_puk_enter_puk_hint : 234 R.string.kg_password_puk_failed; 235 displayMessage = getContext().getString(msgId); 236 } 237 if (KeyguardEsimArea.isEsimLocked(mContext, mSubId)) { 238 displayMessage = getResources() 239 .getString(R.string.kg_sim_lock_esim_instructions, displayMessage); 240 } 241 if (DEBUG) Log.d(LOG_TAG, "getPukPasswordErrorMessage:" 242 + " attemptsRemaining=" + attemptsRemaining + " displayMessage=" + displayMessage); 243 return displayMessage; 244 } 245 246 @Override resetState()247 public void resetState() { 248 super.resetState(); 249 mStateMachine.reset(); 250 } 251 252 @Override shouldLockout(long deadline)253 protected boolean shouldLockout(long deadline) { 254 // SIM PUK doesn't have a timed lockout 255 return false; 256 } 257 258 @Override getPasswordTextViewId()259 protected int getPasswordTextViewId() { 260 return R.id.pukEntry; 261 } 262 263 @Override onFinishInflate()264 protected void onFinishInflate() { 265 super.onFinishInflate(); 266 267 if (mEcaView instanceof EmergencyCarrierArea) { 268 ((EmergencyCarrierArea) mEcaView).setCarrierTextVisible(true); 269 } 270 mSimImageView = findViewById(R.id.keyguard_sim); 271 } 272 273 @Override onAttachedToWindow()274 protected void onAttachedToWindow() { 275 super.onAttachedToWindow(); 276 KeyguardUpdateMonitor.getInstance(mContext).registerCallback(mUpdateMonitorCallback); 277 resetState(); 278 } 279 280 @Override onDetachedFromWindow()281 protected void onDetachedFromWindow() { 282 super.onDetachedFromWindow(); 283 KeyguardUpdateMonitor.getInstance(mContext).removeCallback(mUpdateMonitorCallback); 284 } 285 286 @Override showUsabilityHint()287 public void showUsabilityHint() { 288 } 289 290 @Override onPause()291 public void onPause() { 292 // dismiss the dialog. 293 if (mSimUnlockProgressDialog != null) { 294 mSimUnlockProgressDialog.dismiss(); 295 mSimUnlockProgressDialog = null; 296 } 297 } 298 299 /** 300 * Since the IPC can block, we want to run the request in a separate thread 301 * with a callback. 302 */ 303 private abstract class CheckSimPuk extends Thread { 304 305 private final String mPin, mPuk; 306 private final int mSubId; 307 CheckSimPuk(String puk, String pin, int subId)308 protected CheckSimPuk(String puk, String pin, int subId) { 309 mPuk = puk; 310 mPin = pin; 311 mSubId = subId; 312 } 313 onSimLockChangedResponse(@onNull PinResult result)314 abstract void onSimLockChangedResponse(@NonNull PinResult result); 315 316 @Override run()317 public void run() { 318 if (DEBUG) Log.v(TAG, "call supplyPukReportResult()"); 319 TelephonyManager telephonyManager = 320 ((TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE)) 321 .createForSubscriptionId(mSubId); 322 final PinResult result = telephonyManager.supplyPukReportPinResult(mPuk, mPin); 323 if (result == null) { 324 Log.e(TAG, "Error result for supplyPukReportResult."); 325 post(new Runnable() { 326 @Override 327 public void run() { 328 onSimLockChangedResponse(PinResult.getDefaultFailedResult()); 329 } 330 }); 331 } else { 332 if (DEBUG) { 333 Log.v(TAG, "supplyPukReportResult returned: " + result.toString()); 334 } 335 post(new Runnable() { 336 @Override 337 public void run() { 338 onSimLockChangedResponse(result); 339 } 340 }); 341 } 342 } 343 } 344 getSimUnlockProgressDialog()345 private Dialog getSimUnlockProgressDialog() { 346 if (mSimUnlockProgressDialog == null) { 347 mSimUnlockProgressDialog = new ProgressDialog(mContext); 348 mSimUnlockProgressDialog.setMessage( 349 mContext.getString(R.string.kg_sim_unlock_progress_dialog_message)); 350 mSimUnlockProgressDialog.setIndeterminate(true); 351 mSimUnlockProgressDialog.setCancelable(false); 352 if (!(mContext instanceof Activity)) { 353 mSimUnlockProgressDialog.getWindow().setType( 354 WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); 355 } 356 } 357 return mSimUnlockProgressDialog; 358 } 359 getPukRemainingAttemptsDialog(int remaining)360 private Dialog getPukRemainingAttemptsDialog(int remaining) { 361 String msg = getPukPasswordErrorMessage(remaining, false); 362 if (mRemainingAttemptsDialog == null) { 363 AlertDialog.Builder builder = new AlertDialog.Builder(mContext); 364 builder.setMessage(msg); 365 builder.setCancelable(false); 366 builder.setNeutralButton(R.string.ok, null); 367 mRemainingAttemptsDialog = builder.create(); 368 mRemainingAttemptsDialog.getWindow().setType( 369 WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); 370 } else { 371 mRemainingAttemptsDialog.setMessage(msg); 372 } 373 return mRemainingAttemptsDialog; 374 } 375 checkPuk()376 private boolean checkPuk() { 377 // make sure the puk is at least 8 digits long. 378 if (mPasswordEntry.getText().length() == 8) { 379 mPukText = mPasswordEntry.getText(); 380 return true; 381 } 382 return false; 383 } 384 checkPin()385 private boolean checkPin() { 386 // make sure the PIN is between 4 and 8 digits 387 int length = mPasswordEntry.getText().length(); 388 if (length >= 4 && length <= 8) { 389 mPinText = mPasswordEntry.getText(); 390 return true; 391 } 392 return false; 393 } 394 confirmPin()395 public boolean confirmPin() { 396 return mPinText.equals(mPasswordEntry.getText()); 397 } 398 updateSim()399 private void updateSim() { 400 getSimUnlockProgressDialog().show(); 401 402 if (mCheckSimPukThread == null) { 403 mCheckSimPukThread = new CheckSimPuk(mPukText, mPinText, mSubId) { 404 @Override 405 void onSimLockChangedResponse(final PinResult result) { 406 post(new Runnable() { 407 @Override 408 public void run() { 409 if (mSimUnlockProgressDialog != null) { 410 mSimUnlockProgressDialog.hide(); 411 } 412 resetPasswordText(true /* animate */, 413 /* announce */ 414 result.getType() != PinResult.PIN_RESULT_TYPE_SUCCESS); 415 if (result.getType() == PinResult.PIN_RESULT_TYPE_SUCCESS) { 416 KeyguardUpdateMonitor.getInstance(getContext()) 417 .reportSimUnlocked(mSubId); 418 mRemainingAttempts = -1; 419 mShowDefaultMessage = true; 420 if (mCallback != null) { 421 mCallback.dismiss(true, 422 KeyguardUpdateMonitor.getCurrentUser()); 423 } 424 } else { 425 mShowDefaultMessage = false; 426 if (result.getType() == PinResult.PIN_RESULT_TYPE_INCORRECT) { 427 // show message 428 mSecurityMessageDisplay.setMessage(getPukPasswordErrorMessage( 429 result.getAttemptsRemaining(), false)); 430 if (result.getAttemptsRemaining() <= 2) { 431 // this is getting critical - show dialog 432 getPukRemainingAttemptsDialog( 433 result.getAttemptsRemaining()).show(); 434 } else { 435 // show message 436 mSecurityMessageDisplay.setMessage( 437 getPukPasswordErrorMessage( 438 result.getAttemptsRemaining(), false)); 439 } 440 } else { 441 mSecurityMessageDisplay.setMessage(getContext().getString( 442 R.string.kg_password_puk_failed)); 443 } 444 if (DEBUG) Log.d(LOG_TAG, "verifyPasswordAndUnlock " 445 + " UpdateSim.onSimCheckResponse: " 446 + " attemptsRemaining=" + result.getAttemptsRemaining()); 447 mStateMachine.reset(); 448 } 449 mCheckSimPukThread = null; 450 } 451 }); 452 } 453 }; 454 mCheckSimPukThread.start(); 455 } 456 } 457 458 @Override verifyPasswordAndUnlock()459 protected void verifyPasswordAndUnlock() { 460 mStateMachine.next(); 461 } 462 463 @Override startAppearAnimation()464 public void startAppearAnimation() { 465 // noop. 466 } 467 468 @Override startDisappearAnimation(Runnable finishRunnable)469 public boolean startDisappearAnimation(Runnable finishRunnable) { 470 return false; 471 } 472 473 @Override getTitle()474 public CharSequence getTitle() { 475 return getContext().getString( 476 com.android.internal.R.string.keyguard_accessibility_sim_puk_unlock); 477 } 478 } 479 480 481