1 /* 2 * Copyright (C) 2008 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.phone; 18 19 import android.app.ActionBar; 20 import android.app.AlertDialog; 21 import android.content.DialogInterface; 22 import android.os.AsyncResult; 23 import android.os.Bundle; 24 import android.os.Handler; 25 import android.os.Message; 26 import android.util.Log; 27 import android.preference.PreferenceActivity; 28 import android.preference.PreferenceScreen; 29 import android.view.MenuItem; 30 import android.view.WindowManager; 31 import android.widget.Toast; 32 33 import com.android.internal.telephony.CommandException; 34 import com.android.internal.telephony.Phone; 35 36 /** 37 * FDN settings UI for the Phone app. 38 * Rewritten to look and behave closer to the other preferences. 39 */ 40 public class FdnSetting extends PreferenceActivity 41 implements EditPinPreference.OnPinEnteredListener, DialogInterface.OnCancelListener { 42 43 private static final String LOG_TAG = PhoneGlobals.LOG_TAG; 44 private static final boolean DBG = false; 45 46 private Phone mPhone; 47 48 /** 49 * Events we handle. 50 * The first is used for toggling FDN enable, the second for the PIN change. 51 */ 52 private static final int EVENT_PIN2_ENTRY_COMPLETE = 100; 53 private static final int EVENT_PIN2_CHANGE_COMPLETE = 200; 54 55 // String keys for preference lookup 56 // We only care about the pin preferences here, the manage FDN contacts 57 // Preference is handled solely in xml. 58 private static final String BUTTON_FDN_ENABLE_KEY = "button_fdn_enable_key"; 59 private static final String BUTTON_CHANGE_PIN2_KEY = "button_change_pin2_key"; 60 61 private EditPinPreference mButtonEnableFDN; 62 private EditPinPreference mButtonChangePin2; 63 64 // State variables 65 private String mOldPin; 66 private String mNewPin; 67 private String mPuk2; 68 private static final int PIN_CHANGE_OLD = 0; 69 private static final int PIN_CHANGE_NEW = 1; 70 private static final int PIN_CHANGE_REENTER = 2; 71 private static final int PIN_CHANGE_PUK = 3; 72 private static final int PIN_CHANGE_NEW_PIN_FOR_PUK = 4; 73 private static final int PIN_CHANGE_REENTER_PIN_FOR_PUK = 5; 74 private int mPinChangeState; 75 private boolean mIsPuk2Locked; // Indicates we know that we are PUK2 blocked. 76 77 private static final String SKIP_OLD_PIN_KEY = "skip_old_pin_key"; 78 private static final String PIN_CHANGE_STATE_KEY = "pin_change_state_key"; 79 private static final String OLD_PIN_KEY = "old_pin_key"; 80 private static final String NEW_PIN_KEY = "new_pin_key"; 81 private static final String DIALOG_MESSAGE_KEY = "dialog_message_key"; 82 private static final String DIALOG_PIN_ENTRY_KEY = "dialog_pin_entry_key"; 83 84 // size limits for the pin. 85 private static final int MIN_PIN_LENGTH = 4; 86 private static final int MAX_PIN_LENGTH = 8; 87 88 /** 89 * Delegate to the respective handlers. 90 */ 91 @Override 92 public void onPinEntered(EditPinPreference preference, boolean positiveResult) { 93 if (preference == mButtonEnableFDN) { 94 toggleFDNEnable(positiveResult); 95 } else if (preference == mButtonChangePin2){ 96 updatePINChangeState(positiveResult); 97 } 98 } 99 100 /** 101 * Attempt to toggle FDN activation. 102 */ 103 private void toggleFDNEnable(boolean positiveResult) { 104 if (!positiveResult) { 105 return; 106 } 107 108 // validate the pin first, before submitting it to the RIL for FDN enable. 109 String password = mButtonEnableFDN.getText(); 110 if (validatePin (password, false)) { 111 // get the relevant data for the icc call 112 boolean isEnabled = mPhone.getIccCard().getIccFdnEnabled(); 113 Message onComplete = mFDNHandler.obtainMessage(EVENT_PIN2_ENTRY_COMPLETE); 114 115 // make fdn request 116 mPhone.getIccCard().setIccFdnEnabled(!isEnabled, password, onComplete); 117 } else { 118 // throw up error if the pin is invalid. 119 displayMessage(R.string.invalidPin2); 120 } 121 122 mButtonEnableFDN.setText(""); 123 } 124 125 /** 126 * Attempt to change the pin. 127 */ 128 private void updatePINChangeState(boolean positiveResult) { 129 if (DBG) log("updatePINChangeState positive=" + positiveResult 130 + " mPinChangeState=" + mPinChangeState 131 + " mSkipOldPin=" + mIsPuk2Locked); 132 133 if (!positiveResult) { 134 // reset the state on cancel, either to expect PUK2 or PIN2 135 if (!mIsPuk2Locked) { 136 resetPinChangeState(); 137 } else { 138 resetPinChangeStateForPUK2(); 139 } 140 return; 141 } 142 143 // Progress through the dialog states, generally in this order: 144 // 1. Enter old pin 145 // 2. Enter new pin 146 // 3. Re-Enter new pin 147 // While handling any error conditions that may show up in between. 148 // Also handle the PUK2 entry, if it is requested. 149 // 150 // In general, if any invalid entries are made, the dialog re- 151 // appears with text to indicate what the issue is. 152 switch (mPinChangeState) { 153 case PIN_CHANGE_OLD: 154 mOldPin = mButtonChangePin2.getText(); 155 mButtonChangePin2.setText(""); 156 // if the pin is not valid, display a message and reset the state. 157 if (validatePin (mOldPin, false)) { 158 mPinChangeState = PIN_CHANGE_NEW; 159 displayPinChangeDialog(); 160 } else { 161 displayPinChangeDialog(R.string.invalidPin2, true); 162 } 163 break; 164 case PIN_CHANGE_NEW: 165 mNewPin = mButtonChangePin2.getText(); 166 mButtonChangePin2.setText(""); 167 // if the new pin is not valid, display a message and reset the state. 168 if (validatePin (mNewPin, false)) { 169 mPinChangeState = PIN_CHANGE_REENTER; 170 displayPinChangeDialog(); 171 } else { 172 displayPinChangeDialog(R.string.invalidPin2, true); 173 } 174 break; 175 case PIN_CHANGE_REENTER: 176 // if the re-entered pin is not valid, display a message and reset the state. 177 if (!mNewPin.equals(mButtonChangePin2.getText())) { 178 mPinChangeState = PIN_CHANGE_NEW; 179 mButtonChangePin2.setText(""); 180 displayPinChangeDialog(R.string.mismatchPin2, true); 181 } else { 182 // If the PIN is valid, then we submit the change PIN request. 183 mButtonChangePin2.setText(""); 184 Message onComplete = mFDNHandler.obtainMessage( 185 EVENT_PIN2_CHANGE_COMPLETE); 186 mPhone.getIccCard().changeIccFdnPassword( 187 mOldPin, mNewPin, onComplete); 188 } 189 break; 190 case PIN_CHANGE_PUK: { 191 // Doh! too many incorrect requests, PUK requested. 192 mPuk2 = mButtonChangePin2.getText(); 193 mButtonChangePin2.setText(""); 194 // if the puk is not valid, display 195 // a message and reset the state. 196 if (validatePin (mPuk2, true)) { 197 mPinChangeState = PIN_CHANGE_NEW_PIN_FOR_PUK; 198 displayPinChangeDialog(); 199 } else { 200 displayPinChangeDialog(R.string.invalidPuk2, true); 201 } 202 } 203 break; 204 case PIN_CHANGE_NEW_PIN_FOR_PUK: 205 mNewPin = mButtonChangePin2.getText(); 206 mButtonChangePin2.setText(""); 207 // if the new pin is not valid, display 208 // a message and reset the state. 209 if (validatePin (mNewPin, false)) { 210 mPinChangeState = PIN_CHANGE_REENTER_PIN_FOR_PUK; 211 displayPinChangeDialog(); 212 } else { 213 displayPinChangeDialog(R.string.invalidPin2, true); 214 } 215 break; 216 case PIN_CHANGE_REENTER_PIN_FOR_PUK: 217 // if the re-entered pin is not valid, display 218 // a message and reset the state. 219 if (!mNewPin.equals(mButtonChangePin2.getText())) { 220 mPinChangeState = PIN_CHANGE_NEW_PIN_FOR_PUK; 221 mButtonChangePin2.setText(""); 222 displayPinChangeDialog(R.string.mismatchPin2, true); 223 } else { 224 // Both puk2 and new pin2 are ready to submit 225 mButtonChangePin2.setText(""); 226 Message onComplete = mFDNHandler.obtainMessage( 227 EVENT_PIN2_CHANGE_COMPLETE); 228 mPhone.getIccCard().supplyPuk2(mPuk2, mNewPin, onComplete); 229 } 230 break; 231 } 232 } 233 234 /** 235 * Handler for asynchronous replies from the sim. 236 */ 237 private final Handler mFDNHandler = new Handler() { 238 @Override 239 public void handleMessage(Message msg) { 240 switch (msg.what) { 241 242 // when we are enabling FDN, either we are unsuccessful and display 243 // a toast, or just update the UI. 244 case EVENT_PIN2_ENTRY_COMPLETE: { 245 AsyncResult ar = (AsyncResult) msg.obj; 246 if (ar.exception != null && ar.exception instanceof CommandException) { 247 int attemptsRemaining = msg.arg1; 248 // see if PUK2 is requested and alert the user accordingly. 249 CommandException.Error e = 250 ((CommandException) ar.exception).getCommandError(); 251 switch (e) { 252 case SIM_PUK2: 253 // make sure we set the PUK2 state so that we can skip 254 // some redundant behaviour. 255 displayMessage(R.string.fdn_enable_puk2_requested, 256 attemptsRemaining); 257 resetPinChangeStateForPUK2(); 258 break; 259 case PASSWORD_INCORRECT: 260 displayMessage(R.string.pin2_invalid, attemptsRemaining); 261 break; 262 default: 263 displayMessage(R.string.fdn_failed, attemptsRemaining); 264 break; 265 } 266 } 267 updateEnableFDN(); 268 } 269 break; 270 271 // when changing the pin we need to pay attention to whether or not 272 // the error requests a PUK (usually after too many incorrect tries) 273 // Set the state accordingly. 274 case EVENT_PIN2_CHANGE_COMPLETE: { 275 if (DBG) 276 log("Handle EVENT_PIN2_CHANGE_COMPLETE"); 277 AsyncResult ar = (AsyncResult) msg.obj; 278 if (ar.exception != null) { 279 int attemptsRemaining = msg.arg1; 280 log("Handle EVENT_PIN2_CHANGE_COMPLETE attemptsRemaining=" 281 + attemptsRemaining); 282 CommandException ce = (CommandException) ar.exception; 283 if (ce.getCommandError() == CommandException.Error.SIM_PUK2) { 284 // throw an alert dialog on the screen, displaying the 285 // request for a PUK2. set the cancel listener to 286 // FdnSetting.onCancel(). 287 AlertDialog a = new AlertDialog.Builder(FdnSetting.this) 288 .setMessage(R.string.puk2_requested) 289 .setCancelable(true) 290 .setOnCancelListener(FdnSetting.this) 291 .create(); 292 a.getWindow().addFlags( 293 WindowManager.LayoutParams.FLAG_DIM_BEHIND); 294 a.show(); 295 } else { 296 // set the correct error message depending upon the state. 297 // Reset the state depending upon or knowledge of the PUK state. 298 if (!mIsPuk2Locked) { 299 displayMessage(R.string.badPin2, attemptsRemaining); 300 resetPinChangeState(); 301 } else { 302 displayMessage(R.string.badPuk2, attemptsRemaining); 303 resetPinChangeStateForPUK2(); 304 } 305 } 306 } else { 307 if (mPinChangeState == PIN_CHANGE_PUK) { 308 displayMessage(R.string.pin2_unblocked); 309 } else { 310 displayMessage(R.string.pin2_changed); 311 } 312 313 // reset to normal behaviour on successful change. 314 resetPinChangeState(); 315 } 316 } 317 break; 318 } 319 } 320 }; 321 322 /** 323 * Cancel listener for the PUK2 request alert dialog. 324 */ 325 @Override 326 public void onCancel(DialogInterface dialog) { 327 // set the state of the preference and then display the dialog. 328 resetPinChangeStateForPUK2(); 329 displayPinChangeDialog(0, true); 330 } 331 332 /** 333 * Display a toast for message, like the rest of the settings. 334 */ 335 private final void displayMessage(int strId, int attemptsRemaining) { 336 String s = getString(strId); 337 if ((strId == R.string.badPin2) || (strId == R.string.badPuk2) || 338 (strId == R.string.pin2_invalid)) { 339 if (attemptsRemaining >= 0) { 340 s = getString(strId) + getString(R.string.pin2_attempts, attemptsRemaining); 341 } else { 342 s = getString(strId); 343 } 344 } 345 log("displayMessage: attemptsRemaining=" + attemptsRemaining + " s=" + s); 346 Toast.makeText(this, s, Toast.LENGTH_SHORT).show(); 347 } 348 349 private final void displayMessage(int strId) { 350 displayMessage(strId, -1); 351 } 352 353 /** 354 * The next two functions are for updating the message field on the dialog. 355 */ 356 private final void displayPinChangeDialog() { 357 displayPinChangeDialog(0, true); 358 } 359 360 private final void displayPinChangeDialog(int strId, boolean shouldDisplay) { 361 int msgId; 362 switch (mPinChangeState) { 363 case PIN_CHANGE_OLD: 364 msgId = R.string.oldPin2Label; 365 break; 366 case PIN_CHANGE_NEW: 367 case PIN_CHANGE_NEW_PIN_FOR_PUK: 368 msgId = R.string.newPin2Label; 369 break; 370 case PIN_CHANGE_REENTER: 371 case PIN_CHANGE_REENTER_PIN_FOR_PUK: 372 msgId = R.string.confirmPin2Label; 373 break; 374 case PIN_CHANGE_PUK: 375 default: 376 msgId = R.string.label_puk2_code; 377 break; 378 } 379 380 // append the note / additional message, if needed. 381 if (strId != 0) { 382 mButtonChangePin2.setDialogMessage(getText(msgId) + "\n" + getText(strId)); 383 } else { 384 mButtonChangePin2.setDialogMessage(msgId); 385 } 386 387 // only display if requested. 388 if (shouldDisplay) { 389 mButtonChangePin2.showPinDialog(); 390 } 391 } 392 393 /** 394 * Reset the state of the pin change dialog. 395 */ 396 private final void resetPinChangeState() { 397 if (DBG) log("resetPinChangeState"); 398 mPinChangeState = PIN_CHANGE_OLD; 399 displayPinChangeDialog(0, false); 400 mOldPin = mNewPin = ""; 401 mIsPuk2Locked = false; 402 } 403 404 /** 405 * Reset the state of the pin change dialog solely for PUK2 use. 406 */ 407 private final void resetPinChangeStateForPUK2() { 408 if (DBG) log("resetPinChangeStateForPUK2"); 409 mPinChangeState = PIN_CHANGE_PUK; 410 displayPinChangeDialog(0, false); 411 mOldPin = mNewPin = mPuk2 = ""; 412 mIsPuk2Locked = true; 413 } 414 415 /** 416 * Validate the pin entry. 417 * 418 * @param pin This is the pin to validate 419 * @param isPuk Boolean indicating whether we are to treat 420 * the pin input as a puk. 421 */ 422 private boolean validatePin(String pin, boolean isPuk) { 423 424 // for pin, we have 4-8 numbers, or puk, we use only 8. 425 int pinMinimum = isPuk ? MAX_PIN_LENGTH : MIN_PIN_LENGTH; 426 427 // check validity 428 if (pin == null || pin.length() < pinMinimum || pin.length() > MAX_PIN_LENGTH) { 429 return false; 430 } else { 431 return true; 432 } 433 } 434 435 /** 436 * Reflect the updated FDN state in the UI. 437 */ 438 private void updateEnableFDN() { 439 if (mPhone.getIccCard().getIccFdnEnabled()) { 440 mButtonEnableFDN.setTitle(R.string.enable_fdn_ok); 441 mButtonEnableFDN.setSummary(R.string.fdn_enabled); 442 mButtonEnableFDN.setDialogTitle(R.string.disable_fdn); 443 } else { 444 mButtonEnableFDN.setTitle(R.string.disable_fdn_ok); 445 mButtonEnableFDN.setSummary(R.string.fdn_disabled); 446 mButtonEnableFDN.setDialogTitle(R.string.enable_fdn); 447 } 448 } 449 450 @Override 451 protected void onCreate(Bundle icicle) { 452 super.onCreate(icicle); 453 454 addPreferencesFromResource(R.xml.fdn_setting); 455 456 mPhone = PhoneGlobals.getPhone(); 457 458 //get UI object references 459 PreferenceScreen prefSet = getPreferenceScreen(); 460 mButtonEnableFDN = (EditPinPreference) prefSet.findPreference(BUTTON_FDN_ENABLE_KEY); 461 mButtonChangePin2 = (EditPinPreference) prefSet.findPreference(BUTTON_CHANGE_PIN2_KEY); 462 463 //assign click listener and update state 464 mButtonEnableFDN.setOnPinEnteredListener(this); 465 updateEnableFDN(); 466 467 mButtonChangePin2.setOnPinEnteredListener(this); 468 469 // Only reset the pin change dialog if we're not in the middle of changing it. 470 if (icicle == null) { 471 resetPinChangeState(); 472 } else { 473 mIsPuk2Locked = icicle.getBoolean(SKIP_OLD_PIN_KEY); 474 mPinChangeState = icicle.getInt(PIN_CHANGE_STATE_KEY); 475 mOldPin = icicle.getString(OLD_PIN_KEY); 476 mNewPin = icicle.getString(NEW_PIN_KEY); 477 mButtonChangePin2.setDialogMessage(icicle.getString(DIALOG_MESSAGE_KEY)); 478 mButtonChangePin2.setText(icicle.getString(DIALOG_PIN_ENTRY_KEY)); 479 } 480 481 ActionBar actionBar = getActionBar(); 482 if (actionBar != null) { 483 // android.R.id.home will be triggered in onOptionsItemSelected() 484 actionBar.setDisplayHomeAsUpEnabled(true); 485 } 486 } 487 488 @Override 489 protected void onResume() { 490 super.onResume(); 491 mPhone = PhoneGlobals.getPhone(); 492 updateEnableFDN(); 493 } 494 495 /** 496 * Save the state of the pin change. 497 */ 498 @Override 499 protected void onSaveInstanceState(Bundle out) { 500 super.onSaveInstanceState(out); 501 out.putBoolean(SKIP_OLD_PIN_KEY, mIsPuk2Locked); 502 out.putInt(PIN_CHANGE_STATE_KEY, mPinChangeState); 503 out.putString(OLD_PIN_KEY, mOldPin); 504 out.putString(NEW_PIN_KEY, mNewPin); 505 out.putString(DIALOG_MESSAGE_KEY, mButtonChangePin2.getDialogMessage().toString()); 506 out.putString(DIALOG_PIN_ENTRY_KEY, mButtonChangePin2.getText()); 507 } 508 509 @Override 510 public boolean onOptionsItemSelected(MenuItem item) { 511 final int itemId = item.getItemId(); 512 if (itemId == android.R.id.home) { // See ActionBar#setDisplayHomeAsUpEnabled() 513 CallFeaturesSetting.goUpToTopLevelSetting(this); 514 return true; 515 } 516 return super.onOptionsItemSelected(item); 517 } 518 519 private void log(String msg) { 520 Log.d(LOG_TAG, "FdnSetting: " + msg); 521 } 522 } 523 524