blob: ba06711c8da35b5fb326e4417ef16fa5e46a960f [file] [log] [blame]
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.services.telephony.sip;
import android.app.AlertDialog;
import android.content.Intent;
import android.net.sip.SipManager;
import android.net.sip.SipProfile;
import android.os.Bundle;
import android.os.Parcelable;
import android.preference.CheckBoxPreference;
import android.preference.EditTextPreference;
import android.preference.ListPreference;
import android.preference.Preference;
import android.preference.PreferenceActivity;
import android.preference.PreferenceGroup;
import android.text.TextUtils;
import android.util.Log;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.Button;
import android.widget.Toast;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Arrays;
/**
* The activity class for editing a new or existing SIP profile.
*/
public class SipEditor extends PreferenceActivity
implements Preference.OnPreferenceChangeListener {
private static final String PREFIX = "[SipEditor] ";
private static final boolean VERBOSE = false; /* STOP SHIP if true */
private static final int MENU_SAVE = Menu.FIRST;
private static final int MENU_DISCARD = Menu.FIRST + 1;
private static final int MENU_REMOVE = Menu.FIRST + 2;
private static final String KEY_PROFILE = "profile";
private static final String GET_METHOD_PREFIX = "get";
private static final char SCRAMBLED = '*';
private static final int NA = 0;
private AdvancedSettings mAdvancedSettings;
private SipSharedPreferences mSharedPreferences;
private boolean mDisplayNameSet;
private boolean mHomeButtonClicked;
private boolean mUpdateRequired;
private SipProfileDb mProfileDb;
private SipProfile mOldProfile;
private Button mRemoveButton;
private SipAccountRegistry mSipAccountRegistry;
enum PreferenceKey {
Username(R.string.username, 0, R.string.default_preference_summary),
Password(R.string.password, 0, R.string.default_preference_summary),
DomainAddress(R.string.domain_address, 0, R.string.default_preference_summary),
DisplayName(R.string.display_name, 0, R.string.display_name_summary),
ProxyAddress(R.string.proxy_address, 0, R.string.optional_summary),
Port(R.string.port, R.string.default_port, R.string.default_port),
Transport(R.string.transport, R.string.default_transport, NA),
SendKeepAlive(R.string.send_keepalive, R.string.sip_system_decide, NA),
AuthUserName(R.string.auth_username, 0, R.string.optional_summary);
final int text;
final int initValue;
final int defaultSummary;
Preference preference;
/**
* @param key The key name of the preference.
* @param initValue The initial value of the preference.
* @param defaultSummary The default summary value of the preference
* when the preference value is empty.
*/
PreferenceKey(int text, int initValue, int defaultSummary) {
this.text = text;
this.initValue = initValue;
this.defaultSummary = defaultSummary;
}
String getValue() {
if (preference instanceof EditTextPreference) {
return ((EditTextPreference) preference).getText();
} else if (preference instanceof ListPreference) {
return ((ListPreference) preference).getValue();
}
throw new RuntimeException("getValue() for the preference " + this);
}
void setValue(String value) {
if (preference instanceof EditTextPreference) {
String oldValue = getValue();
((EditTextPreference) preference).setText(value);
if (this != Password) {
if (VERBOSE) {
log(this + ": setValue() " + value + ": " + oldValue + " --> " +
getValue());
}
}
} else if (preference instanceof ListPreference) {
((ListPreference) preference).setValue(value);
}
if (TextUtils.isEmpty(value)) {
preference.setSummary(defaultSummary);
} else if (this == Password) {
preference.setSummary(scramble(value));
} else if ((this == DisplayName)
&& value.equals(getDefaultDisplayName())) {
preference.setSummary(defaultSummary);
} else {
preference.setSummary(value);
}
}
}
@Override
public void onResume() {
super.onResume();
mHomeButtonClicked = false;
if (!SipUtil.isPhoneIdle(this)) {
mAdvancedSettings.show();
getPreferenceScreen().setEnabled(false);
if (mRemoveButton != null) mRemoveButton.setEnabled(false);
} else {
getPreferenceScreen().setEnabled(true);
if (mRemoveButton != null) mRemoveButton.setEnabled(true);
}
}
@Override
public void onCreate(Bundle savedInstanceState) {
if (VERBOSE) log("onCreate, start profile editor");
super.onCreate(savedInstanceState);
mSharedPreferences = new SipSharedPreferences(this);
mProfileDb = new SipProfileDb(this);
mSipAccountRegistry = SipAccountRegistry.getInstance();
setContentView(R.layout.sip_settings_ui);
addPreferencesFromResource(R.xml.sip_edit);
SipProfile p = mOldProfile = (SipProfile) ((savedInstanceState == null)
? getIntent().getParcelableExtra(SipSettings.KEY_SIP_PROFILE)
: savedInstanceState.getParcelable(KEY_PROFILE));
PreferenceGroup screen = (PreferenceGroup) getPreferenceScreen();
for (int i = 0, n = screen.getPreferenceCount(); i < n; i++) {
setupPreference(screen.getPreference(i));
}
if (p == null) {
screen.setTitle(R.string.sip_edit_new_title);
}
mAdvancedSettings = new AdvancedSettings();
loadPreferencesFromProfile(p);
}
@Override
public void onPause() {
if (VERBOSE) log("onPause, finishing: " + isFinishing());
if (!isFinishing()) {
mHomeButtonClicked = true;
validateAndSetResult();
}
super.onPause();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
menu.add(0, MENU_DISCARD, 0, R.string.sip_menu_discard)
.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
menu.add(0, MENU_SAVE, 0, R.string.sip_menu_save)
.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
menu.add(0, MENU_REMOVE, 0, R.string.remove_sip_account)
.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
return true;
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
MenuItem removeMenu = menu.findItem(MENU_REMOVE);
removeMenu.setVisible(mOldProfile != null);
menu.findItem(MENU_SAVE).setEnabled(mUpdateRequired);
return super.onPrepareOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case MENU_SAVE:
validateAndSetResult();
return true;
case MENU_DISCARD:
finish();
return true;
case MENU_REMOVE: {
setRemovedProfileAndFinish();
return true;
}
case android.R.id.home: {
finish();
return true;
}
}
return super.onOptionsItemSelected(item);
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
switch (keyCode) {
case KeyEvent.KEYCODE_BACK:
validateAndSetResult();
return true;
}
return super.onKeyDown(keyCode, event);
}
/**
* Saves a {@link SipProfile} and registers the associated
* {@link android.telecom.PhoneAccount}.
*
* @param p The {@link SipProfile} to register.
* @throws IOException Exception resulting from profile save.
*/
private void saveAndRegisterProfile(SipProfile p) throws IOException {
if (p == null) return;
mProfileDb.saveProfile(p);
mSipAccountRegistry.startSipService(this, p.getUriString());
}
/**
* Deletes a {@link SipProfile} and un-registers the associated
* {@link android.telecom.PhoneAccount}.
*
* @param p The {@link SipProfile} to delete.
*/
private void deleteAndUnregisterProfile(SipProfile p) {
if (p == null) return;
mProfileDb.deleteProfile(p);
mSipAccountRegistry.stopSipService(this, p.getUriString());
}
private void setRemovedProfileAndFinish() {
Intent intent = new Intent(this, SipSettings.class);
setResult(RESULT_FIRST_USER, intent);
Toast.makeText(this, R.string.removing_account, Toast.LENGTH_SHORT)
.show();
replaceProfile(mOldProfile, null);
// do finish() in replaceProfile() in a background thread
}
private void showAlert(Throwable e) {
String msg = e.getMessage();
if (TextUtils.isEmpty(msg)) msg = e.toString();
showAlert(msg);
}
private void showAlert(final String message) {
if (mHomeButtonClicked) {
if (VERBOSE) log("Home button clicked, don't show dialog: " + message);
return;
}
runOnUiThread(new Runnable() {
@Override
public void run() {
new AlertDialog.Builder(SipEditor.this)
.setTitle(android.R.string.dialog_alert_title)
.setIconAttribute(android.R.attr.alertDialogIcon)
.setMessage(message)
.setPositiveButton(R.string.alert_dialog_ok, null)
.show();
}
});
}
private boolean isEditTextEmpty(PreferenceKey key) {
EditTextPreference pref = (EditTextPreference) key.preference;
return TextUtils.isEmpty(pref.getText())
|| pref.getSummary().equals(getString(key.defaultSummary));
}
private void validateAndSetResult() {
boolean allEmpty = true;
CharSequence firstEmptyFieldTitle = null;
for (PreferenceKey key : PreferenceKey.values()) {
Preference p = key.preference;
if (p instanceof EditTextPreference) {
EditTextPreference pref = (EditTextPreference) p;
boolean fieldEmpty = isEditTextEmpty(key);
if (allEmpty && !fieldEmpty) allEmpty = false;
// use default value if display name is empty
if (fieldEmpty) {
switch (key) {
case DisplayName:
pref.setText(getDefaultDisplayName());
break;
case AuthUserName:
case ProxyAddress:
// optional; do nothing
break;
case Port:
pref.setText(getString(R.string.default_port));
break;
default:
if (firstEmptyFieldTitle == null) {
firstEmptyFieldTitle = pref.getTitle();
}
}
} else if (key == PreferenceKey.Port) {
int port = Integer.parseInt(PreferenceKey.Port.getValue());
if ((port < 1000) || (port > 65534)) {
showAlert(getString(R.string.not_a_valid_port));
return;
}
}
}
}
if (allEmpty || !mUpdateRequired) {
finish();
return;
} else if (firstEmptyFieldTitle != null) {
showAlert(getString(R.string.empty_alert, firstEmptyFieldTitle));
return;
}
try {
SipProfile profile = createSipProfile();
Intent intent = new Intent(this, SipSettings.class);
intent.putExtra(SipSettings.KEY_SIP_PROFILE, (Parcelable) profile);
setResult(RESULT_OK, intent);
Toast.makeText(this, R.string.saving_account, Toast.LENGTH_SHORT).show();
replaceProfile(mOldProfile, profile);
// do finish() in replaceProfile() in a background thread
} catch (Exception e) {
log("validateAndSetResult, can not create new SipProfile, exception: " + e);
showAlert(e);
}
}
private void replaceProfile(final SipProfile oldProfile, final SipProfile newProfile) {
// Replace profile in a background thread as it takes time to access the
// storage; do finish() once everything goes fine.
// newProfile may be null if the old profile is to be deleted rather
// than being modified.
new Thread(new Runnable() {
public void run() {
try {
deleteAndUnregisterProfile(oldProfile);
saveAndRegisterProfile(newProfile);
finish();
} catch (Exception e) {
log("replaceProfile, can not save/register new SipProfile, exception: " + e);
showAlert(e);
}
}
}, "SipEditor").start();
}
private String getProfileName() {
return PreferenceKey.Username.getValue() + "@"
+ PreferenceKey.DomainAddress.getValue();
}
private SipProfile createSipProfile() throws Exception {
return new SipProfile.Builder(
PreferenceKey.Username.getValue(),
PreferenceKey.DomainAddress.getValue())
.setProfileName(getProfileName())
.setPassword(PreferenceKey.Password.getValue())
.setOutboundProxy(PreferenceKey.ProxyAddress.getValue())
.setProtocol(PreferenceKey.Transport.getValue())
.setDisplayName(PreferenceKey.DisplayName.getValue())
.setPort(Integer.parseInt(PreferenceKey.Port.getValue()))
.setSendKeepAlive(isAlwaysSendKeepAlive())
.setAutoRegistration(
mSharedPreferences.isReceivingCallsEnabled())
.setAuthUserName(PreferenceKey.AuthUserName.getValue())
.build();
}
public boolean onPreferenceChange(Preference pref, Object newValue) {
if (!mUpdateRequired) {
mUpdateRequired = true;
}
if (pref instanceof CheckBoxPreference) {
invalidateOptionsMenu();
return true;
}
String value = (newValue == null) ? "" : newValue.toString();
if (TextUtils.isEmpty(value)) {
pref.setSummary(getPreferenceKey(pref).defaultSummary);
} else if (pref == PreferenceKey.Password.preference) {
pref.setSummary(scramble(value));
} else {
pref.setSummary(value);
}
if (pref == PreferenceKey.DisplayName.preference) {
((EditTextPreference) pref).setText(value);
checkIfDisplayNameSet();
}
// SAVE menu should be enabled once the user modified some preference.
invalidateOptionsMenu();
return true;
}
private PreferenceKey getPreferenceKey(Preference pref) {
for (PreferenceKey key : PreferenceKey.values()) {
if (key.preference == pref) return key;
}
throw new RuntimeException("not possible to reach here");
}
private void loadPreferencesFromProfile(SipProfile p) {
if (p != null) {
if (VERBOSE) log("loadPreferencesFromProfile, existing profile: " + p.getProfileName());
try {
Class profileClass = SipProfile.class;
for (PreferenceKey key : PreferenceKey.values()) {
Method meth = profileClass.getMethod(GET_METHOD_PREFIX
+ getString(key.text), (Class[])null);
if (key == PreferenceKey.SendKeepAlive) {
boolean value = ((Boolean) meth.invoke(p, (Object[]) null)).booleanValue();
key.setValue(getString(value
? R.string.sip_always_send_keepalive
: R.string.sip_system_decide));
} else {
Object value = meth.invoke(p, (Object[])null);
key.setValue((value == null) ? "" : value.toString());
}
}
checkIfDisplayNameSet();
} catch (Exception e) {
log("loadPreferencesFromProfile, can not load pref from profile, exception: " + e);
}
} else {
if (VERBOSE) log("loadPreferencesFromProfile, edit a new profile");
for (PreferenceKey key : PreferenceKey.values()) {
key.preference.setOnPreferenceChangeListener(this);
// FIXME: android:defaultValue in preference xml file doesn't
// work. Even if we setValue() for each preference in the case
// of (p != null), the dialog still shows android:defaultValue,
// not the value set by setValue(). This happens if
// android:defaultValue is not empty. Is it a bug?
if (key.initValue != 0) {
key.setValue(getString(key.initValue));
}
}
mDisplayNameSet = false;
}
}
private boolean isAlwaysSendKeepAlive() {
ListPreference pref = (ListPreference) PreferenceKey.SendKeepAlive.preference;
return getString(R.string.sip_always_send_keepalive).equals(pref.getValue());
}
private void setCheckBox(PreferenceKey key, boolean checked) {
CheckBoxPreference pref = (CheckBoxPreference) key.preference;
pref.setChecked(checked);
}
private void setupPreference(Preference pref) {
pref.setOnPreferenceChangeListener(this);
for (PreferenceKey key : PreferenceKey.values()) {
String name = getString(key.text);
if (name.equals(pref.getKey())) {
key.preference = pref;
return;
}
}
}
private void checkIfDisplayNameSet() {
String displayName = PreferenceKey.DisplayName.getValue();
mDisplayNameSet = !TextUtils.isEmpty(displayName)
&& !displayName.equals(getDefaultDisplayName());
if (VERBOSE) log("checkIfDisplayNameSet, displayName set: " + mDisplayNameSet);
if (mDisplayNameSet) {
PreferenceKey.DisplayName.preference.setSummary(displayName);
} else {
PreferenceKey.DisplayName.setValue("");
}
}
private static String getDefaultDisplayName() {
return PreferenceKey.Username.getValue();
}
private static String scramble(String s) {
char[] cc = new char[s.length()];
Arrays.fill(cc, SCRAMBLED);
return new String(cc);
}
private class AdvancedSettings implements Preference.OnPreferenceClickListener {
private Preference mAdvancedSettingsTrigger;
private Preference[] mPreferences;
private boolean mShowing = false;
AdvancedSettings() {
mAdvancedSettingsTrigger = getPreferenceScreen().findPreference(
getString(R.string.advanced_settings));
mAdvancedSettingsTrigger.setOnPreferenceClickListener(this);
loadAdvancedPreferences();
}
private void loadAdvancedPreferences() {
PreferenceGroup screen = (PreferenceGroup) getPreferenceScreen();
addPreferencesFromResource(R.xml.sip_advanced_edit);
PreferenceGroup group = (PreferenceGroup) screen.findPreference(
getString(R.string.advanced_settings_container));
screen.removePreference(group);
mPreferences = new Preference[group.getPreferenceCount()];
int order = screen.getPreferenceCount();
for (int i = 0, n = mPreferences.length; i < n; i++) {
Preference pref = group.getPreference(i);
pref.setOrder(order++);
setupPreference(pref);
mPreferences[i] = pref;
}
}
void show() {
mShowing = true;
mAdvancedSettingsTrigger.setSummary(R.string.advanced_settings_hide);
PreferenceGroup screen = (PreferenceGroup) getPreferenceScreen();
for (Preference pref : mPreferences) {
screen.addPreference(pref);
if (VERBOSE) {
log("AdvancedSettings.show, pref: " + pref.getKey() + ", order: " +
pref.getOrder());
}
}
}
private void hide() {
mShowing = false;
mAdvancedSettingsTrigger.setSummary(R.string.advanced_settings_show);
PreferenceGroup screen = (PreferenceGroup) getPreferenceScreen();
for (Preference pref : mPreferences) {
screen.removePreference(pref);
}
}
public boolean onPreferenceClick(Preference preference) {
if (VERBOSE) log("AdvancedSettings.onPreferenceClick");
if (!mShowing) {
show();
} else {
hide();
}
return true;
}
}
private static void log(String msg) {
Log.d(SipUtil.LOG_TAG, PREFIX + msg);
}
}