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