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.app.AlertDialog; 20 import android.app.AlertDialog.Builder; 21 import android.app.Dialog; 22 import android.app.ProgressDialog; 23 import android.content.Context; 24 import android.content.res.ColorStateList; 25 import android.content.res.Configuration; 26 import android.content.res.Resources; 27 import android.content.res.TypedArray; 28 import android.graphics.Color; 29 import android.os.RemoteException; 30 import android.os.ServiceManager; 31 import android.telephony.SubscriptionInfo; 32 import android.telephony.SubscriptionManager; 33 import android.telephony.TelephonyManager; 34 import android.util.AttributeSet; 35 import android.util.Log; 36 import android.view.View; 37 import android.view.WindowManager; 38 import android.widget.ImageView; 39 40 import com.android.internal.telephony.ITelephony; 41 import com.android.internal.telephony.IccCardConstants; 42 import com.android.internal.telephony.IccCardConstants.State; 43 import com.android.internal.telephony.PhoneConstants; 44 45 /** 46 * Displays a PIN pad for unlocking. 47 */ 48 public class KeyguardSimPinView extends KeyguardPinBasedInputView { 49 private static final String LOG_TAG = "KeyguardSimPinView"; 50 private static final boolean DEBUG = KeyguardConstants.DEBUG_SIM_STATES; 51 public static final String TAG = "KeyguardSimPinView"; 52 53 private ProgressDialog mSimUnlockProgressDialog = null; 54 private CheckSimPin mCheckSimPinThread; 55 56 // Below flag is set to true during power-up or when a new SIM card inserted on device. 57 // When this is true and when SIM card is PIN locked state, on PIN lock screen, message would 58 // be displayed to inform user about the number of remaining PIN attempts left. 59 private boolean mShowDefaultMessage = true; 60 private int mRemainingAttempts = -1; 61 private AlertDialog mRemainingAttemptsDialog; 62 private int mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; 63 private ImageView mSimImageView; 64 65 KeyguardUpdateMonitorCallback mUpdateMonitorCallback = new KeyguardUpdateMonitorCallback() { 66 @Override 67 public void onSimStateChanged(int subId, int slotId, State simState) { 68 if (DEBUG) Log.v(TAG, "onSimStateChanged(subId=" + subId + ",state=" + simState + ")"); 69 switch(simState) { 70 case READY: { 71 mRemainingAttempts = -1; 72 resetState(); 73 break; 74 } 75 default: 76 resetState(); 77 } 78 } 79 }; 80 81 public KeyguardSimPinView(Context context) { 82 this(context, null); 83 } 84 85 public KeyguardSimPinView(Context context, AttributeSet attrs) { 86 super(context, attrs); 87 } 88 89 @Override 90 public void resetState() { 91 super.resetState(); 92 if (DEBUG) Log.v(TAG, "Resetting state"); 93 handleSubInfoChangeIfNeeded(); 94 if (mShowDefaultMessage) { 95 showDefaultMessage(); 96 } 97 boolean isEsimLocked = KeyguardEsimArea.isEsimLocked(mContext, mSubId); 98 99 KeyguardEsimArea esimButton = findViewById(R.id.keyguard_esim_area); 100 esimButton.setVisibility(isEsimLocked ? View.VISIBLE : View.GONE); 101 } 102 103 private void setLockedSimMessage() { 104 boolean isEsimLocked = KeyguardEsimArea.isEsimLocked(mContext, mSubId); 105 int count = TelephonyManager.getDefault().getSimCount(); 106 Resources rez = getResources(); 107 String msg; 108 TypedArray array = mContext.obtainStyledAttributes(new int[] { R.attr.wallpaperTextColor }); 109 int color = array.getColor(0, Color.WHITE); 110 array.recycle(); 111 if (count < 2) { 112 msg = rez.getString(R.string.kg_sim_pin_instructions); 113 } else { 114 SubscriptionInfo info = KeyguardUpdateMonitor.getInstance(mContext). 115 getSubscriptionInfoForSubId(mSubId); 116 CharSequence displayName = info != null ? info.getDisplayName() : ""; // don't crash 117 msg = rez.getString(R.string.kg_sim_pin_instructions_multi, displayName); 118 if (info != null) { 119 color = info.getIconTint(); 120 } 121 } 122 if (isEsimLocked) { 123 msg = rez.getString(R.string.kg_sim_lock_esim_instructions, msg); 124 } 125 126 if (mSecurityMessageDisplay != null) { 127 mSecurityMessageDisplay.setMessage(msg); 128 } 129 mSimImageView.setImageTintList(ColorStateList.valueOf(color)); 130 } 131 132 private void showDefaultMessage() { 133 setLockedSimMessage(); 134 if (mRemainingAttempts >= 0) { 135 return; 136 } 137 138 // Sending empty PIN here to query the number of remaining PIN attempts 139 new CheckSimPin("", mSubId) { 140 void onSimCheckResponse(final int result, final int attemptsRemaining) { 141 Log.d(LOG_TAG, "onSimCheckResponse " + " dummy One result" + result + 142 " attemptsRemaining=" + attemptsRemaining); 143 if (attemptsRemaining >= 0) { 144 mRemainingAttempts = attemptsRemaining; 145 setLockedSimMessage(); 146 } 147 } 148 }.start(); 149 } 150 151 private void handleSubInfoChangeIfNeeded() { 152 KeyguardUpdateMonitor monitor = KeyguardUpdateMonitor.getInstance(mContext); 153 int subId = monitor.getNextSubIdForState(IccCardConstants.State.PIN_REQUIRED); 154 if (subId != mSubId && SubscriptionManager.isValidSubscriptionId(subId)) { 155 mSubId = subId; 156 mShowDefaultMessage = true; 157 mRemainingAttempts = -1; 158 } 159 } 160 161 @Override 162 protected void onConfigurationChanged(Configuration newConfig) { 163 super.onConfigurationChanged(newConfig); 164 resetState(); 165 } 166 167 @Override 168 protected int getPromptReasonStringRes(int reason) { 169 // No message on SIM Pin 170 return 0; 171 } 172 173 private String getPinPasswordErrorMessage(int attemptsRemaining, boolean isDefault) { 174 String displayMessage; 175 int msgId; 176 if (attemptsRemaining == 0) { 177 displayMessage = getContext().getString(R.string.kg_password_wrong_pin_code_pukked); 178 } else if (attemptsRemaining > 0) { 179 msgId = isDefault ? R.plurals.kg_password_default_pin_message : 180 R.plurals.kg_password_wrong_pin_code; 181 displayMessage = getContext().getResources() 182 .getQuantityString(msgId, attemptsRemaining, attemptsRemaining); 183 } else { 184 msgId = isDefault ? R.string.kg_sim_pin_instructions : R.string.kg_password_pin_failed; 185 displayMessage = getContext().getString(msgId); 186 } 187 if (KeyguardEsimArea.isEsimLocked(mContext, mSubId)) { 188 displayMessage = getResources() 189 .getString(R.string.kg_sim_lock_esim_instructions, displayMessage); 190 } 191 if (DEBUG) Log.d(LOG_TAG, "getPinPasswordErrorMessage:" 192 + " attemptsRemaining=" + attemptsRemaining + " displayMessage=" + displayMessage); 193 return displayMessage; 194 } 195 196 @Override 197 protected boolean shouldLockout(long deadline) { 198 // SIM PIN doesn't have a timed lockout 199 return false; 200 } 201 202 @Override 203 protected int getPasswordTextViewId() { 204 return R.id.simPinEntry; 205 } 206 207 @Override 208 protected void onFinishInflate() { 209 super.onFinishInflate(); 210 211 if (mEcaView instanceof EmergencyCarrierArea) { 212 ((EmergencyCarrierArea) mEcaView).setCarrierTextVisible(true); 213 } 214 mSimImageView = findViewById(R.id.keyguard_sim); 215 } 216 217 @Override 218 public void showUsabilityHint() { 219 220 } 221 222 @Override 223 public void onResume(int reason) { 224 super.onResume(reason); 225 KeyguardUpdateMonitor.getInstance(mContext).registerCallback(mUpdateMonitorCallback); 226 resetState(); 227 } 228 229 @Override 230 public void onPause() { 231 // dismiss the dialog. 232 if (mSimUnlockProgressDialog != null) { 233 mSimUnlockProgressDialog.dismiss(); 234 mSimUnlockProgressDialog = null; 235 } 236 KeyguardUpdateMonitor.getInstance(mContext).removeCallback(mUpdateMonitorCallback); 237 } 238 239 /** 240 * Since the IPC can block, we want to run the request in a separate thread 241 * with a callback. 242 */ 243 private abstract class CheckSimPin extends Thread { 244 private final String mPin; 245 private int mSubId; 246 247 protected CheckSimPin(String pin, int subId) { 248 mPin = pin; 249 mSubId = subId; 250 } 251 252 abstract void onSimCheckResponse(final int result, final int attemptsRemaining); 253 254 @Override 255 public void run() { 256 try { 257 if (DEBUG) { 258 Log.v(TAG, "call supplyPinReportResultForSubscriber(subid=" + mSubId + ")"); 259 } 260 final int[] result = ITelephony.Stub.asInterface(ServiceManager 261 .checkService("phone")).supplyPinReportResultForSubscriber(mSubId, mPin); 262 if (DEBUG) { 263 Log.v(TAG, "supplyPinReportResult returned: " + result[0] + " " + result[1]); 264 } 265 post(new Runnable() { 266 @Override 267 public void run() { 268 onSimCheckResponse(result[0], result[1]); 269 } 270 }); 271 } catch (RemoteException e) { 272 Log.e(TAG, "RemoteException for supplyPinReportResult:", e); 273 post(new Runnable() { 274 @Override 275 public void run() { 276 onSimCheckResponse(PhoneConstants.PIN_GENERAL_FAILURE, -1); 277 } 278 }); 279 } 280 } 281 } 282 283 private Dialog getSimUnlockProgressDialog() { 284 if (mSimUnlockProgressDialog == null) { 285 mSimUnlockProgressDialog = new ProgressDialog(mContext); 286 mSimUnlockProgressDialog.setMessage( 287 mContext.getString(R.string.kg_sim_unlock_progress_dialog_message)); 288 mSimUnlockProgressDialog.setIndeterminate(true); 289 mSimUnlockProgressDialog.setCancelable(false); 290 mSimUnlockProgressDialog.getWindow().setType( 291 WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); 292 } 293 return mSimUnlockProgressDialog; 294 } 295 296 private Dialog getSimRemainingAttemptsDialog(int remaining) { 297 String msg = getPinPasswordErrorMessage(remaining, false); 298 if (mRemainingAttemptsDialog == null) { 299 Builder builder = new AlertDialog.Builder(mContext); 300 builder.setMessage(msg); 301 builder.setCancelable(false); 302 builder.setNeutralButton(R.string.ok, null); 303 mRemainingAttemptsDialog = builder.create(); 304 mRemainingAttemptsDialog.getWindow().setType( 305 WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); 306 } else { 307 mRemainingAttemptsDialog.setMessage(msg); 308 } 309 return mRemainingAttemptsDialog; 310 } 311 312 @Override 313 protected void verifyPasswordAndUnlock() { 314 String entry = mPasswordEntry.getText(); 315 316 if (entry.length() < 4) { 317 // otherwise, display a message to the user, and don't submit. 318 mSecurityMessageDisplay.setMessage(R.string.kg_invalid_sim_pin_hint); 319 resetPasswordText(true /* animate */, true /* announce */); 320 mCallback.userActivity(); 321 return; 322 } 323 324 getSimUnlockProgressDialog().show(); 325 326 if (mCheckSimPinThread == null) { 327 mCheckSimPinThread = new CheckSimPin(mPasswordEntry.getText(), mSubId) { 328 @Override 329 void onSimCheckResponse(final int result, final int attemptsRemaining) { 330 post(new Runnable() { 331 @Override 332 public void run() { 333 mRemainingAttempts = attemptsRemaining; 334 if (mSimUnlockProgressDialog != null) { 335 mSimUnlockProgressDialog.hide(); 336 } 337 resetPasswordText(true /* animate */, 338 result != PhoneConstants.PIN_RESULT_SUCCESS /* announce */); 339 if (result == PhoneConstants.PIN_RESULT_SUCCESS) { 340 KeyguardUpdateMonitor.getInstance(getContext()) 341 .reportSimUnlocked(mSubId); 342 mRemainingAttempts = -1; 343 mShowDefaultMessage = true; 344 if (mCallback != null) { 345 mCallback.dismiss(true, KeyguardUpdateMonitor.getCurrentUser()); 346 } 347 } else { 348 mShowDefaultMessage = false; 349 if (result == PhoneConstants.PIN_PASSWORD_INCORRECT) { 350 if (attemptsRemaining <= 2) { 351 // this is getting critical - show dialog 352 getSimRemainingAttemptsDialog(attemptsRemaining).show(); 353 } else { 354 // show message 355 mSecurityMessageDisplay.setMessage( 356 getPinPasswordErrorMessage(attemptsRemaining, false)); 357 } 358 } else { 359 // "PIN operation failed!" - no idea what this was and no way to 360 // find out. :/ 361 mSecurityMessageDisplay.setMessage(getContext().getString( 362 R.string.kg_password_pin_failed)); 363 } 364 if (DEBUG) Log.d(LOG_TAG, "verifyPasswordAndUnlock " 365 + " CheckSimPin.onSimCheckResponse: " + result 366 + " attemptsRemaining=" + attemptsRemaining); 367 } 368 mCallback.userActivity(); 369 mCheckSimPinThread = null; 370 } 371 }); 372 } 373 }; 374 mCheckSimPinThread.start(); 375 } 376 } 377 378 @Override 379 public void startAppearAnimation() { 380 // noop. 381 } 382 383 @Override 384 public boolean startDisappearAnimation(Runnable finishRunnable) { 385 return false; 386 } 387 388 @Override 389 public CharSequence getTitle() { 390 return getContext().getString( 391 com.android.internal.R.string.keyguard_accessibility_sim_pin_unlock); 392 } 393 } 394 395