1/** 2 * Copyright (C) 2014 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.phone.settings; 18 19import android.app.Dialog; 20import android.content.DialogInterface; 21import android.content.Intent; 22import android.database.Cursor; 23import android.os.AsyncResult; 24import android.os.Bundle; 25import android.os.Handler; 26import android.os.Message; 27import android.os.UserManager; 28import android.preference.Preference; 29import android.preference.PreferenceActivity; 30import android.preference.PreferenceScreen; 31import android.preference.SwitchPreference; 32import android.provider.ContactsContract.CommonDataKinds; 33import android.provider.Settings; 34import android.telephony.TelephonyManager; 35import android.text.BidiFormatter; 36import android.text.TextDirectionHeuristics; 37import android.text.TextUtils; 38import android.util.Log; 39import android.view.MenuItem; 40import android.widget.ListAdapter; 41import android.widget.Toast; 42 43import com.android.internal.telephony.CallForwardInfo; 44import com.android.internal.telephony.Phone; 45import com.android.internal.telephony.PhoneConstants; 46import com.android.internal.telephony.util.NotificationChannelController; 47import com.android.phone.EditPhoneNumberPreference; 48import com.android.phone.PhoneGlobals; 49import com.android.phone.R; 50import com.android.phone.SubscriptionInfoHelper; 51 52import java.util.Collection; 53import java.util.HashMap; 54import java.util.HashSet; 55import java.util.Iterator; 56import java.util.Map; 57 58public class VoicemailSettingsActivity extends PreferenceActivity 59 implements DialogInterface.OnClickListener, 60 Preference.OnPreferenceChangeListener, 61 EditPhoneNumberPreference.OnDialogClosedListener, 62 EditPhoneNumberPreference.GetDefaultNumberListener{ 63 private static final String LOG_TAG = VoicemailSettingsActivity.class.getSimpleName(); 64 private static final boolean DBG = (PhoneGlobals.DBG_LEVEL >= 2); 65 66 /** 67 * Intent action to bring up Voicemail Provider settings 68 * DO NOT RENAME. There are existing apps which use this intent value. 69 */ 70 public static final String ACTION_ADD_VOICEMAIL = 71 "com.android.phone.CallFeaturesSetting.ADD_VOICEMAIL"; 72 73 /** 74 * Intent action to bring up the {@code VoicemailSettingsActivity}. 75 * DO NOT RENAME. There are existing apps which use this intent value. 76 */ 77 public static final String ACTION_CONFIGURE_VOICEMAIL = 78 "com.android.phone.CallFeaturesSetting.CONFIGURE_VOICEMAIL"; 79 80 // Extra put in the return from VM provider config containing voicemail number to set 81 public static final String VM_NUMBER_EXTRA = "com.android.phone.VoicemailNumber"; 82 // Extra put in the return from VM provider config containing call forwarding number to set 83 public static final String FWD_NUMBER_EXTRA = "com.android.phone.ForwardingNumber"; 84 // Extra put in the return from VM provider config containing call forwarding number to set 85 public static final String FWD_NUMBER_TIME_EXTRA = "com.android.phone.ForwardingNumberTime"; 86 // If the VM provider returns non null value in this extra we will force the user to 87 // choose another VM provider 88 public static final String SIGNOUT_EXTRA = "com.android.phone.Signout"; 89 90 /** 91 * String Extra put into ACTION_ADD_VOICEMAIL call to indicate which provider should be hidden 92 * in the list of providers presented to the user. This allows a provider which is being 93 * disabled (e.g. GV user logging out) to force the user to pick some other provider. 94 */ 95 public static final String IGNORE_PROVIDER_EXTRA = "com.android.phone.ProviderToIgnore"; 96 97 /** 98 * String Extra put into ACTION_ADD_VOICEMAIL to indicate that the voicemail setup screen should 99 * be opened. 100 */ 101 public static final String SETUP_VOICEMAIL_EXTRA = "com.android.phone.SetupVoicemail"; 102 103 // TODO: Define these preference keys in XML. 104 private static final String BUTTON_VOICEMAIL_KEY = "button_voicemail_key"; 105 private static final String BUTTON_VOICEMAIL_PROVIDER_KEY = "button_voicemail_provider_key"; 106 private static final String BUTTON_VOICEMAIL_SETTING_KEY = "button_voicemail_setting_key"; 107 108 /** Event for Async voicemail change call */ 109 private static final int EVENT_VOICEMAIL_CHANGED = 500; 110 private static final int EVENT_FORWARDING_CHANGED = 501; 111 private static final int EVENT_FORWARDING_GET_COMPLETED = 502; 112 113 /** Handle to voicemail pref */ 114 private static final int VOICEMAIL_PREF_ID = 1; 115 private static final int VOICEMAIL_PROVIDER_CFG_ID = 2; 116 117 /** 118 * Results of reading forwarding settings 119 */ 120 private CallForwardInfo[] mForwardingReadResults = null; 121 122 /** 123 * Result of forwarding number change. 124 * Keys are reasons (eg. unconditional forwarding). 125 */ 126 private Map<Integer, AsyncResult> mForwardingChangeResults = null; 127 128 /** 129 * Expected CF read result types. 130 * This set keeps track of the CF types for which we've issued change 131 * commands so we can tell when we've received all of the responses. 132 */ 133 private Collection<Integer> mExpectedChangeResultReasons = null; 134 135 /** 136 * Result of vm number change 137 */ 138 private AsyncResult mVoicemailChangeResult = null; 139 140 /** 141 * Previous VM provider setting so we can return to it in case of failure. 142 */ 143 private String mPreviousVMProviderKey = null; 144 145 /** 146 * Id of the dialog being currently shown. 147 */ 148 private int mCurrentDialogId = 0; 149 150 /** 151 * Flag indicating that we are invoking settings for the voicemail provider programmatically 152 * due to vm provider change. 153 */ 154 private boolean mVMProviderSettingsForced = false; 155 156 /** 157 * Flag indicating that we are making changes to vm or fwd numbers 158 * due to vm provider change. 159 */ 160 private boolean mChangingVMorFwdDueToProviderChange = false; 161 162 /** 163 * True if we are in the process of vm & fwd number change and vm has already been changed. 164 * This is used to decide what to do in case of rollback. 165 */ 166 private boolean mVMChangeCompletedSuccessfully = false; 167 168 /** 169 * True if we had full or partial failure setting forwarding numbers and so need to roll them 170 * back. 171 */ 172 private boolean mFwdChangesRequireRollback = false; 173 174 /** 175 * Id of error msg to display to user once we are done reverting the VM provider to the previous 176 * one. 177 */ 178 private int mVMOrFwdSetError = 0; 179 180 /** string to hold old voicemail number as it is being updated. */ 181 private String mOldVmNumber; 182 183 // New call forwarding settings and vm number we will be setting 184 // Need to save these since before we get to saving we need to asynchronously 185 // query the existing forwarding settings. 186 private CallForwardInfo[] mNewFwdSettings; 187 private String mNewVMNumber; 188 189 /** 190 * Used to indicate that the voicemail preference should be shown. 191 */ 192 private boolean mShowVoicemailPreference = false; 193 194 private boolean mForeground; 195 private Phone mPhone; 196 private SubscriptionInfoHelper mSubscriptionInfoHelper; 197 198 private EditPhoneNumberPreference mSubMenuVoicemailSettings = null; 199 private VoicemailProviderListPreference mVoicemailProviders; 200 private PreferenceScreen mVoicemailSettings; 201 private Preference mVoicemailNotificationPreference; 202 203 //********************************************************************************************* 204 // Preference Activity Methods 205 //********************************************************************************************* 206 207 @Override 208 protected void onCreate(Bundle icicle) { 209 super.onCreate(icicle); 210 // Make sure we are running as the primary user only 211 UserManager userManager = getApplicationContext().getSystemService(UserManager.class); 212 if (!userManager.isPrimaryUser()) { 213 Toast.makeText(this, R.string.voice_number_setting_primary_user_only, 214 Toast.LENGTH_SHORT).show(); 215 finish(); 216 return; 217 } 218 // Show the voicemail preference in onResume if the calling intent specifies the 219 // ACTION_ADD_VOICEMAIL action. 220 mShowVoicemailPreference = (icicle == null) && 221 TextUtils.equals(getIntent().getAction(), ACTION_ADD_VOICEMAIL); 222 223 mSubscriptionInfoHelper = new SubscriptionInfoHelper(this, getIntent()); 224 mSubscriptionInfoHelper.setActionBarTitle( 225 getActionBar(), getResources(), R.string.voicemail_settings_with_label); 226 mPhone = mSubscriptionInfoHelper.getPhone(); 227 addPreferencesFromResource(R.xml.voicemail_settings); 228 229 mVoicemailNotificationPreference = 230 findPreference(getString(R.string.voicemail_notifications_key)); 231 final Intent intent = new Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS); 232 intent.putExtra(Settings.EXTRA_CHANNEL_ID, 233 NotificationChannelController.CHANNEL_ID_VOICE_MAIL); 234 intent.putExtra(Settings.EXTRA_APP_PACKAGE, mPhone.getContext().getPackageName()); 235 mVoicemailNotificationPreference.setIntent(intent); 236 } 237 238 @Override 239 protected void onResume() { 240 super.onResume(); 241 mForeground = true; 242 243 PreferenceScreen prefSet = getPreferenceScreen(); 244 245 if (mSubMenuVoicemailSettings == null) { 246 mSubMenuVoicemailSettings = 247 (EditPhoneNumberPreference) findPreference(BUTTON_VOICEMAIL_KEY); 248 } 249 if (mSubMenuVoicemailSettings != null) { 250 mSubMenuVoicemailSettings.setParentActivity(this, VOICEMAIL_PREF_ID, this); 251 mSubMenuVoicemailSettings.setDialogOnClosedListener(this); 252 mSubMenuVoicemailSettings.setDialogTitle(R.string.voicemail_settings_number_label); 253 } 254 255 mVoicemailProviders = (VoicemailProviderListPreference) findPreference( 256 BUTTON_VOICEMAIL_PROVIDER_KEY); 257 mVoicemailProviders.init(mPhone, getIntent()); 258 mVoicemailProviders.setOnPreferenceChangeListener(this); 259 mPreviousVMProviderKey = mVoicemailProviders.getValue(); 260 261 mVoicemailSettings = (PreferenceScreen) findPreference(BUTTON_VOICEMAIL_SETTING_KEY); 262 263 maybeHidePublicSettings(); 264 265 updateVMPreferenceWidgets(mVoicemailProviders.getValue()); 266 267 // check the intent that started this activity and pop up the voicemail 268 // dialog if we've been asked to. 269 // If we have at least one non default VM provider registered then bring up 270 // the selection for the VM provider, otherwise bring up a VM number dialog. 271 // We only bring up the dialog the first time we are called (not after orientation change) 272 if (mShowVoicemailPreference) { 273 if (DBG) log("ACTION_ADD_VOICEMAIL Intent is thrown"); 274 if (mVoicemailProviders.hasMoreThanOneVoicemailProvider()) { 275 if (DBG) log("Voicemail data has more than one provider."); 276 simulatePreferenceClick(mVoicemailProviders); 277 } else { 278 onPreferenceChange(mVoicemailProviders, VoicemailProviderListPreference.DEFAULT_KEY); 279 mVoicemailProviders.setValue(VoicemailProviderListPreference.DEFAULT_KEY); 280 } 281 mShowVoicemailPreference = false; 282 } 283 284 updateVoiceNumberField(); 285 mVMProviderSettingsForced = false; 286 } 287 288 /** 289 * Hides a subset of voicemail settings if required by the intent extra. This is used by the 290 * default dialer to show "advanced" voicemail settings from its own custom voicemail settings 291 * UI. 292 */ 293 private void maybeHidePublicSettings() { 294 if(!getIntent().getBooleanExtra(TelephonyManager.EXTRA_HIDE_PUBLIC_SETTINGS, false)){ 295 return; 296 } 297 if (DBG) { 298 log("maybeHidePublicSettings: settings hidden by EXTRA_HIDE_PUBLIC_SETTINGS"); 299 } 300 PreferenceScreen preferenceScreen = getPreferenceScreen(); 301 preferenceScreen.removePreference(mVoicemailNotificationPreference); 302 } 303 304 @Override 305 public void onPause() { 306 super.onPause(); 307 mForeground = false; 308 } 309 310 @Override 311 public boolean onOptionsItemSelected(MenuItem item) { 312 if (item.getItemId() == android.R.id.home) { 313 onBackPressed(); 314 return true; 315 } 316 return super.onOptionsItemSelected(item); 317 } 318 319 @Override 320 public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) { 321 if (preference == mSubMenuVoicemailSettings) { 322 return true; 323 } else if (preference.getKey().equals(mVoicemailSettings.getKey())) { 324 // Check key instead of comparing reference because closing the voicemail notification 325 // ringtone dialog invokes onResume(), but leaves the old preference screen up, 326 // TODO: Revert to checking reference after migrating voicemail to its own activity. 327 if (DBG) log("onPreferenceTreeClick: Voicemail Settings Preference is clicked."); 328 329 final Dialog dialog = ((PreferenceScreen) preference).getDialog(); 330 if (dialog != null) { 331 dialog.getActionBar().setDisplayHomeAsUpEnabled(false); 332 } 333 334 mSubMenuVoicemailSettings = 335 (EditPhoneNumberPreference) findPreference(BUTTON_VOICEMAIL_KEY); 336 mSubMenuVoicemailSettings.setParentActivity(this, VOICEMAIL_PREF_ID, this); 337 mSubMenuVoicemailSettings.setDialogOnClosedListener(this); 338 mSubMenuVoicemailSettings.setDialogTitle(R.string.voicemail_settings_number_label); 339 updateVoiceNumberField(); 340 341 if (preference.getIntent() != null) { 342 if (DBG) log("Invoking cfg intent " + preference.getIntent().getPackage()); 343 344 // onActivityResult() will be responsible for resetting some of variables. 345 this.startActivityForResult(preference.getIntent(), VOICEMAIL_PROVIDER_CFG_ID); 346 return true; 347 } else { 348 if (DBG) log("onPreferenceTreeClick(). No intent; use default behavior in xml."); 349 350 // onActivityResult() will not be called, so reset variables here. 351 mPreviousVMProviderKey = VoicemailProviderListPreference.DEFAULT_KEY; 352 mVMProviderSettingsForced = false; 353 return false; 354 } 355 } 356 return false; 357 } 358 359 /** 360 * Implemented to support onPreferenceChangeListener to look for preference changes. 361 * 362 * @param preference is the preference to be changed 363 * @param objValue should be the value of the selection, NOT its localized 364 * display value. 365 */ 366 @Override 367 public boolean onPreferenceChange(Preference preference, Object objValue) { 368 if (DBG) log("onPreferenceChange: \"" + preference + "\" changed to \"" + objValue + "\""); 369 370 if (preference == mVoicemailProviders) { 371 final String newProviderKey = (String) objValue; 372 373 // If previous provider key and the new one is same, we don't need to handle it. 374 if (mPreviousVMProviderKey.equals(newProviderKey)) { 375 if (DBG) log("No change is made to the VM provider setting."); 376 return true; 377 } 378 updateVMPreferenceWidgets(newProviderKey); 379 380 final VoicemailProviderSettings newProviderSettings = 381 VoicemailProviderSettingsUtil.load(this, newProviderKey); 382 383 // If the user switches to a voice mail provider and we have numbers stored for it we 384 // will automatically change the phone's voice mail and forwarding number to the stored 385 // ones. Otherwise we will bring up provider's configuration UI. 386 if (newProviderSettings == null) { 387 // Force the user into a configuration of the chosen provider 388 Log.w(LOG_TAG, "Saved preferences not found - invoking config"); 389 mVMProviderSettingsForced = true; 390 simulatePreferenceClick(mVoicemailSettings); 391 } else { 392 if (DBG) log("Saved preferences found - switching to them"); 393 // Set this flag so if we get a failure we revert to previous provider 394 mChangingVMorFwdDueToProviderChange = true; 395 saveVoiceMailAndForwardingNumber(newProviderKey, newProviderSettings); 396 } 397 } 398 // Always let the preference setting proceed. 399 return true; 400 } 401 402 /** 403 * Implemented for EditPhoneNumberPreference.GetDefaultNumberListener. 404 * This method set the default values for the various 405 * EditPhoneNumberPreference dialogs. 406 */ 407 @Override 408 public String onGetDefaultNumber(EditPhoneNumberPreference preference) { 409 if (preference == mSubMenuVoicemailSettings) { 410 // update the voicemail number field, which takes care of the 411 // mSubMenuVoicemailSettings itself, so we should return null. 412 if (DBG) log("updating default for voicemail dialog"); 413 updateVoiceNumberField(); 414 return null; 415 } 416 417 String vmDisplay = mPhone.getVoiceMailNumber(); 418 if (TextUtils.isEmpty(vmDisplay)) { 419 // if there is no voicemail number, we just return null to 420 // indicate no contribution. 421 return null; 422 } 423 424 // Return the voicemail number prepended with "VM: " 425 if (DBG) log("updating default for call forwarding dialogs"); 426 return getString(R.string.voicemail_abbreviated) + " " + vmDisplay; 427 } 428 429 @Override 430 protected void onActivityResult(int requestCode, int resultCode, Intent data) { 431 if (DBG) { 432 log("onActivityResult: requestCode: " + requestCode 433 + ", resultCode: " + resultCode 434 + ", data: " + data); 435 } 436 437 // there are cases where the contact picker may end up sending us more than one 438 // request. We want to ignore the request if we're not in the correct state. 439 if (requestCode == VOICEMAIL_PROVIDER_CFG_ID) { 440 boolean failure = false; 441 442 // No matter how the processing of result goes lets clear the flag 443 if (DBG) log("mVMProviderSettingsForced: " + mVMProviderSettingsForced); 444 final boolean isVMProviderSettingsForced = mVMProviderSettingsForced; 445 mVMProviderSettingsForced = false; 446 447 String vmNum = null; 448 if (resultCode != RESULT_OK) { 449 if (DBG) log("onActivityResult: vm provider cfg result not OK."); 450 failure = true; 451 } else { 452 if (data == null) { 453 if (DBG) log("onActivityResult: vm provider cfg result has no data"); 454 failure = true; 455 } else { 456 if (data.getBooleanExtra(SIGNOUT_EXTRA, false)) { 457 if (DBG) log("Provider requested signout"); 458 if (isVMProviderSettingsForced) { 459 if (DBG) log("Going back to previous provider on signout"); 460 switchToPreviousVoicemailProvider(); 461 } else { 462 final String victim = mVoicemailProviders.getKey(); 463 if (DBG) log("Relaunching activity and ignoring " + victim); 464 Intent i = new Intent(ACTION_ADD_VOICEMAIL); 465 i.putExtra(IGNORE_PROVIDER_EXTRA, victim); 466 i.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); 467 this.startActivity(i); 468 } 469 return; 470 } 471 vmNum = data.getStringExtra(VM_NUMBER_EXTRA); 472 if (vmNum == null || vmNum.length() == 0) { 473 if (DBG) log("onActivityResult: vm provider cfg result has no vmnum"); 474 failure = true; 475 } 476 } 477 } 478 if (failure) { 479 if (DBG) log("Failure in return from voicemail provider."); 480 if (isVMProviderSettingsForced) { 481 switchToPreviousVoicemailProvider(); 482 } 483 484 return; 485 } 486 mChangingVMorFwdDueToProviderChange = isVMProviderSettingsForced; 487 final String fwdNum = data.getStringExtra(FWD_NUMBER_EXTRA); 488 489 // TODO: It would be nice to load the current network setting for this and 490 // send it to the provider when it's config is invoked so it can use this as default 491 final int fwdNumTime = data.getIntExtra(FWD_NUMBER_TIME_EXTRA, 20); 492 493 if (DBG) log("onActivityResult: cfg result has forwarding number " + fwdNum); 494 saveVoiceMailAndForwardingNumber(mVoicemailProviders.getKey(), 495 new VoicemailProviderSettings(vmNum, fwdNum, fwdNumTime)); 496 return; 497 } 498 499 if (requestCode == VOICEMAIL_PREF_ID) { 500 if (resultCode != RESULT_OK) { 501 if (DBG) log("onActivityResult: contact picker result not OK."); 502 return; 503 } 504 505 Cursor cursor = null; 506 try { 507 cursor = getContentResolver().query(data.getData(), 508 new String[] { CommonDataKinds.Phone.NUMBER }, null, null, null); 509 if ((cursor == null) || (!cursor.moveToFirst())) { 510 if (DBG) log("onActivityResult: bad contact data, no results found."); 511 return; 512 } 513 if (mSubMenuVoicemailSettings != null) { 514 mSubMenuVoicemailSettings.onPickActivityResult(cursor.getString(0)); 515 } else { 516 Log.w(LOG_TAG, "VoicemailSettingsActivity destroyed while setting contacts."); 517 } 518 return; 519 } finally { 520 if (cursor != null) { 521 cursor.close(); 522 } 523 } 524 } 525 526 super.onActivityResult(requestCode, resultCode, data); 527 } 528 529 /** 530 * Simulates user clicking on a passed preference. 531 * Usually needed when the preference is a dialog preference and we want to invoke 532 * a dialog for this preference programmatically. 533 * TODO: figure out if there is a cleaner way to cause preference dlg to come up 534 */ 535 private void simulatePreferenceClick(Preference preference) { 536 // Go through settings until we find our setting 537 // and then simulate a click on it to bring up the dialog 538 final ListAdapter adapter = getPreferenceScreen().getRootAdapter(); 539 for (int idx = 0; idx < adapter.getCount(); idx++) { 540 if (adapter.getItem(idx) == preference) { 541 getPreferenceScreen().onItemClick(this.getListView(), 542 null, idx, adapter.getItemId(idx)); 543 break; 544 } 545 } 546 } 547 548 549 //********************************************************************************************* 550 // Activity Dialog Methods 551 //********************************************************************************************* 552 553 @Override 554 protected void onPrepareDialog(int id, Dialog dialog) { 555 super.onPrepareDialog(id, dialog); 556 mCurrentDialogId = id; 557 } 558 559 // dialog creation method, called by showDialog() 560 @Override 561 protected Dialog onCreateDialog(int dialogId) { 562 return VoicemailDialogUtil.getDialog(this, dialogId); 563 } 564 565 @Override 566 public void onDialogClosed(EditPhoneNumberPreference preference, int buttonClicked) { 567 if (DBG) log("onDialogClosed: Button clicked is " + buttonClicked); 568 569 if (buttonClicked == DialogInterface.BUTTON_NEGATIVE) { 570 return; 571 } 572 573 if (preference == mSubMenuVoicemailSettings) { 574 VoicemailProviderSettings newSettings = new VoicemailProviderSettings( 575 mSubMenuVoicemailSettings.getPhoneNumber(), 576 VoicemailProviderSettings.NO_FORWARDING); 577 saveVoiceMailAndForwardingNumber(mVoicemailProviders.getKey(), newSettings); 578 } 579 } 580 581 /** 582 * Wrapper around showDialog() that will silently do nothing if we're 583 * not in the foreground. 584 * 585 * This is useful here because most of the dialogs we display from 586 * this class are triggered by asynchronous events (like 587 * success/failure messages from the telephony layer) and it's 588 * possible for those events to come in even after the user has gone 589 * to a different screen. 590 */ 591 // TODO: this is too brittle: it's still easy to accidentally add new 592 // code here that calls showDialog() directly (which will result in a 593 // WindowManager$BadTokenException if called after the activity has 594 // been stopped.) 595 // 596 // It would be cleaner to do the "if (mForeground)" check in one 597 // central place, maybe by using a single Handler for all asynchronous 598 // events (and have *that* discard events if we're not in the 599 // foreground.) 600 // 601 // Unfortunately it's not that simple, since we sometimes need to do 602 // actual work to handle these events whether or not we're in the 603 // foreground (see the Handler code in mSetOptionComplete for 604 // example.) 605 // 606 // TODO: It's a bit worrisome that we don't do anything in error cases when we're not in the 607 // foreground. Consider displaying a toast instead. 608 private void showDialogIfForeground(int id) { 609 if (mForeground) { 610 showDialog(id); 611 } 612 } 613 614 private void dismissDialogSafely(int id) { 615 try { 616 dismissDialog(id); 617 } catch (IllegalArgumentException e) { 618 // This is expected in the case where we were in the background 619 // at the time we would normally have shown the dialog, so we didn't 620 // show it. 621 } 622 } 623 624 // This is a method implemented for DialogInterface.OnClickListener. 625 // Used with the error dialog to close the app, voicemail dialog to just dismiss. 626 // Close button is mapped to BUTTON_POSITIVE for the errors that close the activity, 627 // while those that are mapped to BUTTON_NEUTRAL only move the preference focus. 628 public void onClick(DialogInterface dialog, int which) { 629 if (DBG) log("onClick: button clicked is " + which); 630 631 dialog.dismiss(); 632 switch (which){ 633 case DialogInterface.BUTTON_NEGATIVE: 634 if (mCurrentDialogId == VoicemailDialogUtil.FWD_GET_RESPONSE_ERROR_DIALOG) { 635 // We failed to get current forwarding settings and the user 636 // does not wish to continue. 637 switchToPreviousVoicemailProvider(); 638 } 639 break; 640 case DialogInterface.BUTTON_POSITIVE: 641 if (mCurrentDialogId == VoicemailDialogUtil.FWD_GET_RESPONSE_ERROR_DIALOG) { 642 // We failed to get current forwarding settings but the user 643 // wishes to continue changing settings to the new vm provider 644 setVoicemailNumberWithCarrier(); 645 } else { 646 finish(); 647 } 648 return; 649 default: 650 // just let the dialog close and go back to the input 651 } 652 653 // In all dialogs, all buttons except BUTTON_POSITIVE lead to the end of user interaction 654 // with settings UI. If we were called to explicitly configure voice mail then 655 // we finish the settings activity here to come back to whatever the user was doing. 656 final String action = getIntent() != null ? getIntent().getAction() : null; 657 if (ACTION_ADD_VOICEMAIL.equals(action)) { 658 finish(); 659 } 660 } 661 662 663 //********************************************************************************************* 664 // Voicemail Methods 665 //********************************************************************************************* 666 667 /** 668 * TODO: Refactor to make it easier to understand what's done in the different stages. 669 */ 670 private void saveVoiceMailAndForwardingNumber( 671 String key, VoicemailProviderSettings newSettings) { 672 if (DBG) log("saveVoiceMailAndForwardingNumber: " + newSettings.toString()); 673 mNewVMNumber = newSettings.getVoicemailNumber(); 674 mNewVMNumber = (mNewVMNumber == null) ? "" : mNewVMNumber; 675 mNewFwdSettings = newSettings.getForwardingSettings(); 676 677 // Call forwarding is not suppported on CDMA. 678 if (mPhone.getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA) { 679 if (DBG) log("Ignoring forwarding setting since this is CDMA phone"); 680 mNewFwdSettings = VoicemailProviderSettings.NO_FORWARDING; 681 } 682 683 // Throw a warning if the voicemail is the same and we did not change forwarding. 684 if (mNewVMNumber.equals(mOldVmNumber) 685 && mNewFwdSettings == VoicemailProviderSettings.NO_FORWARDING) { 686 showDialogIfForeground(VoicemailDialogUtil.VM_NOCHANGE_ERROR_DIALOG); 687 return; 688 } 689 690 VoicemailProviderSettingsUtil.save(this, key, newSettings); 691 mVMChangeCompletedSuccessfully = false; 692 mFwdChangesRequireRollback = false; 693 mVMOrFwdSetError = 0; 694 695 if (mNewFwdSettings == VoicemailProviderSettings.NO_FORWARDING 696 || key.equals(mPreviousVMProviderKey)) { 697 if (DBG) log("Set voicemail number. No changes to forwarding number."); 698 setVoicemailNumberWithCarrier(); 699 } else { 700 if (DBG) log("Reading current forwarding settings."); 701 int numSettingsReasons = VoicemailProviderSettings.FORWARDING_SETTINGS_REASONS.length; 702 mForwardingReadResults = new CallForwardInfo[numSettingsReasons]; 703 for (int i = 0; i < mForwardingReadResults.length; i++) { 704 mPhone.getCallForwardingOption( 705 VoicemailProviderSettings.FORWARDING_SETTINGS_REASONS[i], 706 mGetOptionComplete.obtainMessage(EVENT_FORWARDING_GET_COMPLETED, i, 0)); 707 } 708 showDialogIfForeground(VoicemailDialogUtil.VM_FWD_READING_DIALOG); 709 } 710 } 711 712 private final Handler mGetOptionComplete = new Handler() { 713 @Override 714 public void handleMessage(Message msg) { 715 AsyncResult result = (AsyncResult) msg.obj; 716 switch (msg.what) { 717 case EVENT_FORWARDING_GET_COMPLETED: 718 handleForwardingSettingsReadResult(result, msg.arg1); 719 break; 720 } 721 } 722 }; 723 724 private void handleForwardingSettingsReadResult(AsyncResult ar, int idx) { 725 if (DBG) Log.d(LOG_TAG, "handleForwardingSettingsReadResult: " + idx); 726 727 Throwable error = null; 728 if (ar.exception != null) { 729 error = ar.exception; 730 if (DBG) Log.d(LOG_TAG, "FwdRead: ar.exception=" + error.getMessage()); 731 } 732 if (ar.userObj instanceof Throwable) { 733 error = (Throwable) ar.userObj; 734 if (DBG) Log.d(LOG_TAG, "FwdRead: userObj=" + error.getMessage()); 735 } 736 737 // We may have already gotten an error and decided to ignore the other results. 738 if (mForwardingReadResults == null) { 739 if (DBG) Log.d(LOG_TAG, "Ignoring fwd reading result: " + idx); 740 return; 741 } 742 743 // In case of error ignore other results, show an error dialog 744 if (error != null) { 745 if (DBG) Log.d(LOG_TAG, "Error discovered for fwd read : " + idx); 746 mForwardingReadResults = null; 747 dismissDialogSafely(VoicemailDialogUtil.VM_FWD_READING_DIALOG); 748 showDialogIfForeground(VoicemailDialogUtil.FWD_GET_RESPONSE_ERROR_DIALOG); 749 return; 750 } 751 752 // Get the forwarding info. 753 mForwardingReadResults[idx] = CallForwardInfoUtil.getCallForwardInfo( 754 (CallForwardInfo[]) ar.result, 755 VoicemailProviderSettings.FORWARDING_SETTINGS_REASONS[idx]); 756 757 // Check if we got all the results already 758 boolean done = true; 759 for (int i = 0; i < mForwardingReadResults.length; i++) { 760 if (mForwardingReadResults[i] == null) { 761 done = false; 762 break; 763 } 764 } 765 766 if (done) { 767 if (DBG) Log.d(LOG_TAG, "Done receiving fwd info"); 768 dismissDialogSafely(VoicemailDialogUtil.VM_FWD_READING_DIALOG); 769 770 if (mPreviousVMProviderKey.equals(VoicemailProviderListPreference.DEFAULT_KEY)) { 771 VoicemailProviderSettingsUtil.save(mPhone.getContext(), 772 VoicemailProviderListPreference.DEFAULT_KEY, 773 new VoicemailProviderSettings(mOldVmNumber, mForwardingReadResults)); 774 } 775 saveVoiceMailAndForwardingNumberStage2(); 776 } 777 } 778 779 private void resetForwardingChangeState() { 780 mForwardingChangeResults = new HashMap<Integer, AsyncResult>(); 781 mExpectedChangeResultReasons = new HashSet<Integer>(); 782 } 783 784 // Called after we are done saving the previous forwarding settings if we needed. 785 private void saveVoiceMailAndForwardingNumberStage2() { 786 mForwardingChangeResults = null; 787 mVoicemailChangeResult = null; 788 789 resetForwardingChangeState(); 790 for (int i = 0; i < mNewFwdSettings.length; i++) { 791 CallForwardInfo fi = mNewFwdSettings[i]; 792 CallForwardInfo fiForReason = 793 CallForwardInfoUtil.infoForReason(mForwardingReadResults, fi.reason); 794 final boolean doUpdate = CallForwardInfoUtil.isUpdateRequired(fiForReason, fi); 795 796 if (doUpdate) { 797 if (DBG) log("Setting fwd #: " + i + ": " + fi.toString()); 798 mExpectedChangeResultReasons.add(i); 799 800 CallForwardInfoUtil.setCallForwardingOption(mPhone, fi, 801 mSetOptionComplete.obtainMessage( 802 EVENT_FORWARDING_CHANGED, fi.reason, 0)); 803 } 804 } 805 showDialogIfForeground(VoicemailDialogUtil.VM_FWD_SAVING_DIALOG); 806 } 807 808 809 /** 810 * Callback to handle option update completions 811 */ 812 private final Handler mSetOptionComplete = new Handler() { 813 @Override 814 public void handleMessage(Message msg) { 815 AsyncResult result = (AsyncResult) msg.obj; 816 boolean done = false; 817 switch (msg.what) { 818 case EVENT_VOICEMAIL_CHANGED: 819 mVoicemailChangeResult = result; 820 mVMChangeCompletedSuccessfully = isVmChangeSuccess(); 821 PhoneGlobals.getInstance().refreshMwiIndicator( 822 mSubscriptionInfoHelper.getSubId()); 823 done = true; 824 break; 825 case EVENT_FORWARDING_CHANGED: 826 mForwardingChangeResults.put(msg.arg1, result); 827 if (result.exception != null) { 828 Log.w(LOG_TAG, "Error in setting fwd# " + msg.arg1 + ": " + 829 result.exception.getMessage()); 830 } 831 if (isForwardingCompleted()) { 832 if (isFwdChangeSuccess()) { 833 if (DBG) log("Overall fwd changes completed ok, starting vm change"); 834 setVoicemailNumberWithCarrier(); 835 } else { 836 Log.w(LOG_TAG, "Overall fwd changes completed in failure. " + 837 "Check if we need to try rollback for some settings."); 838 mFwdChangesRequireRollback = false; 839 Iterator<Map.Entry<Integer,AsyncResult>> it = 840 mForwardingChangeResults.entrySet().iterator(); 841 while (it.hasNext()) { 842 Map.Entry<Integer,AsyncResult> entry = it.next(); 843 if (entry.getValue().exception == null) { 844 // If at least one succeeded we have to revert 845 Log.i(LOG_TAG, "Rollback will be required"); 846 mFwdChangesRequireRollback = true; 847 break; 848 } 849 } 850 if (!mFwdChangesRequireRollback) { 851 Log.i(LOG_TAG, "No rollback needed."); 852 } 853 done = true; 854 } 855 } 856 break; 857 default: 858 // TODO: should never reach this, may want to throw exception 859 } 860 861 if (done) { 862 if (DBG) log("All VM provider related changes done"); 863 if (mForwardingChangeResults != null) { 864 dismissDialogSafely(VoicemailDialogUtil.VM_FWD_SAVING_DIALOG); 865 } 866 handleSetVmOrFwdMessage(); 867 } 868 } 869 }; 870 871 /** 872 * Callback to handle option revert completions 873 */ 874 private final Handler mRevertOptionComplete = new Handler() { 875 @Override 876 public void handleMessage(Message msg) { 877 AsyncResult result = (AsyncResult) msg.obj; 878 switch (msg.what) { 879 case EVENT_VOICEMAIL_CHANGED: 880 if (DBG) log("VM revert complete msg"); 881 mVoicemailChangeResult = result; 882 break; 883 884 case EVENT_FORWARDING_CHANGED: 885 if (DBG) log("FWD revert complete msg "); 886 mForwardingChangeResults.put(msg.arg1, result); 887 if (result.exception != null) { 888 if (DBG) log("Error in reverting fwd# " + msg.arg1 + ": " + 889 result.exception.getMessage()); 890 } 891 break; 892 893 default: 894 // TODO: should never reach this, may want to throw exception 895 } 896 897 final boolean done = (!mVMChangeCompletedSuccessfully || mVoicemailChangeResult != null) 898 && (!mFwdChangesRequireRollback || isForwardingCompleted()); 899 if (done) { 900 if (DBG) log("All VM reverts done"); 901 dismissDialogSafely(VoicemailDialogUtil.VM_REVERTING_DIALOG); 902 onRevertDone(); 903 } 904 } 905 }; 906 907 private void setVoicemailNumberWithCarrier() { 908 if (DBG) log("save voicemail #: " + mNewVMNumber); 909 910 mVoicemailChangeResult = null; 911 mPhone.setVoiceMailNumber( 912 mPhone.getVoiceMailAlphaTag().toString(), 913 mNewVMNumber, 914 Message.obtain(mSetOptionComplete, EVENT_VOICEMAIL_CHANGED)); 915 } 916 917 private void switchToPreviousVoicemailProvider() { 918 if (DBG) log("switchToPreviousVoicemailProvider " + mPreviousVMProviderKey); 919 920 if (mPreviousVMProviderKey == null) { 921 return; 922 } 923 924 if (mVMChangeCompletedSuccessfully || mFwdChangesRequireRollback) { 925 showDialogIfForeground(VoicemailDialogUtil.VM_REVERTING_DIALOG); 926 final VoicemailProviderSettings prevSettings = 927 VoicemailProviderSettingsUtil.load(this, mPreviousVMProviderKey); 928 if (prevSettings == null) { 929 Log.e(LOG_TAG, "VoicemailProviderSettings for the key \"" 930 + mPreviousVMProviderKey + "\" is null but should be loaded."); 931 return; 932 } 933 934 if (mVMChangeCompletedSuccessfully) { 935 mNewVMNumber = prevSettings.getVoicemailNumber(); 936 Log.i(LOG_TAG, "VM change is already completed successfully." 937 + "Have to revert VM back to " + mNewVMNumber + " again."); 938 mPhone.setVoiceMailNumber( 939 mPhone.getVoiceMailAlphaTag().toString(), 940 mNewVMNumber, 941 Message.obtain(mRevertOptionComplete, EVENT_VOICEMAIL_CHANGED)); 942 } 943 944 if (mFwdChangesRequireRollback) { 945 Log.i(LOG_TAG, "Requested to rollback forwarding changes."); 946 947 final CallForwardInfo[] prevFwdSettings = prevSettings.getForwardingSettings(); 948 if (prevFwdSettings != null) { 949 Map<Integer, AsyncResult> results = mForwardingChangeResults; 950 resetForwardingChangeState(); 951 for (int i = 0; i < prevFwdSettings.length; i++) { 952 CallForwardInfo fi = prevFwdSettings[i]; 953 if (DBG) log("Reverting fwd #: " + i + ": " + fi.toString()); 954 // Only revert the settings for which the update succeeded. 955 AsyncResult result = results.get(fi.reason); 956 if (result != null && result.exception == null) { 957 mExpectedChangeResultReasons.add(fi.reason); 958 CallForwardInfoUtil.setCallForwardingOption(mPhone, fi, 959 mRevertOptionComplete.obtainMessage( 960 EVENT_FORWARDING_CHANGED, i, 0)); 961 } 962 } 963 } 964 } 965 } else { 966 if (DBG) log("No need to revert"); 967 onRevertDone(); 968 } 969 } 970 971 972 //********************************************************************************************* 973 // Voicemail Handler Helpers 974 //********************************************************************************************* 975 976 /** 977 * Updates the look of the VM preference widgets based on current VM provider settings. 978 * Note that the provider name is loaded fxrorm the found activity via loadLabel in 979 * {@link VoicemailProviderListPreference#initVoiceMailProviders()} in order for it to be 980 * localizable. 981 */ 982 private void updateVMPreferenceWidgets(String currentProviderSetting) { 983 final String key = currentProviderSetting; 984 final VoicemailProviderListPreference.VoicemailProvider provider = 985 mVoicemailProviders.getVoicemailProvider(key); 986 987 /* This is the case when we are coming up on a freshly wiped phone and there is no 988 persisted value for the list preference mVoicemailProviders. 989 In this case we want to show the UI asking the user to select a voicemail provider as 990 opposed to silently falling back to default one. */ 991 if (provider == null) { 992 if (DBG) log("updateVMPreferenceWidget: key: " + key + " -> null."); 993 994 mVoicemailProviders.setSummary(getString(R.string.sum_voicemail_choose_provider)); 995 mVoicemailSettings.setEnabled(false); 996 mVoicemailSettings.setIntent(null); 997 } else { 998 if (DBG) log("updateVMPreferenceWidget: key: " + key + " -> " + provider.toString()); 999 1000 final String providerName = provider.name; 1001 mVoicemailProviders.setSummary(providerName); 1002 mVoicemailSettings.setEnabled(true); 1003 mVoicemailSettings.setIntent(provider.intent); 1004 } 1005 } 1006 1007 /** 1008 * Update the voicemail number from what we've recorded on the sim. 1009 */ 1010 private void updateVoiceNumberField() { 1011 if (DBG) log("updateVoiceNumberField()"); 1012 1013 mOldVmNumber = mPhone.getVoiceMailNumber(); 1014 if (TextUtils.isEmpty(mOldVmNumber)) { 1015 mSubMenuVoicemailSettings.setPhoneNumber(""); 1016 mSubMenuVoicemailSettings.setSummary(getString(R.string.voicemail_number_not_set)); 1017 } else { 1018 mSubMenuVoicemailSettings.setPhoneNumber(mOldVmNumber); 1019 mSubMenuVoicemailSettings.setSummary(BidiFormatter.getInstance().unicodeWrap( 1020 mOldVmNumber, TextDirectionHeuristics.LTR)); 1021 } 1022 } 1023 1024 private void handleSetVmOrFwdMessage() { 1025 if (DBG) log("handleSetVMMessage: set VM request complete"); 1026 1027 if (!isFwdChangeSuccess()) { 1028 handleVmOrFwdSetError(VoicemailDialogUtil.FWD_SET_RESPONSE_ERROR_DIALOG); 1029 } else if (!isVmChangeSuccess()) { 1030 handleVmOrFwdSetError(VoicemailDialogUtil.VM_RESPONSE_ERROR_DIALOG); 1031 } else { 1032 handleVmAndFwdSetSuccess(VoicemailDialogUtil.VM_CONFIRM_DIALOG); 1033 } 1034 } 1035 1036 /** 1037 * Called when Voicemail Provider or its forwarding settings failed. Rolls back partly made 1038 * changes to those settings and show "failure" dialog. 1039 * 1040 * @param dialogId ID of the dialog to show for the specific error case. Either 1041 * {@link #FWD_SET_RESPONSE_ERROR_DIALOG} or {@link #VM_RESPONSE_ERROR_DIALOG} 1042 */ 1043 private void handleVmOrFwdSetError(int dialogId) { 1044 if (mChangingVMorFwdDueToProviderChange) { 1045 mVMOrFwdSetError = dialogId; 1046 mChangingVMorFwdDueToProviderChange = false; 1047 switchToPreviousVoicemailProvider(); 1048 return; 1049 } 1050 mChangingVMorFwdDueToProviderChange = false; 1051 showDialogIfForeground(dialogId); 1052 updateVoiceNumberField(); 1053 } 1054 1055 /** 1056 * Called when Voicemail Provider and its forwarding settings were successfully finished. 1057 * This updates a bunch of variables and show "success" dialog. 1058 */ 1059 private void handleVmAndFwdSetSuccess(int dialogId) { 1060 if (DBG) log("handleVmAndFwdSetSuccess: key is " + mVoicemailProviders.getKey()); 1061 1062 mPreviousVMProviderKey = mVoicemailProviders.getKey(); 1063 mChangingVMorFwdDueToProviderChange = false; 1064 showDialogIfForeground(dialogId); 1065 updateVoiceNumberField(); 1066 } 1067 1068 private void onRevertDone() { 1069 if (DBG) log("onRevertDone: Changing provider key back to " + mPreviousVMProviderKey); 1070 1071 updateVMPreferenceWidgets(mPreviousVMProviderKey); 1072 updateVoiceNumberField(); 1073 if (mVMOrFwdSetError != 0) { 1074 showDialogIfForeground(mVMOrFwdSetError); 1075 mVMOrFwdSetError = 0; 1076 } 1077 } 1078 1079 1080 //********************************************************************************************* 1081 // Voicemail State Helpers 1082 //********************************************************************************************* 1083 1084 /** 1085 * Return true if there is a change result for every reason for which we expect a result. 1086 */ 1087 private boolean isForwardingCompleted() { 1088 if (mForwardingChangeResults == null) { 1089 return true; 1090 } 1091 1092 for (Integer reason : mExpectedChangeResultReasons) { 1093 if (mForwardingChangeResults.get(reason) == null) { 1094 return false; 1095 } 1096 } 1097 1098 return true; 1099 } 1100 1101 private boolean isFwdChangeSuccess() { 1102 if (mForwardingChangeResults == null) { 1103 return true; 1104 } 1105 1106 for (AsyncResult result : mForwardingChangeResults.values()) { 1107 Throwable exception = result.exception; 1108 if (exception != null) { 1109 String msg = exception.getMessage(); 1110 msg = (msg != null) ? msg : ""; 1111 Log.w(LOG_TAG, "Failed to change forwarding setting. Reason: " + msg); 1112 return false; 1113 } 1114 } 1115 return true; 1116 } 1117 1118 private boolean isVmChangeSuccess() { 1119 if (mVoicemailChangeResult.exception != null) { 1120 String msg = mVoicemailChangeResult.exception.getMessage(); 1121 msg = (msg != null) ? msg : ""; 1122 Log.w(LOG_TAG, "Failed to change voicemail. Reason: " + msg); 1123 return false; 1124 } 1125 return true; 1126 } 1127 1128 private static void log(String msg) { 1129 Log.d(LOG_TAG, msg); 1130 } 1131} 1132