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