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