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 17package com.android.keyguard; 18 19import android.content.Context; 20import android.animation.AnimatorSet.Builder; 21import android.app.Activity; 22import android.app.AlertDialog; 23import android.app.Dialog; 24import android.app.ProgressDialog; 25import android.os.RemoteException; 26import android.os.ServiceManager; 27import android.text.Editable; 28import android.text.InputType; 29import android.text.TextWatcher; 30import android.text.method.DigitsKeyListener; 31import android.util.AttributeSet; 32import android.util.Log; 33import android.view.View; 34import android.view.WindowManager; 35import android.widget.TextView.OnEditorActionListener; 36 37import com.android.internal.telephony.ITelephony; 38import com.android.internal.telephony.PhoneConstants; 39 40 41/** 42 * Displays a PIN pad for entering a PUK (Pin Unlock Kode) provided by a carrier. 43 */ 44public class KeyguardSimPukView extends KeyguardAbsKeyInputView 45 implements KeyguardSecurityView, OnEditorActionListener, TextWatcher { 46 private static final String LOG_TAG = "KeyguardSimPukView"; 47 private static final boolean DEBUG = KeyguardViewMediator.DEBUG; 48 public static final String TAG = "KeyguardSimPukView"; 49 50 private ProgressDialog mSimUnlockProgressDialog = null; 51 private CheckSimPuk mCheckSimPukThread; 52 private String mPukText; 53 private String mPinText; 54 private StateMachine mStateMachine = new StateMachine(); 55 private AlertDialog mRemainingAttemptsDialog; 56 57 private class StateMachine { 58 final int ENTER_PUK = 0; 59 final int ENTER_PIN = 1; 60 final int CONFIRM_PIN = 2; 61 final int DONE = 3; 62 private int state = ENTER_PUK; 63 64 public void next() { 65 int msg = 0; 66 if (state == ENTER_PUK) { 67 if (checkPuk()) { 68 state = ENTER_PIN; 69 msg = R.string.kg_puk_enter_pin_hint; 70 } else { 71 msg = R.string.kg_invalid_sim_puk_hint; 72 } 73 } else if (state == ENTER_PIN) { 74 if (checkPin()) { 75 state = CONFIRM_PIN; 76 msg = R.string.kg_enter_confirm_pin_hint; 77 } else { 78 msg = R.string.kg_invalid_sim_pin_hint; 79 } 80 } else if (state == CONFIRM_PIN) { 81 if (confirmPin()) { 82 state = DONE; 83 msg = R.string.keyguard_sim_unlock_progress_dialog_message; 84 updateSim(); 85 } else { 86 state = ENTER_PIN; // try again? 87 msg = R.string.kg_invalid_confirm_pin_hint; 88 } 89 } 90 mPasswordEntry.setText(null); 91 if (msg != 0) { 92 mSecurityMessageDisplay.setMessage(msg, true); 93 } 94 } 95 96 void reset() { 97 mPinText=""; 98 mPukText=""; 99 state = ENTER_PUK; 100 mSecurityMessageDisplay.setMessage(R.string.kg_puk_enter_puk_hint, true); 101 mPasswordEntry.requestFocus(); 102 } 103 } 104 105 private String getPukPasswordErrorMessage(int attemptsRemaining) { 106 String displayMessage; 107 108 if (attemptsRemaining == 0) { 109 displayMessage = getContext().getString(R.string.kg_password_wrong_puk_code_dead); 110 } else if (attemptsRemaining > 0) { 111 displayMessage = getContext().getResources() 112 .getQuantityString(R.plurals.kg_password_wrong_puk_code, attemptsRemaining, 113 attemptsRemaining); 114 } else { 115 displayMessage = getContext().getString(R.string.kg_password_puk_failed); 116 } 117 if (DEBUG) Log.d(LOG_TAG, "getPukPasswordErrorMessage:" 118 + " attemptsRemaining=" + attemptsRemaining + " displayMessage=" + displayMessage); 119 return displayMessage; 120 } 121 122 public KeyguardSimPukView(Context context) { 123 this(context, null); 124 } 125 126 public KeyguardSimPukView(Context context, AttributeSet attrs) { 127 super(context, attrs); 128 } 129 130 public void resetState() { 131 mStateMachine.reset(); 132 mPasswordEntry.setEnabled(true); 133 } 134 135 @Override 136 protected boolean shouldLockout(long deadline) { 137 // SIM PUK doesn't have a timed lockout 138 return false; 139 } 140 141 @Override 142 protected int getPasswordTextViewId() { 143 return R.id.pinEntry; 144 } 145 146 @Override 147 protected void onFinishInflate() { 148 super.onFinishInflate(); 149 150 final View ok = findViewById(R.id.key_enter); 151 if (ok != null) { 152 ok.setOnClickListener(new View.OnClickListener() { 153 @Override 154 public void onClick(View v) { 155 doHapticKeyClick(); 156 verifyPasswordAndUnlock(); 157 } 158 }); 159 } 160 161 // The delete button is of the PIN keyboard itself in some (e.g. tablet) layouts, 162 // not a separate view 163 View pinDelete = findViewById(R.id.delete_button); 164 if (pinDelete != null) { 165 pinDelete.setVisibility(View.VISIBLE); 166 pinDelete.setOnClickListener(new OnClickListener() { 167 public void onClick(View v) { 168 CharSequence str = mPasswordEntry.getText(); 169 if (str.length() > 0) { 170 mPasswordEntry.setText(str.subSequence(0, str.length()-1)); 171 } 172 doHapticKeyClick(); 173 } 174 }); 175 pinDelete.setOnLongClickListener(new View.OnLongClickListener() { 176 public boolean onLongClick(View v) { 177 mPasswordEntry.setText(""); 178 doHapticKeyClick(); 179 return true; 180 } 181 }); 182 } 183 184 mPasswordEntry.setKeyListener(DigitsKeyListener.getInstance()); 185 mPasswordEntry.setInputType(InputType.TYPE_CLASS_NUMBER 186 | InputType.TYPE_NUMBER_VARIATION_PASSWORD); 187 188 mPasswordEntry.requestFocus(); 189 190 mSecurityMessageDisplay.setTimeout(0); // don't show ownerinfo/charging status by default 191 } 192 193 @Override 194 public void showUsabilityHint() { 195 } 196 197 @Override 198 public void onPause() { 199 // dismiss the dialog. 200 if (mSimUnlockProgressDialog != null) { 201 mSimUnlockProgressDialog.dismiss(); 202 mSimUnlockProgressDialog = null; 203 } 204 } 205 206 /** 207 * Since the IPC can block, we want to run the request in a separate thread 208 * with a callback. 209 */ 210 private abstract class CheckSimPuk extends Thread { 211 212 private final String mPin, mPuk; 213 214 protected CheckSimPuk(String puk, String pin) { 215 mPuk = puk; 216 mPin = pin; 217 } 218 219 abstract void onSimLockChangedResponse(final int result, final int attemptsRemaining); 220 221 @Override 222 public void run() { 223 try { 224 Log.v(TAG, "call supplyPukReportResult()"); 225 final int[] result = ITelephony.Stub.asInterface(ServiceManager 226 .checkService("phone")).supplyPukReportResult(mPuk, mPin); 227 Log.v(TAG, "supplyPukReportResult returned: " + result[0] + " " + result[1]); 228 post(new Runnable() { 229 public void run() { 230 onSimLockChangedResponse(result[0], result[1]); 231 } 232 }); 233 } catch (RemoteException e) { 234 Log.e(TAG, "RemoteException for supplyPukReportResult:", e); 235 post(new Runnable() { 236 public void run() { 237 onSimLockChangedResponse(PhoneConstants.PIN_GENERAL_FAILURE, -1); 238 } 239 }); 240 } 241 } 242 } 243 244 private Dialog getSimUnlockProgressDialog() { 245 if (mSimUnlockProgressDialog == null) { 246 mSimUnlockProgressDialog = new ProgressDialog(mContext); 247 mSimUnlockProgressDialog.setMessage( 248 mContext.getString(R.string.kg_sim_unlock_progress_dialog_message)); 249 mSimUnlockProgressDialog.setIndeterminate(true); 250 mSimUnlockProgressDialog.setCancelable(false); 251 if (!(mContext instanceof Activity)) { 252 mSimUnlockProgressDialog.getWindow().setType( 253 WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); 254 } 255 } 256 return mSimUnlockProgressDialog; 257 } 258 259 private Dialog getPukRemainingAttemptsDialog(int remaining) { 260 String msg = getPukPasswordErrorMessage(remaining); 261 if (mRemainingAttemptsDialog == null) { 262 AlertDialog.Builder builder = new AlertDialog.Builder(mContext); 263 builder.setMessage(msg); 264 builder.setCancelable(false); 265 builder.setNeutralButton(R.string.ok, null); 266 mRemainingAttemptsDialog = builder.create(); 267 mRemainingAttemptsDialog.getWindow().setType( 268 WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); 269 } else { 270 mRemainingAttemptsDialog.setMessage(msg); 271 } 272 return mRemainingAttemptsDialog; 273 } 274 275 private boolean checkPuk() { 276 // make sure the puk is at least 8 digits long. 277 if (mPasswordEntry.getText().length() >= 8) { 278 mPukText = mPasswordEntry.getText().toString(); 279 return true; 280 } 281 return false; 282 } 283 284 private boolean checkPin() { 285 // make sure the PIN is between 4 and 8 digits 286 int length = mPasswordEntry.getText().length(); 287 if (length >= 4 && length <= 8) { 288 mPinText = mPasswordEntry.getText().toString(); 289 return true; 290 } 291 return false; 292 } 293 294 public boolean confirmPin() { 295 return mPinText.equals(mPasswordEntry.getText().toString()); 296 } 297 298 private void updateSim() { 299 getSimUnlockProgressDialog().show(); 300 301 if (mCheckSimPukThread == null) { 302 mCheckSimPukThread = new CheckSimPuk(mPukText, mPinText) { 303 void onSimLockChangedResponse(final int result, final int attemptsRemaining) { 304 post(new Runnable() { 305 public void run() { 306 if (mSimUnlockProgressDialog != null) { 307 mSimUnlockProgressDialog.hide(); 308 } 309 if (result == PhoneConstants.PIN_RESULT_SUCCESS) { 310 KeyguardUpdateMonitor.getInstance(getContext()).reportSimUnlocked(); 311 mCallback.dismiss(true); 312 } else { 313 if (result == PhoneConstants.PIN_PASSWORD_INCORRECT) { 314 if (attemptsRemaining <= 2) { 315 // this is getting critical - show dialog 316 getPukRemainingAttemptsDialog(attemptsRemaining).show(); 317 } else { 318 // show message 319 mSecurityMessageDisplay.setMessage( 320 getPukPasswordErrorMessage(attemptsRemaining), true); 321 } 322 } else { 323 mSecurityMessageDisplay.setMessage(getContext().getString( 324 R.string.kg_password_puk_failed), true); 325 } 326 if (DEBUG) Log.d(LOG_TAG, "verifyPasswordAndUnlock " 327 + " UpdateSim.onSimCheckResponse: " 328 + " attemptsRemaining=" + attemptsRemaining); 329 mStateMachine.reset(); 330 } 331 mCheckSimPukThread = null; 332 } 333 }); 334 } 335 }; 336 mCheckSimPukThread.start(); 337 } 338 } 339 340 @Override 341 protected void verifyPasswordAndUnlock() { 342 mStateMachine.next(); 343 } 344} 345 346 347