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