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 com.android.internal.telephony.ITelephony; 20 21import android.content.Context; 22import android.app.Activity; 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.view.View; 33import android.view.WindowManager; 34import android.widget.TextView.OnEditorActionListener; 35 36/** 37 * Displays a PIN pad for entering a PUK (Pin Unlock Kode) provided by a carrier. 38 */ 39public class KeyguardSimPukView extends KeyguardAbsKeyInputView 40 implements KeyguardSecurityView, OnEditorActionListener, TextWatcher { 41 42 private ProgressDialog mSimUnlockProgressDialog = null; 43 private volatile boolean mCheckInProgress; 44 private String mPukText; 45 private String mPinText; 46 private StateMachine mStateMachine = new StateMachine(); 47 48 private class StateMachine { 49 final int ENTER_PUK = 0; 50 final int ENTER_PIN = 1; 51 final int CONFIRM_PIN = 2; 52 final int DONE = 3; 53 private int state = ENTER_PUK; 54 55 public void next() { 56 int msg = 0; 57 if (state == ENTER_PUK) { 58 if (checkPuk()) { 59 state = ENTER_PIN; 60 msg = R.string.kg_puk_enter_pin_hint; 61 } else { 62 msg = R.string.kg_invalid_sim_puk_hint; 63 } 64 } else if (state == ENTER_PIN) { 65 if (checkPin()) { 66 state = CONFIRM_PIN; 67 msg = R.string.kg_enter_confirm_pin_hint; 68 } else { 69 msg = R.string.kg_invalid_sim_pin_hint; 70 } 71 } else if (state == CONFIRM_PIN) { 72 if (confirmPin()) { 73 state = DONE; 74 msg = R.string.keyguard_sim_unlock_progress_dialog_message; 75 updateSim(); 76 } else { 77 state = ENTER_PIN; // try again? 78 msg = R.string.kg_invalid_confirm_pin_hint; 79 } 80 } 81 mPasswordEntry.setText(null); 82 if (msg != 0) { 83 mSecurityMessageDisplay.setMessage(msg, true); 84 } 85 } 86 87 void reset() { 88 mPinText=""; 89 mPukText=""; 90 state = ENTER_PUK; 91 mSecurityMessageDisplay.setMessage(R.string.kg_puk_enter_puk_hint, true); 92 mPasswordEntry.requestFocus(); 93 } 94 } 95 96 public KeyguardSimPukView(Context context) { 97 this(context, null); 98 } 99 100 public KeyguardSimPukView(Context context, AttributeSet attrs) { 101 super(context, attrs); 102 } 103 104 public void resetState() { 105 mStateMachine.reset(); 106 mPasswordEntry.setEnabled(true); 107 } 108 109 @Override 110 protected boolean shouldLockout(long deadline) { 111 // SIM PUK doesn't have a timed lockout 112 return false; 113 } 114 115 @Override 116 protected int getPasswordTextViewId() { 117 return R.id.pinEntry; 118 } 119 120 @Override 121 protected void onFinishInflate() { 122 super.onFinishInflate(); 123 124 final View ok = findViewById(R.id.key_enter); 125 if (ok != null) { 126 ok.setOnClickListener(new View.OnClickListener() { 127 @Override 128 public void onClick(View v) { 129 doHapticKeyClick(); 130 verifyPasswordAndUnlock(); 131 } 132 }); 133 } 134 135 // The delete button is of the PIN keyboard itself in some (e.g. tablet) layouts, 136 // not a separate view 137 View pinDelete = findViewById(R.id.delete_button); 138 if (pinDelete != null) { 139 pinDelete.setVisibility(View.VISIBLE); 140 pinDelete.setOnClickListener(new OnClickListener() { 141 public void onClick(View v) { 142 CharSequence str = mPasswordEntry.getText(); 143 if (str.length() > 0) { 144 mPasswordEntry.setText(str.subSequence(0, str.length()-1)); 145 } 146 doHapticKeyClick(); 147 } 148 }); 149 pinDelete.setOnLongClickListener(new View.OnLongClickListener() { 150 public boolean onLongClick(View v) { 151 mPasswordEntry.setText(""); 152 doHapticKeyClick(); 153 return true; 154 } 155 }); 156 } 157 158 mPasswordEntry.setKeyListener(DigitsKeyListener.getInstance()); 159 mPasswordEntry.setInputType(InputType.TYPE_CLASS_NUMBER 160 | InputType.TYPE_NUMBER_VARIATION_PASSWORD); 161 162 mPasswordEntry.requestFocus(); 163 164 mSecurityMessageDisplay.setTimeout(0); // don't show ownerinfo/charging status by default 165 } 166 167 @Override 168 public void showUsabilityHint() { 169 } 170 171 @Override 172 public void onPause() { 173 // dismiss the dialog. 174 if (mSimUnlockProgressDialog != null) { 175 mSimUnlockProgressDialog.dismiss(); 176 mSimUnlockProgressDialog = null; 177 } 178 } 179 180 /** 181 * Since the IPC can block, we want to run the request in a separate thread 182 * with a callback. 183 */ 184 private abstract class CheckSimPuk extends Thread { 185 186 private final String mPin, mPuk; 187 188 protected CheckSimPuk(String puk, String pin) { 189 mPuk = puk; 190 mPin = pin; 191 } 192 193 abstract void onSimLockChangedResponse(boolean success); 194 195 @Override 196 public void run() { 197 try { 198 final boolean result = ITelephony.Stub.asInterface(ServiceManager 199 .checkService("phone")).supplyPuk(mPuk, mPin); 200 201 post(new Runnable() { 202 public void run() { 203 onSimLockChangedResponse(result); 204 } 205 }); 206 } catch (RemoteException e) { 207 post(new Runnable() { 208 public void run() { 209 onSimLockChangedResponse(false); 210 } 211 }); 212 } 213 } 214 } 215 216 private Dialog getSimUnlockProgressDialog() { 217 if (mSimUnlockProgressDialog == null) { 218 mSimUnlockProgressDialog = new ProgressDialog(mContext); 219 mSimUnlockProgressDialog.setMessage( 220 mContext.getString(R.string.kg_sim_unlock_progress_dialog_message)); 221 mSimUnlockProgressDialog.setIndeterminate(true); 222 mSimUnlockProgressDialog.setCancelable(false); 223 if (!(mContext instanceof Activity)) { 224 mSimUnlockProgressDialog.getWindow().setType( 225 WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); 226 } 227 } 228 return mSimUnlockProgressDialog; 229 } 230 231 private boolean checkPuk() { 232 // make sure the puk is at least 8 digits long. 233 if (mPasswordEntry.getText().length() >= 8) { 234 mPukText = mPasswordEntry.getText().toString(); 235 return true; 236 } 237 return false; 238 } 239 240 private boolean checkPin() { 241 // make sure the PIN is between 4 and 8 digits 242 int length = mPasswordEntry.getText().length(); 243 if (length >= 4 && length <= 8) { 244 mPinText = mPasswordEntry.getText().toString(); 245 return true; 246 } 247 return false; 248 } 249 250 public boolean confirmPin() { 251 return mPinText.equals(mPasswordEntry.getText().toString()); 252 } 253 254 private void updateSim() { 255 getSimUnlockProgressDialog().show(); 256 257 if (!mCheckInProgress) { 258 mCheckInProgress = true; 259 new CheckSimPuk(mPukText, mPinText) { 260 void onSimLockChangedResponse(final boolean success) { 261 post(new Runnable() { 262 public void run() { 263 if (mSimUnlockProgressDialog != null) { 264 mSimUnlockProgressDialog.hide(); 265 } 266 if (success) { 267 mCallback.dismiss(true); 268 } else { 269 mStateMachine.reset(); 270 mSecurityMessageDisplay.setMessage(R.string.kg_invalid_puk, true); 271 } 272 mCheckInProgress = false; 273 } 274 }); 275 } 276 }.start(); 277 } 278 } 279 280 @Override 281 protected void verifyPasswordAndUnlock() { 282 mStateMachine.next(); 283 } 284} 285 286 287