diff options
Diffstat (limited to 'sip/src/com')
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); + } +} |