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