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