1848e54f850ee183dc85bd1c97b1f7285f98fab0
[android/platform/packages/apps/Phone.git] / src / com / android / phone / CallFeaturesSetting.java
1 /*
2  * Copyright (C) 2008 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 package com.android.phone;
18
19 import android.app.ActionBar;
20 import android.app.Activity;
21 import android.app.AlertDialog;
22 import android.app.Dialog;
23 import android.app.ProgressDialog;
24 import android.content.ContentResolver;
25 import android.content.Context;
26 import android.content.DialogInterface;
27 import android.content.Intent;
28 import android.content.SharedPreferences;
29 import android.content.SharedPreferences.Editor;
30 import android.content.pm.ActivityInfo;
31 import android.content.pm.PackageManager;
32 import android.content.pm.ResolveInfo;
33 import android.database.Cursor;
34 import android.database.sqlite.SQLiteException;
35 import android.media.AudioManager;
36 import android.media.RingtoneManager;
37 import android.net.Uri;
38 import android.net.sip.SipManager;
39 import android.os.AsyncResult;
40 import android.os.Bundle;
41 import android.os.Handler;
42 import android.os.Message;
43 import android.os.UserHandle;
44 import android.os.Vibrator;
45 import android.preference.CheckBoxPreference;
46 import android.preference.ListPreference;
47 import android.preference.Preference;
48 import android.preference.PreferenceActivity;
49 import android.preference.PreferenceGroup;
50 import android.preference.PreferenceManager;
51 import android.preference.PreferenceScreen;
52 import android.provider.ContactsContract.CommonDataKinds;
53 import android.provider.MediaStore;
54 import android.provider.Settings;
55 import android.provider.Settings.SettingNotFoundException;
56 import android.telephony.PhoneNumberUtils;
57 import android.text.TextUtils;
58 import android.util.Log;
59 import android.view.MenuItem;
60 import android.view.WindowManager;
61 import android.widget.ListAdapter;
62
63 import com.android.internal.telephony.CallForwardInfo;
64 import com.android.internal.telephony.CommandsInterface;
65 import com.android.internal.telephony.Phone;
66 import com.android.internal.telephony.PhoneConstants;
67 import com.android.internal.telephony.cdma.TtyIntent;
68 import com.android.phone.sip.SipSharedPreferences;
69
70 import java.util.Collection;
71 import java.util.HashMap;
72 import java.util.HashSet;
73 import java.util.Iterator;
74 import java.util.List;
75 import java.util.Map;
76
77 /**
78  * Top level "Call settings" UI; see res/xml/call_feature_setting.xml
79  *
80  * This preference screen is the root of the "Call settings" hierarchy
81  * available from the Phone app; the settings here let you control various
82  * features related to phone calls (including voicemail settings, SIP
83  * settings, the "Respond via SMS" feature, and others.)  It's used only
84  * on voice-capable phone devices.
85  *
86  * Note that this activity is part of the package com.android.phone, even
87  * though you reach it from the "Phone" app (i.e. DialtactsActivity) which
88  * is from the package com.android.contacts.
89  *
90  * For the "Mobile network settings" screen under the main Settings app,
91  * See {@link MobileNetworkSettings}.
92  *
93  * @see com.android.phone.MobileNetworkSettings
94  */
95 public class CallFeaturesSetting extends PreferenceActivity
96         implements DialogInterface.OnClickListener,
97         Preference.OnPreferenceChangeListener,
98         EditPhoneNumberPreference.OnDialogClosedListener,
99         EditPhoneNumberPreference.GetDefaultNumberListener{
100     private static final String LOG_TAG = "CallFeaturesSetting";
101     private static final boolean DBG = (PhoneGlobals.DBG_LEVEL >= 2);
102
103     /**
104      * Intent action to bring up Voicemail Provider settings.
105      *
106      * @see #IGNORE_PROVIDER_EXTRA
107      */
108     public static final String ACTION_ADD_VOICEMAIL =
109             "com.android.phone.CallFeaturesSetting.ADD_VOICEMAIL";
110     // intent action sent by this activity to a voice mail provider
111     // to trigger its configuration UI
112     public static final String ACTION_CONFIGURE_VOICEMAIL =
113             "com.android.phone.CallFeaturesSetting.CONFIGURE_VOICEMAIL";
114     // Extra put in the return from VM provider config containing voicemail number to set
115     public static final String VM_NUMBER_EXTRA = "com.android.phone.VoicemailNumber";
116     // Extra put in the return from VM provider config containing call forwarding number to set
117     public static final String FWD_NUMBER_EXTRA = "com.android.phone.ForwardingNumber";
118     // Extra put in the return from VM provider config containing call forwarding number to set
119     public static final String FWD_NUMBER_TIME_EXTRA = "com.android.phone.ForwardingNumberTime";
120     // If the VM provider returns non null value in this extra we will force the user to
121     // choose another VM provider
122     public static final String SIGNOUT_EXTRA = "com.android.phone.Signout";
123     //Information about logical "up" Activity
124     private static final String UP_ACTIVITY_PACKAGE = "com.android.dialer";
125     private static final String UP_ACTIVITY_CLASS =
126             "com.android.dialer.DialtactsActivity";
127
128     // Used to tell the saving logic to leave forwarding number as is
129     public static final CallForwardInfo[] FWD_SETTINGS_DONT_TOUCH = null;
130     // Suffix appended to provider key for storing vm number
131     public static final String VM_NUMBER_TAG = "#VMNumber";
132     // Suffix appended to provider key for storing forwarding settings
133     public static final String FWD_SETTINGS_TAG = "#FWDSettings";
134     // Suffix appended to forward settings key for storing length of settings array
135     public static final String FWD_SETTINGS_LENGTH_TAG = "#Length";
136     // Suffix appended to forward settings key for storing an individual setting
137     public static final String FWD_SETTING_TAG = "#Setting";
138     // Suffixes appended to forward setting key for storing an individual setting properties
139     public static final String FWD_SETTING_STATUS = "#Status";
140     public static final String FWD_SETTING_REASON = "#Reason";
141     public static final String FWD_SETTING_NUMBER = "#Number";
142     public static final String FWD_SETTING_TIME = "#Time";
143
144     // Key identifying the default vocie mail provider
145     public static final String DEFAULT_VM_PROVIDER_KEY = "";
146
147     /**
148      * String Extra put into ACTION_ADD_VOICEMAIL call to indicate which provider should be hidden
149      * in the list of providers presented to the user. This allows a provider which is being
150      * disabled (e.g. GV user logging out) to force the user to pick some other provider.
151      */
152     public static final String IGNORE_PROVIDER_EXTRA = "com.android.phone.ProviderToIgnore";
153
154     // string constants
155     private static final String NUM_PROJECTION[] = {CommonDataKinds.Phone.NUMBER};
156
157     // String keys for preference lookup
158     // TODO: Naming these "BUTTON_*" is confusing since they're not actually buttons(!)
159     private static final String BUTTON_VOICEMAIL_KEY = "button_voicemail_key";
160     private static final String BUTTON_VOICEMAIL_PROVIDER_KEY = "button_voicemail_provider_key";
161     private static final String BUTTON_VOICEMAIL_SETTING_KEY = "button_voicemail_setting_key";
162     // New preference key for voicemail notification vibration
163     /* package */ static final String BUTTON_VOICEMAIL_NOTIFICATION_VIBRATE_KEY =
164             "button_voicemail_notification_vibrate_key";
165     // Old preference key for voicemail notification vibration. Used for migration to the new
166     // preference key only.
167     /* package */ static final String BUTTON_VOICEMAIL_NOTIFICATION_VIBRATE_WHEN_KEY =
168             "button_voicemail_notification_vibrate_when_key";
169     /* package */ static final String BUTTON_VOICEMAIL_NOTIFICATION_RINGTONE_KEY =
170             "button_voicemail_notification_ringtone_key";
171     private static final String BUTTON_FDN_KEY   = "button_fdn_key";
172     private static final String BUTTON_RESPOND_VIA_SMS_KEY   = "button_respond_via_sms_key";
173
174     private static final String BUTTON_RINGTONE_KEY    = "button_ringtone_key";
175     private static final String BUTTON_VIBRATE_ON_RING = "button_vibrate_on_ring";
176     private static final String BUTTON_PLAY_DTMF_TONE  = "button_play_dtmf_tone";
177     private static final String BUTTON_DTMF_KEY        = "button_dtmf_settings";
178     private static final String BUTTON_RETRY_KEY       = "button_auto_retry_key";
179     private static final String BUTTON_TTY_KEY         = "button_tty_mode_key";
180     private static final String BUTTON_HAC_KEY         = "button_hac_key";
181     private static final String BUTTON_DIALPAD_AUTOCOMPLETE = "button_dialpad_autocomplete";
182
183     private static final String BUTTON_GSM_UMTS_OPTIONS = "button_gsm_more_expand_key";
184     private static final String BUTTON_CDMA_OPTIONS = "button_cdma_more_expand_key";
185
186     private static final String VM_NUMBERS_SHARED_PREFERENCES_NAME = "vm_numbers";
187
188     private static final String BUTTON_SIP_CALL_OPTIONS =
189             "sip_call_options_key";
190     private static final String BUTTON_SIP_CALL_OPTIONS_WIFI_ONLY =
191             "sip_call_options_wifi_only_key";
192     private static final String SIP_SETTINGS_CATEGORY_KEY =
193             "sip_settings_category_key";
194
195     private Intent mContactListIntent;
196
197     /** Event for Async voicemail change call */
198     private static final int EVENT_VOICEMAIL_CHANGED        = 500;
199     private static final int EVENT_FORWARDING_CHANGED       = 501;
200     private static final int EVENT_FORWARDING_GET_COMPLETED = 502;
201
202     private static final int MSG_UPDATE_RINGTONE_SUMMARY = 1;
203     private static final int MSG_UPDATE_VOICEMAIL_RINGTONE_SUMMARY = 2;
204
205     // preferred TTY mode
206     // Phone.TTY_MODE_xxx
207     static final int preferredTtyMode = Phone.TTY_MODE_OFF;
208
209     public static final String HAC_KEY = "HACSetting";
210     public static final String HAC_VAL_ON = "ON";
211     public static final String HAC_VAL_OFF = "OFF";
212
213     /** Handle to voicemail pref */
214     private static final int VOICEMAIL_PREF_ID = 1;
215     private static final int VOICEMAIL_PROVIDER_CFG_ID = 2;
216
217     private Phone mPhone;
218
219     private AudioManager mAudioManager;
220     private SipManager mSipManager;
221
222     private static final int VM_NOCHANGE_ERROR = 400;
223     private static final int VM_RESPONSE_ERROR = 500;
224     private static final int FW_SET_RESPONSE_ERROR = 501;
225     private static final int FW_GET_RESPONSE_ERROR = 502;
226
227
228     // dialog identifiers for voicemail
229     private static final int VOICEMAIL_DIALOG_CONFIRM = 600;
230     private static final int VOICEMAIL_FWD_SAVING_DIALOG = 601;
231     private static final int VOICEMAIL_FWD_READING_DIALOG = 602;
232     private static final int VOICEMAIL_REVERTING_DIALOG = 603;
233
234     // status message sent back from handlers
235     private static final int MSG_OK = 100;
236
237     // special statuses for voicemail controls.
238     private static final int MSG_VM_EXCEPTION = 400;
239     private static final int MSG_FW_SET_EXCEPTION = 401;
240     private static final int MSG_FW_GET_EXCEPTION = 402;
241     private static final int MSG_VM_OK = 600;
242     private static final int MSG_VM_NOCHANGE = 700;
243
244     // voicemail notification vibration string constants
245     private static final String VOICEMAIL_VIBRATION_ALWAYS = "always";
246     private static final String VOICEMAIL_VIBRATION_NEVER = "never";
247
248     private EditPhoneNumberPreference mSubMenuVoicemailSettings;
249
250     private Runnable mRingtoneLookupRunnable;
251     private final Handler mRingtoneLookupComplete = new Handler() {
252         @Override
253         public void handleMessage(Message msg) {
254             switch (msg.what) {
255                 case MSG_UPDATE_RINGTONE_SUMMARY:
256                     mRingtonePreference.setSummary((CharSequence) msg.obj);
257                     break;
258                 case MSG_UPDATE_VOICEMAIL_RINGTONE_SUMMARY:
259                     mVoicemailNotificationRingtone.setSummary((CharSequence) msg.obj);
260                     break;
261             }
262         }
263     };
264
265     private Preference mRingtonePreference;
266     private CheckBoxPreference mVibrateWhenRinging;
267     /** Whether dialpad plays DTMF tone or not. */
268     private CheckBoxPreference mPlayDtmfTone;
269     private CheckBoxPreference mDialpadAutocomplete;
270     private CheckBoxPreference mButtonAutoRetry;
271     private CheckBoxPreference mButtonHAC;
272     private ListPreference mButtonDTMF;
273     private ListPreference mButtonTTY;
274     private ListPreference mButtonSipCallOptions;
275     private ListPreference mVoicemailProviders;
276     private PreferenceScreen mVoicemailSettings;
277     private Preference mVoicemailNotificationRingtone;
278     private CheckBoxPreference mVoicemailNotificationVibrate;
279     private SipSharedPreferences mSipSharedPreferences;
280
281     private class VoiceMailProvider {
282         public VoiceMailProvider(String name, Intent intent) {
283             this.name = name;
284             this.intent = intent;
285         }
286         public String name;
287         public Intent intent;
288     }
289
290     /**
291      * Forwarding settings we are going to save.
292      */
293     private static final int [] FORWARDING_SETTINGS_REASONS = new int[] {
294         CommandsInterface.CF_REASON_UNCONDITIONAL,
295         CommandsInterface.CF_REASON_BUSY,
296         CommandsInterface.CF_REASON_NO_REPLY,
297         CommandsInterface.CF_REASON_NOT_REACHABLE
298     };
299
300     private class VoiceMailProviderSettings {
301         /**
302          * Constructs settings object, setting all conditional forwarding to the specified number
303          */
304         public VoiceMailProviderSettings(String voicemailNumber, String forwardingNumber,
305                 int timeSeconds) {
306             this.voicemailNumber = voicemailNumber;
307             if (forwardingNumber == null || forwardingNumber.length() == 0) {
308                 this.forwardingSettings = FWD_SETTINGS_DONT_TOUCH;
309             } else {
310                 this.forwardingSettings = new CallForwardInfo[FORWARDING_SETTINGS_REASONS.length];
311                 for (int i = 0; i < this.forwardingSettings.length; i++) {
312                     CallForwardInfo fi = new CallForwardInfo();
313                     this.forwardingSettings[i] = fi;
314                     fi.reason = FORWARDING_SETTINGS_REASONS[i];
315                     fi.status = (fi.reason == CommandsInterface.CF_REASON_UNCONDITIONAL) ? 0 : 1;
316                     fi.serviceClass = CommandsInterface.SERVICE_CLASS_VOICE;
317                     fi.toa = PhoneNumberUtils.TOA_International;
318                     fi.number = forwardingNumber;
319                     fi.timeSeconds = timeSeconds;
320                 }
321             }
322         }
323
324         public VoiceMailProviderSettings(String voicemailNumber, CallForwardInfo[] infos) {
325             this.voicemailNumber = voicemailNumber;
326             this.forwardingSettings = infos;
327         }
328
329         @Override
330         public boolean equals(Object o) {
331             if (o == null) return false;
332             if (!(o instanceof VoiceMailProviderSettings)) return false;
333             final VoiceMailProviderSettings v = (VoiceMailProviderSettings)o;
334
335             return ((this.voicemailNumber == null &&
336                         v.voicemailNumber == null) ||
337                     this.voicemailNumber != null &&
338                         this.voicemailNumber.equals(v.voicemailNumber))
339                     &&
340                     forwardingSettingsEqual(this.forwardingSettings,
341                             v.forwardingSettings);
342         }
343
344         private boolean forwardingSettingsEqual(CallForwardInfo[] infos1,
345                 CallForwardInfo[] infos2) {
346             if (infos1 == infos2) return true;
347             if (infos1 == null || infos2 == null) return false;
348             if (infos1.length != infos2.length) return false;
349             for (int i = 0; i < infos1.length; i++) {
350                 CallForwardInfo i1 = infos1[i];
351                 CallForwardInfo i2 = infos2[i];
352                 if (i1.status != i2.status ||
353                     i1.reason != i2.reason ||
354                     i1.serviceClass != i2.serviceClass ||
355                     i1.toa != i2.toa ||
356                     i1.number != i2.number ||
357                     i1.timeSeconds != i2.timeSeconds) {
358                     return false;
359                 }
360             }
361             return true;
362         }
363
364         @Override
365         public String toString() {
366             return voicemailNumber + ((forwardingSettings != null ) ? (", " +
367                     forwardingSettings.toString()) : "");
368         }
369
370         public String voicemailNumber;
371         public CallForwardInfo[] forwardingSettings;
372     }
373
374     private SharedPreferences mPerProviderSavedVMNumbers;
375
376     /**
377      * Results of reading forwarding settings
378      */
379     private CallForwardInfo[] mForwardingReadResults = null;
380
381     /**
382      * Result of forwarding number change.
383      * Keys are reasons (eg. unconditional forwarding).
384      */
385     private Map<Integer, AsyncResult> mForwardingChangeResults = null;
386
387     /**
388      * Expected CF read result types.
389      * This set keeps track of the CF types for which we've issued change
390      * commands so we can tell when we've received all of the responses.
391      */
392     private Collection<Integer> mExpectedChangeResultReasons = null;
393
394     /**
395      * Result of vm number change
396      */
397     private AsyncResult mVoicemailChangeResult = null;
398
399     /**
400      * Previous VM provider setting so we can return to it in case of failure.
401      */
402     private String mPreviousVMProviderKey = null;
403
404     /**
405      * Id of the dialog being currently shown.
406      */
407     private int mCurrentDialogId = 0;
408
409     /**
410      * Flag indicating that we are invoking settings for the voicemail provider programmatically
411      * due to vm provider change.
412      */
413     private boolean mVMProviderSettingsForced = false;
414
415     /**
416      * Flag indicating that we are making changes to vm or fwd numbers
417      * due to vm provider change.
418      */
419     private boolean mChangingVMorFwdDueToProviderChange = false;
420
421     /**
422      * True if we are in the process of vm & fwd number change and vm has already been changed.
423      * This is used to decide what to do in case of rollback.
424      */
425     private boolean mVMChangeCompletedSuccessfully = false;
426
427     /**
428      * True if we had full or partial failure setting forwarding numbers and so need to roll them
429      * back.
430      */
431     private boolean mFwdChangesRequireRollback = false;
432
433     /**
434      * Id of error msg to display to user once we are done reverting the VM provider to the previous
435      * one.
436      */
437     private int mVMOrFwdSetError = 0;
438
439     /**
440      * Data about discovered voice mail settings providers.
441      * Is populated by querying which activities can handle ACTION_CONFIGURE_VOICEMAIL.
442      * They key in this map is package name + activity name.
443      * We always add an entry for the default provider with a key of empty
444      * string and intent value of null.
445      * @see #initVoiceMailProviders()
446      */
447     private final Map<String, VoiceMailProvider> mVMProvidersData =
448             new HashMap<String, VoiceMailProvider>();
449
450     /** string to hold old voicemail number as it is being updated. */
451     private String mOldVmNumber;
452
453     // New call forwarding settings and vm number we will be setting
454     // Need to save these since before we get to saving we need to asynchronously
455     // query the existing forwarding settings.
456     private CallForwardInfo[] mNewFwdSettings;
457     private String mNewVMNumber;
458
459     private boolean mForeground;
460
461     @Override
462     public void onPause() {
463         super.onPause();
464         mForeground = false;
465     }
466
467     /**
468      * We have to pull current settings from the network for all kinds of
469      * voicemail providers so we can tell whether we have to update them,
470      * so use this bit to keep track of whether we're reading settings for the
471      * default provider and should therefore save them out when done.
472      */
473     private boolean mReadingSettingsForDefaultProvider = false;
474
475     /*
476      * Click Listeners, handle click based on objects attached to UI.
477      */
478
479     // Click listener for all toggle events
480     @Override
481     public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
482         if (preference == mSubMenuVoicemailSettings) {
483             return true;
484         } else if (preference == mPlayDtmfTone) {
485             Settings.System.putInt(getContentResolver(), Settings.System.DTMF_TONE_WHEN_DIALING,
486                     mPlayDtmfTone.isChecked() ? 1 : 0);
487         } else if (preference == mDialpadAutocomplete) {
488             Settings.Secure.putInt(getContentResolver(), Settings.Secure.DIALPAD_AUTOCOMPLETE,
489                     mDialpadAutocomplete.isChecked() ? 1 : 0);
490         } else if (preference == mButtonDTMF) {
491             return true;
492         } else if (preference == mButtonTTY) {
493             return true;
494         } else if (preference == mButtonAutoRetry) {
495             android.provider.Settings.Global.putInt(mPhone.getContext().getContentResolver(),
496                     android.provider.Settings.Global.CALL_AUTO_RETRY,
497                     mButtonAutoRetry.isChecked() ? 1 : 0);
498             return true;
499         } else if (preference == mButtonHAC) {
500             int hac = mButtonHAC.isChecked() ? 1 : 0;
501             // Update HAC value in Settings database
502             Settings.System.putInt(mPhone.getContext().getContentResolver(),
503                     Settings.System.HEARING_AID, hac);
504
505             // Update HAC Value in AudioManager
506             mAudioManager.setParameter(HAC_KEY, hac != 0 ? HAC_VAL_ON : HAC_VAL_OFF);
507             return true;
508         } else if (preference == mVoicemailSettings) {
509             if (DBG) log("onPreferenceTreeClick: Voicemail Settings Preference is clicked.");
510             if (preference.getIntent() != null) {
511                 if (DBG) {
512                     log("onPreferenceTreeClick: Invoking cfg intent "
513                             + preference.getIntent().getPackage());
514                 }
515
516                 // onActivityResult() will be responsible for resetting some of variables.
517                 this.startActivityForResult(preference.getIntent(), VOICEMAIL_PROVIDER_CFG_ID);
518                 return true;
519             } else {
520                 if (DBG) {
521                     log("onPreferenceTreeClick:"
522                             + " No Intent is available. Use default behavior defined in xml.");
523                 }
524
525                 // There's no onActivityResult(), so we need to take care of some of variables
526                 // which should be reset here.
527                 mPreviousVMProviderKey = DEFAULT_VM_PROVIDER_KEY;
528                 mVMProviderSettingsForced = false;
529
530                 // This should let the preference use default behavior in the xml.
531                 return false;
532             }
533         }
534         return false;
535     }
536
537     /**
538      * Implemented to support onPreferenceChangeListener to look for preference
539      * changes.
540      *
541      * @param preference is the preference to be changed
542      * @param objValue should be the value of the selection, NOT its localized
543      * display value.
544      */
545     @Override
546     public boolean onPreferenceChange(Preference preference, Object objValue) {
547         if (DBG) {
548             log("onPreferenceChange(). preferenece: \"" + preference + "\""
549                     + ", value: \"" + objValue + "\"");
550         }
551         if (preference == mVibrateWhenRinging) {
552             boolean doVibrate = (Boolean) objValue;
553             Settings.System.putInt(mPhone.getContext().getContentResolver(),
554                     Settings.System.VIBRATE_WHEN_RINGING, doVibrate ? 1 : 0);
555         } else if (preference == mButtonDTMF) {
556             int index = mButtonDTMF.findIndexOfValue((String) objValue);
557             Settings.System.putInt(mPhone.getContext().getContentResolver(),
558                     Settings.System.DTMF_TONE_TYPE_WHEN_DIALING, index);
559         } else if (preference == mButtonTTY) {
560             handleTTYChange(preference, objValue);
561         } else if (preference == mVoicemailProviders) {
562             final String newProviderKey = (String) objValue;
563             if (DBG) {
564                 log("Voicemail Provider changes from \"" + mPreviousVMProviderKey
565                     + "\" to \"" + newProviderKey + "\".");
566             }
567             // If previous provider key and the new one is same, we don't need to handle it.
568             if (mPreviousVMProviderKey.equals(newProviderKey)) {
569                 if (DBG) log("No change is made toward VM provider setting.");
570                 return true;
571             }
572             updateVMPreferenceWidgets(newProviderKey);
573
574             final VoiceMailProviderSettings newProviderSettings =
575                     loadSettingsForVoiceMailProvider(newProviderKey);
576
577             // If the user switches to a voice mail provider and we have a
578             // numbers stored for it we will automatically change the
579             // phone's
580             // voice mail and forwarding number to the stored ones.
581             // Otherwise we will bring up provider's configuration UI.
582
583             if (newProviderSettings == null) {
584                 // Force the user into a configuration of the chosen provider
585                 Log.w(LOG_TAG, "Saved preferences not found - invoking config");
586                 mVMProviderSettingsForced = true;
587                 simulatePreferenceClick(mVoicemailSettings);
588             } else {
589                 if (DBG) log("Saved preferences found - switching to them");
590                 // Set this flag so if we get a failure we revert to previous provider
591                 mChangingVMorFwdDueToProviderChange = true;
592                 saveVoiceMailAndForwardingNumber(newProviderKey, newProviderSettings);
593             }
594         } else if (preference == mButtonSipCallOptions) {
595             handleSipCallOptionsChange(objValue);
596         }
597         // always let the preference setting proceed.
598         return true;
599     }
600
601     @Override
602     public void onDialogClosed(EditPhoneNumberPreference preference, int buttonClicked) {
603         if (DBG) log("onPreferenceClick: request preference click on dialog close: " +
604                 buttonClicked);
605         if (buttonClicked == DialogInterface.BUTTON_NEGATIVE) {
606             return;
607         }
608
609         if (preference == mSubMenuVoicemailSettings) {
610             handleVMBtnClickRequest();
611         }
612     }
613
614     /**
615      * Implemented for EditPhoneNumberPreference.GetDefaultNumberListener.
616      * This method set the default values for the various
617      * EditPhoneNumberPreference dialogs.
618      */
619     @Override
620     public String onGetDefaultNumber(EditPhoneNumberPreference preference) {
621         if (preference == mSubMenuVoicemailSettings) {
622             // update the voicemail number field, which takes care of the
623             // mSubMenuVoicemailSettings itself, so we should return null.
624             if (DBG) log("updating default for voicemail dialog");
625             updateVoiceNumberField();
626             return null;
627         }
628
629         String vmDisplay = mPhone.getVoiceMailNumber();
630         if (TextUtils.isEmpty(vmDisplay)) {
631             // if there is no voicemail number, we just return null to
632             // indicate no contribution.
633             return null;
634         }
635
636         // Return the voicemail number prepended with "VM: "
637         if (DBG) log("updating default for call forwarding dialogs");
638         return getString(R.string.voicemail_abbreviated) + " " + vmDisplay;
639     }
640
641
642     // override the startsubactivity call to make changes in state consistent.
643     @Override
644     public void startActivityForResult(Intent intent, int requestCode) {
645         if (requestCode == -1) {
646             // this is an intent requested from the preference framework.
647             super.startActivityForResult(intent, requestCode);
648             return;
649         }
650
651         if (DBG) log("startSubActivity: starting requested subactivity");
652         super.startActivityForResult(intent, requestCode);
653     }
654
655     private void switchToPreviousVoicemailProvider() {
656         if (DBG) log("switchToPreviousVoicemailProvider " + mPreviousVMProviderKey);
657         if (mPreviousVMProviderKey != null) {
658             if (mVMChangeCompletedSuccessfully || mFwdChangesRequireRollback) {
659                 // we have to revert with carrier
660                 if (DBG) {
661                     log("Needs to rollback."
662                             + " mVMChangeCompletedSuccessfully=" + mVMChangeCompletedSuccessfully
663                             + ", mFwdChangesRequireRollback=" + mFwdChangesRequireRollback);
664                 }
665
666                 showDialogIfForeground(VOICEMAIL_REVERTING_DIALOG);
667                 final VoiceMailProviderSettings prevSettings =
668                         loadSettingsForVoiceMailProvider(mPreviousVMProviderKey);
669                 if (prevSettings == null) {
670                     // prevSettings never becomes null since it should be already loaded!
671                     Log.e(LOG_TAG, "VoiceMailProviderSettings for the key \""
672                             + mPreviousVMProviderKey + "\" becomes null, which is unexpected.");
673                     if (DBG) {
674                         Log.e(LOG_TAG,
675                                 "mVMChangeCompletedSuccessfully: " + mVMChangeCompletedSuccessfully
676                                 + ", mFwdChangesRequireRollback: " + mFwdChangesRequireRollback);
677                     }
678                 }
679                 if (mVMChangeCompletedSuccessfully) {
680                     mNewVMNumber = prevSettings.voicemailNumber;
681                     Log.i(LOG_TAG, "VM change is already completed successfully."
682                             + "Have to revert VM back to " + mNewVMNumber + " again.");
683                     mPhone.setVoiceMailNumber(
684                             mPhone.getVoiceMailAlphaTag().toString(),
685                             mNewVMNumber,
686                             Message.obtain(mRevertOptionComplete, EVENT_VOICEMAIL_CHANGED));
687                 }
688                 if (mFwdChangesRequireRollback) {
689                     Log.i(LOG_TAG, "Requested to rollback Fwd changes.");
690                     final CallForwardInfo[] prevFwdSettings =
691                         prevSettings.forwardingSettings;
692                     if (prevFwdSettings != null) {
693                         Map<Integer, AsyncResult> results =
694                             mForwardingChangeResults;
695                         resetForwardingChangeState();
696                         for (int i = 0; i < prevFwdSettings.length; i++) {
697                             CallForwardInfo fi = prevFwdSettings[i];
698                             if (DBG) log("Reverting fwd #: " + i + ": " + fi.toString());
699                             // Only revert the settings for which the update
700                             // succeeded
701                             AsyncResult result = results.get(fi.reason);
702                             if (result != null && result.exception == null) {
703                                 mExpectedChangeResultReasons.add(fi.reason);
704                                 mPhone.setCallForwardingOption(
705                                         (fi.status == 1 ?
706                                                 CommandsInterface.CF_ACTION_REGISTRATION :
707                                                 CommandsInterface.CF_ACTION_DISABLE),
708                                         fi.reason,
709                                         fi.number,
710                                         fi.timeSeconds,
711                                         mRevertOptionComplete.obtainMessage(
712                                                 EVENT_FORWARDING_CHANGED, i, 0));
713                             }
714                         }
715                     }
716                 }
717             } else {
718                 if (DBG) log("No need to revert");
719                 onRevertDone();
720             }
721         }
722     }
723
724     private void onRevertDone() {
725         if (DBG) log("Flipping provider key back to " + mPreviousVMProviderKey);
726         mVoicemailProviders.setValue(mPreviousVMProviderKey);
727         updateVMPreferenceWidgets(mPreviousVMProviderKey);
728         updateVoiceNumberField();
729         if (mVMOrFwdSetError != 0) {
730             showVMDialog(mVMOrFwdSetError);
731             mVMOrFwdSetError = 0;
732         }
733     }
734
735     @Override
736     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
737         if (DBG) {
738             log("onActivityResult: requestCode: " + requestCode
739                     + ", resultCode: " + resultCode
740                     + ", data: " + data);
741         }
742         // there are cases where the contact picker may end up sending us more than one
743         // request.  We want to ignore the request if we're not in the correct state.
744         if (requestCode == VOICEMAIL_PROVIDER_CFG_ID) {
745             boolean failure = false;
746
747             // No matter how the processing of result goes lets clear the flag
748             if (DBG) log("mVMProviderSettingsForced: " + mVMProviderSettingsForced);
749             final boolean isVMProviderSettingsForced = mVMProviderSettingsForced;
750             mVMProviderSettingsForced = false;
751
752             String vmNum = null;
753             if (resultCode != RESULT_OK) {
754                 if (DBG) log("onActivityResult: vm provider cfg result not OK.");
755                 failure = true;
756             } else {
757                 if (data == null) {
758                     if (DBG) log("onActivityResult: vm provider cfg result has no data");
759                     failure = true;
760                 } else {
761                     if (data.getBooleanExtra(SIGNOUT_EXTRA, false)) {
762                         if (DBG) log("Provider requested signout");
763                         if (isVMProviderSettingsForced) {
764                             if (DBG) log("Going back to previous provider on signout");
765                             switchToPreviousVoicemailProvider();
766                         } else {
767                             final String victim = getCurrentVoicemailProviderKey();
768                             if (DBG) log("Relaunching activity and ignoring " + victim);
769                             Intent i = new Intent(ACTION_ADD_VOICEMAIL);
770                             i.putExtra(IGNORE_PROVIDER_EXTRA, victim);
771                             i.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
772                             this.startActivity(i);
773                         }
774                         return;
775                     }
776                     vmNum = data.getStringExtra(VM_NUMBER_EXTRA);
777                     if (vmNum == null || vmNum.length() == 0) {
778                         if (DBG) log("onActivityResult: vm provider cfg result has no vmnum");
779                         failure = true;
780                     }
781                 }
782             }
783             if (failure) {
784                 if (DBG) log("Failure in return from voicemail provider");
785                 if (isVMProviderSettingsForced) {
786                     switchToPreviousVoicemailProvider();
787                 } else {
788                     if (DBG) log("Not switching back the provider since this is not forced config");
789                 }
790                 return;
791             }
792             mChangingVMorFwdDueToProviderChange = isVMProviderSettingsForced;
793             final String fwdNum = data.getStringExtra(FWD_NUMBER_EXTRA);
794
795             // TODO(iliat): It would be nice to load the current network setting for this and
796             // send it to the provider when it's config is invoked so it can use this as default
797             final int fwdNumTime = data.getIntExtra(FWD_NUMBER_TIME_EXTRA, 20);
798
799             if (DBG) log("onActivityResult: vm provider cfg result " +
800                     (fwdNum != null ? "has" : " does not have") + " forwarding number");
801             saveVoiceMailAndForwardingNumber(getCurrentVoicemailProviderKey(),
802                     new VoiceMailProviderSettings(vmNum, fwdNum, fwdNumTime));
803             return;
804         }
805
806         if (requestCode == VOICEMAIL_PREF_ID) {
807             if (resultCode != RESULT_OK) {
808                 if (DBG) log("onActivityResult: contact picker result not OK.");
809                 return;
810             }
811
812             Cursor cursor = null;
813             try {
814                 cursor = getContentResolver().query(data.getData(),
815                     NUM_PROJECTION, null, null, null);
816                 if ((cursor == null) || (!cursor.moveToFirst())) {
817                     if (DBG) log("onActivityResult: bad contact data, no results found.");
818                     return;
819                 }
820                 mSubMenuVoicemailSettings.onPickActivityResult(cursor.getString(0));
821                 return;
822             } finally {
823                 if (cursor != null) {
824                     cursor.close();
825                 }
826             }
827         }
828
829         super.onActivityResult(requestCode, resultCode, data);
830     }
831
832     // Voicemail button logic
833     private void handleVMBtnClickRequest() {
834         // normally called on the dialog close.
835
836         // Since we're stripping the formatting out on the getPhoneNumber()
837         // call now, we won't need to do so here anymore.
838
839         saveVoiceMailAndForwardingNumber(
840                 getCurrentVoicemailProviderKey(),
841                 new VoiceMailProviderSettings(mSubMenuVoicemailSettings.getPhoneNumber(),
842                         FWD_SETTINGS_DONT_TOUCH));
843     }
844
845
846     /**
847      * Wrapper around showDialog() that will silently do nothing if we're
848      * not in the foreground.
849      *
850      * This is useful here because most of the dialogs we display from
851      * this class are triggered by asynchronous events (like
852      * success/failure messages from the telephony layer) and it's
853      * possible for those events to come in even after the user has gone
854      * to a different screen.
855      */
856     // TODO: this is too brittle: it's still easy to accidentally add new
857     // code here that calls showDialog() directly (which will result in a
858     // WindowManager$BadTokenException if called after the activity has
859     // been stopped.)
860     //
861     // It would be cleaner to do the "if (mForeground)" check in one
862     // central place, maybe by using a single Handler for all asynchronous
863     // events (and have *that* discard events if we're not in the
864     // foreground.)
865     //
866     // Unfortunately it's not that simple, since we sometimes need to do
867     // actual work to handle these events whether or not we're in the
868     // foreground (see the Handler code in mSetOptionComplete for
869     // example.)
870     private void showDialogIfForeground(int id) {
871         if (mForeground) {
872             showDialog(id);
873         }
874     }
875
876     private void dismissDialogSafely(int id) {
877         try {
878             dismissDialog(id);
879         } catch (IllegalArgumentException e) {
880             // This is expected in the case where we were in the background
881             // at the time we would normally have shown the dialog, so we didn't
882             // show it.
883         }
884     }
885
886     private void saveVoiceMailAndForwardingNumber(String key,
887             VoiceMailProviderSettings newSettings) {
888         if (DBG) log("saveVoiceMailAndForwardingNumber: " + newSettings.toString());
889         mNewVMNumber = newSettings.voicemailNumber;
890         // empty vm number == clearing the vm number ?
891         if (mNewVMNumber == null) {
892             mNewVMNumber = "";
893         }
894
895         mNewFwdSettings = newSettings.forwardingSettings;
896         if (DBG) log("newFwdNumber " +
897                 String.valueOf((mNewFwdSettings != null ? mNewFwdSettings.length : 0))
898                 + " settings");
899
900         // No fwd settings on CDMA
901         if (mPhone.getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA) {
902             if (DBG) log("ignoring forwarding setting since this is CDMA phone");
903             mNewFwdSettings = FWD_SETTINGS_DONT_TOUCH;
904         }
905
906         //throw a warning if the vm is the same and we do not touch forwarding.
907         if (mNewVMNumber.equals(mOldVmNumber) && mNewFwdSettings == FWD_SETTINGS_DONT_TOUCH) {
908             showVMDialog(MSG_VM_NOCHANGE);
909             return;
910         }
911
912         maybeSaveSettingsForVoicemailProvider(key, newSettings);
913         mVMChangeCompletedSuccessfully = false;
914         mFwdChangesRequireRollback = false;
915         mVMOrFwdSetError = 0;
916         if (!key.equals(mPreviousVMProviderKey)) {
917             mReadingSettingsForDefaultProvider =
918                     mPreviousVMProviderKey.equals(DEFAULT_VM_PROVIDER_KEY);
919             if (DBG) log("Reading current forwarding settings");
920             mForwardingReadResults = new CallForwardInfo[FORWARDING_SETTINGS_REASONS.length];
921             for (int i = 0; i < FORWARDING_SETTINGS_REASONS.length; i++) {
922                 mForwardingReadResults[i] = null;
923                 mPhone.getCallForwardingOption(FORWARDING_SETTINGS_REASONS[i],
924                         mGetOptionComplete.obtainMessage(EVENT_FORWARDING_GET_COMPLETED, i, 0));
925             }
926             showDialogIfForeground(VOICEMAIL_FWD_READING_DIALOG);
927         } else {
928             saveVoiceMailAndForwardingNumberStage2();
929         }
930     }
931
932     private final Handler mGetOptionComplete = new Handler() {
933         @Override
934         public void handleMessage(Message msg) {
935             AsyncResult result = (AsyncResult) msg.obj;
936             switch (msg.what) {
937                 case EVENT_FORWARDING_GET_COMPLETED:
938                     handleForwardingSettingsReadResult(result, msg.arg1);
939                     break;
940             }
941         }
942     };
943
944     private void handleForwardingSettingsReadResult(AsyncResult ar, int idx) {
945         if (DBG) Log.d(LOG_TAG, "handleForwardingSettingsReadResult: " + idx);
946         Throwable error = null;
947         if (ar.exception != null) {
948             if (DBG) Log.d(LOG_TAG, "FwdRead: ar.exception=" +
949                     ar.exception.getMessage());
950             error = ar.exception;
951         }
952         if (ar.userObj instanceof Throwable) {
953             if (DBG) Log.d(LOG_TAG, "FwdRead: userObj=" +
954                     ((Throwable)ar.userObj).getMessage());
955             error = (Throwable)ar.userObj;
956         }
957
958         // We may have already gotten an error and decided to ignore the other results.
959         if (mForwardingReadResults == null) {
960             if (DBG) Log.d(LOG_TAG, "ignoring fwd reading result: " + idx);
961             return;
962         }
963
964         // In case of error ignore other results, show an error dialog
965         if (error != null) {
966             if (DBG) Log.d(LOG_TAG, "Error discovered for fwd read : " + idx);
967             mForwardingReadResults = null;
968             dismissDialogSafely(VOICEMAIL_FWD_READING_DIALOG);
969             showVMDialog(MSG_FW_GET_EXCEPTION);
970             return;
971         }
972
973         // Get the forwarding info
974         final CallForwardInfo cfInfoArray[] = (CallForwardInfo[]) ar.result;
975         CallForwardInfo fi = null;
976         for (int i = 0 ; i < cfInfoArray.length; i++) {
977             if ((cfInfoArray[i].serviceClass & CommandsInterface.SERVICE_CLASS_VOICE) != 0) {
978                 fi = cfInfoArray[i];
979                 break;
980             }
981         }
982         if (fi == null) {
983
984             // In case we go nothing it means we need this reason disabled
985             // so create a CallForwardInfo for capturing this
986             if (DBG) Log.d(LOG_TAG, "Creating default info for " + idx);
987             fi = new CallForwardInfo();
988             fi.status = 0;
989             fi.reason = FORWARDING_SETTINGS_REASONS[idx];
990             fi.serviceClass = CommandsInterface.SERVICE_CLASS_VOICE;
991         } else {
992             // if there is not a forwarding number, ensure the entry is set to "not active."
993             if (fi.number == null || fi.number.length() == 0) {
994                 fi.status = 0;
995             }
996
997             if (DBG) Log.d(LOG_TAG, "Got  " + fi.toString() + " for " + idx);
998         }
999         mForwardingReadResults[idx] = fi;
1000
1001         // Check if we got all the results already
1002         boolean done = true;
1003         for (int i = 0; i < mForwardingReadResults.length; i++) {
1004             if (mForwardingReadResults[i] == null) {
1005                 done = false;
1006                 break;
1007             }
1008         }
1009         if (done) {
1010             if (DBG) Log.d(LOG_TAG, "Done receiving fwd info");
1011             dismissDialogSafely(VOICEMAIL_FWD_READING_DIALOG);
1012             if (mReadingSettingsForDefaultProvider) {
1013                 maybeSaveSettingsForVoicemailProvider(DEFAULT_VM_PROVIDER_KEY,
1014                         new VoiceMailProviderSettings(this.mOldVmNumber,
1015                                 mForwardingReadResults));
1016                 mReadingSettingsForDefaultProvider = false;
1017             }
1018             saveVoiceMailAndForwardingNumberStage2();
1019         } else {
1020             if (DBG) Log.d(LOG_TAG, "Not done receiving fwd info");
1021         }
1022     }
1023
1024     private CallForwardInfo infoForReason(CallForwardInfo[] infos, int reason) {
1025         CallForwardInfo result = null;
1026         if (null != infos) {
1027             for (CallForwardInfo info : infos) {
1028                 if (info.reason == reason) {
1029                     result = info;
1030                     break;
1031                 }
1032             }
1033         }
1034         return result;
1035     }
1036
1037     private boolean isUpdateRequired(CallForwardInfo oldInfo,
1038             CallForwardInfo newInfo) {
1039         boolean result = true;
1040         if (0 == newInfo.status) {
1041             // If we're disabling a type of forwarding, and it's already
1042             // disabled for the account, don't make any change
1043             if (oldInfo != null && oldInfo.status == 0) {
1044                 result = false;
1045             }
1046         }
1047         return result;
1048     }
1049
1050     private void resetForwardingChangeState() {
1051         mForwardingChangeResults = new HashMap<Integer, AsyncResult>();
1052         mExpectedChangeResultReasons = new HashSet<Integer>();
1053     }
1054
1055     // Called after we are done saving the previous forwarding settings if
1056     // we needed.
1057     private void saveVoiceMailAndForwardingNumberStage2() {
1058         mForwardingChangeResults = null;
1059         mVoicemailChangeResult = null;
1060         if (mNewFwdSettings != FWD_SETTINGS_DONT_TOUCH) {
1061             resetForwardingChangeState();
1062             for (int i = 0; i < mNewFwdSettings.length; i++) {
1063                 CallForwardInfo fi = mNewFwdSettings[i];
1064
1065                 final boolean doUpdate = isUpdateRequired(infoForReason(
1066                             mForwardingReadResults, fi.reason), fi);
1067
1068                 if (doUpdate) {
1069                     if (DBG) log("Setting fwd #: " + i + ": " + fi.toString());
1070                     mExpectedChangeResultReasons.add(i);
1071
1072                     mPhone.setCallForwardingOption(
1073                             fi.status == 1 ?
1074                                     CommandsInterface.CF_ACTION_REGISTRATION :
1075                                     CommandsInterface.CF_ACTION_DISABLE,
1076                             fi.reason,
1077                             fi.number,
1078                             fi.timeSeconds,
1079                             mSetOptionComplete.obtainMessage(
1080                                     EVENT_FORWARDING_CHANGED, fi.reason, 0));
1081                 }
1082             }
1083             showDialogIfForeground(VOICEMAIL_FWD_SAVING_DIALOG);
1084         } else {
1085             if (DBG) log("Not touching fwd #");
1086             setVMNumberWithCarrier();
1087         }
1088     }
1089
1090     private void setVMNumberWithCarrier() {
1091         if (DBG) log("save voicemail #: " + mNewVMNumber);
1092         mPhone.setVoiceMailNumber(
1093                 mPhone.getVoiceMailAlphaTag().toString(),
1094                 mNewVMNumber,
1095                 Message.obtain(mSetOptionComplete, EVENT_VOICEMAIL_CHANGED));
1096     }
1097
1098     /**
1099      * Callback to handle option update completions
1100      */
1101     private final Handler mSetOptionComplete = new Handler() {
1102         @Override
1103         public void handleMessage(Message msg) {
1104             AsyncResult result = (AsyncResult) msg.obj;
1105             boolean done = false;
1106             switch (msg.what) {
1107                 case EVENT_VOICEMAIL_CHANGED:
1108                     mVoicemailChangeResult = result;
1109                     mVMChangeCompletedSuccessfully = checkVMChangeSuccess() == null;
1110                     if (DBG) log("VM change complete msg, VM change done = " +
1111                             String.valueOf(mVMChangeCompletedSuccessfully));
1112                     done = true;
1113                     break;
1114                 case EVENT_FORWARDING_CHANGED:
1115                     mForwardingChangeResults.put(msg.arg1, result);
1116                     if (result.exception != null) {
1117                         Log.w(LOG_TAG, "Error in setting fwd# " + msg.arg1 + ": " +
1118                                 result.exception.getMessage());
1119                     } else {
1120                         if (DBG) log("Success in setting fwd# " + msg.arg1);
1121                     }
1122                     final boolean completed = checkForwardingCompleted();
1123                     if (completed) {
1124                         if (checkFwdChangeSuccess() == null) {
1125                             if (DBG) log("Overall fwd changes completed ok, starting vm change");
1126                             setVMNumberWithCarrier();
1127                         } else {
1128                             Log.w(LOG_TAG, "Overall fwd changes completed in failure. " +
1129                                     "Check if we need to try rollback for some settings.");
1130                             mFwdChangesRequireRollback = false;
1131                             Iterator<Map.Entry<Integer,AsyncResult>> it =
1132                                 mForwardingChangeResults.entrySet().iterator();
1133                             while (it.hasNext()) {
1134                                 Map.Entry<Integer,AsyncResult> entry = it.next();
1135                                 if (entry.getValue().exception == null) {
1136                                     // If at least one succeeded we have to revert
1137                                     Log.i(LOG_TAG, "Rollback will be required");
1138                                     mFwdChangesRequireRollback = true;
1139                                     break;
1140                                 }
1141                             }
1142                             if (!mFwdChangesRequireRollback) {
1143                                 Log.i(LOG_TAG, "No rollback needed.");
1144                             }
1145                             done = true;
1146                         }
1147                     }
1148                     break;
1149                 default:
1150                     // TODO: should never reach this, may want to throw exception
1151             }
1152             if (done) {
1153                 if (DBG) log("All VM provider related changes done");
1154                 if (mForwardingChangeResults != null) {
1155                     dismissDialogSafely(VOICEMAIL_FWD_SAVING_DIALOG);
1156                 }
1157                 handleSetVMOrFwdMessage();
1158             }
1159         }
1160     };
1161
1162     /**
1163      * Callback to handle option revert completions
1164      */
1165     private final Handler mRevertOptionComplete = new Handler() {
1166         @Override
1167         public void handleMessage(Message msg) {
1168             AsyncResult result = (AsyncResult) msg.obj;
1169             switch (msg.what) {
1170                 case EVENT_VOICEMAIL_CHANGED:
1171                     mVoicemailChangeResult = result;
1172                     if (DBG) log("VM revert complete msg");
1173                     break;
1174                 case EVENT_FORWARDING_CHANGED:
1175                     mForwardingChangeResults.put(msg.arg1, result);
1176                     if (result.exception != null) {
1177                         if (DBG) log("Error in reverting fwd# " + msg.arg1 + ": " +
1178                                 result.exception.getMessage());
1179                     } else {
1180                         if (DBG) log("Success in reverting fwd# " + msg.arg1);
1181                     }
1182                     if (DBG) log("FWD revert complete msg ");
1183                     break;
1184                 default:
1185                     // TODO: should never reach this, may want to throw exception
1186             }
1187             final boolean done =
1188                 (!mVMChangeCompletedSuccessfully || mVoicemailChangeResult != null) &&
1189                 (!mFwdChangesRequireRollback || checkForwardingCompleted());
1190             if (done) {
1191                 if (DBG) log("All VM reverts done");
1192                 dismissDialogSafely(VOICEMAIL_REVERTING_DIALOG);
1193                 onRevertDone();
1194             }
1195         }
1196     };
1197
1198     /**
1199      * @return true if forwarding change has completed
1200      */
1201     private boolean checkForwardingCompleted() {
1202         boolean result;
1203         if (mForwardingChangeResults == null) {
1204             result = true;
1205         } else {
1206             // return true iff there is a change result for every reason for
1207             // which we expected a result
1208             result = true;
1209             for (Integer reason : mExpectedChangeResultReasons) {
1210                 if (mForwardingChangeResults.get(reason) == null) {
1211                     result = false;
1212                     break;
1213                 }
1214             }
1215         }
1216         return result;
1217     }
1218     /**
1219      * @return error string or null if successful
1220      */
1221     private String checkFwdChangeSuccess() {
1222         String result = null;
1223         Iterator<Map.Entry<Integer,AsyncResult>> it =
1224             mForwardingChangeResults.entrySet().iterator();
1225         while (it.hasNext()) {
1226             Map.Entry<Integer,AsyncResult> entry = it.next();
1227             Throwable exception = entry.getValue().exception;
1228             if (exception != null) {
1229                 result = exception.getMessage();
1230                 if (result == null) {
1231                     result = "";
1232                 }
1233                 break;
1234             }
1235         }
1236         return result;
1237     }
1238
1239     /**
1240      * @return error string or null if successful
1241      */
1242     private String checkVMChangeSuccess() {
1243         if (mVoicemailChangeResult.exception != null) {
1244             final String msg = mVoicemailChangeResult.exception.getMessage();
1245             if (msg == null) {
1246                 return "";
1247             }
1248             return msg;
1249         }
1250         return null;
1251     }
1252
1253     private void handleSetVMOrFwdMessage() {
1254         if (DBG) {
1255             log("handleSetVMMessage: set VM request complete");
1256         }
1257         boolean success = true;
1258         boolean fwdFailure = false;
1259         String exceptionMessage = "";
1260         if (mForwardingChangeResults != null) {
1261             exceptionMessage = checkFwdChangeSuccess();
1262             if (exceptionMessage != null) {
1263                 success = false;
1264                 fwdFailure = true;
1265             }
1266         }
1267         if (success) {
1268             exceptionMessage = checkVMChangeSuccess();
1269             if (exceptionMessage != null) {
1270                 success = false;
1271             }
1272         }
1273         if (success) {
1274             if (DBG) log("change VM success!");
1275             handleVMAndFwdSetSuccess(MSG_VM_OK);
1276         } else {
1277             if (fwdFailure) {
1278                 Log.w(LOG_TAG, "Failed to change fowarding setting. Reason: " + exceptionMessage);
1279                 handleVMOrFwdSetError(MSG_FW_SET_EXCEPTION);
1280             } else {
1281                 Log.w(LOG_TAG, "Failed to change voicemail. Reason: " + exceptionMessage);
1282                 handleVMOrFwdSetError(MSG_VM_EXCEPTION);
1283             }
1284         }
1285     }
1286
1287     /**
1288      * Called when Voicemail Provider or its forwarding settings failed. Rolls back partly made
1289      * changes to those settings and show "failure" dialog.
1290      *
1291      * @param msgId Message ID used for the specific error case. {@link #MSG_FW_SET_EXCEPTION} or
1292      * {@link #MSG_VM_EXCEPTION}
1293      */
1294     private void handleVMOrFwdSetError(int msgId) {
1295         if (mChangingVMorFwdDueToProviderChange) {
1296             mVMOrFwdSetError = msgId;
1297             mChangingVMorFwdDueToProviderChange = false;
1298             switchToPreviousVoicemailProvider();
1299             return;
1300         }
1301         mChangingVMorFwdDueToProviderChange = false;
1302         showVMDialog(msgId);
1303         updateVoiceNumberField();
1304     }
1305
1306     /**
1307      * Called when Voicemail Provider and its forwarding settings were successfully finished.
1308      * This updates a bunch of variables and show "success" dialog.
1309      */
1310     private void handleVMAndFwdSetSuccess(int msg) {
1311         if (DBG) {
1312             log("handleVMAndFwdSetSuccess(). current voicemail provider key: "
1313                     + getCurrentVoicemailProviderKey());
1314         }
1315         mPreviousVMProviderKey = getCurrentVoicemailProviderKey();
1316         mChangingVMorFwdDueToProviderChange = false;
1317         showVMDialog(msg);
1318         updateVoiceNumberField();
1319     }
1320
1321     /**
1322      * Update the voicemail number from what we've recorded on the sim.
1323      */
1324     private void updateVoiceNumberField() {
1325         if (DBG) {
1326             log("updateVoiceNumberField(). mSubMenuVoicemailSettings=" + mSubMenuVoicemailSettings);
1327         }
1328         if (mSubMenuVoicemailSettings == null) {
1329             return;
1330         }
1331
1332         mOldVmNumber = mPhone.getVoiceMailNumber();
1333         if (mOldVmNumber == null) {
1334             mOldVmNumber = "";
1335         }
1336         mSubMenuVoicemailSettings.setPhoneNumber(mOldVmNumber);
1337         final String summary = (mOldVmNumber.length() > 0) ? mOldVmNumber :
1338                 getString(R.string.voicemail_number_not_set);
1339         mSubMenuVoicemailSettings.setSummary(summary);
1340     }
1341
1342     /*
1343      * Helper Methods for Activity class.
1344      * The initial query commands are split into two pieces now
1345      * for individual expansion.  This combined with the ability
1346      * to cancel queries allows for a much better user experience,
1347      * and also ensures that the user only waits to update the
1348      * data that is relevant.
1349      */
1350
1351     @Override
1352     protected void onPrepareDialog(int id, Dialog dialog) {
1353         super.onPrepareDialog(id, dialog);
1354         mCurrentDialogId = id;
1355     }
1356
1357     // dialog creation method, called by showDialog()
1358     @Override
1359     protected Dialog onCreateDialog(int id) {
1360         if ((id == VM_RESPONSE_ERROR) || (id == VM_NOCHANGE_ERROR) ||
1361             (id == FW_SET_RESPONSE_ERROR) || (id == FW_GET_RESPONSE_ERROR) ||
1362                 (id == VOICEMAIL_DIALOG_CONFIRM)) {
1363
1364             AlertDialog.Builder b = new AlertDialog.Builder(this);
1365
1366             int msgId;
1367             int titleId = R.string.error_updating_title;
1368             switch (id) {
1369                 case VOICEMAIL_DIALOG_CONFIRM:
1370                     msgId = R.string.vm_changed;
1371                     titleId = R.string.voicemail;
1372                     // Set Button 2
1373                     b.setNegativeButton(R.string.close_dialog, this);
1374                     break;
1375                 case VM_NOCHANGE_ERROR:
1376                     // even though this is technically an error,
1377                     // keep the title friendly.
1378                     msgId = R.string.no_change;
1379                     titleId = R.string.voicemail;
1380                     // Set Button 2
1381                     b.setNegativeButton(R.string.close_dialog, this);
1382                     break;
1383                 case VM_RESPONSE_ERROR:
1384                     msgId = R.string.vm_change_failed;
1385                     // Set Button 1
1386                     b.setPositiveButton(R.string.close_dialog, this);
1387                     break;
1388                 case FW_SET_RESPONSE_ERROR:
1389                     msgId = R.string.fw_change_failed;
1390                     // Set Button 1
1391                     b.setPositiveButton(R.string.close_dialog, this);
1392                     break;
1393                 case FW_GET_RESPONSE_ERROR:
1394                     msgId = R.string.fw_get_in_vm_failed;
1395                     b.setPositiveButton(R.string.alert_dialog_yes, this);
1396                     b.setNegativeButton(R.string.alert_dialog_no, this);
1397                     break;
1398                 default:
1399                     msgId = R.string.exception_error;
1400                     // Set Button 3, tells the activity that the error is
1401                     // not recoverable on dialog exit.
1402                     b.setNeutralButton(R.string.close_dialog, this);
1403                     break;
1404             }
1405
1406             b.setTitle(getText(titleId));
1407             String message = getText(msgId).toString();
1408             b.setMessage(message);
1409             b.setCancelable(false);
1410             AlertDialog dialog = b.create();
1411
1412             // make the dialog more obvious by bluring the background.
1413             dialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_BLUR_BEHIND);
1414
1415             return dialog;
1416         } else if (id == VOICEMAIL_FWD_SAVING_DIALOG || id == VOICEMAIL_FWD_READING_DIALOG ||
1417                 id == VOICEMAIL_REVERTING_DIALOG) {
1418             ProgressDialog dialog = new ProgressDialog(this);
1419             dialog.setTitle(getText(R.string.updating_title));
1420             dialog.setIndeterminate(true);
1421             dialog.setCancelable(false);
1422             dialog.setMessage(getText(
1423                     id == VOICEMAIL_FWD_SAVING_DIALOG ? R.string.updating_settings :
1424                     (id == VOICEMAIL_REVERTING_DIALOG ? R.string.reverting_settings :
1425                     R.string.reading_settings)));
1426             return dialog;
1427         }
1428
1429
1430         return null;
1431     }
1432
1433     // This is a method implemented for DialogInterface.OnClickListener.
1434     // Used with the error dialog to close the app, voicemail dialog to just dismiss.
1435     // Close button is mapped to BUTTON_POSITIVE for the errors that close the activity,
1436     // while those that are mapped to BUTTON_NEUTRAL only move the preference focus.
1437     public void onClick(DialogInterface dialog, int which) {
1438         dialog.dismiss();
1439         switch (which){
1440             case DialogInterface.BUTTON_NEUTRAL:
1441                 if (DBG) log("Neutral button");
1442                 break;
1443             case DialogInterface.BUTTON_NEGATIVE:
1444                 if (DBG) log("Negative button");
1445                 if (mCurrentDialogId == FW_GET_RESPONSE_ERROR) {
1446                     // We failed to get current forwarding settings and the user
1447                     // does not wish to continue.
1448                     switchToPreviousVoicemailProvider();
1449                 }
1450                 break;
1451             case DialogInterface.BUTTON_POSITIVE:
1452                 if (DBG) log("Positive button");
1453                 if (mCurrentDialogId == FW_GET_RESPONSE_ERROR) {
1454                     // We failed to get current forwarding settings but the user
1455                     // wishes to continue changing settings to the new vm provider
1456                     saveVoiceMailAndForwardingNumberStage2();
1457                 } else {
1458                     finish();
1459                 }
1460                 return;
1461             default:
1462                 // just let the dialog close and go back to the input
1463         }
1464         // In all dialogs, all buttons except BUTTON_POSITIVE lead to the end of user interaction
1465         // with settings UI. If we were called to explicitly configure voice mail then
1466         // we finish the settings activity here to come back to whatever the user was doing.
1467         if (getIntent().getAction().equals(ACTION_ADD_VOICEMAIL)) {
1468             finish();
1469         }
1470     }
1471
1472     // set the app state with optional status.
1473     private void showVMDialog(int msgStatus) {
1474         switch (msgStatus) {
1475             // It's a bit worrisome to punt in the error cases here when we're
1476             // not in the foreground; maybe toast instead?
1477             case MSG_VM_EXCEPTION:
1478                 showDialogIfForeground(VM_RESPONSE_ERROR);
1479                 break;
1480             case MSG_FW_SET_EXCEPTION:
1481                 showDialogIfForeground(FW_SET_RESPONSE_ERROR);
1482                 break;
1483             case MSG_FW_GET_EXCEPTION:
1484                 showDialogIfForeground(FW_GET_RESPONSE_ERROR);
1485                 break;
1486             case MSG_VM_NOCHANGE:
1487                 showDialogIfForeground(VM_NOCHANGE_ERROR);
1488                 break;
1489             case MSG_VM_OK:
1490                 showDialogIfForeground(VOICEMAIL_DIALOG_CONFIRM);
1491                 break;
1492             case MSG_OK:
1493             default:
1494                 // This should never happen.
1495         }
1496     }
1497
1498     /*
1499      * Activity class methods
1500      */
1501
1502     @Override
1503     protected void onCreate(Bundle icicle) {
1504         super.onCreate(icicle);
1505         if (DBG) log("onCreate(). Intent: " + getIntent());
1506         mPhone = PhoneGlobals.getPhone();
1507
1508         addPreferencesFromResource(R.xml.call_feature_setting);
1509
1510         mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
1511
1512         // get buttons
1513         PreferenceScreen prefSet = getPreferenceScreen();
1514         mSubMenuVoicemailSettings = (EditPhoneNumberPreference)findPreference(BUTTON_VOICEMAIL_KEY);
1515         if (mSubMenuVoicemailSettings != null) {
1516             mSubMenuVoicemailSettings.setParentActivity(this, VOICEMAIL_PREF_ID, this);
1517             mSubMenuVoicemailSettings.setDialogOnClosedListener(this);
1518             mSubMenuVoicemailSettings.setDialogTitle(R.string.voicemail_settings_number_label);
1519         }
1520
1521         mRingtonePreference = findPreference(BUTTON_RINGTONE_KEY);
1522         mVibrateWhenRinging = (CheckBoxPreference) findPreference(BUTTON_VIBRATE_ON_RING);
1523         mPlayDtmfTone = (CheckBoxPreference) findPreference(BUTTON_PLAY_DTMF_TONE);
1524         mDialpadAutocomplete = (CheckBoxPreference) findPreference(BUTTON_DIALPAD_AUTOCOMPLETE);
1525         mButtonDTMF = (ListPreference) findPreference(BUTTON_DTMF_KEY);
1526         mButtonAutoRetry = (CheckBoxPreference) findPreference(BUTTON_RETRY_KEY);
1527         mButtonHAC = (CheckBoxPreference) findPreference(BUTTON_HAC_KEY);
1528         mButtonTTY = (ListPreference) findPreference(BUTTON_TTY_KEY);
1529         mVoicemailProviders = (ListPreference) findPreference(BUTTON_VOICEMAIL_PROVIDER_KEY);
1530         if (mVoicemailProviders != null) {
1531             mVoicemailProviders.setOnPreferenceChangeListener(this);
1532             mVoicemailSettings = (PreferenceScreen)findPreference(BUTTON_VOICEMAIL_SETTING_KEY);
1533             mVoicemailNotificationRingtone =
1534                     findPreference(BUTTON_VOICEMAIL_NOTIFICATION_RINGTONE_KEY);
1535             mVoicemailNotificationVibrate =
1536                     (CheckBoxPreference) findPreference(BUTTON_VOICEMAIL_NOTIFICATION_VIBRATE_KEY);
1537             initVoiceMailProviders();
1538         }
1539
1540         if (mVibrateWhenRinging != null) {
1541             Vibrator vibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
1542             if (vibrator != null && vibrator.hasVibrator()) {
1543                 mVibrateWhenRinging.setOnPreferenceChangeListener(this);
1544             } else {
1545                 prefSet.removePreference(mVibrateWhenRinging);
1546                 mVibrateWhenRinging = null;
1547             }
1548         }
1549
1550         final ContentResolver contentResolver = getContentResolver();
1551
1552         if (mPlayDtmfTone != null) {
1553             mPlayDtmfTone.setChecked(Settings.System.getInt(contentResolver,
1554                     Settings.System.DTMF_TONE_WHEN_DIALING, 1) != 0);
1555         }
1556
1557         if (mDialpadAutocomplete != null) {
1558             mDialpadAutocomplete.setChecked(Settings.Secure.getInt(contentResolver,
1559                     Settings.Secure.DIALPAD_AUTOCOMPLETE, 0) != 0);
1560         }
1561
1562         if (mButtonDTMF != null) {
1563             if (getResources().getBoolean(R.bool.dtmf_type_enabled)) {
1564                 mButtonDTMF.setOnPreferenceChangeListener(this);
1565             } else {
1566                 prefSet.removePreference(mButtonDTMF);
1567                 mButtonDTMF = null;
1568             }
1569         }
1570
1571         if (mButtonAutoRetry != null) {
1572             if (getResources().getBoolean(R.bool.auto_retry_enabled)) {
1573                 mButtonAutoRetry.setOnPreferenceChangeListener(this);
1574             } else {
1575                 prefSet.removePreference(mButtonAutoRetry);
1576                 mButtonAutoRetry = null;
1577             }
1578         }
1579
1580         if (mButtonHAC != null) {
1581             if (getResources().getBoolean(R.bool.hac_enabled)) {
1582
1583                 mButtonHAC.setOnPreferenceChangeListener(this);
1584             } else {
1585                 prefSet.removePreference(mButtonHAC);
1586                 mButtonHAC = null;
1587             }
1588         }
1589
1590         if (mButtonTTY != null) {
1591             if (getResources().getBoolean(R.bool.tty_enabled)) {
1592                 mButtonTTY.setOnPreferenceChangeListener(this);
1593             } else {
1594                 prefSet.removePreference(mButtonTTY);
1595                 mButtonTTY = null;
1596             }
1597         }
1598
1599         if (!getResources().getBoolean(R.bool.world_phone)) {
1600             Preference options = prefSet.findPreference(BUTTON_CDMA_OPTIONS);
1601             if (options != null)
1602                 prefSet.removePreference(options);
1603             options = prefSet.findPreference(BUTTON_GSM_UMTS_OPTIONS);
1604             if (options != null)
1605                 prefSet.removePreference(options);
1606
1607             int phoneType = mPhone.getPhoneType();
1608             if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
1609                 Preference fdnButton = prefSet.findPreference(BUTTON_FDN_KEY);
1610                 if (fdnButton != null)
1611                     prefSet.removePreference(fdnButton);
1612                 if (!getResources().getBoolean(R.bool.config_voice_privacy_disable)) {
1613                     addPreferencesFromResource(R.xml.cdma_call_privacy);
1614                 }
1615             } else if (phoneType == PhoneConstants.PHONE_TYPE_GSM) {
1616                 addPreferencesFromResource(R.xml.gsm_umts_call_options);
1617             } else {
1618                 throw new IllegalStateException("Unexpected phone type: " + phoneType);
1619             }
1620         }
1621
1622         // create intent to bring up contact list
1623         mContactListIntent = new Intent(Intent.ACTION_GET_CONTENT);
1624         mContactListIntent.setType(android.provider.Contacts.Phones.CONTENT_ITEM_TYPE);
1625
1626         // check the intent that started this activity and pop up the voicemail
1627         // dialog if we've been asked to.
1628         // If we have at least one non default VM provider registered then bring up
1629         // the selection for the VM provider, otherwise bring up a VM number dialog.
1630         // We only bring up the dialog the first time we are called (not after orientation change)
1631         if (icicle == null) {
1632             if (getIntent().getAction().equals(ACTION_ADD_VOICEMAIL) &&
1633                     mVoicemailProviders != null) {
1634                 if (DBG) {
1635                     log("ACTION_ADD_VOICEMAIL Intent is thrown. current VM data size: "
1636                             + mVMProvidersData.size());
1637                 }
1638                 if (mVMProvidersData.size() > 1) {
1639                     simulatePreferenceClick(mVoicemailProviders);
1640                 } else {
1641                     onPreferenceChange(mVoicemailProviders, DEFAULT_VM_PROVIDER_KEY);
1642                     mVoicemailProviders.setValue(DEFAULT_VM_PROVIDER_KEY);
1643                 }
1644             }
1645         }
1646         updateVoiceNumberField();
1647         mVMProviderSettingsForced = false;
1648         createSipCallSettings();
1649
1650         mRingtoneLookupRunnable = new Runnable() {
1651             @Override
1652             public void run() {
1653                 if (mRingtonePreference != null) {
1654                     updateRingtoneName(RingtoneManager.TYPE_RINGTONE, mRingtonePreference,
1655                             MSG_UPDATE_RINGTONE_SUMMARY);
1656                 }
1657                 if (mVoicemailNotificationRingtone != null) {
1658                     updateRingtoneName(RingtoneManager.TYPE_NOTIFICATION,
1659                             mVoicemailNotificationRingtone, MSG_UPDATE_VOICEMAIL_RINGTONE_SUMMARY);
1660                 }
1661             }
1662         };
1663
1664         ActionBar actionBar = getActionBar();
1665         if (actionBar != null) {
1666             // android.R.id.home will be triggered in onOptionsItemSelected()
1667             actionBar.setDisplayHomeAsUpEnabled(true);
1668         }
1669     }
1670
1671     /**
1672      * Updates ringtone name. This is a method copied from com.android.settings.SoundSettings
1673      *
1674      * @see com.android.settings.SoundSettings
1675      */
1676     private void updateRingtoneName(int type, Preference preference, int msg) {
1677         if (preference == null) return;
1678         final Uri ringtoneUri;
1679         boolean defaultRingtone = false;
1680         if (type == RingtoneManager.TYPE_RINGTONE) {
1681             // For ringtones, we can just lookup the system default because changing the settings
1682             // in Call Settings changes the system default.
1683             ringtoneUri = RingtoneManager.getActualDefaultRingtoneUri(this, type);
1684         } else {
1685             final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(
1686                     mPhone.getContext());
1687             // for voicemail notifications, we use the value saved in Phone's shared preferences.
1688             String uriString = prefs.getString(preference.getKey(), null);
1689             if (TextUtils.isEmpty(uriString)) {
1690                 // silent ringtone
1691                 ringtoneUri = null;
1692             } else {
1693                 if (uriString.equals(Settings.System.DEFAULT_NOTIFICATION_URI.toString())) {
1694                     // If it turns out that the voicemail notification is set to the system
1695                     // default notification, we retrieve the actual URI to prevent it from showing
1696                     // up as "Unknown Ringtone".
1697                     defaultRingtone = true;
1698                     ringtoneUri = RingtoneManager.getActualDefaultRingtoneUri(this, type);
1699                 } else {
1700                     ringtoneUri = Uri.parse(uriString);
1701                 }
1702             }
1703         }
1704         CharSequence summary = getString(com.android.internal.R.string.ringtone_unknown);
1705         // Is it a silent ringtone?
1706         if (ringtoneUri == null) {
1707             summary = getString(com.android.internal.R.string.ringtone_silent);
1708         } else {
1709             // Fetch the ringtone title from the media provider
1710             try {
1711                 Cursor cursor = getContentResolver().query(ringtoneUri,
1712                         new String[] { MediaStore.Audio.Media.TITLE }, null, null, null);
1713                 if (cursor != null) {
1714                     if (cursor.moveToFirst()) {
1715                         summary = cursor.getString(0);
1716                     }
1717                     cursor.close();
1718                 }
1719             } catch (SQLiteException sqle) {
1720                 // Unknown title for the ringtone
1721             }
1722         }
1723         if (defaultRingtone) {
1724             summary = mPhone.getContext().getString(
1725                     R.string.default_notification_description, summary);
1726         }
1727         mRingtoneLookupComplete.sendMessage(mRingtoneLookupComplete.obtainMessage(msg, summary));
1728     }
1729
1730     private void createSipCallSettings() {
1731         // Add Internet call settings.
1732         if (PhoneUtils.isVoipSupported()) {
1733             mSipManager = SipManager.newInstance(this);
1734             mSipSharedPreferences = new SipSharedPreferences(this);
1735             addPreferencesFromResource(R.xml.sip_settings_category);
1736             mButtonSipCallOptions = getSipCallOptionPreference();
1737             mButtonSipCallOptions.setOnPreferenceChangeListener(this);
1738             mButtonSipCallOptions.setValueIndex(
1739                     mButtonSipCallOptions.findIndexOfValue(
1740                             mSipSharedPreferences.getSipCallOption()));
1741             mButtonSipCallOptions.setSummary(mButtonSipCallOptions.getEntry());
1742         }
1743     }
1744
1745     // Gets the call options for SIP depending on whether SIP is allowed only
1746     // on Wi-Fi only; also make the other options preference invisible.
1747     private ListPreference getSipCallOptionPreference() {
1748         ListPreference wifiAnd3G = (ListPreference)
1749                 findPreference(BUTTON_SIP_CALL_OPTIONS);
1750         ListPreference wifiOnly = (ListPreference)
1751                 findPreference(BUTTON_SIP_CALL_OPTIONS_WIFI_ONLY);
1752         PreferenceGroup sipSettings = (PreferenceGroup)
1753                 findPreference(SIP_SETTINGS_CATEGORY_KEY);
1754         if (SipManager.isSipWifiOnly(this)) {
1755             sipSettings.removePreference(wifiAnd3G);
1756             return wifiOnly;
1757         } else {
1758             sipSettings.removePreference(wifiOnly);
1759             return wifiAnd3G;
1760         }
1761     }
1762
1763     @Override
1764     protected void onResume() {
1765         super.onResume();
1766         mForeground = true;
1767
1768         if (isAirplaneModeOn()) {
1769             Preference sipSettings = findPreference(SIP_SETTINGS_CATEGORY_KEY);
1770             PreferenceScreen screen = getPreferenceScreen();
1771             int count = screen.getPreferenceCount();
1772             for (int i = 0 ; i < count ; ++i) {
1773                 Preference pref = screen.getPreference(i);
1774                 if (pref != sipSettings) pref.setEnabled(false);
1775             }
1776             return;
1777         }
1778
1779         if (mVibrateWhenRinging != null) {
1780             mVibrateWhenRinging.setChecked(getVibrateWhenRinging(this));
1781         }
1782
1783         if (mButtonDTMF != null) {
1784             int dtmf = Settings.System.getInt(getContentResolver(),
1785                     Settings.System.DTMF_TONE_TYPE_WHEN_DIALING, Constants.DTMF_TONE_TYPE_NORMAL);
1786             mButtonDTMF.setValueIndex(dtmf);
1787         }
1788
1789         if (mButtonAutoRetry != null) {
1790             int autoretry = Settings.Global.getInt(getContentResolver(),
1791                     Settings.Global.CALL_AUTO_RETRY, 0);
1792             mButtonAutoRetry.setChecked(autoretry != 0);
1793         }
1794
1795         if (mButtonHAC != null) {
1796             int hac = Settings.System.getInt(getContentResolver(), Settings.System.HEARING_AID, 0);
1797             mButtonHAC.setChecked(hac != 0);
1798         }
1799
1800         if (mButtonTTY != null) {
1801             int settingsTtyMode = Settings.Secure.getInt(getContentResolver(),
1802                     Settings.Secure.PREFERRED_TTY_MODE,
1803                     Phone.TTY_MODE_OFF);
1804             mButtonTTY.setValue(Integer.toString(settingsTtyMode));
1805             updatePreferredTtyModeSummary(settingsTtyMode);
1806         }
1807
1808         SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(
1809                 mPhone.getContext());
1810         if (migrateVoicemailVibrationSettingsIfNeeded(prefs)) {
1811             mVoicemailNotificationVibrate.setChecked(prefs.getBoolean(
1812                     BUTTON_VOICEMAIL_NOTIFICATION_VIBRATE_KEY, false));
1813         }
1814
1815         lookupRingtoneName();
1816     }
1817
1818     // Migrate settings from BUTTON_VOICEMAIL_NOTIFICATION_VIBRATE_WHEN_KEY to
1819     // BUTTON_VOICEMAIL_NOTIFICATION_VIBRATE_KEY, if the latter does not exist.
1820     // Returns true if migration was performed.
1821     public static boolean migrateVoicemailVibrationSettingsIfNeeded(SharedPreferences prefs) {
1822         if (!prefs.contains(BUTTON_VOICEMAIL_NOTIFICATION_VIBRATE_KEY)) {
1823             String vibrateWhen = prefs.getString(
1824                     BUTTON_VOICEMAIL_NOTIFICATION_VIBRATE_WHEN_KEY, VOICEMAIL_VIBRATION_NEVER);
1825             // If vibrateWhen is always, then voicemailVibrate should be True.
1826             // otherwise if vibrateWhen is "only in silent mode", or "never", then
1827             // voicemailVibrate = False.
1828             boolean voicemailVibrate = vibrateWhen.equals(VOICEMAIL_VIBRATION_ALWAYS);
1829             final SharedPreferences.Editor editor = prefs.edit();
1830             editor.putBoolean(BUTTON_VOICEMAIL_NOTIFICATION_VIBRATE_KEY, voicemailVibrate);
1831             editor.commit();
1832             return true;
1833         }
1834         return false;
1835     }
1836
1837     /**
1838      * Obtain the setting for "vibrate when ringing" setting.
1839      *
1840      * Watch out: if the setting is missing in the device, this will try obtaining the old
1841      * "vibrate on ring" setting from AudioManager, and save the previous setting to the new one.
1842      */
1843     public static boolean getVibrateWhenRinging(Context context) {
1844         Vibrator vibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
1845         if (vibrator == null || !vibrator.hasVibrator()) {
1846             return false;
1847         }
1848         return Settings.System.getInt(context.getContentResolver(),
1849                 Settings.System.VIBRATE_WHEN_RINGING, 0) != 0;
1850     }
1851
1852     /**
1853      * Lookups ringtone name asynchronously and updates the relevant Preference.
1854      */
1855     private void lookupRingtoneName() {
1856         new Thread(mRingtoneLookupRunnable).start();
1857     }
1858
1859     private boolean isAirplaneModeOn() {
1860         return Settings.System.getInt(getContentResolver(),
1861                 Settings.System.AIRPLANE_MODE_ON, 0) != 0;
1862     }
1863
1864     private void handleTTYChange(Preference preference, Object objValue) {
1865         int buttonTtyMode;
1866         buttonTtyMode = Integer.valueOf((String) objValue).intValue();
1867         int settingsTtyMode = android.provider.Settings.Secure.getInt(
1868                 getContentResolver(),
1869                 android.provider.Settings.Secure.PREFERRED_TTY_MODE, preferredTtyMode);
1870         if (DBG) log("handleTTYChange: requesting set TTY mode enable (TTY) to" +
1871                 Integer.toString(buttonTtyMode));
1872
1873         if (buttonTtyMode != settingsTtyMode) {
1874             switch(buttonTtyMode) {
1875             case Phone.TTY_MODE_OFF:
1876             case Phone.TTY_MODE_FULL:
1877             case Phone.TTY_MODE_HCO:
1878             case Phone.TTY_MODE_VCO:
1879                 android.provider.Settings.Secure.putInt(getContentResolver(),
1880                         android.provider.Settings.Secure.PREFERRED_TTY_MODE, buttonTtyMode);
1881                 break;
1882             default:
1883                 buttonTtyMode = Phone.TTY_MODE_OFF;
1884             }
1885
1886             mButtonTTY.setValue(Integer.toString(buttonTtyMode));
1887             updatePreferredTtyModeSummary(buttonTtyMode);
1888             Intent ttyModeChanged = new Intent(TtyIntent.TTY_PREFERRED_MODE_CHANGE_ACTION);
1889             ttyModeChanged.putExtra(TtyIntent.TTY_PREFFERED_MODE, buttonTtyMode);
1890             sendBroadcastAsUser(ttyModeChanged, UserHandle.ALL);
1891         }
1892     }
1893
1894     private void handleSipCallOptionsChange(Object objValue) {
1895         String option = objValue.toString();
1896         mSipSharedPreferences.setSipCallOption(option);
1897         mButtonSipCallOptions.setValueIndex(
1898                 mButtonSipCallOptions.findIndexOfValue(option));
1899         mButtonSipCallOptions.setSummary(mButtonSipCallOptions.getEntry());
1900     }
1901
1902     private void updatePreferredTtyModeSummary(int TtyMode) {
1903         String [] txts = getResources().getStringArray(R.array.tty_mode_entries);
1904         switch(TtyMode) {
1905             case Phone.TTY_MODE_OFF:
1906             case Phone.TTY_MODE_HCO:
1907             case Phone.TTY_MODE_VCO:
1908             case Phone.TTY_MODE_FULL:
1909                 mButtonTTY.setSummary(txts[TtyMode]);
1910                 break;
1911             default:
1912                 mButtonTTY.setEnabled(false);
1913                 mButtonTTY.setSummary(txts[Phone.TTY_MODE_OFF]);
1914         }
1915     }
1916
1917     private static void log(String msg) {
1918         Log.d(LOG_TAG, msg);
1919     }
1920
1921     /**
1922      * Updates the look of the VM preference widgets based on current VM provider settings.
1923      * Note that the provider name is loaded form the found activity via loadLabel in
1924      * {@link #initVoiceMailProviders()} in order for it to be localizable.
1925      */
1926     private void updateVMPreferenceWidgets(String currentProviderSetting) {
1927         final String key = currentProviderSetting;
1928         final VoiceMailProvider provider = mVMProvidersData.get(key);
1929
1930         /* This is the case when we are coming up on a freshly wiped phone and there is no
1931          persisted value for the list preference mVoicemailProviders.
1932          In this case we want to show the UI asking the user to select a voicemail provider as
1933          opposed to silently falling back to default one. */
1934         if (provider == null) {
1935             if (DBG) {
1936                 log("updateVMPreferenceWidget: provider for the key \"" + key + "\" is null.");
1937             }
1938             mVoicemailProviders.setSummary(getString(R.string.sum_voicemail_choose_provider));
1939             mVoicemailSettings.setEnabled(false);
1940             mVoicemailSettings.setIntent(null);
1941
1942             mVoicemailNotificationVibrate.setEnabled(false);
1943         } else {
1944             if (DBG) {
1945                 log("updateVMPreferenceWidget: provider for the key \"" + key + "\".."
1946                         + "name: " + provider.name
1947                         + ", intent: " + provider.intent);
1948             }
1949             final String providerName = provider.name;
1950             mVoicemailProviders.setSummary(providerName);
1951             mVoicemailSettings.setEnabled(true);
1952             mVoicemailSettings.setIntent(provider.intent);
1953
1954             mVoicemailNotificationVibrate.setEnabled(true);
1955         }
1956     }
1957
1958     /**
1959      * Enumerates existing VM providers and puts their data into the list and populates
1960      * the preference list objects with their names.
1961      * In case we are called with ACTION_ADD_VOICEMAIL intent the intent may have
1962      * an extra string called IGNORE_PROVIDER_EXTRA with "package.activityName" of the provider
1963      * which should be hidden when we bring up the list of possible VM providers to choose.
1964      */
1965     private void initVoiceMailProviders() {
1966         if (DBG) log("initVoiceMailProviders()");
1967         mPerProviderSavedVMNumbers =
1968                 this.getApplicationContext().getSharedPreferences(
1969                         VM_NUMBERS_SHARED_PREFERENCES_NAME, MODE_PRIVATE);
1970
1971         String providerToIgnore = null;
1972         if (getIntent().getAction().equals(ACTION_ADD_VOICEMAIL)) {
1973             if (getIntent().hasExtra(IGNORE_PROVIDER_EXTRA)) {
1974                 providerToIgnore = getIntent().getStringExtra(IGNORE_PROVIDER_EXTRA);
1975             }
1976             if (DBG) log("Found ACTION_ADD_VOICEMAIL. providerToIgnore=" + providerToIgnore);
1977             if (providerToIgnore != null) {
1978                 // IGNORE_PROVIDER_EXTRA implies we want to remove the choice from the list.
1979                 deleteSettingsForVoicemailProvider(providerToIgnore);
1980             }
1981         }
1982
1983         mVMProvidersData.clear();
1984
1985         // Stick the default element which is always there
1986         final String myCarrier = getString(R.string.voicemail_default);
1987         mVMProvidersData.put(DEFAULT_VM_PROVIDER_KEY, new VoiceMailProvider(myCarrier, null));
1988
1989         // Enumerate providers
1990         PackageManager pm = getPackageManager();
1991         Intent intent = new Intent();
1992         intent.setAction(ACTION_CONFIGURE_VOICEMAIL);
1993         List<ResolveInfo> resolveInfos = pm.queryIntentActivities(intent, 0);
1994         int len = resolveInfos.size() + 1; // +1 for the default choice we will insert.
1995
1996         // Go through the list of discovered providers populating the data map
1997         // skip the provider we were instructed to ignore if there was one
1998         for (int i = 0; i < resolveInfos.size(); i++) {
1999             final ResolveInfo ri= resolveInfos.get(i);
2000             final ActivityInfo currentActivityInfo = ri.activityInfo;
2001             final String key = makeKeyForActivity(currentActivityInfo);
2002             if (key.equals(providerToIgnore)) {
2003                 if (DBG) log("Ignoring key: " + key);
2004                 len--;
2005                 continue;
2006             }
2007             if (DBG) log("Loading key: " + key);
2008             final String nameForDisplay = ri.loadLabel(pm).toString();
2009             Intent providerIntent = new Intent();
2010             providerIntent.setAction(ACTION_CONFIGURE_VOICEMAIL);
2011             providerIntent.setClassName(currentActivityInfo.packageName,
2012                     currentActivityInfo.name);
2013             if (DBG) {
2014                 log("Store loaded VoiceMailProvider. key: " + key
2015                         + " -> name: " + nameForDisplay + ", intent: " + providerIntent);
2016             }
2017             mVMProvidersData.put(
2018                     key,
2019                     new VoiceMailProvider(nameForDisplay, providerIntent));
2020
2021         }
2022
2023         // Now we know which providers to display - create entries and values array for
2024         // the list preference
2025         String [] entries = new String [len];
2026         String [] values = new String [len];
2027         entries[0] = myCarrier;
2028         values[0] = DEFAULT_VM_PROVIDER_KEY;
2029         int entryIdx = 1;
2030         for (int i = 0; i < resolveInfos.size(); i++) {
2031             final String key = makeKeyForActivity(resolveInfos.get(i).activityInfo);
2032             if (!mVMProvidersData.containsKey(key)) {
2033                 continue;
2034             }
2035             entries[entryIdx] = mVMProvidersData.get(key).name;
2036             values[entryIdx] = key;
2037             entryIdx++;
2038         }
2039
2040         // ListPreference is now updated.
2041         mVoicemailProviders.setEntries(entries);
2042         mVoicemailProviders.setEntryValues(values);
2043
2044         // Remember the current Voicemail Provider key as a "previous" key. This will be used
2045         // when we fail to update Voicemail Provider, which requires rollback.
2046         // We will update this when the VM Provider setting is successfully updated.
2047         mPreviousVMProviderKey = getCurrentVoicemailProviderKey();
2048         if (DBG) log("Set up the first mPreviousVMProviderKey: " + mPreviousVMProviderKey);
2049
2050         // Finally update the preference texts.
2051         updateVMPreferenceWidgets(mPreviousVMProviderKey);
2052     }
2053
2054     private String makeKeyForActivity(ActivityInfo ai) {
2055         return ai.name;
2056     }
2057
2058     /**
2059      * Simulates user clicking on a passed preference.
2060      * Usually needed when the preference is a dialog preference and we want to invoke
2061      * a dialog for this preference programmatically.
2062      * TODO(iliat): figure out if there is a cleaner way to cause preference dlg to come up
2063      */
2064     private void simulatePreferenceClick(Preference preference) {
2065         // Go through settings until we find our setting
2066         // and then simulate a click on it to bring up the dialog
2067         final ListAdapter adapter = getPreferenceScreen().getRootAdapter();
2068         for (int idx = 0; idx < adapter.getCount(); idx++) {
2069             if (adapter.getItem(idx) == preference) {
2070                 getPreferenceScreen().onItemClick(this.getListView(),
2071                         null, idx, adapter.getItemId(idx));
2072                 break;
2073             }
2074         }
2075     }
2076
2077     /**
2078      * Saves new VM provider settings associating them with the currently selected
2079      * provider if settings are different than the ones already stored for this
2080      * provider.
2081      * Later on these will be used when the user switches a provider.
2082      */
2083     private void maybeSaveSettingsForVoicemailProvider(String key,
2084             VoiceMailProviderSettings newSettings) {
2085         if (mVoicemailProviders == null) {
2086             return;
2087         }
2088         final VoiceMailProviderSettings curSettings = loadSettingsForVoiceMailProvider(key);
2089         if (newSettings.equals(curSettings)) {
2090             if (DBG) {
2091                 log("maybeSaveSettingsForVoicemailProvider:"
2092                         + " Not saving setting for " + key + " since they have not changed");
2093             }
2094             return;
2095         }
2096         if (DBG) log("Saving settings for " + key + ": " + newSettings.toString());
2097         Editor editor = mPerProviderSavedVMNumbers.edit();
2098         editor.putString(key + VM_NUMBER_TAG, newSettings.voicemailNumber);
2099         String fwdKey = key + FWD_SETTINGS_TAG;
2100         CallForwardInfo[] s = newSettings.forwardingSettings;
2101         if (s != FWD_SETTINGS_DONT_TOUCH) {
2102             editor.putInt(fwdKey + FWD_SETTINGS_LENGTH_TAG, s.length);
2103             for (int i = 0; i < s.length; i++) {
2104                 final String settingKey = fwdKey + FWD_SETTING_TAG + String.valueOf(i);
2105                 final CallForwardInfo fi = s[i];
2106                 editor.putInt(settingKey + FWD_SETTING_STATUS, fi.status);
2107                 editor.putInt(settingKey + FWD_SETTING_REASON, fi.reason);
2108                 editor.putString(settingKey + FWD_SETTING_NUMBER, fi.number);
2109                 editor.putInt(settingKey + FWD_SETTING_TIME, fi.timeSeconds);
2110             }
2111         } else {
2112             editor.putInt(fwdKey + FWD_SETTINGS_LENGTH_TAG, 0);
2113         }
2114         editor.apply();
2115     }
2116
2117     /**
2118      * Returns settings previously stored for the currently selected
2119      * voice mail provider. If none is stored returns null.
2120      * If the user switches to a voice mail provider and we have settings
2121      * stored for it we will automatically change the phone's voice mail number
2122      * and forwarding number to the stored one. Otherwise we will bring up provider's configuration
2123      * UI.
2124      */
2125     private VoiceMailProviderSettings loadSettingsForVoiceMailProvider(String key) {
2126         final String vmNumberSetting = mPerProviderSavedVMNumbers.getString(key + VM_NUMBER_TAG,
2127                 null);
2128         if (vmNumberSetting == null) {
2129             Log.w(LOG_TAG, "VoiceMailProvider settings for the key \"" + key + "\""
2130                     + " was not found. Returning null.");
2131             return null;
2132         }
2133
2134         CallForwardInfo[] cfi = FWD_SETTINGS_DONT_TOUCH;
2135         String fwdKey = key + FWD_SETTINGS_TAG;
2136         final int fwdLen = mPerProviderSavedVMNumbers.getInt(fwdKey + FWD_SETTINGS_LENGTH_TAG, 0);
2137         if (fwdLen > 0) {
2138             cfi = new CallForwardInfo[fwdLen];
2139             for (int i = 0; i < cfi.length; i++) {
2140                 final String settingKey = fwdKey + FWD_SETTING_TAG + String.valueOf(i);
2141                 cfi[i] = new CallForwardInfo();
2142                 cfi[i].status = mPerProviderSavedVMNumbers.getInt(
2143                         settingKey + FWD_SETTING_STATUS, 0);
2144                 cfi[i].reason = mPerProviderSavedVMNumbers.getInt(
2145                         settingKey + FWD_SETTING_REASON,
2146                         CommandsInterface.CF_REASON_ALL_CONDITIONAL);
2147                 cfi[i].serviceClass = CommandsInterface.SERVICE_CLASS_VOICE;
2148                 cfi[i].toa = PhoneNumberUtils.TOA_International;
2149                 cfi[i].number = mPerProviderSavedVMNumbers.getString(
2150                         settingKey + FWD_SETTING_NUMBER, "");
2151                 cfi[i].timeSeconds = mPerProviderSavedVMNumbers.getInt(
2152                         settingKey + FWD_SETTING_TIME, 20);
2153             }
2154         }
2155
2156         VoiceMailProviderSettings settings =  new VoiceMailProviderSettings(vmNumberSetting, cfi);
2157         if (DBG) log("Loaded settings for " + key + ": " + settings.toString());
2158         return settings;
2159     }
2160
2161     /**
2162      * Deletes settings for the specified provider.
2163      */
2164     private void deleteSettingsForVoicemailProvider(String key) {
2165         if (DBG) log("Deleting settings for" + key);
2166         if (mVoicemailProviders == null) {
2167             return;
2168         }
2169         mPerProviderSavedVMNumbers.edit()
2170             .putString(key + VM_NUMBER_TAG, null)
2171             .putInt(key + FWD_SETTINGS_TAG + FWD_SETTINGS_LENGTH_TAG, 0)
2172             .commit();
2173     }
2174
2175     private String getCurrentVoicemailProviderKey() {
2176         final String key = mVoicemailProviders.getValue();
2177         return (key != null) ? key : DEFAULT_VM_PROVIDER_KEY;
2178     }
2179
2180     @Override
2181     public boolean onOptionsItemSelected(MenuItem item) {
2182         final int itemId = item.getItemId();
2183         if (itemId == android.R.id.home) {  // See ActionBar#setDisplayHomeAsUpEnabled()
2184             Intent intent = new Intent();
2185             intent.setClassName(UP_ACTIVITY_PACKAGE, UP_ACTIVITY_CLASS);
2186             intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
2187             startActivity(intent);
2188             finish();
2189             return true;
2190         }
2191         return super.onOptionsItemSelected(item);
2192     }
2193
2194     /**
2195      * Finish current Activity and go up to the top level Settings ({@link CallFeaturesSetting}).
2196      * This is useful for implementing "HomeAsUp" capability for second-level Settings.
2197      */
2198     public static void goUpToTopLevelSetting(Activity activity) {
2199         Intent intent = new Intent(activity, CallFeaturesSetting.class);
2200         intent.setAction(Intent.ACTION_MAIN);
2201         intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
2202         activity.startActivity(intent);
2203         activity.finish();
2204     }
2205 }