• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2010 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.services.telephony.sip;
18 
19 import android.app.AlertDialog;
20 import android.content.Intent;
21 import android.net.sip.SipProfile;
22 import android.os.Bundle;
23 import android.os.Parcelable;
24 import android.preference.CheckBoxPreference;
25 import android.preference.EditTextPreference;
26 import android.preference.ListPreference;
27 import android.preference.Preference;
28 import android.preference.PreferenceActivity;
29 import android.preference.PreferenceGroup;
30 import android.text.TextUtils;
31 import android.util.Log;
32 import android.view.KeyEvent;
33 import android.view.Menu;
34 import android.view.MenuItem;
35 import android.widget.Button;
36 import android.widget.Toast;
37 
38 import java.io.IOException;
39 import java.lang.reflect.Method;
40 import java.util.Arrays;
41 
42 /**
43  * The activity class for editing a new or existing SIP profile.
44  */
45 public class SipEditor extends PreferenceActivity
46         implements Preference.OnPreferenceChangeListener {
47     private static final String PREFIX = "[SipEditor] ";
48     private static final boolean VERBOSE = false; /* STOP SHIP if true */
49 
50     private static final int MENU_SAVE = Menu.FIRST;
51     private static final int MENU_DISCARD = Menu.FIRST + 1;
52     private static final int MENU_REMOVE = Menu.FIRST + 2;
53 
54     private static final String KEY_PROFILE = "profile";
55     private static final String GET_METHOD_PREFIX = "get";
56     private static final char SCRAMBLED = '*';
57     private static final int NA = 0;
58 
59     private AdvancedSettings mAdvancedSettings;
60     private SipPreferences mSipPreferences;
61     private boolean mDisplayNameSet;
62     private boolean mHomeButtonClicked;
63     private boolean mUpdateRequired;
64 
65     private SipProfileDb mProfileDb;
66     private SipProfile mOldProfile;
67     private Button mRemoveButton;
68     private SipAccountRegistry mSipAccountRegistry;
69 
70     enum PreferenceKey {
71         Username(R.string.username, 0, R.string.default_preference_summary_username),
72         Password(R.string.password, 0, R.string.default_preference_summary_password),
73         DomainAddress(R.string.domain_address, 0,
74                 R.string.default_preference_summary_domain_address),
75         DisplayName(R.string.display_name, 0, R.string.display_name_summary),
76         ProxyAddress(R.string.proxy_address, 0, R.string.optional_summary),
77         Port(R.string.port, R.string.default_port, R.string.default_port),
78         Transport(R.string.transport, R.string.default_transport, NA),
79         SendKeepAlive(R.string.send_keepalive, R.string.sip_system_decide, NA),
80         AuthUserName(R.string.auth_username, 0, R.string.optional_summary);
81 
82         final int text;
83         final int initValue;
84         final int defaultSummary;
85         Preference preference;
86 
87         /**
88          * @param key The key name of the preference.
89          * @param initValue The initial value of the preference.
90          * @param defaultSummary The default summary value of the preference
91          *        when the preference value is empty.
92          */
PreferenceKey(int text, int initValue, int defaultSummary)93         PreferenceKey(int text, int initValue, int defaultSummary) {
94             this.text = text;
95             this.initValue = initValue;
96             this.defaultSummary = defaultSummary;
97         }
98 
getValue()99         String getValue() {
100             if (preference instanceof EditTextPreference) {
101                 return ((EditTextPreference) preference).getText();
102             } else if (preference instanceof ListPreference) {
103                 return ((ListPreference) preference).getValue();
104             }
105             throw new RuntimeException("getValue() for the preference " + this);
106         }
107 
setValue(String value)108         void setValue(String value) {
109             if (preference instanceof EditTextPreference) {
110                 String oldValue = getValue();
111                 ((EditTextPreference) preference).setText(value);
112                 if (this != Password) {
113                     if (VERBOSE) {
114                         log(this + ": setValue() " + value + ": " + oldValue + " --> " +
115                                 getValue());
116                     }
117                 }
118             } else if (preference instanceof ListPreference) {
119                 ((ListPreference) preference).setValue(value);
120             }
121 
122             if (TextUtils.isEmpty(value)) {
123                 preference.setSummary(defaultSummary);
124             } else if (this == Password) {
125                 preference.setSummary(scramble(value));
126             } else if ((this == DisplayName)
127                     && value.equals(getDefaultDisplayName())) {
128                 preference.setSummary(defaultSummary);
129             } else {
130                 preference.setSummary(value);
131             }
132         }
133     }
134 
135     @Override
onResume()136     public void onResume() {
137         super.onResume();
138         mHomeButtonClicked = false;
139         if (!SipUtil.isPhoneIdle(this)) {
140             mAdvancedSettings.show();
141             getPreferenceScreen().setEnabled(false);
142             if (mRemoveButton != null) mRemoveButton.setEnabled(false);
143         } else {
144             getPreferenceScreen().setEnabled(true);
145             if (mRemoveButton != null) mRemoveButton.setEnabled(true);
146         }
147     }
148 
149     @Override
onCreate(Bundle savedInstanceState)150     public void onCreate(Bundle savedInstanceState) {
151         if (VERBOSE) log("onCreate, start profile editor");
152         super.onCreate(savedInstanceState);
153 
154         mSipPreferences = new SipPreferences(this);
155         mProfileDb = new SipProfileDb(this);
156         mSipAccountRegistry = SipAccountRegistry.getInstance();
157 
158         setContentView(R.layout.sip_settings_ui);
159         addPreferencesFromResource(R.xml.sip_edit);
160 
161         SipProfile p = mOldProfile = (SipProfile) ((savedInstanceState == null)
162                 ? getIntent().getParcelableExtra(SipSettings.KEY_SIP_PROFILE)
163                 : savedInstanceState.getParcelable(KEY_PROFILE));
164 
165         PreferenceGroup screen = (PreferenceGroup) getPreferenceScreen();
166         for (int i = 0, n = screen.getPreferenceCount(); i < n; i++) {
167             setupPreference(screen.getPreference(i));
168         }
169 
170         if (p == null) {
171             screen.setTitle(R.string.sip_edit_new_title);
172         }
173 
174         mAdvancedSettings = new AdvancedSettings();
175 
176         loadPreferencesFromProfile(p);
177     }
178 
179     @Override
onPause()180     public void onPause() {
181         if (VERBOSE) log("onPause, finishing: " + isFinishing());
182         if (!isFinishing()) {
183             mHomeButtonClicked = true;
184             validateAndSetResult();
185         }
186         super.onPause();
187     }
188 
189     @Override
onCreateOptionsMenu(Menu menu)190     public boolean onCreateOptionsMenu(Menu menu) {
191         super.onCreateOptionsMenu(menu);
192         menu.add(0, MENU_DISCARD, 0, R.string.sip_menu_discard)
193                 .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
194         menu.add(0, MENU_SAVE, 0, R.string.sip_menu_save)
195                 .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
196         menu.add(0, MENU_REMOVE, 0, R.string.remove_sip_account)
197                 .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
198         return true;
199     }
200 
201     @Override
onPrepareOptionsMenu(Menu menu)202     public boolean onPrepareOptionsMenu(Menu menu) {
203         MenuItem removeMenu = menu.findItem(MENU_REMOVE);
204         removeMenu.setVisible(mOldProfile != null);
205         menu.findItem(MENU_SAVE).setEnabled(mUpdateRequired);
206         return super.onPrepareOptionsMenu(menu);
207     }
208 
209     @Override
onOptionsItemSelected(MenuItem item)210     public boolean onOptionsItemSelected(MenuItem item) {
211         switch (item.getItemId()) {
212             case MENU_SAVE:
213                 validateAndSetResult();
214                 return true;
215 
216             case MENU_DISCARD:
217                 finish();
218                 return true;
219 
220             case MENU_REMOVE: {
221                 setRemovedProfileAndFinish();
222                 return true;
223             }
224             case android.R.id.home: {
225                 finish();
226                 return true;
227             }
228         }
229         return super.onOptionsItemSelected(item);
230     }
231 
232     @Override
onKeyDown(int keyCode, KeyEvent event)233     public boolean onKeyDown(int keyCode, KeyEvent event) {
234         switch (keyCode) {
235             case KeyEvent.KEYCODE_BACK:
236                 validateAndSetResult();
237                 return true;
238         }
239         return super.onKeyDown(keyCode, event);
240     }
241 
242     /**
243      * Saves a {@link SipProfile} and registers the associated
244      * {@link android.telecom.PhoneAccount}.
245      *
246      * @param p The {@link SipProfile} to register.
247      * @param enableProfile {@code true} if profile should be enabled, too.
248      * @throws IOException Exception resulting from profile save.
249      */
saveAndRegisterProfile(SipProfile p, boolean enableProfile)250     private void saveAndRegisterProfile(SipProfile p, boolean enableProfile) throws IOException {
251         if (p == null) return;
252         mProfileDb.saveProfile(p);
253         mSipAccountRegistry.startSipService(this, p.getProfileName(), enableProfile);
254     }
255 
256     /**
257      * Deletes a {@link SipProfile} and un-registers the associated
258      * {@link android.telecom.PhoneAccount}.
259      *
260      * @param p The {@link SipProfile} to delete.
261      */
deleteAndUnregisterProfile(SipProfile p)262     private void deleteAndUnregisterProfile(SipProfile p) {
263         if (p == null) return;
264         mProfileDb.deleteProfile(p);
265         mSipAccountRegistry.stopSipService(this, p.getProfileName());
266     }
267 
setRemovedProfileAndFinish()268     private void setRemovedProfileAndFinish() {
269         Intent intent = new Intent(this, SipSettings.class);
270         setResult(RESULT_FIRST_USER, intent);
271         Toast.makeText(this, R.string.removing_account, Toast.LENGTH_SHORT)
272                 .show();
273         replaceProfile(mOldProfile, null);
274         // do finish() in replaceProfile() in a background thread
275     }
276 
showAlert(Throwable e)277     private void showAlert(Throwable e) {
278         String msg = e.getMessage();
279         if (TextUtils.isEmpty(msg)) msg = e.toString();
280         showAlert(msg);
281     }
282 
showAlert(final String message)283     private void showAlert(final String message) {
284         if (mHomeButtonClicked) {
285             if (VERBOSE) log("Home button clicked, don't show dialog: " + message);
286             return;
287         }
288         runOnUiThread(new Runnable() {
289             @Override
290             public void run() {
291                 new AlertDialog.Builder(SipEditor.this)
292                         .setTitle(android.R.string.dialog_alert_title)
293                         .setIconAttribute(android.R.attr.alertDialogIcon)
294                         .setMessage(message)
295                         .setPositiveButton(R.string.alert_dialog_ok, null)
296                         .show();
297             }
298         });
299     }
300 
isEditTextEmpty(PreferenceKey key)301     private boolean isEditTextEmpty(PreferenceKey key) {
302         EditTextPreference pref = (EditTextPreference) key.preference;
303         return TextUtils.isEmpty(pref.getText())
304                 || pref.getSummary().equals(getString(key.defaultSummary));
305     }
306 
validateAndSetResult()307     private void validateAndSetResult() {
308         boolean allEmpty = true;
309         CharSequence firstEmptyFieldTitle = null;
310         for (PreferenceKey key : PreferenceKey.values()) {
311             Preference p = key.preference;
312             if (p instanceof EditTextPreference) {
313                 EditTextPreference pref = (EditTextPreference) p;
314                 boolean fieldEmpty = isEditTextEmpty(key);
315                 if (allEmpty && !fieldEmpty) allEmpty = false;
316 
317                 // use default value if display name is empty
318                 if (fieldEmpty) {
319                     switch (key) {
320                         case DisplayName:
321                             pref.setText(getDefaultDisplayName());
322                             break;
323                         case AuthUserName:
324                         case ProxyAddress:
325                             // optional; do nothing
326                             break;
327                         case Port:
328                             pref.setText(getString(R.string.default_port));
329                             break;
330                         default:
331                             if (firstEmptyFieldTitle == null) {
332                                 firstEmptyFieldTitle = pref.getTitle();
333                             }
334                     }
335                 } else if (key == PreferenceKey.Port) {
336                     int port = Integer.parseInt(PreferenceKey.Port.getValue());
337                     if ((port < 1000) || (port > 65534)) {
338                         showAlert(getString(R.string.not_a_valid_port));
339                         return;
340                     }
341                 }
342             }
343         }
344 
345         if (!mUpdateRequired) {
346             finish();
347             return;
348         } else if (allEmpty) {
349             showAlert(getString(R.string.all_empty_alert));
350             return;
351         } else if (firstEmptyFieldTitle != null) {
352             showAlert(getString(R.string.empty_alert, firstEmptyFieldTitle));
353             return;
354         }
355         try {
356             SipProfile profile = createSipProfile();
357             Intent intent = new Intent(this, SipSettings.class);
358             intent.putExtra(SipSettings.KEY_SIP_PROFILE, (Parcelable) profile);
359             setResult(RESULT_OK, intent);
360             Toast.makeText(this, R.string.saving_account, Toast.LENGTH_SHORT).show();
361 
362             replaceProfile(mOldProfile, profile);
363             // do finish() in replaceProfile() in a background thread
364         } catch (Exception e) {
365             log("validateAndSetResult, can not create new SipProfile, exception: " + e);
366             showAlert(e);
367         }
368     }
369 
replaceProfile(final SipProfile oldProfile, final SipProfile newProfile)370     private void replaceProfile(final SipProfile oldProfile, final SipProfile newProfile) {
371         // Replace profile in a background thread as it takes time to access the
372         // storage; do finish() once everything goes fine.
373         // newProfile may be null if the old profile is to be deleted rather
374         // than being modified.
375         new Thread(new Runnable() {
376             public void run() {
377                 try {
378                     deleteAndUnregisterProfile(oldProfile);
379                     boolean autoEnableNewProfile = oldProfile == null;
380                     saveAndRegisterProfile(newProfile, autoEnableNewProfile);
381                     finish();
382                 } catch (Exception e) {
383                     log("replaceProfile, can not save/register new SipProfile, exception: " + e);
384                     showAlert(e);
385                 }
386             }
387         }, "SipEditor").start();
388     }
389 
getProfileName()390     private String getProfileName() {
391         return PreferenceKey.Username.getValue() + "@"
392                 + PreferenceKey.DomainAddress.getValue();
393     }
394 
createSipProfile()395     private SipProfile createSipProfile() throws Exception {
396         return new SipProfile.Builder(
397                 PreferenceKey.Username.getValue(),
398                 PreferenceKey.DomainAddress.getValue())
399                 .setProfileName(getProfileName())
400                 .setPassword(PreferenceKey.Password.getValue())
401                 .setOutboundProxy(PreferenceKey.ProxyAddress.getValue())
402                 .setProtocol(PreferenceKey.Transport.getValue())
403                 .setDisplayName(PreferenceKey.DisplayName.getValue())
404                 .setPort(Integer.parseInt(PreferenceKey.Port.getValue()))
405                 .setSendKeepAlive(isAlwaysSendKeepAlive())
406                 .setAutoRegistration(
407                         mSipPreferences.isReceivingCallsEnabled())
408                 .setAuthUserName(PreferenceKey.AuthUserName.getValue())
409                 .build();
410     }
411 
onPreferenceChange(Preference pref, Object newValue)412     public boolean onPreferenceChange(Preference pref, Object newValue) {
413         if (!mUpdateRequired) {
414             mUpdateRequired = true;
415         }
416 
417         if (pref instanceof CheckBoxPreference) {
418             invalidateOptionsMenu();
419             return true;
420         }
421         String value = (newValue == null) ? "" : newValue.toString();
422         if (TextUtils.isEmpty(value)) {
423             pref.setSummary(getPreferenceKey(pref).defaultSummary);
424         } else if (pref == PreferenceKey.Password.preference) {
425             pref.setSummary(scramble(value));
426         } else {
427             pref.setSummary(value);
428         }
429 
430         if (pref == PreferenceKey.DisplayName.preference) {
431             ((EditTextPreference) pref).setText(value);
432             checkIfDisplayNameSet();
433         }
434 
435         // SAVE menu should be enabled once the user modified some preference.
436         invalidateOptionsMenu();
437         return true;
438     }
439 
getPreferenceKey(Preference pref)440     private PreferenceKey getPreferenceKey(Preference pref) {
441         for (PreferenceKey key : PreferenceKey.values()) {
442             if (key.preference == pref) return key;
443         }
444         throw new RuntimeException("not possible to reach here");
445     }
446 
loadPreferencesFromProfile(SipProfile p)447     private void loadPreferencesFromProfile(SipProfile p) {
448         if (p != null) {
449             if (VERBOSE) log("loadPreferencesFromProfile, existing profile: " + p.getProfileName());
450             try {
451                 Class profileClass = SipProfile.class;
452                 for (PreferenceKey key : PreferenceKey.values()) {
453                     Method meth = profileClass.getMethod(GET_METHOD_PREFIX
454                             + getString(key.text), (Class[])null);
455                     if (key == PreferenceKey.SendKeepAlive) {
456                         boolean value = ((Boolean) meth.invoke(p, (Object[]) null)).booleanValue();
457                         key.setValue(getString(value
458                                 ? R.string.sip_always_send_keepalive
459                                 : R.string.sip_system_decide));
460                     } else {
461                         Object value = meth.invoke(p, (Object[])null);
462                         key.setValue((value == null) ? "" : value.toString());
463                     }
464                 }
465                 checkIfDisplayNameSet();
466             } catch (Exception e) {
467                 log("loadPreferencesFromProfile, can not load pref from profile, exception: " + e);
468             }
469         } else {
470             if (VERBOSE) log("loadPreferencesFromProfile, edit a new profile");
471             for (PreferenceKey key : PreferenceKey.values()) {
472                 key.preference.setOnPreferenceChangeListener(this);
473 
474                 // FIXME: android:defaultValue in preference xml file doesn't
475                 // work. Even if we setValue() for each preference in the case
476                 // of (p != null), the dialog still shows android:defaultValue,
477                 // not the value set by setValue(). This happens if
478                 // android:defaultValue is not empty. Is it a bug?
479                 if (key.initValue != 0) {
480                     key.setValue(getString(key.initValue));
481                 }
482             }
483             mDisplayNameSet = false;
484         }
485     }
486 
isAlwaysSendKeepAlive()487     private boolean isAlwaysSendKeepAlive() {
488         ListPreference pref = (ListPreference) PreferenceKey.SendKeepAlive.preference;
489         return getString(R.string.sip_always_send_keepalive).equals(pref.getValue());
490     }
491 
setCheckBox(PreferenceKey key, boolean checked)492     private void setCheckBox(PreferenceKey key, boolean checked) {
493         CheckBoxPreference pref = (CheckBoxPreference) key.preference;
494         pref.setChecked(checked);
495     }
496 
setupPreference(Preference pref)497     private void setupPreference(Preference pref) {
498         pref.setOnPreferenceChangeListener(this);
499         for (PreferenceKey key : PreferenceKey.values()) {
500             String name = getString(key.text);
501             if (name.equals(pref.getKey())) {
502                 key.preference = pref;
503                 return;
504             }
505         }
506     }
507 
checkIfDisplayNameSet()508     private void checkIfDisplayNameSet() {
509         String displayName = PreferenceKey.DisplayName.getValue();
510         mDisplayNameSet = !TextUtils.isEmpty(displayName)
511                 && !displayName.equals(getDefaultDisplayName());
512         if (VERBOSE) log("checkIfDisplayNameSet, displayName set: " + mDisplayNameSet);
513         if (mDisplayNameSet) {
514             PreferenceKey.DisplayName.preference.setSummary(displayName);
515         } else {
516             PreferenceKey.DisplayName.setValue("");
517         }
518     }
519 
getDefaultDisplayName()520     private static String getDefaultDisplayName() {
521         return PreferenceKey.Username.getValue();
522     }
523 
scramble(String s)524     private static String scramble(String s) {
525         char[] cc = new char[s.length()];
526         Arrays.fill(cc, SCRAMBLED);
527         return new String(cc);
528     }
529 
530     private class AdvancedSettings implements Preference.OnPreferenceClickListener {
531         private Preference mAdvancedSettingsTrigger;
532         private Preference[] mPreferences;
533         private boolean mShowing = false;
534 
AdvancedSettings()535         AdvancedSettings() {
536             mAdvancedSettingsTrigger = getPreferenceScreen().findPreference(
537                     getString(R.string.advanced_settings));
538             mAdvancedSettingsTrigger.setOnPreferenceClickListener(this);
539 
540             loadAdvancedPreferences();
541         }
542 
loadAdvancedPreferences()543         private void loadAdvancedPreferences() {
544             PreferenceGroup screen = (PreferenceGroup) getPreferenceScreen();
545 
546             addPreferencesFromResource(R.xml.sip_advanced_edit);
547             PreferenceGroup group = (PreferenceGroup) screen.findPreference(
548                     getString(R.string.advanced_settings_container));
549             screen.removePreference(group);
550 
551             mPreferences = new Preference[group.getPreferenceCount()];
552             int order = screen.getPreferenceCount();
553             for (int i = 0, n = mPreferences.length; i < n; i++) {
554                 Preference pref = group.getPreference(i);
555                 pref.setOrder(order++);
556                 setupPreference(pref);
557                 mPreferences[i] = pref;
558             }
559         }
560 
show()561         void show() {
562             mShowing = true;
563             mAdvancedSettingsTrigger.setSummary(R.string.advanced_settings_hide);
564             PreferenceGroup screen = (PreferenceGroup) getPreferenceScreen();
565             for (Preference pref : mPreferences) {
566                 screen.addPreference(pref);
567                 if (VERBOSE) {
568                     log("AdvancedSettings.show, pref: " + pref.getKey() + ", order: " +
569                             pref.getOrder());
570                 }
571             }
572         }
573 
hide()574         private void hide() {
575             mShowing = false;
576             mAdvancedSettingsTrigger.setSummary(R.string.advanced_settings_show);
577             PreferenceGroup screen = (PreferenceGroup) getPreferenceScreen();
578             for (Preference pref : mPreferences) {
579                 screen.removePreference(pref);
580             }
581         }
582 
onPreferenceClick(Preference preference)583         public boolean onPreferenceClick(Preference preference) {
584             if (VERBOSE) log("AdvancedSettings.onPreferenceClick");
585             if (!mShowing) {
586                 show();
587             } else {
588                 hide();
589             }
590             return true;
591         }
592     }
593 
log(String msg)594     private static void log(String msg) {
595         Log.d(SipUtil.LOG_TAG, PREFIX + msg);
596     }
597 }
598