summaryrefslogtreecommitdiffstats
path: root/sip/src/com
diff options
context:
space:
mode:
Diffstat (limited to 'sip/src/com')
-rw-r--r--sip/src/com/android/services/telephony/sip/SipBroadcastReceiver.java97
-rw-r--r--sip/src/com/android/services/telephony/sip/SipConnection.java109
-rw-r--r--sip/src/com/android/services/telephony/sip/SipConnectionService.java154
-rw-r--r--sip/src/com/android/services/telephony/sip/SipEditor.java660
-rw-r--r--sip/src/com/android/services/telephony/sip/SipProfileDb.java146
-rw-r--r--sip/src/com/android/services/telephony/sip/SipSettings.java512
-rw-r--r--sip/src/com/android/services/telephony/sip/SipSharedPreferences.java114
-rw-r--r--sip/src/com/android/services/telephony/sip/SipUtil.java50
8 files changed, 1842 insertions, 0 deletions
diff --git a/sip/src/com/android/services/telephony/sip/SipBroadcastReceiver.java b/sip/src/com/android/services/telephony/sip/SipBroadcastReceiver.java
new file mode 100644
index 000000000..b949351e8
--- /dev/null
+++ b/sip/src/com/android/services/telephony/sip/SipBroadcastReceiver.java
@@ -0,0 +1,97 @@
+/*
+ * 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 com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.PhoneFactory;
+import com.android.internal.telephony.sip.SipPhone;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.net.sip.SipAudioCall;
+import android.net.sip.SipException;
+import android.net.sip.SipManager;
+import android.net.sip.SipProfile;
+import android.util.Log;
+
+import java.util.List;
+
+/**
+ * Broadcast receiver that handles SIP-related intents.
+ */
+public class SipBroadcastReceiver extends BroadcastReceiver {
+ private static final String PREFIX = "[SipBroadcastReceiver] ";
+ private static final boolean VERBOSE = true; /* STOP SHIP if true */
+
+ @Override
+ public void onReceive(Context context, final Intent intent) {
+ String action = intent.getAction();
+
+ if (!SipUtil.isVoipSupported(context)) {
+ if (VERBOSE) log("SIP VOIP not supported: " + action);
+ return;
+ }
+
+ if (action.equals(SipManager.ACTION_SIP_INCOMING_CALL)) {
+ takeCall(context, intent);
+ } else if (action.equals(SipManager.ACTION_SIP_SERVICE_UP)) {
+ registerAllProfiles(context);
+ } else {
+ if (VERBOSE) log("onReceive, action not processed: " + action);
+ }
+ }
+
+ private void takeCall(Context context, Intent intent) {
+ if (VERBOSE) log("takeCall, intent: " + intent);
+ // TODO(sail): Add support for incoming SIP calls.
+ }
+
+ private void registerAllProfiles(final Context context) {
+ if (VERBOSE) log("registerAllProfiles, start auto registration");
+ final SipSharedPreferences sipSharedPreferences = new SipSharedPreferences(context);
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+ SipManager sipManager = SipManager.newInstance(context);
+ SipProfileDb profileDb = new SipProfileDb(context);
+ String primaryProfile = sipSharedPreferences.getPrimaryAccount();
+
+ List<SipProfile> sipProfileList = profileDb.retrieveSipProfileList();
+
+ for (SipProfile profile : sipProfileList) {
+ boolean isPrimaryProfile = profile.getUriString().equals(primaryProfile);
+ if (profile.getAutoRegistration() || isPrimaryProfile) {
+ if (VERBOSE) log("registerAllProfiles, profile: " + profile);
+ try {
+ sipManager.open(profile,
+ SipUtil.createIncomingCallPendingIntent(context), null);
+ } catch (SipException e) {
+ log("registerAllProfiles, profile: " + profile.getProfileName() +
+ ", exception: " + e);
+ }
+ }
+ }
+ }}
+ ).start();
+ }
+
+ private static void log(String msg) {
+ Log.d(SipUtil.LOG_TAG, PREFIX + msg);
+ }
+}
diff --git a/sip/src/com/android/services/telephony/sip/SipConnection.java b/sip/src/com/android/services/telephony/sip/SipConnection.java
new file mode 100644
index 000000000..40239a4d7
--- /dev/null
+++ b/sip/src/com/android/services/telephony/sip/SipConnection.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2014 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.telecomm.CallAudioState;
+import android.telecomm.Connection;
+import android.util.Log;
+
+import java.util.List;
+
+public class SipConnection extends Connection {
+ private static final String PREFIX = "[SipConnection] ";
+ private static final boolean VERBOSE = true; /* STOP SHIP if true */
+
+ private final com.android.internal.telephony.Connection mConnection;
+
+ public SipConnection(com.android.internal.telephony.Connection connection) {
+ if (VERBOSE) log("new SipConnection, connection: " + connection);
+ mConnection = connection;
+ }
+
+ @Override
+ protected void onSetAudioState(CallAudioState state) {
+ if (VERBOSE) log("onSetAudioState: " + state);
+ }
+
+ @Override
+ protected void onSetState(int state) {
+ if (VERBOSE) log("onSetState, state: " + Connection.stateToString(state));
+ }
+
+ @Override
+ protected void onPlayDtmfTone(char c) {
+ if (VERBOSE) log("onPlayDtmfTone");
+ }
+
+ @Override
+ protected void onStopDtmfTone() {
+ if (VERBOSE) log("onStopDtmfTone");
+ }
+
+ @Override
+ protected void onDisconnect() {
+ if (VERBOSE) log("onDisconnect");
+ }
+
+ @Override
+ protected void onSeparate() {
+ if (VERBOSE) log("onSeparate");
+ }
+
+ @Override
+ protected void onAbort() {
+ if (VERBOSE) log("onAbort");
+ }
+
+ @Override
+ protected void onHold() {
+ if (VERBOSE) log("onHold");
+ }
+
+ @Override
+ protected void onUnhold() {
+ if (VERBOSE) log("onUnhold");
+ }
+
+ @Override
+ protected void onAnswer() {
+ if (VERBOSE) log("onAnswer");
+ }
+
+ @Override
+ protected void onReject() {
+ if (VERBOSE) log("onReject");
+ }
+
+ @Override
+ protected void onPostDialContinue(boolean proceed) {
+ if (VERBOSE) log("onPostDialContinue, proceed: " + proceed);
+ }
+
+ @Override
+ protected void onChildrenChanged(List<Connection> children) {
+ if (VERBOSE) log("onChildrenChanged, children: " + children);
+ }
+
+ @Override
+ protected void onPhoneAccountClicked() {
+ if (VERBOSE) log("onPhoneAccountClicked");
+ }
+
+ private static void log(String msg) {
+ Log.d(SipUtil.LOG_TAG, PREFIX + msg);
+ }
+}
diff --git a/sip/src/com/android/services/telephony/sip/SipConnectionService.java b/sip/src/com/android/services/telephony/sip/SipConnectionService.java
new file mode 100644
index 000000000..9b7b33fe2
--- /dev/null
+++ b/sip/src/com/android/services/telephony/sip/SipConnectionService.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2014 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.content.Context;
+import android.net.sip.SipException;
+import android.net.sip.SipManager;
+import android.net.sip.SipProfile;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.provider.Settings;
+import android.telecomm.Connection;
+import android.telecomm.ConnectionRequest;
+import android.telecomm.ConnectionService;
+import android.telecomm.Response;
+import android.telephony.DisconnectCause;
+import android.telephony.PhoneNumberUtils;
+import android.util.Log;
+
+import com.android.internal.telephony.CallStateException;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneFactory;
+import com.android.internal.telephony.sip.SipPhone;
+
+import java.util.HashMap;
+
+public class SipConnectionService extends ConnectionService {
+ private static final String PREFIX = "[SipConnectionService] ";
+ private static final boolean VERBOSE = true; /* STOP SHIP if true */
+
+ private class GetSipProfileTask extends AsyncTask<Void, Void, SipProfile> {
+ private final ConnectionRequest mRequest;
+ private final OutgoingCallResponse mResponse;
+ private final SipProfileDb mSipProfileDb;
+ private final SipSharedPreferences mSipSharedPreferences;
+
+ GetSipProfileTask(
+ Context context,
+ ConnectionRequest request,
+ OutgoingCallResponse response) {
+ mRequest = request;
+ mResponse = response;
+ mSipProfileDb = new SipProfileDb(context);
+ mSipSharedPreferences = new SipSharedPreferences(context);
+ }
+
+ @Override
+ protected SipProfile doInBackground(Void... params) {
+ String primarySipUri = mSipSharedPreferences.getPrimaryAccount();
+ for (SipProfile profile : mSipProfileDb.retrieveSipProfileList()) {
+ if (profile.getUriString().equals(primarySipUri)) {
+ return profile;
+ }
+ }
+ // TODO(sail): Handle non-primary profiles by showing dialog.
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(SipProfile profile) {
+ onSipProfileChosen(profile, mRequest, mResponse);
+ }
+ }
+
+ @Override
+ protected void onCreateConnections(
+ ConnectionRequest request,
+ OutgoingCallResponse<Connection> callback) {
+ if (VERBOSE) log("onCreateConnections, request: " + request);
+ new GetSipProfileTask(this, request, callback).execute();
+ }
+
+ @Override
+ protected void onCreateConferenceConnection(
+ String token,
+ Connection connection,
+ Response<String, Connection> callback) {
+ if (VERBOSE) log("onCreateConferenceConnection, connection: " + connection);
+ }
+
+ @Override
+ protected void onCreateIncomingConnection(
+ ConnectionRequest request,
+ Response<ConnectionRequest, Connection> callback) {
+ if (VERBOSE) log("onCreateIncomingConnection, request: " + request);
+ }
+
+ @Override
+ protected void onConnectionAdded(Connection connection) {
+ if (VERBOSE) log("onConnectionAdded, connection: " + connection);
+ }
+
+ @Override
+ protected void onConnectionRemoved(Connection connection) {
+ if (VERBOSE) log("onConnectionRemoved, connection: " + connection);
+ }
+
+ private void onSipProfileChosen(
+ SipProfile profile,
+ ConnectionRequest request,
+ OutgoingCallResponse response) {
+ if (profile != null) {
+ String sipUri = profile.getUriString();
+ SipPhone phone = null;
+ try {
+ SipManager.newInstance(this).open(profile);
+ phone = (SipPhone) PhoneFactory.makeSipPhone(sipUri);
+ startCallWithPhone(phone, request, response);
+ } catch (SipException e) {
+ log("Failed to make a SIP phone: " + e);
+ response.onFailure(request, DisconnectCause.ERROR_UNSPECIFIED,
+ "Failed to make a SIP phone: " + e);
+ }
+ } else {
+ response.onFailure(request, DisconnectCause.ERROR_UNSPECIFIED,
+ "Failed to find SIP profile");
+ }
+ }
+
+ protected void startCallWithPhone(
+ Phone phone,
+ ConnectionRequest request,
+ OutgoingCallResponse<Connection> response) {
+ String number = request.getHandle().getSchemeSpecificPart();
+ try {
+ com.android.internal.telephony.Connection connection =
+ phone.dial(number, request.getVideoState());
+ SipConnection sipConnection = new SipConnection(connection);
+ response.onSuccess(request, sipConnection);
+ } catch (CallStateException e) {
+ log("Call to Phone.dial failed with exception: " + e);
+ response.onFailure(request, DisconnectCause.ERROR_UNSPECIFIED,
+ "Call to Phone.dial failed with exception: " + e);
+ }
+ }
+
+ private static void log(String msg) {
+ Log.d(SipUtil.LOG_TAG, PREFIX + msg);
+ }
+}
diff --git a/sip/src/com/android/services/telephony/sip/SipEditor.java b/sip/src/com/android/services/telephony/sip/SipEditor.java
new file mode 100644
index 000000000..a76529686
--- /dev/null
+++ b/sip/src/com/android/services/telephony/sip/SipEditor.java
@@ -0,0 +1,660 @@
+/*
+ * 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 com.android.internal.telephony.CallManager;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneConstants;
+import com.android.services.telephony.sip.SipUtil;
+
+import android.app.ActionBar;
+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.view.View;
+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 = true; /* 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 PrimaryAccountSelector mPrimaryAccountSelector;
+ private AdvancedSettings mAdvancedSettings;
+ private SipSharedPreferences mSharedPreferences;
+ private boolean mDisplayNameSet;
+ private boolean mHomeButtonClicked;
+ private boolean mUpdateRequired;
+ private boolean mUpdatedFieldIsEmpty;
+
+ private SipManager mSipManager;
+ private SipProfileDb mProfileDb;
+ private SipProfile mOldProfile;
+ private CallManager mCallManager;
+ private Button mRemoveButton;
+
+ 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 (mCallManager.getState() != PhoneConstants.State.IDLE) {
+ 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);
+
+ mSipManager = SipManager.newInstance(this);
+ mSharedPreferences = new SipSharedPreferences(this);
+ mProfileDb = new SipProfileDb(this);
+ mCallManager = CallManager.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();
+ mPrimaryAccountSelector = new PrimaryAccountSelector(p);
+
+ 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;
+ }
+ }
+ 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);
+ }
+
+ private void saveAndRegisterProfile(SipProfile p) throws IOException {
+ if (p == null) return;
+ mProfileDb.saveProfile(p);
+ if (p.getAutoRegistration() || mSharedPreferences.isPrimaryAccount(p.getUriString())) {
+ try {
+ mSipManager.open(p, SipUtil.createIncomingCallPendingIntent(this), null);
+ } catch (Exception e) {
+ log("saveAndRegisterProfile, register failed for profile: " + p.getUriString() +
+ ", exception: " + e);
+ }
+ }
+ }
+
+ private void deleteAndUnregisterProfile(SipProfile p) {
+ if (p == null) return;
+ mProfileDb.deleteProfile(p);
+ unregisterProfile(p.getUriString());
+ }
+
+ private void unregisterProfile(String uri) {
+ try {
+ mSipManager.close(uri);
+ } catch (Exception e) {
+ log("unregisterProfile, unregister failed for profile: " + uri + ", exception: " + e);
+ }
+ }
+
+ 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 unregisterOldPrimaryAccount() {
+ String primaryAccountUri = mSharedPreferences.getPrimaryAccount();
+ if (VERBOSE) log("unregisterOldPrimaryAccount, old primary: " + primaryAccountUri);
+ if ((primaryAccountUri != null) && !mSharedPreferences.isReceivingCallsEnabled()) {
+ if (VERBOSE) log("unregisterOldPrimaryAccount, calling unregister");
+ unregisterProfile(primaryAccountUri);
+ }
+ }
+
+ 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 {
+ // if new profile is primary, unregister the old primary account
+ if ((newProfile != null) && mPrimaryAccountSelector.isSelected()) {
+ unregisterOldPrimaryAccount();
+ }
+
+ mPrimaryAccountSelector.commit(newProfile);
+ 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 (mOldProfile != null) {
+ unregisterProfile(mOldProfile.getUriString());
+ }
+ }
+ 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);
+ }
+
+ // only takes care of the primary account setting in SipSharedSettings
+ private class PrimaryAccountSelector {
+ private CheckBoxPreference mCheckbox;
+ private final boolean mWasPrimaryAccount;
+
+ // @param profile profile to be edited; null if adding new profile
+ PrimaryAccountSelector(SipProfile profile) {
+ mCheckbox = (CheckBoxPreference) getPreferenceScreen()
+ .findPreference(getString(R.string.set_primary));
+ boolean noPrimaryAccountSet = !mSharedPreferences.hasPrimaryAccount();
+ boolean editNewProfile = (profile == null);
+ mWasPrimaryAccount = !editNewProfile && mSharedPreferences.isPrimaryAccount(
+ profile.getUriString());
+
+ if (VERBOSE) {
+ log(" noPrimaryAccountSet: " + noPrimaryAccountSet);
+ log(" editNewProfile: " + editNewProfile);
+ log(" mWasPrimaryAccount: " + mWasPrimaryAccount);
+ }
+
+ mCheckbox.setChecked(mWasPrimaryAccount || (editNewProfile && noPrimaryAccountSet));
+ }
+
+ boolean isSelected() {
+ return mCheckbox.isChecked();
+ }
+
+ // profile is null if the user removes it
+ void commit(SipProfile profile) {
+ if ((profile != null) && mCheckbox.isChecked()) {
+ mSharedPreferences.setPrimaryAccount(profile.getUriString());
+ } else if (mWasPrimaryAccount) {
+ mSharedPreferences.unsetPrimaryAccount();
+ }
+ if (VERBOSE) {
+ log("PrimaryAccountSelector.commit, new primary account: " +
+ mSharedPreferences.getPrimaryAccount());
+ }
+ }
+ }
+
+ 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);
+ }
+}
diff --git a/sip/src/com/android/services/telephony/sip/SipProfileDb.java b/sip/src/com/android/services/telephony/sip/SipProfileDb.java
new file mode 100644
index 000000000..bf4b6bbe0
--- /dev/null
+++ b/sip/src/com/android/services/telephony/sip/SipProfileDb.java
@@ -0,0 +1,146 @@
+/*
+ * 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 com.android.internal.os.AtomicFile;
+
+import android.content.Context;
+import android.net.sip.SipProfile;
+import android.util.Log;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Utility class that helps perform operations on the SipProfile database.
+ */
+public class SipProfileDb {
+ private static final String PREFIX = "[SipProfileDb] ";
+ private static final boolean VERBOSE = true; /* STOP SHIP if true */
+
+ private static final String PROFILES_DIR = "/profiles/";
+ private static final String PROFILE_OBJ_FILE = ".pobj";
+
+ private String mProfilesDirectory;
+ private SipSharedPreferences mSipSharedPreferences;
+ private int mProfilesCount = -1;
+
+ public SipProfileDb(Context context) {
+ mProfilesDirectory = context.getFilesDir().getAbsolutePath() + PROFILES_DIR;
+ mSipSharedPreferences = new SipSharedPreferences(context);
+ }
+
+ public void deleteProfile(SipProfile p) {
+ synchronized(SipProfileDb.class) {
+ deleteProfile(new File(mProfilesDirectory + p.getProfileName()));
+ if (mProfilesCount < 0) retrieveSipProfileListInternal();
+ mSipSharedPreferences.setProfilesCount(--mProfilesCount);
+ }
+ }
+
+ private void deleteProfile(File file) {
+ if (file.isDirectory()) {
+ for (File child : file.listFiles()) deleteProfile(child);
+ }
+ file.delete();
+ }
+
+ public void saveProfile(SipProfile p) throws IOException {
+ synchronized(SipProfileDb.class) {
+ if (mProfilesCount < 0) retrieveSipProfileListInternal();
+ File f = new File(mProfilesDirectory + p.getProfileName());
+ if (!f.exists()) f.mkdirs();
+ AtomicFile atomicFile = new AtomicFile(new File(f, PROFILE_OBJ_FILE));
+ FileOutputStream fos = null;
+ ObjectOutputStream oos = null;
+ try {
+ fos = atomicFile.startWrite();
+ oos = new ObjectOutputStream(fos);
+ oos.writeObject(p);
+ oos.flush();
+ mSipSharedPreferences.setProfilesCount(++mProfilesCount);
+ atomicFile.finishWrite(fos);
+ } catch (IOException e) {
+ atomicFile.failWrite(fos);
+ throw e;
+ } finally {
+ if (oos != null) oos.close();
+ }
+ }
+ }
+
+ public int getProfilesCount() {
+ return (mProfilesCount < 0) ? mSipSharedPreferences.getProfilesCount() : mProfilesCount;
+ }
+
+ public List<SipProfile> retrieveSipProfileList() {
+ synchronized(SipProfileDb.class) {
+ return retrieveSipProfileListInternal();
+ }
+ }
+
+ private List<SipProfile> retrieveSipProfileListInternal() {
+ List<SipProfile> sipProfileList = Collections.synchronizedList(
+ new ArrayList<SipProfile>());
+
+ File root = new File(mProfilesDirectory);
+ String[] dirs = root.list();
+ if (dirs == null) return sipProfileList;
+ for (String dir : dirs) {
+ File f = new File(new File(root, dir), PROFILE_OBJ_FILE);
+ if (!f.exists()) continue;
+ try {
+ SipProfile p = deserialize(f);
+ if (p == null) continue;
+ if (!dir.equals(p.getProfileName())) continue;
+
+ sipProfileList.add(p);
+ } catch (IOException e) {
+ log("retrieveSipProfileListInternal, exception: " + e);
+ }
+ }
+ mProfilesCount = sipProfileList.size();
+ mSipSharedPreferences.setProfilesCount(mProfilesCount);
+ return sipProfileList;
+ }
+
+ private SipProfile deserialize(File profileObjectFile) throws IOException {
+ AtomicFile atomicFile = new AtomicFile(profileObjectFile);
+ ObjectInputStream ois = null;
+ try {
+ ois = new ObjectInputStream(atomicFile.openRead());
+ SipProfile p = (SipProfile) ois.readObject();
+ return p;
+ } catch (ClassNotFoundException e) {
+ log("deserialize, exception: " + e);
+ } finally {
+ if (ois!= null) ois.close();
+ }
+ return null;
+ }
+
+ private static void log(String msg) {
+ Log.d(SipUtil.LOG_TAG, PREFIX + msg);
+ }
+}
diff --git a/sip/src/com/android/services/telephony/sip/SipSettings.java b/sip/src/com/android/services/telephony/sip/SipSettings.java
new file mode 100644
index 000000000..cf35e31fa
--- /dev/null
+++ b/sip/src/com/android/services/telephony/sip/SipSettings.java
@@ -0,0 +1,512 @@
+/*
+ * 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 com.android.internal.telephony.CallManager;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneConstants;
+
+import android.app.ActionBar;
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.net.sip.SipErrorCode;
+import android.net.sip.SipException;
+import android.net.sip.SipManager;
+import android.net.sip.SipProfile;
+import android.net.sip.SipRegistrationListener;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.os.Process;
+import android.preference.CheckBoxPreference;
+import android.preference.Preference;
+import android.preference.Preference.OnPreferenceClickListener;
+import android.preference.PreferenceActivity;
+import android.preference.PreferenceCategory;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.Menu;
+import android.view.MenuItem;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * The PreferenceActivity class for managing sip profile preferences.
+ */
+public class SipSettings extends PreferenceActivity {
+ private static final String PREFIX = "[SipSettings] ";
+ private static final boolean VERBOSE = true; /* STOP SHIP if true */
+
+ public static final String SIP_SHARED_PREFERENCES = "SIP_PREFERENCES";
+
+ private static final int MENU_ADD_ACCOUNT = Menu.FIRST;
+
+ static final String KEY_SIP_PROFILE = "sip_profile";
+
+ private static final String BUTTON_SIP_RECEIVE_CALLS =
+ "sip_receive_calls_key";
+ private static final String PREF_SIP_LIST = "sip_account_list";
+
+ private static final int REQUEST_ADD_OR_EDIT_SIP_PROFILE = 1;
+
+ private PackageManager mPackageManager;
+ private SipManager mSipManager;
+ private CallManager mCallManager;
+ private SipProfileDb mProfileDb;
+
+ private SipProfile mProfile; // profile that's being edited
+
+ private CheckBoxPreference mButtonSipReceiveCalls;
+ private PreferenceCategory mSipListContainer;
+ private Map<String, SipPreference> mSipPreferenceMap;
+ private List<SipProfile> mSipProfileList;
+ private SipSharedPreferences mSipSharedPreferences;
+ private int mUid = Process.myUid();
+
+ private class SipPreference extends Preference {
+ SipProfile mProfile;
+ SipPreference(Context c, SipProfile p) {
+ super(c);
+ setProfile(p);
+ }
+
+ SipProfile getProfile() {
+ return mProfile;
+ }
+
+ void setProfile(SipProfile p) {
+ mProfile = p;
+ setTitle(getProfileName(p));
+ updateSummary(mSipSharedPreferences.isReceivingCallsEnabled()
+ ? getString(R.string.registration_status_checking_status)
+ : getString(R.string.registration_status_not_receiving));
+ }
+
+ void updateSummary(String registrationStatus) {
+ int profileUid = mProfile.getCallingUid();
+ boolean isPrimary = mProfile.getUriString().equals(
+ mSipSharedPreferences.getPrimaryAccount());
+ if (VERBOSE) {
+ log("SipPreference.updateSummary, profile uid: " + profileUid +
+ " isPrimary: " + isPrimary +
+ " registration: " + registrationStatus +
+ " Primary: " + mSipSharedPreferences.getPrimaryAccount() +
+ " status: " + registrationStatus);
+ }
+ String summary = "";
+ if ((profileUid > 0) && (profileUid != mUid)) {
+ // from third party apps
+ summary = getString(R.string.third_party_account_summary,
+ getPackageNameFromUid(profileUid));
+ } else if (isPrimary) {
+ summary = getString(R.string.primary_account_summary_with,
+ registrationStatus);
+ } else {
+ summary = registrationStatus;
+ }
+ setSummary(summary);
+ }
+ }
+
+ private String getPackageNameFromUid(int uid) {
+ try {
+ String[] pkgs = mPackageManager.getPackagesForUid(uid);
+ ApplicationInfo ai = mPackageManager.getApplicationInfo(pkgs[0], 0);
+ return ai.loadLabel(mPackageManager).toString();
+ } catch (PackageManager.NameNotFoundException e) {
+ log("getPackageNameFromUid, cannot find name of uid: " + uid + ", exception: " + e);
+ }
+ return "uid:" + uid;
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ mSipManager = SipManager.newInstance(this);
+ mSipSharedPreferences = new SipSharedPreferences(this);
+ mProfileDb = new SipProfileDb(this);
+
+ mPackageManager = getPackageManager();
+ setContentView(R.layout.sip_settings_ui);
+ addPreferencesFromResource(R.xml.sip_setting);
+ mSipListContainer = (PreferenceCategory) findPreference(PREF_SIP_LIST);
+ registerForReceiveCallsCheckBox();
+ mCallManager = CallManager.getInstance();
+
+ updateProfilesStatus();
+
+ ActionBar actionBar = getActionBar();
+ if (actionBar != null) {
+ actionBar.setDisplayHomeAsUpEnabled(true);
+ }
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+
+ if (mCallManager.getState() != PhoneConstants.State.IDLE) {
+ mButtonSipReceiveCalls.setEnabled(false);
+ } else {
+ mButtonSipReceiveCalls.setEnabled(true);
+ }
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ unregisterForContextMenu(getListView());
+ }
+
+ @Override
+ protected void onActivityResult(final int requestCode, final int resultCode,
+ final Intent intent) {
+ if (resultCode != RESULT_OK && resultCode != RESULT_FIRST_USER) return;
+ new Thread() {
+ @Override
+ public void run() {
+ try {
+ if (mProfile != null) {
+ if (VERBOSE) log("onActivityResult, remove: " + mProfile.getProfileName());
+ deleteProfile(mProfile);
+ }
+
+ SipProfile profile = intent.getParcelableExtra(KEY_SIP_PROFILE);
+ if (resultCode == RESULT_OK) {
+ if (VERBOSE) log("onActivityResult, new: " + profile.getProfileName());
+ addProfile(profile);
+ }
+ updateProfilesStatus();
+ } catch (IOException e) {
+ log("onActivityResult, can not handle the profile: " + e);
+ }
+ }
+ }.start();
+ }
+
+ private void registerForReceiveCallsCheckBox() {
+ mButtonSipReceiveCalls = (CheckBoxPreference) findPreference
+ (BUTTON_SIP_RECEIVE_CALLS);
+ mButtonSipReceiveCalls.setChecked(
+ mSipSharedPreferences.isReceivingCallsEnabled());
+ mButtonSipReceiveCalls.setOnPreferenceClickListener(
+ new OnPreferenceClickListener() {
+ public boolean onPreferenceClick(Preference preference) {
+ final boolean enabled = ((CheckBoxPreference) preference).isChecked();
+ new Thread(new Runnable() {
+ public void run() {
+ handleSipReceiveCallsOption(enabled);
+ }
+ }).start();
+ return true;
+ }
+ });
+ }
+
+ private synchronized void handleSipReceiveCallsOption(boolean enabled) {
+ mSipSharedPreferences.setReceivingCallsEnabled(enabled);
+ List<SipProfile> sipProfileList = mProfileDb.retrieveSipProfileList();
+ for (SipProfile p : sipProfileList) {
+ String sipUri = p.getUriString();
+ p = updateAutoRegistrationFlag(p, enabled);
+ try {
+ if (enabled) {
+ mSipManager.open(p, SipUtil.createIncomingCallPendingIntent(this), null);
+ } else {
+ mSipManager.close(sipUri);
+ if (mSipSharedPreferences.isPrimaryAccount(sipUri)) {
+ // re-open in order to make calls
+ mSipManager.open(p);
+ }
+ }
+ } catch (Exception e) {
+ log("handleSipReceiveCallsOption, register failed: " + e);
+ }
+ }
+ updateProfilesStatus();
+ }
+
+ private SipProfile updateAutoRegistrationFlag(
+ SipProfile p, boolean enabled) {
+ SipProfile newProfile = new SipProfile.Builder(p)
+ .setAutoRegistration(enabled)
+ .build();
+ try {
+ mProfileDb.deleteProfile(p);
+ mProfileDb.saveProfile(newProfile);
+ } catch (Exception e) {
+ log("updateAutoRegistrationFlag, exception: " + e);
+ }
+ return newProfile;
+ }
+
+ private void updateProfilesStatus() {
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ retrieveSipLists();
+ } catch (Exception e) {
+ log("updateProfilesStatus, exception: " + e);
+ }
+ }
+ }).start();
+ }
+
+ private String getProfileName(SipProfile profile) {
+ String profileName = profile.getProfileName();
+ if (TextUtils.isEmpty(profileName)) {
+ profileName = profile.getUserName() + "@" + profile.getSipDomain();
+ }
+ return profileName;
+ }
+
+ private void retrieveSipLists() {
+ mSipPreferenceMap = new LinkedHashMap<String, SipPreference>();
+ mSipProfileList = mProfileDb.retrieveSipProfileList();
+ processActiveProfilesFromSipService();
+ Collections.sort(mSipProfileList, new Comparator<SipProfile>() {
+ @Override
+ public int compare(SipProfile p1, SipProfile p2) {
+ return getProfileName(p1).compareTo(getProfileName(p2));
+ }
+
+ public boolean equals(SipProfile p) {
+ // not used
+ return false;
+ }
+ });
+ mSipListContainer.removeAll();
+ for (SipProfile p : mSipProfileList) {
+ addPreferenceFor(p);
+ }
+
+ if (!mSipSharedPreferences.isReceivingCallsEnabled()) return;
+ for (SipProfile p : mSipProfileList) {
+ if (mUid == p.getCallingUid()) {
+ try {
+ mSipManager.setRegistrationListener(
+ p.getUriString(), createRegistrationListener());
+ } catch (SipException e) {
+ log("retrieveSipLists, cannot set registration listener: " + e);
+ }
+ }
+ }
+ }
+
+ private void processActiveProfilesFromSipService() {
+ SipProfile[] activeList = mSipManager.getListOfProfiles();
+ for (SipProfile activeProfile : activeList) {
+ SipProfile profile = getProfileFromList(activeProfile);
+ if (profile == null) {
+ mSipProfileList.add(activeProfile);
+ } else {
+ profile.setCallingUid(activeProfile.getCallingUid());
+ }
+ }
+ }
+
+ private SipProfile getProfileFromList(SipProfile activeProfile) {
+ for (SipProfile p : mSipProfileList) {
+ if (p.getUriString().equals(activeProfile.getUriString())) {
+ return p;
+ }
+ }
+ return null;
+ }
+
+ private void addPreferenceFor(SipProfile p) {
+ String status;
+ if (VERBOSE) log("addPreferenceFor, profile uri: " + p.getUri());
+ SipPreference pref = new SipPreference(this, p);
+ mSipPreferenceMap.put(p.getUriString(), pref);
+ mSipListContainer.addPreference(pref);
+
+ pref.setOnPreferenceClickListener(
+ new Preference.OnPreferenceClickListener() {
+ @Override
+ public boolean onPreferenceClick(Preference pref) {
+ handleProfileClick(((SipPreference) pref).mProfile);
+ return true;
+ }
+ });
+ }
+
+ private void handleProfileClick(final SipProfile profile) {
+ int uid = profile.getCallingUid();
+ if (uid == mUid || uid == 0) {
+ startSipEditor(profile);
+ return;
+ }
+ new AlertDialog.Builder(this)
+ .setTitle(R.string.alert_dialog_close)
+ .setIconAttribute(android.R.attr.alertDialogIcon)
+ .setPositiveButton(R.string.close_profile,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int w) {
+ deleteProfile(profile);
+ unregisterProfile(profile);
+ }
+ })
+ .setNegativeButton(android.R.string.cancel, null)
+ .show();
+ }
+
+ private void unregisterProfile(final SipProfile p) {
+ // run it on background thread for better UI response
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ mSipManager.close(p.getUriString());
+ } catch (Exception e) {
+ log("unregisterProfile, unregister failed, SipService died? Exception: " + e);
+ }
+ }
+ }, "unregisterProfile").start();
+ }
+
+ void deleteProfile(SipProfile p) {
+ mSipProfileList.remove(p);
+ SipPreference pref = mSipPreferenceMap.remove(p.getUriString());
+ mSipListContainer.removePreference(pref);
+ }
+
+ private void addProfile(SipProfile p) throws IOException {
+ try {
+ mSipManager.setRegistrationListener(p.getUriString(),
+ createRegistrationListener());
+ } catch (Exception e) {
+ log("addProfile, cannot set registration listener: " + e);
+ }
+ mSipProfileList.add(p);
+ addPreferenceFor(p);
+ }
+
+ private void startSipEditor(final SipProfile profile) {
+ mProfile = profile;
+ Intent intent = new Intent(this, SipEditor.class);
+ intent.putExtra(KEY_SIP_PROFILE, (Parcelable) profile);
+ startActivityForResult(intent, REQUEST_ADD_OR_EDIT_SIP_PROFILE);
+ }
+
+ private void showRegistrationMessage(final String profileUri,
+ final String message) {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ SipPreference pref = mSipPreferenceMap.get(profileUri);
+ if (pref != null) {
+ pref.updateSummary(message);
+ }
+ }
+ });
+ }
+
+ private SipRegistrationListener createRegistrationListener() {
+ return new SipRegistrationListener() {
+ @Override
+ public void onRegistrationDone(String profileUri, long expiryTime) {
+ showRegistrationMessage(profileUri, getString(
+ R.string.registration_status_done));
+ }
+
+ @Override
+ public void onRegistering(String profileUri) {
+ showRegistrationMessage(profileUri, getString(
+ R.string.registration_status_registering));
+ }
+
+ @Override
+ public void onRegistrationFailed(String profileUri, int errorCode,
+ String message) {
+ switch (errorCode) {
+ case SipErrorCode.IN_PROGRESS:
+ showRegistrationMessage(profileUri, getString(
+ R.string.registration_status_still_trying));
+ break;
+ case SipErrorCode.INVALID_CREDENTIALS:
+ showRegistrationMessage(profileUri, getString(
+ R.string.registration_status_invalid_credentials));
+ break;
+ case SipErrorCode.SERVER_UNREACHABLE:
+ showRegistrationMessage(profileUri, getString(
+ R.string.registration_status_server_unreachable));
+ break;
+ case SipErrorCode.DATA_CONNECTION_LOST:
+ if (SipManager.isSipWifiOnly(getApplicationContext())){
+ showRegistrationMessage(profileUri, getString(
+ R.string.registration_status_no_wifi_data));
+ } else {
+ showRegistrationMessage(profileUri, getString(
+ R.string.registration_status_no_data));
+ }
+ break;
+ case SipErrorCode.CLIENT_ERROR:
+ showRegistrationMessage(profileUri, getString(
+ R.string.registration_status_not_running));
+ break;
+ default:
+ showRegistrationMessage(profileUri, getString(
+ R.string.registration_status_failed_try_later,
+ message));
+ }
+ }
+ };
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ super.onCreateOptionsMenu(menu);
+ menu.add(0, MENU_ADD_ACCOUNT, 0, R.string.add_sip_account)
+ .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
+ return true;
+ }
+
+ @Override
+ public boolean onPrepareOptionsMenu(Menu menu) {
+ menu.findItem(MENU_ADD_ACCOUNT).setEnabled(
+ mCallManager.getState() == PhoneConstants.State.IDLE);
+ return super.onPrepareOptionsMenu(menu);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ final int itemId = item.getItemId();
+ switch (itemId) {
+ case MENU_ADD_ACCOUNT: {
+ startSipEditor(null);
+ return true;
+ }
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ private static void log(String msg) {
+ Log.d(SipUtil.LOG_TAG, PREFIX + msg);
+ }
+}
diff --git a/sip/src/com/android/services/telephony/sip/SipSharedPreferences.java b/sip/src/com/android/services/telephony/sip/SipSharedPreferences.java
new file mode 100644
index 000000000..ffb1513d2
--- /dev/null
+++ b/sip/src/com/android/services/telephony/sip/SipSharedPreferences.java
@@ -0,0 +1,114 @@
+/*
+ * 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.content.ContentResolver;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.provider.Settings;
+import android.provider.Settings.SettingNotFoundException;
+import android.text.TextUtils;
+import android.util.Log;
+
+/**
+ * Wrapper for SIP's shared preferences.
+ */
+public class SipSharedPreferences {
+ private static final String PREFIX = "[SipSharedPreferences] ";
+ private static final boolean VERBOSE = true; /* STOP SHIP if true */
+
+ private static final String SIP_SHARED_PREFERENCES = "SIP_PREFERENCES";
+ private static final String KEY_PRIMARY_ACCOUNT = "primary";
+ private static final String KEY_NUMBER_OF_PROFILES = "profiles";
+
+ private SharedPreferences mPreferences;
+ private Context mContext;
+
+ public SipSharedPreferences(Context context) {
+ mPreferences = context.getSharedPreferences(
+ SIP_SHARED_PREFERENCES, Context.MODE_WORLD_READABLE);
+ mContext = context;
+ }
+
+ public void setPrimaryAccount(String accountUri) {
+ SharedPreferences.Editor editor = mPreferences.edit();
+ editor.putString(KEY_PRIMARY_ACCOUNT, accountUri);
+ editor.apply();
+ }
+
+ public void unsetPrimaryAccount() {
+ setPrimaryAccount(null);
+ }
+
+ /** Returns the primary account URI or null if it does not exist. */
+ public String getPrimaryAccount() {
+ return mPreferences.getString(KEY_PRIMARY_ACCOUNT, null);
+ }
+
+ public boolean isPrimaryAccount(String accountUri) {
+ return accountUri.equals(
+ mPreferences.getString(KEY_PRIMARY_ACCOUNT, null));
+ }
+
+ public boolean hasPrimaryAccount() {
+ return !TextUtils.isEmpty(
+ mPreferences.getString(KEY_PRIMARY_ACCOUNT, null));
+ }
+
+ public void setProfilesCount(int number) {
+ SharedPreferences.Editor editor = mPreferences.edit();
+ editor.putInt(KEY_NUMBER_OF_PROFILES, number);
+ editor.apply();
+ }
+
+ public int getProfilesCount() {
+ return mPreferences.getInt(KEY_NUMBER_OF_PROFILES, 0);
+ }
+
+ public void setSipCallOption(String option) {
+ Settings.System.putString(mContext.getContentResolver(),
+ Settings.System.SIP_CALL_OPTIONS, option);
+ }
+
+ public String getSipCallOption() {
+ String option = Settings.System.getString(mContext.getContentResolver(),
+ Settings.System.SIP_CALL_OPTIONS);
+ return (option != null) ? option
+ : mContext.getString(R.string.sip_address_only);
+ }
+
+ public void setReceivingCallsEnabled(boolean enabled) {
+ Settings.System.putInt(mContext.getContentResolver(),
+ Settings.System.SIP_RECEIVE_CALLS, (enabled ? 1 : 0));
+ }
+
+ public boolean isReceivingCallsEnabled() {
+ try {
+ return (Settings.System.getInt(mContext.getContentResolver(),
+ Settings.System.SIP_RECEIVE_CALLS) != 0);
+ } catch (SettingNotFoundException e) {
+ log("isReceivingCallsEnabled, option not set; use default value, exception: " + e);
+ return false;
+ }
+ }
+
+ // TODO: back up to Android Backup
+
+ private static void log(String msg) {
+ Log.d(SipUtil.LOG_TAG, PREFIX + msg);
+ }
+}
diff --git a/sip/src/com/android/services/telephony/sip/SipUtil.java b/sip/src/com/android/services/telephony/sip/SipUtil.java
new file mode 100644
index 000000000..39b565523
--- /dev/null
+++ b/sip/src/com/android/services/telephony/sip/SipUtil.java
@@ -0,0 +1,50 @@
+/*
+ * 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.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.net.sip.SipManager;
+
+public class SipUtil {
+ public static final String LOG_TAG = "SIP";
+
+ private static boolean sIsVoipSupported;
+ private static boolean sIsVoipSupportedInitialized;
+
+ private SipUtil() {
+ }
+
+ public static boolean isVoipSupported(Context context) {
+ if (!sIsVoipSupportedInitialized) {
+ sIsVoipSupported = SipManager.isVoipSupported(context) &&
+ context.getResources().getBoolean(
+ com.android.internal.R.bool.config_built_in_sip_phone) &&
+ context.getResources().getBoolean(
+ com.android.internal.R.bool.config_voice_capable);
+ }
+ return sIsVoipSupported;
+ }
+
+ public static PendingIntent createIncomingCallPendingIntent(Context context) {
+ Intent intent = new Intent(context, SipBroadcastReceiver.class);
+ intent.setAction(SipManager.ACTION_SIP_INCOMING_CALL);
+ return PendingIntent.getBroadcast(context, 0, intent,
+ PendingIntent.FLAG_UPDATE_CURRENT);
+ }
+}