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