1 /* 2 * Copyright (C) 2020 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.res.ColorStateList; 25 import android.content.res.Resources; 26 import android.content.res.TypedArray; 27 import android.graphics.Color; 28 import android.telephony.PinResult; 29 import android.telephony.SubscriptionInfo; 30 import android.telephony.SubscriptionManager; 31 import android.telephony.TelephonyManager; 32 import android.util.Log; 33 import android.view.View; 34 import android.view.WindowManager; 35 import android.widget.ImageView; 36 37 import com.android.internal.util.LatencyTracker; 38 import com.android.internal.widget.LockPatternUtils; 39 import com.android.keyguard.KeyguardSecurityModel.SecurityMode; 40 import com.android.systemui.R; 41 import com.android.systemui.classifier.FalsingCollector; 42 43 public class KeyguardSimPukViewController 44 extends KeyguardPinBasedInputViewController<KeyguardSimPukView> { 45 private static final boolean DEBUG = KeyguardConstants.DEBUG; 46 public static final String TAG = "KeyguardSimPukView"; 47 48 private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; 49 private final TelephonyManager mTelephonyManager; 50 51 private String mPukText; 52 private String mPinText; 53 private int mRemainingAttempts; 54 // Below flag is set to true during power-up or when a new SIM card inserted on device. 55 // When this is true and when SIM card is PUK locked state, on PIN lock screen, message would 56 // be displayed to inform user about the number of remaining PUK attempts left. 57 private boolean mShowDefaultMessage; 58 private StateMachine mStateMachine = new StateMachine(); 59 private int mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; 60 private CheckSimPuk mCheckSimPukThread; 61 private ProgressDialog mSimUnlockProgressDialog; 62 63 KeyguardUpdateMonitorCallback mUpdateMonitorCallback = new KeyguardUpdateMonitorCallback() { 64 @Override 65 public void onSimStateChanged(int subId, int slotId, int simState) { 66 if (DEBUG) Log.v(TAG, "onSimStateChanged(subId=" + subId + ",state=" + simState + ")"); 67 // If the SIM is unlocked via a key sequence through the emergency dialer, it will 68 // move into the READY state and the PUK lock keyguard should be removed. 69 if (simState == TelephonyManager.SIM_STATE_READY) { 70 mRemainingAttempts = -1; 71 mShowDefaultMessage = true; 72 getKeyguardSecurityCallback().dismiss(true, KeyguardUpdateMonitor.getCurrentUser()); 73 } else { 74 resetState(); 75 } 76 } 77 }; 78 private ImageView mSimImageView; 79 private AlertDialog mRemainingAttemptsDialog; 80 KeyguardSimPukViewController(KeyguardSimPukView view, KeyguardUpdateMonitor keyguardUpdateMonitor, SecurityMode securityMode, LockPatternUtils lockPatternUtils, KeyguardSecurityCallback keyguardSecurityCallback, KeyguardMessageAreaController.Factory messageAreaControllerFactory, LatencyTracker latencyTracker, LiftToActivateListener liftToActivateListener, TelephonyManager telephonyManager, FalsingCollector falsingCollector, EmergencyButtonController emergencyButtonController)81 protected KeyguardSimPukViewController(KeyguardSimPukView view, 82 KeyguardUpdateMonitor keyguardUpdateMonitor, 83 SecurityMode securityMode, LockPatternUtils lockPatternUtils, 84 KeyguardSecurityCallback keyguardSecurityCallback, 85 KeyguardMessageAreaController.Factory messageAreaControllerFactory, 86 LatencyTracker latencyTracker, LiftToActivateListener liftToActivateListener, 87 TelephonyManager telephonyManager, FalsingCollector falsingCollector, 88 EmergencyButtonController emergencyButtonController) { 89 super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback, 90 messageAreaControllerFactory, latencyTracker, liftToActivateListener, 91 emergencyButtonController, falsingCollector); 92 mKeyguardUpdateMonitor = keyguardUpdateMonitor; 93 mTelephonyManager = telephonyManager; 94 mSimImageView = mView.findViewById(R.id.keyguard_sim); 95 } 96 97 @Override onViewAttached()98 protected void onViewAttached() { 99 super.onViewAttached(); 100 mKeyguardUpdateMonitor.registerCallback(mUpdateMonitorCallback); 101 } 102 103 @Override onViewDetached()104 protected void onViewDetached() { 105 super.onViewDetached(); 106 mKeyguardUpdateMonitor.removeCallback(mUpdateMonitorCallback); 107 } 108 109 @Override resetState()110 void resetState() { 111 super.resetState(); 112 mStateMachine.reset(); 113 } 114 115 @Override verifyPasswordAndUnlock()116 protected void verifyPasswordAndUnlock() { 117 mStateMachine.next(); 118 } 119 120 private class StateMachine { 121 static final int ENTER_PUK = 0; 122 static final int ENTER_PIN = 1; 123 static final int CONFIRM_PIN = 2; 124 static final int DONE = 3; 125 126 private int mState = ENTER_PUK; 127 next()128 public void next() { 129 int msg = 0; 130 if (mState == ENTER_PUK) { 131 if (checkPuk()) { 132 mState = ENTER_PIN; 133 msg = com.android.systemui.R.string.kg_puk_enter_pin_hint; 134 } else { 135 msg = com.android.systemui.R.string.kg_invalid_sim_puk_hint; 136 } 137 } else if (mState == ENTER_PIN) { 138 if (checkPin()) { 139 mState = CONFIRM_PIN; 140 msg = com.android.systemui.R.string.kg_enter_confirm_pin_hint; 141 } else { 142 msg = com.android.systemui.R.string.kg_invalid_sim_pin_hint; 143 } 144 } else if (mState == CONFIRM_PIN) { 145 if (confirmPin()) { 146 mState = DONE; 147 msg = com.android.systemui.R.string.keyguard_sim_unlock_progress_dialog_message; 148 updateSim(); 149 } else { 150 mState = ENTER_PIN; // try again? 151 msg = com.android.systemui.R.string.kg_invalid_confirm_pin_hint; 152 } 153 } 154 mView.resetPasswordText(true /* animate */, true /* announce */); 155 if (msg != 0) { 156 mMessageAreaController.setMessage(msg); 157 } 158 } 159 160 reset()161 void reset() { 162 mPinText = ""; 163 mPukText = ""; 164 mState = ENTER_PUK; 165 handleSubInfoChangeIfNeeded(); 166 if (mShowDefaultMessage) { 167 showDefaultMessage(); 168 } 169 boolean isEsimLocked = KeyguardEsimArea.isEsimLocked(mView.getContext(), mSubId); 170 171 KeyguardEsimArea esimButton = mView.findViewById(R.id.keyguard_esim_area); 172 esimButton.setVisibility(isEsimLocked ? View.VISIBLE : View.GONE); 173 mPasswordEntry.requestFocus(); 174 } 175 } 176 showDefaultMessage()177 private void showDefaultMessage() { 178 if (mRemainingAttempts >= 0) { 179 mMessageAreaController.setMessage(mView.getPukPasswordErrorMessage( 180 mRemainingAttempts, true, 181 KeyguardEsimArea.isEsimLocked(mView.getContext(), mSubId))); 182 return; 183 } 184 185 boolean isEsimLocked = KeyguardEsimArea.isEsimLocked(mView.getContext(), mSubId); 186 int count = 1; 187 if (mTelephonyManager != null) { 188 count = mTelephonyManager.getActiveModemCount(); 189 } 190 Resources rez = mView.getResources(); 191 String msg; 192 TypedArray array = mView.getContext().obtainStyledAttributes( 193 new int[] { android.R.attr.textColor }); 194 int color = array.getColor(0, Color.WHITE); 195 array.recycle(); 196 if (count < 2) { 197 msg = rez.getString(R.string.kg_puk_enter_puk_hint); 198 } else { 199 SubscriptionInfo info = mKeyguardUpdateMonitor.getSubscriptionInfoForSubId(mSubId); 200 CharSequence displayName = info != null ? info.getDisplayName() : ""; 201 msg = rez.getString(R.string.kg_puk_enter_puk_hint_multi, displayName); 202 if (info != null) { 203 color = info.getIconTint(); 204 } 205 } 206 if (isEsimLocked) { 207 msg = rez.getString(R.string.kg_sim_lock_esim_instructions, msg); 208 } 209 mMessageAreaController.setMessage(msg); 210 mSimImageView.setImageTintList(ColorStateList.valueOf(color)); 211 212 // Sending empty PUK here to query the number of remaining PIN attempts 213 new CheckSimPuk("", "", mSubId) { 214 void onSimLockChangedResponse(final PinResult result) { 215 if (result == null) Log.e(TAG, "onSimCheckResponse, pin result is NULL"); 216 else { 217 Log.d(TAG, "onSimCheckResponse " + " empty One result " 218 + result.toString()); 219 if (result.getAttemptsRemaining() >= 0) { 220 mRemainingAttempts = result.getAttemptsRemaining(); 221 mMessageAreaController.setMessage( 222 mView.getPukPasswordErrorMessage( 223 result.getAttemptsRemaining(), true, 224 KeyguardEsimArea.isEsimLocked(mView.getContext(), mSubId))); 225 } 226 } 227 } 228 }.start(); 229 } 230 checkPuk()231 private boolean checkPuk() { 232 // make sure the puk is at least 8 digits long. 233 if (mPasswordEntry.getText().length() == 8) { 234 mPukText = mPasswordEntry.getText(); 235 return true; 236 } 237 return false; 238 } 239 checkPin()240 private boolean checkPin() { 241 // make sure the PIN is between 4 and 8 digits 242 int length = mPasswordEntry.getText().length(); 243 if (length >= 4 && length <= 8) { 244 mPinText = mPasswordEntry.getText(); 245 return true; 246 } 247 return false; 248 } 249 confirmPin()250 public boolean confirmPin() { 251 return mPinText.equals(mPasswordEntry.getText()); 252 } 253 254 255 256 updateSim()257 private void updateSim() { 258 getSimUnlockProgressDialog().show(); 259 260 if (mCheckSimPukThread == null) { 261 mCheckSimPukThread = new CheckSimPuk(mPukText, mPinText, mSubId) { 262 @Override 263 void onSimLockChangedResponse(final PinResult result) { 264 mView.post(() -> { 265 if (mSimUnlockProgressDialog != null) { 266 mSimUnlockProgressDialog.hide(); 267 } 268 mView.resetPasswordText(true /* animate */, 269 /* announce */ 270 result.getResult() != PinResult.PIN_RESULT_TYPE_SUCCESS); 271 if (result.getResult() == PinResult.PIN_RESULT_TYPE_SUCCESS) { 272 mKeyguardUpdateMonitor.reportSimUnlocked(mSubId); 273 mRemainingAttempts = -1; 274 mShowDefaultMessage = true; 275 276 getKeyguardSecurityCallback().dismiss( 277 true, KeyguardUpdateMonitor.getCurrentUser()); 278 } else { 279 mShowDefaultMessage = false; 280 if (result.getResult() == PinResult.PIN_RESULT_TYPE_INCORRECT) { 281 // show message 282 mMessageAreaController.setMessage(mView.getPukPasswordErrorMessage( 283 result.getAttemptsRemaining(), false, 284 KeyguardEsimArea.isEsimLocked(mView.getContext(), mSubId))); 285 if (result.getAttemptsRemaining() <= 2) { 286 // this is getting critical - show dialog 287 getPukRemainingAttemptsDialog( 288 result.getAttemptsRemaining()).show(); 289 } else { 290 // show message 291 mMessageAreaController.setMessage( 292 mView.getPukPasswordErrorMessage( 293 result.getAttemptsRemaining(), false, 294 KeyguardEsimArea.isEsimLocked( 295 mView.getContext(), mSubId))); 296 } 297 } else { 298 mMessageAreaController.setMessage(mView.getResources().getString( 299 R.string.kg_password_puk_failed)); 300 } 301 if (DEBUG) { 302 Log.d(TAG, "verifyPasswordAndUnlock " 303 + " UpdateSim.onSimCheckResponse: " 304 + " attemptsRemaining=" + result.getAttemptsRemaining()); 305 } 306 } 307 mStateMachine.reset(); 308 mCheckSimPukThread = null; 309 }); 310 } 311 }; 312 mCheckSimPukThread.start(); 313 } 314 } 315 316 @Override shouldLockout(long deadline)317 protected boolean shouldLockout(long deadline) { 318 // SIM PUK doesn't have a timed lockout 319 return false; 320 } 321 getSimUnlockProgressDialog()322 private Dialog getSimUnlockProgressDialog() { 323 if (mSimUnlockProgressDialog == null) { 324 mSimUnlockProgressDialog = new ProgressDialog(mView.getContext()); 325 mSimUnlockProgressDialog.setMessage( 326 mView.getResources().getString(R.string.kg_sim_unlock_progress_dialog_message)); 327 mSimUnlockProgressDialog.setIndeterminate(true); 328 mSimUnlockProgressDialog.setCancelable(false); 329 if (!(mView.getContext() instanceof Activity)) { 330 mSimUnlockProgressDialog.getWindow().setType( 331 WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); 332 } 333 } 334 return mSimUnlockProgressDialog; 335 } 336 handleSubInfoChangeIfNeeded()337 private void handleSubInfoChangeIfNeeded() { 338 int subId = mKeyguardUpdateMonitor.getNextSubIdForState( 339 TelephonyManager.SIM_STATE_PUK_REQUIRED); 340 if (subId != mSubId && SubscriptionManager.isValidSubscriptionId(subId)) { 341 mSubId = subId; 342 mShowDefaultMessage = true; 343 mRemainingAttempts = -1; 344 } 345 } 346 347 getPukRemainingAttemptsDialog(int remaining)348 private Dialog getPukRemainingAttemptsDialog(int remaining) { 349 String msg = mView.getPukPasswordErrorMessage(remaining, false, 350 KeyguardEsimArea.isEsimLocked(mView.getContext(), mSubId)); 351 if (mRemainingAttemptsDialog == null) { 352 AlertDialog.Builder builder = new AlertDialog.Builder(mView.getContext()); 353 builder.setMessage(msg); 354 builder.setCancelable(false); 355 builder.setNeutralButton(R.string.ok, null); 356 mRemainingAttemptsDialog = builder.create(); 357 mRemainingAttemptsDialog.getWindow().setType( 358 WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); 359 } else { 360 mRemainingAttemptsDialog.setMessage(msg); 361 } 362 return mRemainingAttemptsDialog; 363 } 364 365 @Override onPause()366 public void onPause() { 367 // dismiss the dialog. 368 if (mSimUnlockProgressDialog != null) { 369 mSimUnlockProgressDialog.dismiss(); 370 mSimUnlockProgressDialog = null; 371 } 372 } 373 374 /** 375 * Since the IPC can block, we want to run the request in a separate thread 376 * with a callback. 377 */ 378 private abstract class CheckSimPuk extends Thread { 379 380 private final String mPin, mPuk; 381 private final int mSubId; 382 CheckSimPuk(String puk, String pin, int subId)383 protected CheckSimPuk(String puk, String pin, int subId) { 384 mPuk = puk; 385 mPin = pin; 386 mSubId = subId; 387 } 388 onSimLockChangedResponse(@onNull PinResult result)389 abstract void onSimLockChangedResponse(@NonNull PinResult result); 390 391 @Override run()392 public void run() { 393 if (DEBUG) { 394 Log.v(TAG, "call supplyIccLockPuk(subid=" + mSubId + ")"); 395 } 396 TelephonyManager telephonyManager = mTelephonyManager.createForSubscriptionId(mSubId); 397 final PinResult result = telephonyManager.supplyIccLockPuk(mPuk, mPin); 398 if (DEBUG) { 399 Log.v(TAG, "supplyIccLockPuk returned: " + result.toString()); 400 } 401 mView.post(() -> onSimLockChangedResponse(result)); 402 } 403 } 404 405 } 406