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