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