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 static com.android.systemui.util.PluralMessageFormaterKt.icuMessageFormat; 20 21 import android.annotation.NonNull; 22 import android.app.AlertDialog; 23 import android.app.AlertDialog.Builder; 24 import android.app.Dialog; 25 import android.app.ProgressDialog; 26 import android.content.res.ColorStateList; 27 import android.content.res.Resources; 28 import android.content.res.TypedArray; 29 import android.graphics.Color; 30 import android.telephony.PinResult; 31 import android.telephony.SubscriptionInfo; 32 import android.telephony.SubscriptionManager; 33 import android.telephony.TelephonyManager; 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.util.LatencyTracker; 40 import com.android.internal.widget.LockPatternUtils; 41 import com.android.keyguard.KeyguardSecurityModel.SecurityMode; 42 import com.android.systemui.R; 43 import com.android.systemui.classifier.FalsingCollector; 44 import com.android.systemui.flags.FeatureFlags; 45 46 public class KeyguardSimPinViewController 47 extends KeyguardPinBasedInputViewController<KeyguardSimPinView> { 48 public static final String TAG = "KeyguardSimPinView"; 49 private static final String LOG_TAG = "KeyguardSimPinView"; 50 private static final boolean DEBUG = KeyguardConstants.DEBUG_SIM_STATES; 51 private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; 52 private final TelephonyManager mTelephonyManager; 53 54 private ProgressDialog mSimUnlockProgressDialog; 55 private CheckSimPin mCheckSimPinThread; 56 private int mRemainingAttempts; 57 // Below flag is set to true during power-up or when a new SIM card inserted on device. 58 // When this is true and when SIM card is PIN locked state, on PIN lock screen, message would 59 // be displayed to inform user about the number of remaining PIN attempts left. 60 private boolean mShowDefaultMessage; 61 private int mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; 62 private AlertDialog mRemainingAttemptsDialog; 63 private ImageView mSimImageView; 64 65 KeyguardUpdateMonitorCallback mUpdateMonitorCallback = new KeyguardUpdateMonitorCallback() { 66 @Override 67 public void onSimStateChanged(int subId, int slotId, int simState) { 68 if (DEBUG) Log.v(TAG, "onSimStateChanged(subId=" + subId + ",state=" + simState + ")"); 69 if (simState == TelephonyManager.SIM_STATE_READY) { 70 mRemainingAttempts = -1; 71 resetState(); 72 } else { 73 resetState(); 74 } 75 } 76 }; 77 KeyguardSimPinViewController(KeyguardSimPinView view, KeyguardUpdateMonitor keyguardUpdateMonitor, SecurityMode securityMode, LockPatternUtils lockPatternUtils, KeyguardSecurityCallback keyguardSecurityCallback, KeyguardMessageAreaController.Factory messageAreaControllerFactory, LatencyTracker latencyTracker, LiftToActivateListener liftToActivateListener, TelephonyManager telephonyManager, FalsingCollector falsingCollector, EmergencyButtonController emergencyButtonController, FeatureFlags featureFlags)78 protected KeyguardSimPinViewController(KeyguardSimPinView view, 79 KeyguardUpdateMonitor keyguardUpdateMonitor, 80 SecurityMode securityMode, LockPatternUtils lockPatternUtils, 81 KeyguardSecurityCallback keyguardSecurityCallback, 82 KeyguardMessageAreaController.Factory messageAreaControllerFactory, 83 LatencyTracker latencyTracker, LiftToActivateListener liftToActivateListener, 84 TelephonyManager telephonyManager, FalsingCollector falsingCollector, 85 EmergencyButtonController emergencyButtonController, FeatureFlags featureFlags) { 86 super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback, 87 messageAreaControllerFactory, latencyTracker, liftToActivateListener, 88 emergencyButtonController, falsingCollector, featureFlags); 89 mKeyguardUpdateMonitor = keyguardUpdateMonitor; 90 mTelephonyManager = telephonyManager; 91 mSimImageView = mView.findViewById(R.id.keyguard_sim); 92 } 93 94 @Override onViewAttached()95 protected void onViewAttached() { 96 super.onViewAttached(); 97 } 98 99 @Override resetState()100 void resetState() { 101 super.resetState(); 102 if (DEBUG) Log.v(TAG, "Resetting state"); 103 handleSubInfoChangeIfNeeded(); 104 mMessageAreaController.setMessage(""); 105 if (mShowDefaultMessage) { 106 showDefaultMessage(); 107 } 108 109 mView.setESimLocked(KeyguardEsimArea.isEsimLocked(mView.getContext(), mSubId), mSubId); 110 } 111 112 @Override startDisappearAnimation(Runnable finishRunnable)113 public boolean startDisappearAnimation(Runnable finishRunnable) { 114 return false; 115 } 116 117 @Override onResume(int reason)118 public void onResume(int reason) { 119 super.onResume(reason); 120 mKeyguardUpdateMonitor.registerCallback(mUpdateMonitorCallback); 121 mView.resetState(); 122 } 123 124 @Override onPause()125 public void onPause() { 126 super.onPause(); 127 mKeyguardUpdateMonitor.removeCallback(mUpdateMonitorCallback); 128 129 // dismiss the dialog. 130 if (mSimUnlockProgressDialog != null) { 131 mSimUnlockProgressDialog.dismiss(); 132 mSimUnlockProgressDialog = null; 133 } 134 } 135 136 @Override verifyPasswordAndUnlock()137 protected void verifyPasswordAndUnlock() { 138 String entry = mPasswordEntry.getText(); 139 140 // A SIM PIN is 4 to 8 decimal digits according to 141 // GSM 02.17 version 5.0.1, Section 5.6 PIN Management 142 if ((entry.length() < 4) || (entry.length() > 8)) { 143 // otherwise, display a message to the user, and don't submit. 144 mMessageAreaController.setMessage( 145 com.android.systemui.R.string.kg_invalid_sim_pin_hint); 146 mView.resetPasswordText(true /* animate */, true /* announce */); 147 getKeyguardSecurityCallback().userActivity(); 148 return; 149 } 150 151 getSimUnlockProgressDialog().show(); 152 153 if (mCheckSimPinThread == null) { 154 mCheckSimPinThread = new CheckSimPin(mPasswordEntry.getText(), mSubId) { 155 @Override 156 void onSimCheckResponse(final PinResult result) { 157 mView.post(() -> { 158 mRemainingAttempts = result.getAttemptsRemaining(); 159 if (mSimUnlockProgressDialog != null) { 160 mSimUnlockProgressDialog.hide(); 161 } 162 mView.resetPasswordText(true /* animate */, 163 /* announce */ 164 result.getResult() != PinResult.PIN_RESULT_TYPE_SUCCESS); 165 if (result.getResult() == PinResult.PIN_RESULT_TYPE_SUCCESS) { 166 mKeyguardUpdateMonitor.reportSimUnlocked(mSubId); 167 mRemainingAttempts = -1; 168 mShowDefaultMessage = true; 169 getKeyguardSecurityCallback().dismiss( 170 true, KeyguardUpdateMonitor.getCurrentUser(), 171 SecurityMode.SimPin); 172 } else { 173 mShowDefaultMessage = false; 174 if (result.getResult() == PinResult.PIN_RESULT_TYPE_INCORRECT) { 175 if (result.getAttemptsRemaining() <= 2) { 176 // this is getting critical - show dialog 177 getSimRemainingAttemptsDialog( 178 result.getAttemptsRemaining()).show(); 179 } else { 180 // show message 181 mMessageAreaController.setMessage( 182 getPinPasswordErrorMessage( 183 result.getAttemptsRemaining(), false)); 184 } 185 } else { 186 // "PIN operation failed!" - no idea what this was and no way to 187 // find out. :/ 188 mMessageAreaController.setMessage(mView.getResources().getString( 189 R.string.kg_password_pin_failed)); 190 } 191 if (DEBUG) { 192 Log.d(LOG_TAG, "verifyPasswordAndUnlock " 193 + " CheckSimPin.onSimCheckResponse: " + result 194 + " attemptsRemaining=" + result.getAttemptsRemaining()); 195 } 196 } 197 getKeyguardSecurityCallback().userActivity(); 198 mCheckSimPinThread = null; 199 }); 200 } 201 }; 202 mCheckSimPinThread.start(); 203 } 204 } 205 getSimUnlockProgressDialog()206 private Dialog getSimUnlockProgressDialog() { 207 if (mSimUnlockProgressDialog == null) { 208 mSimUnlockProgressDialog = new ProgressDialog(mView.getContext()); 209 mSimUnlockProgressDialog.setMessage( 210 mView.getResources().getString(R.string.kg_sim_unlock_progress_dialog_message)); 211 mSimUnlockProgressDialog.setIndeterminate(true); 212 mSimUnlockProgressDialog.setCancelable(false); 213 mSimUnlockProgressDialog.getWindow().setType( 214 WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); 215 } 216 return mSimUnlockProgressDialog; 217 } 218 219 getSimRemainingAttemptsDialog(int remaining)220 private Dialog getSimRemainingAttemptsDialog(int remaining) { 221 String msg = getPinPasswordErrorMessage(remaining, false); 222 if (mRemainingAttemptsDialog == null) { 223 Builder builder = new AlertDialog.Builder(mView.getContext()); 224 builder.setMessage(msg); 225 builder.setCancelable(false); 226 builder.setNeutralButton(R.string.ok, null); 227 mRemainingAttemptsDialog = builder.create(); 228 mRemainingAttemptsDialog.getWindow().setType( 229 WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); 230 } else { 231 mRemainingAttemptsDialog.setMessage(msg); 232 } 233 return mRemainingAttemptsDialog; 234 } 235 236 getPinPasswordErrorMessage(int attemptsRemaining, boolean isDefault)237 private String getPinPasswordErrorMessage(int attemptsRemaining, boolean isDefault) { 238 String displayMessage; 239 int msgId; 240 if (attemptsRemaining == 0) { 241 displayMessage = mView.getResources().getString( 242 R.string.kg_password_wrong_pin_code_pukked); 243 } else if (attemptsRemaining > 0) { 244 msgId = isDefault ? R.string.kg_password_default_pin_message : 245 R.string.kg_password_wrong_pin_code; 246 displayMessage = icuMessageFormat(mView.getResources(), msgId, attemptsRemaining); 247 } else { 248 msgId = isDefault ? R.string.kg_sim_pin_instructions : R.string.kg_password_pin_failed; 249 displayMessage = mView.getResources().getString(msgId); 250 } 251 if (KeyguardEsimArea.isEsimLocked(mView.getContext(), mSubId)) { 252 displayMessage = mView.getResources() 253 .getString(R.string.kg_sim_lock_esim_instructions, displayMessage); 254 } 255 if (DEBUG) { 256 Log.d(LOG_TAG, "getPinPasswordErrorMessage: attemptsRemaining=" 257 + attemptsRemaining + " displayMessage=" + displayMessage); 258 } 259 return displayMessage; 260 } 261 showDefaultMessage()262 private void showDefaultMessage() { 263 setLockedSimMessage(); 264 if (mRemainingAttempts >= 0) { 265 return; 266 } 267 268 // Sending empty PIN here to query the number of remaining PIN attempts 269 new CheckSimPin("", mSubId) { 270 void onSimCheckResponse(final PinResult result) { 271 Log.d(LOG_TAG, "onSimCheckResponse " + " empty One result " 272 + result.toString()); 273 if (result.getAttemptsRemaining() >= 0) { 274 mRemainingAttempts = result.getAttemptsRemaining(); 275 setLockedSimMessage(); 276 } 277 } 278 }.start(); 279 } 280 281 /** 282 * Since the IPC can block, we want to run the request in a separate thread 283 * with a callback. 284 */ 285 private abstract class CheckSimPin extends Thread { 286 private final String mPin; 287 private int mSubId; 288 CheckSimPin(String pin, int subId)289 protected CheckSimPin(String pin, int subId) { 290 mPin = pin; 291 mSubId = subId; 292 } 293 onSimCheckResponse(@onNull PinResult result)294 abstract void onSimCheckResponse(@NonNull PinResult result); 295 296 @Override run()297 public void run() { 298 if (DEBUG) { 299 Log.v(TAG, "call supplyIccLockPin(subid=" + mSubId + ")"); 300 } 301 TelephonyManager telephonyManager = mTelephonyManager.createForSubscriptionId(mSubId); 302 final PinResult result = telephonyManager.supplyIccLockPin(mPin); 303 if (DEBUG) { 304 Log.v(TAG, "supplyIccLockPin returned: " + result.toString()); 305 } 306 mView.post(() -> onSimCheckResponse(result)); 307 } 308 } 309 setLockedSimMessage()310 private void setLockedSimMessage() { 311 boolean isEsimLocked = KeyguardEsimArea.isEsimLocked(mView.getContext(), mSubId); 312 int count = 1; 313 if (mTelephonyManager != null) { 314 count = mTelephonyManager.getActiveModemCount(); 315 } 316 Resources rez = mView.getResources(); 317 String msg; 318 TypedArray array = mView.getContext().obtainStyledAttributes( 319 new int[] { android.R.attr.textColor }); 320 int color = array.getColor(0, Color.WHITE); 321 array.recycle(); 322 if (count < 2) { 323 msg = rez.getString(R.string.kg_sim_pin_instructions); 324 } else { 325 SubscriptionInfo info = mKeyguardUpdateMonitor.getSubscriptionInfoForSubId(mSubId); 326 CharSequence displayName = info != null ? info.getDisplayName() : ""; // don't crash 327 msg = rez.getString(R.string.kg_sim_pin_instructions_multi, displayName); 328 if (info != null) { 329 color = info.getIconTint(); 330 } 331 } 332 if (isEsimLocked) { 333 msg = rez.getString(R.string.kg_sim_lock_esim_instructions, msg); 334 } 335 336 if (mView.getVisibility() == View.VISIBLE) { 337 mMessageAreaController.setMessage(msg); 338 } 339 mSimImageView.setImageTintList(ColorStateList.valueOf(color)); 340 } 341 handleSubInfoChangeIfNeeded()342 private void handleSubInfoChangeIfNeeded() { 343 int subId = mKeyguardUpdateMonitor 344 .getNextSubIdForState(TelephonyManager.SIM_STATE_PIN_REQUIRED); 345 if (subId != mSubId && SubscriptionManager.isValidSubscriptionId(subId)) { 346 mSubId = subId; 347 mShowDefaultMessage = true; 348 mRemainingAttempts = -1; 349 } 350 } 351 } 352