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