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