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 com.android.internal.telephony.ITelephony; 20 import com.android.internal.telephony.IccCardConstants; 21 import com.android.internal.telephony.IccCardConstants.State; 22 import com.android.internal.telephony.PhoneConstants; 23 24 import android.content.Context; 25 import android.content.res.ColorStateList; 26 import android.content.res.Resources; 27 import android.app.AlertDialog; 28 import android.app.AlertDialog.Builder; 29 import android.app.Dialog; 30 import android.app.ProgressDialog; 31 import android.graphics.Color; 32 import android.os.RemoteException; 33 import android.os.ServiceManager; 34 import android.telephony.SubscriptionInfo; 35 import android.telephony.SubscriptionManager; 36 import android.telephony.TelephonyManager; 37 import android.util.AttributeSet; 38 import android.util.Log; 39 import android.view.WindowManager; 40 import android.widget.ImageView; 41 42 /** 43 * Displays a PIN pad for unlocking. 44 */ 45 public class KeyguardSimPinView extends KeyguardPinBasedInputView { 46 private static final String LOG_TAG = "KeyguardSimPinView"; 47 private static final boolean DEBUG = KeyguardConstants.DEBUG_SIM_STATES; 48 public static final String TAG = "KeyguardSimPinView"; 49 50 private ProgressDialog mSimUnlockProgressDialog = null; 51 private CheckSimPin mCheckSimPinThread; 52 53 private AlertDialog mRemainingAttemptsDialog; 54 private int mSubId; 55 private ImageView mSimImageView; 56 57 KeyguardUpdateMonitorCallback mUpdateMonitorCallback = new KeyguardUpdateMonitorCallback() { 58 @Override 59 public void onSimStateChanged(int subId, int slotId, State simState) { 60 if (DEBUG) Log.v(TAG, "onSimStateChanged(subId=" + subId + ",state=" + simState + ")"); 61 resetState(); 62 }; 63 }; 64 KeyguardSimPinView(Context context)65 public KeyguardSimPinView(Context context) { 66 this(context, null); 67 } 68 KeyguardSimPinView(Context context, AttributeSet attrs)69 public KeyguardSimPinView(Context context, AttributeSet attrs) { 70 super(context, attrs); 71 } 72 resetState()73 public void resetState() { 74 super.resetState(); 75 if (DEBUG) Log.v(TAG, "Resetting state"); 76 KeyguardUpdateMonitor monitor = KeyguardUpdateMonitor.getInstance(mContext); 77 mSubId = monitor.getNextSubIdForState(IccCardConstants.State.PIN_REQUIRED); 78 if (SubscriptionManager.isValidSubscriptionId(mSubId)) { 79 int count = TelephonyManager.getDefault().getSimCount(); 80 Resources rez = getResources(); 81 final String msg; 82 int color = Color.WHITE; 83 if (count < 2) { 84 msg = rez.getString(R.string.kg_sim_pin_instructions); 85 } else { 86 SubscriptionInfo info = monitor.getSubscriptionInfoForSubId(mSubId); 87 CharSequence displayName = info != null ? info.getDisplayName() : ""; // don't crash 88 msg = rez.getString(R.string.kg_sim_pin_instructions_multi, displayName); 89 if (info != null) { 90 color = info.getIconTint(); 91 } 92 } 93 mSecurityMessageDisplay.setMessage(msg, true); 94 mSimImageView.setImageTintList(ColorStateList.valueOf(color)); 95 } 96 } 97 getPinPasswordErrorMessage(int attemptsRemaining)98 private String getPinPasswordErrorMessage(int attemptsRemaining) { 99 String displayMessage; 100 101 if (attemptsRemaining == 0) { 102 displayMessage = getContext().getString(R.string.kg_password_wrong_pin_code_pukked); 103 } else if (attemptsRemaining > 0) { 104 displayMessage = getContext().getResources() 105 .getQuantityString(R.plurals.kg_password_wrong_pin_code, attemptsRemaining, 106 attemptsRemaining); 107 } else { 108 displayMessage = getContext().getString(R.string.kg_password_pin_failed); 109 } 110 if (DEBUG) Log.d(LOG_TAG, "getPinPasswordErrorMessage:" 111 + " attemptsRemaining=" + attemptsRemaining + " displayMessage=" + displayMessage); 112 return displayMessage; 113 } 114 115 @Override shouldLockout(long deadline)116 protected boolean shouldLockout(long deadline) { 117 // SIM PIN doesn't have a timed lockout 118 return false; 119 } 120 121 @Override getPasswordTextViewId()122 protected int getPasswordTextViewId() { 123 return R.id.simPinEntry; 124 } 125 126 @Override onFinishInflate()127 protected void onFinishInflate() { 128 super.onFinishInflate(); 129 130 mSecurityMessageDisplay.setTimeout(0); // don't show ownerinfo/charging status by default 131 if (mEcaView instanceof EmergencyCarrierArea) { 132 ((EmergencyCarrierArea) mEcaView).setCarrierTextVisible(true); 133 } 134 mSimImageView = (ImageView) findViewById(R.id.keyguard_sim); 135 } 136 137 @Override onAttachedToWindow()138 protected void onAttachedToWindow() { 139 super.onAttachedToWindow(); 140 KeyguardUpdateMonitor.getInstance(mContext).registerCallback(mUpdateMonitorCallback); 141 } 142 143 @Override onDetachedFromWindow()144 protected void onDetachedFromWindow() { 145 super.onDetachedFromWindow(); 146 KeyguardUpdateMonitor.getInstance(mContext).removeCallback(mUpdateMonitorCallback); 147 } 148 149 @Override showUsabilityHint()150 public void showUsabilityHint() { 151 } 152 153 @Override onPause()154 public void onPause() { 155 // dismiss the dialog. 156 if (mSimUnlockProgressDialog != null) { 157 mSimUnlockProgressDialog.dismiss(); 158 mSimUnlockProgressDialog = null; 159 } 160 } 161 162 /** 163 * Since the IPC can block, we want to run the request in a separate thread 164 * with a callback. 165 */ 166 private abstract class CheckSimPin extends Thread { 167 private final String mPin; 168 private int mSubId; 169 CheckSimPin(String pin, int subId)170 protected CheckSimPin(String pin, int subId) { 171 mPin = pin; 172 mSubId = subId; 173 } 174 onSimCheckResponse(final int result, final int attemptsRemaining)175 abstract void onSimCheckResponse(final int result, final int attemptsRemaining); 176 177 @Override run()178 public void run() { 179 try { 180 if (DEBUG) { 181 Log.v(TAG, "call supplyPinReportResultForSubscriber(subid=" + mSubId + ")"); 182 } 183 final int[] result = ITelephony.Stub.asInterface(ServiceManager 184 .checkService("phone")).supplyPinReportResultForSubscriber(mSubId, mPin); 185 if (DEBUG) { 186 Log.v(TAG, "supplyPinReportResult returned: " + result[0] + " " + result[1]); 187 } 188 post(new Runnable() { 189 public void run() { 190 onSimCheckResponse(result[0], result[1]); 191 } 192 }); 193 } catch (RemoteException e) { 194 Log.e(TAG, "RemoteException for supplyPinReportResult:", e); 195 post(new Runnable() { 196 public void run() { 197 onSimCheckResponse(PhoneConstants.PIN_GENERAL_FAILURE, -1); 198 } 199 }); 200 } 201 } 202 } 203 getSimUnlockProgressDialog()204 private Dialog getSimUnlockProgressDialog() { 205 if (mSimUnlockProgressDialog == null) { 206 mSimUnlockProgressDialog = new ProgressDialog(mContext); 207 mSimUnlockProgressDialog.setMessage( 208 mContext.getString(R.string.kg_sim_unlock_progress_dialog_message)); 209 mSimUnlockProgressDialog.setIndeterminate(true); 210 mSimUnlockProgressDialog.setCancelable(false); 211 mSimUnlockProgressDialog.getWindow().setType( 212 WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); 213 } 214 return mSimUnlockProgressDialog; 215 } 216 getSimRemainingAttemptsDialog(int remaining)217 private Dialog getSimRemainingAttemptsDialog(int remaining) { 218 String msg = getPinPasswordErrorMessage(remaining); 219 if (mRemainingAttemptsDialog == null) { 220 Builder builder = new AlertDialog.Builder(mContext); 221 builder.setMessage(msg); 222 builder.setCancelable(false); 223 builder.setNeutralButton(R.string.ok, null); 224 mRemainingAttemptsDialog = builder.create(); 225 mRemainingAttemptsDialog.getWindow().setType( 226 WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); 227 } else { 228 mRemainingAttemptsDialog.setMessage(msg); 229 } 230 return mRemainingAttemptsDialog; 231 } 232 233 @Override verifyPasswordAndUnlock()234 protected void verifyPasswordAndUnlock() { 235 String entry = mPasswordEntry.getText(); 236 237 if (entry.length() < 4) { 238 // otherwise, display a message to the user, and don't submit. 239 mSecurityMessageDisplay.setMessage(R.string.kg_invalid_sim_pin_hint, true); 240 resetPasswordText(true); 241 mCallback.userActivity(); 242 return; 243 } 244 245 getSimUnlockProgressDialog().show(); 246 247 if (mCheckSimPinThread == null) { 248 mCheckSimPinThread = new CheckSimPin(mPasswordEntry.getText(), mSubId) { 249 void onSimCheckResponse(final int result, final int attemptsRemaining) { 250 post(new Runnable() { 251 public void run() { 252 if (mSimUnlockProgressDialog != null) { 253 mSimUnlockProgressDialog.hide(); 254 } 255 resetPasswordText(true /* animate */); 256 if (result == PhoneConstants.PIN_RESULT_SUCCESS) { 257 KeyguardUpdateMonitor.getInstance(getContext()) 258 .reportSimUnlocked(mSubId); 259 mCallback.dismiss(true); 260 } else { 261 if (result == PhoneConstants.PIN_PASSWORD_INCORRECT) { 262 if (attemptsRemaining <= 2) { 263 // this is getting critical - show dialog 264 getSimRemainingAttemptsDialog(attemptsRemaining).show(); 265 } else { 266 // show message 267 mSecurityMessageDisplay.setMessage( 268 getPinPasswordErrorMessage(attemptsRemaining), true); 269 } 270 } else { 271 // "PIN operation failed!" - no idea what this was and no way to 272 // find out. :/ 273 mSecurityMessageDisplay.setMessage(getContext().getString( 274 R.string.kg_password_pin_failed), true); 275 } 276 if (DEBUG) Log.d(LOG_TAG, "verifyPasswordAndUnlock " 277 + " CheckSimPin.onSimCheckResponse: " + result 278 + " attemptsRemaining=" + attemptsRemaining); 279 } 280 mCallback.userActivity(); 281 mCheckSimPinThread = null; 282 } 283 }); 284 } 285 }; 286 mCheckSimPinThread.start(); 287 } 288 } 289 290 @Override startAppearAnimation()291 public void startAppearAnimation() { 292 // noop. 293 } 294 295 @Override startDisappearAnimation(Runnable finishRunnable)296 public boolean startDisappearAnimation(Runnable finishRunnable) { 297 return false; 298 } 299 } 300 301