SIP Part 1, move sip code
This CL moves the SIP code to
sip/src/com/android/services/telephony/sip
Moving the SIP code helps with a couple of things:
- remove legacy dependency on old telephony code
(PhoneUtils, etc...)
- separate SIP from the PSTN connection code which
is very different from SIP code
- get the code ready for a future move out of
Telephony all together
Bug: 14999064
Change-Id: Id32de6517d31be4aa177b2764d5bac1e1f9851c2
diff --git a/sip/Android.mk b/sip/Android.mk
new file mode 100644
index 0000000..cefb86d
--- /dev/null
+++ b/sip/Android.mk
@@ -0,0 +1,31 @@
+# Copyright 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_RESOURCE_DIR := $(addprefix $(LOCAL_PATH)/, res)
+
+LOCAL_JAVA_LIBRARIES := telephony-common voip-common
+
+LOCAL_PACKAGE_NAME := com.android.services.telephony.sip
+
+LOCAL_PROGUARD_FLAG_FILES := proguard.flags
+
+include $(BUILD_PACKAGE)
+include $(CLEAR_VARS)
diff --git a/sip/AndroidManifest.xml b/sip/AndroidManifest.xml
new file mode 100644
index 0000000..0249abd
--- /dev/null
+++ b/sip/AndroidManifest.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.services.telephony.sip">
+
+</manifest>
+
diff --git a/sip/proguard.flags b/sip/proguard.flags
new file mode 100644
index 0000000..23ae8c1
--- /dev/null
+++ b/sip/proguard.flags
@@ -0,0 +1,3 @@
+-verbose
+-keep class com.android.services.telephony.sip.SipConnectionService
+-keep class com.android.services.telephony.sip.SipBroadcastReceiver
diff --git a/sip/res/layout/sip_settings_ui.xml b/sip/res/layout/sip_settings_ui.xml
new file mode 100644
index 0000000..872c4b5
--- /dev/null
+++ b/sip/res/layout/sip_settings_ui.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 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.
+*/
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <ListView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@android:id/list"
+ android:layout_width="match_parent"
+ android:layout_height="0dip"
+ android:layout_weight="1"
+ android:drawSelectorOnTop="false"
+ android:scrollbarAlwaysDrawVerticalTrack="true"
+ />
+
+</LinearLayout>
diff --git a/sip/res/values/array.xml b/sip/res/values/array.xml
new file mode 100644
index 0000000..245c09b
--- /dev/null
+++ b/sip/res/values/array.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<!-- Array resources for the Phone app. -->
+<resources>
+ <string-array translatable="false" name="transport_types">
+ <item>UDP</item>
+ <item>TCP</item>
+ </string-array>
+
+ <string-array translatable="true" name="sip_call_options_entries">
+ <item>@string/sip_call_options_entry_1</item>
+ <item>@string/sip_call_options_entry_2</item>
+ <item>@string/sip_call_options_entry_3</item>
+ </string-array>
+
+ <string-array translatable="true" name="sip_call_options_wifi_only_entries">
+ <item>@string/sip_call_options_wifi_only_entry_1</item>
+ <item>@string/sip_call_options_entry_2</item>
+ <item>@string/sip_call_options_entry_3</item>
+ </string-array>
+
+ <string-array translatable="false" name="sip_call_options_values">
+ <item>@string/sip_always</item>
+ <item>@string/sip_address_only</item>
+ <item>@string/sip_ask_me_each_time</item>
+ </string-array>
+
+ <string-array translatable="false" name="phone_type_values">
+ <item>@string/pstn_phone</item>
+ <item>@string/internet_phone</item>
+ </string-array>
+
+ <string-array translatable="true" name="sip_send_keepalive_options">
+ <item>@string/sip_system_decide</item>
+ <item>@string/sip_always_send_keepalive</item>
+ </string-array>
+
+</resources>
diff --git a/sip/res/values/strings.xml b/sip/res/values/strings.xml
new file mode 100644
index 0000000..41499b2
--- /dev/null
+++ b/sip/res/values/strings.xml
@@ -0,0 +1,210 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+
+ <!-- Title displayed SIP settings coming after other call settings in the call features screen. [CHAR LIMIT=NONE] -->
+ <string name="sip_settings">Internet call settings</string>
+ <!-- Title displayed SIP account settings in the sip settings category. [CHAR LIMIT=NONE] -->
+ <string name="sip_accounts">Internet calling (SIP) accounts</string>
+ <!-- Title displayed SIP account settings in the sip settings category. [CHAR LIMIT=NONE] -->
+ <string name="sip_accounts_title">Accounts</string>
+ <!-- Title displayed for the button of receiving incoming SIP calls flag. Enabled for receiving calls. [CHAR LIMIT=NONE] -->
+ <string name="sip_receive_calls">Receive incoming calls</string>
+ <!-- Help user to understand that it will require more battery usage if the 'receiving incoming call' is enabled. [CHAR LIMIT=NONE] -->
+ <string name="sip_receive_calls_summary">Reduces battery life</string>
+ <!-- Title for the dialog of selecting the way we handle an outgoing call. [CHAR LIMIT=NONE] -->
+ <string name="sip_call_options_title">Use Internet calling</string>
+ <!-- Title for the dialog of selecting the way we handle an outgoing call (Wi-Fi only). [CHAR LIMIT=NONE] -->
+ <string name="sip_call_options_wifi_only_title">Use Internet calling (Wi-Fi only)</string>
+
+ <!-- Item of the Internet call option dialog: for routing all outgoing calls via SIP. [CHAR LIMIT=NONE] -->
+ <string name="sip_call_options_entry_1">For all calls when data network is available</string>
+ <!-- Item of the Internet call option dialog: for routing a outgoing call via SIP if the destination is a SIP URI. [CHAR LIMIT=NONE] -->
+ <string name="sip_call_options_entry_2">Only for Internet calls</string>
+ <!-- Item of the Internet call option dialog: for asking user to select the way for each outgoing call. [CHAR LIMIT=NONE] -->
+ <string name="sip_call_options_entry_3">Ask for each call</string>
+ <!-- Item of the Internet call (Wi-Fi only) option dialog: for routing all outgoing calls via SIP. [CHAR LIMIT=NONE] -->
+ <string name="sip_call_options_wifi_only_entry_1">For all calls</string>
+
+ <!-- Title of the dialog to select the phone type for the outgoing call. [CHAR LIMIT=NONE] -->
+ <string name ="pick_outgoing_call_phone_type">Place call</string>
+
+ <!-- Title of the dialog to select the phone among the list of phones for the outgoing call. [CHAR LIMIT=NONE] -->
+ <string name ="pick_outgoing_sip_phone">Use Internet calling account:</string>
+
+ <!-- Text of checkbox to remember the sip phone for all outbound calls. [CHAR LIMIT=NONE] -->
+ <string name ="remember_my_choice">Always use to place Internet calls</string>
+
+ <!-- Help text of checkbox to unset the default sip phone for all outbound calls. [CHAR LIMIT=NONE] -->
+ <string name ="reset_my_choice_hint">You can change which Internet calling account to use by default from the Phone > Settings > Internet call settings > Accounts screen.</string>
+
+ <!-- Item of the dialog to select the phone type for outgoing call. [CHAR LIMIT=NONE] -->
+ <string name ="pstn_phone">Cell phone call</string>
+
+ <!-- Item of the dialog to select the phone type for outgoing call. [CHAR LIMIT=NONE] -->
+ <string name ="internet_phone">Internet call</string>
+
+ <!-- Title of the dialog to redirect the user to SIP settings. [CHAR LIMIT=NONE] -->
+ <string name ="no_sip_account_found_title">No Internet calling account</string>
+ <!-- Message of the dialog to redirect the user to SIP settings. [CHAR LIMIT=NONE] -->
+ <string name ="no_sip_account_found">There are no Internet calling accounts on this phone. Do you want to add one now?</string>
+ <!-- Menu item for no sip account found dialog -->
+ <string name="sip_menu_add">Add</string>
+
+ <!-- Title for the button to add a new sip account. [CHAR LIMIT=NONE] -->
+ <string name="add_sip_account">Add account</string>
+ <!-- Title for the button to remove a sip account. [CHAR LIMIT=NONE] -->
+ <string name="remove_sip_account">Remove account</string>
+ <!-- String of the category which lists all sip accounts. [CHAR LIMIT=NONE] -->
+ <string name="sip_account_list">SIP accounts</string>
+ <!-- Toast message to indicate that the system is saving the account. [CHAR LIMIT=NONE] -->
+ <string name="saving_account">Saving the account\u2026</string>
+ <!-- Toast message to indicate that the system is removing the account. [CHAR LIMIT=NONE] -->
+ <string name="removing_account">Removing the account\u2026</string>
+
+ <!-- Menu item when the menu button is pressed in the sip account editor. [CHAR LIMIT=NONE] -->
+ <string name="sip_menu_save">Save</string>
+ <!-- Menu item when the menu button is pressed in the sip account editor. [CHAR LIMIT=NONE] -->
+ <string name="sip_menu_discard">Discard</string>
+
+ <!-- Title of the alert dialog. [CHAR LIMIT=NONE] -->
+ <string name="alert_dialog_close">Close the profile</string>
+ <!-- Title of the button to show in a alert dialog. [CHAR LIMIT=NONE] -->
+ <string name="alert_dialog_ok">OK</string>
+ <!-- Title of the button to show in a alert dialog. [CHAR LIMIT=NONE] -->
+ <string name="close_profile">Close</string>
+
+ <!-- Summary of a SIP account in the list of SIP accounts, consists of primary account status and registration status. This string indicates that this SIP account is a primary account and the string placeholder is the registration status, which will be one of the strings with name starting with "registration_status_" below. [CHAR LIMIT=NONE] -->
+ <string name="primary_account_summary_with">Primary account. <xliff:g id="registration_status" example="Registering...">%s</xliff:g></string>
+ <!-- Text of Internet-call registration status, checking current registration status with SIP service [CHAR LIMIT=NONE] -->
+ <string name="registration_status_checking_status">Checking status...</string>
+ <!-- Text of Internet-call registration status, registering with SIP server in order to receive calls on this account [CHAR LIMIT=NONE] -->
+ <string name="registration_status_registering">Registering\u2026</string>
+ <!-- Text of Internet-call registration status, still trying to register with SIP server [CHAR LIMIT=NONE] -->
+ <string name="registration_status_still_trying">Still trying\u2026</string>
+ <!-- Text of Internet-call registration status, currently not receiving calls on this account [CHAR LIMIT=NONE] -->
+ <string name="registration_status_not_receiving">Not receiving calls.</string>
+ <!-- Text of Internet-call registration status, no data connection [CHAR LIMIT=NONE] -->
+ <string name="registration_status_no_data">Account registration stopped because there is no Internet connection.</string>
+ <!-- Text of Internet-call registration status, no Wi-Fi connection [CHAR LIMIT=NONE] -->
+ <string name="registration_status_no_wifi_data">Account registration stopped because there is no Wi-Fi connection.</string>
+ <!-- Text of Internet-call registration status, registration process is not running due to some error [CHAR LIMIT=NONE] -->
+ <string name="registration_status_not_running">Account registration unsuccessful.</string>
+ <!-- Text of Internet-call registration status, currently we can receive calls on this account [CHAR LIMIT=NONE] -->
+ <string name="registration_status_done">Receiving calls.</string>
+ <!-- Text of Internet-call registration status, registration failed with a reason; will try again later [CHAR LIMIT=NONE] -->
+ <string name="registration_status_failed_try_later">Account registration unsuccessful: (<xliff:g id="registration_error_message" example="timed out">%s</xliff:g>); will try later</string>
+ <!-- Text of Internet-call registration status, registration failed due to invalid credentials [CHAR LIMIT=NONE] -->
+ <string name="registration_status_invalid_credentials">Account registration unsuccessful: Incorrect username or password.</string>
+ <!-- Text of Internet-call registration status, registration failed due to unknown host exception [CHAR LIMIT=NONE] -->
+ <string name="registration_status_server_unreachable">Account registration unsuccessful: Check the server name.</string>
+ <!-- Text for describing the account's owner and status. For example, 'Active, in use by Google Talk', it means that the account managed by SIP service was registered by the application 'Google Talk' and the status is active. [CHAR LIMIT=NONE] -->
+ <string name="third_party_account_summary">This account is currently in use by the <xliff:g id="account_owner" example="Google Talk">%s</xliff:g> app.</string>
+
+
+ <!-- Title of the sip editor screen. [CHAR LIMIT=NONE] -->
+ <string name="sip_edit_title">SIP account details</string>
+ <!-- Title of the sip editor screen. [CHAR LIMIT=NONE] -->
+ <string name="sip_edit_new_title">SIP account details</string>
+
+ <!-- Text of the domain address preference. [CHAR LIMIT=NONE] -->
+ <string name="domain_address_title">Server</string>
+ <!-- Text of the username preference. [CHAR LIMIT=NONE] -->
+ <string name="username_title">Username</string>
+ <!-- Text of the password preference. [CHAR LIMIT=NONE] -->
+ <string name="password_title">Password</string>
+ <!-- Text of the display name preference. [CHAR LIMIT=NONE] -->
+ <string name="display_name_title">Display name</string>
+ <!-- Text of the outbound proxy address preference. [CHAR LIMIT=NONE] -->
+ <string name="proxy_address_title">Outbound proxy address</string>
+ <!-- Text of the sip server/proxy port number. [CHAR LIMIT=NONE] -->
+ <string name="port_title">Port number</string>
+ <!-- Text of the transport type preference. [CHAR LIMIT=NONE] -->
+ <string name="transport_title">Transport type</string>
+ <!-- Text of the keepalive preference. [CHAR LIMIT=NONE] -->
+ <string name="send_keepalive_title">Send keep-alive</string>
+ <!-- Text of the set-primary preference. Simplified from "Make this my primary account". [CHAR LIMIT=NONE] -->
+ <string name="set_primary_title">Set as primary account</string>
+ <!-- Text of the set-primary preference summary. [CHAR LIMIT=NONE] -->
+ <string name="set_primary_summary">Used for outbound calls</string>
+ <!-- Text of the advanced settings section. [CHAR LIMIT=NONE] -->
+ <string name="advanced_settings">Optional settings</string>
+ <!-- Text of the username used in authentication. [CHAR LIMIT=NONE] -->
+ <string name="auth_username_title">Authentication username</string>
+ <!-- Help text of the auth_username field. [CHAR LIMIT=NONE] -->
+ <string name="auth_username_summary">Username used for authentication</string>
+
+ <!-- Initial status of the preferences is '<Not set>'. [CHAR LIMIT=NONE] -->
+ <string name="default_preference_summary"><Not set></string>
+ <!-- Default value for the display-name preference summary. [CHAR LIMIT=NONE] -->
+ <string name="display_name_summary"><Same as username></string>
+ <!-- Default value for the outbound-proxy-address preference summary. [CHAR LIMIT=NONE] -->
+ <string name="optional_summary"><Optional></string>
+
+ <!-- Default sip server/proxy port number. -->
+ <string translatable="false" name="default_port">5060</string>
+ <!-- Default sip server/proxy transport protocol. -->
+ <string translatable="false" name="default_transport">UDP</string>
+
+
+ <!-- Hint to show the advanced settings section. [CHAR LIMIT=NONE] -->
+ <string name="advanced_settings_show">▷ Touch to show all</string>
+ <!-- Hint to hide the advanced settings section. [CHAR LIMIT=NONE] -->
+ <string name="advanced_settings_hide">▽ Touch to hide all</string>
+
+ <!-- Text in an alert dialog telling the user that some input field (name of which is also part of the strings being translated above) is required and can not be left blank. [CHAR LIMIT=NONE] -->
+ <string name="empty_alert"><xliff:g id="input_field_name" example="Username, Password...">%s</xliff:g> is required and can\'t be left blank.</string>
+ <!-- Showing that port number is out of range in an alert dialog. [CHAR LIMIT=NONE] -->
+ <string name="not_a_valid_port">Port number should be within 1000 and 65534.</string>
+
+ <!-- Title of the alert dialog to notify user that there is no Internet connectivity. [CHAR LIMIT=40] -->
+ <string name="no_internet_available_title">No Internet connection</string>
+ <!-- Title of the alert dialog to notify user that there is no Wi-Fi connectivity. [CHAR LIMIT=40] -->
+ <string name="no_wifi_available_title">No Wi-Fi connection</string>
+ <!-- Message of the alert dialog to notify user that there is no Internet connectivity. [CHAR LIMIT=NONE] -->
+ <string name="no_internet_available">To place an Internet call, check your Internet connection first.</string>
+ <!-- Message of the alert dialog to notify user that there is no Wi-Fi connectivity. [CHAR LIMIT=NONE] -->
+ <string name="no_wifi_available">You need to be connected to a Wi-Fi network for Internet calls (use the Wireless & Network settings).</string>
+ <!-- Title of the alert dialog to notify user that Internet call is not supported. [CHAR LIMIT=40] -->
+ <string name="no_voip">Internet calling not supported</string>
+
+ <!-- Option for whether the SIP service should send keepalive messages. [CHAR LIMIT=40] -->
+ <string translatable="true" name="sip_system_decide">Automatic</string>
+ <!-- Option for whether the SIP service should send keepalive messages. [CHAR LIMIT=40] -->
+ <string translatable="true" name="sip_always_send_keepalive">Always send</string>
+
+ <!-- Do not translate the following strings. Used for the Internet call options. Need to be in-sync with Settings.System.SIP_ strings. -->
+ <string translatable="false" name="sip_always">SIP_ALWAYS</string>
+ <string translatable="false" name="sip_address_only">SIP_ADDRESS_ONLY</string>
+ <string translatable="false" name="sip_ask_me_each_time">SIP_ASK_ME_EACH_TIME</string>
+
+ <!-- Do not translate the following strings. Used for the preference keys -->
+ <string translatable="false" name="domain_address">SipDomain</string>
+ <string translatable="false" name="username">UserName</string>
+ <string translatable="false" name="password">Password</string>
+ <string translatable="false" name="display_name">DisplayName</string>
+ <string translatable="false" name="proxy_address">ProxyAddress</string>
+ <string translatable="false" name="port">Port</string>
+ <string translatable="false" name="transport">Protocol</string>
+ <string translatable="false" name="send_keepalive">SendKeepAlive</string>
+ <string translatable="false" name="set_primary">SetPrimary</string>
+ <string translatable="false" name="advanced_settings_container">advanced settings container</string>
+ <string translatable="false" name="auth_username">AuthUserName</string>
+
+ <!-- Label for SIP connection service. -->
+ <string name="sip_connection_service_label">Built-in Internet calling</string>
+</resources>
diff --git a/sip/res/xml/sip_advanced_edit.xml b/sip/res/xml/sip_advanced_edit.xml
new file mode 100644
index 0000000..b7974ba
--- /dev/null
+++ b/sip/res/xml/sip_advanced_edit.xml
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+ android:persistent="false">
+
+ <PreferenceCategory
+ android:key="@string/advanced_settings_container"
+ android:persistent="false">
+
+ <EditTextPreference
+ android:key="@string/auth_username"
+ android:title="@string/auth_username_title"
+ android:dialogTitle="@string/auth_username_title"
+ android:summary="@string/auth_username_summary"
+ android:persistent="false"
+ android:singleLine="true"/>
+
+ <EditTextPreference
+ android:key="@string/display_name"
+ android:title="@string/display_name_title"
+ android:dialogTitle="@string/display_name_title"
+ android:summary="@string/display_name_summary"
+ android:persistent="false"
+ android:singleLine="true"/>
+
+ <EditTextPreference
+ android:key="@string/proxy_address"
+ android:title="@string/proxy_address_title"
+ android:dialogTitle="@string/proxy_address_title"
+ android:summary="@string/optional_summary"
+ android:persistent="false"
+ android:singleLine="true"/>
+
+ <EditTextPreference
+ android:key="@string/port"
+ android:title="@string/port_title"
+ android:summary="@string/default_port"
+ android:dialogTitle="@string/port_title"
+ android:persistent="false"
+ android:inputType="number|numberDecimal"
+ android:singleLine="true"/>
+
+ <ListPreference
+ android:key="@string/transport"
+ android:title="@string/transport_title"
+ android:entries="@array/transport_types"
+ android:entryValues="@array/transport_types"
+ android:summary="@string/default_transport"
+ android:persistent="false"
+ android:dialogTitle="@string/transport_title"/>
+
+ <ListPreference
+ android:key="@string/send_keepalive"
+ android:title="@string/send_keepalive_title"
+ android:entries="@array/sip_send_keepalive_options"
+ android:entryValues="@array/sip_send_keepalive_options"
+ android:persistent="false"
+ android:summary="@string/sip_system_decide"/>
+ </PreferenceCategory>
+</PreferenceScreen>
diff --git a/sip/res/xml/sip_edit.xml b/sip/res/xml/sip_edit.xml
new file mode 100644
index 0000000..f091fc1
--- /dev/null
+++ b/sip/res/xml/sip_edit.xml
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+ android:title="@string/sip_edit_title"
+ android:persistent="false">
+
+ <EditTextPreference
+ android:key="@string/username"
+ android:title="@string/username_title"
+ android:dialogTitle="@string/username_title"
+ android:summary="@string/default_preference_summary"
+ android:persistent="false"
+ android:singleLine="true"
+ android:inputType="textNoSuggestions"/>
+
+ <EditTextPreference
+ android:key="@string/password"
+ android:title="@string/password_title"
+ android:dialogTitle="@string/password_title"
+ android:password="true"
+ android:summary="@string/default_preference_summary"
+ android:persistent="false"
+ android:singleLine="true"/>
+
+ <EditTextPreference
+ android:key="@string/domain_address"
+ android:title="@string/domain_address_title"
+ android:dialogTitle="@string/domain_address_title"
+ android:summary="@string/default_preference_summary"
+ android:persistent="false"
+ android:singleLine="true"
+ android:inputType="textNoSuggestions"/>
+
+ <CheckBoxPreference
+ android:key="@string/set_primary"
+ android:title="@string/set_primary_title"
+ android:persistent="false"
+ android:summary="@string/set_primary_summary"/>
+
+ <PreferenceScreen
+ android:key="@string/advanced_settings"
+ android:title="@string/advanced_settings"
+ android:summary="@string/advanced_settings_show"
+ android:persistent="false"/>
+</PreferenceScreen>
diff --git a/sip/res/xml/sip_setting.xml b/sip/res/xml/sip_setting.xml
new file mode 100644
index 0000000..c754404
--- /dev/null
+++ b/sip/res/xml/sip_setting.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+ android:title="@string/sip_accounts">
+
+ <CheckBoxPreference
+ android:key="sip_receive_calls_key"
+ android:title="@string/sip_receive_calls"
+ android:summary="@string/sip_receive_calls_summary"
+ android:persistent="true"/>
+
+ <PreferenceCategory android:key="sip_account_list" android:title="@string/sip_account_list"/>
+</PreferenceScreen>
diff --git a/sip/res/xml/sip_settings_category.xml b/sip/res/xml/sip_settings_category.xml
new file mode 100644
index 0000000..17ee577
--- /dev/null
+++ b/sip/res/xml/sip_settings_category.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:phone="http://schemas.android.com/apk/res/com.android.phone">
+ <PreferenceCategory
+ android:key="sip_settings_category_key"
+ android:title="@string/sip_settings"
+ android:persistent="false">
+
+ <PreferenceScreen
+ android:key="sip_account_settings_key"
+ android:title="@string/sip_accounts_title"
+ android:persistent="false">
+
+ <intent android:action="android.intent.action.MAIN"
+ android:targetPackage="com.android.phone"
+ android:targetClass="com.android.services.telephony.sip.SipSettings" />
+ </PreferenceScreen>
+
+ <ListPreference
+ android:key="sip_call_options_key"
+ android:title="@string/sip_call_options_title"
+ android:persistent="true"
+ android:entries="@array/sip_call_options_entries"
+ android:entryValues="@array/sip_call_options_values"/>
+
+ <ListPreference
+ android:key="sip_call_options_wifi_only_key"
+ android:title="@string/sip_call_options_title"
+ android:dialogTitle="@string/sip_call_options_wifi_only_title"
+ android:persistent="true"
+ android:entries="@array/sip_call_options_wifi_only_entries"
+ android:entryValues="@array/sip_call_options_values"/>
+ </PreferenceCategory>
+</PreferenceScreen>
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 0000000..b949351
--- /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 0000000..40239a4
--- /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 0000000..9b7b33f
--- /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 0000000..a765296
--- /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 0000000..bf4b6bb
--- /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 0000000..cf35e31
--- /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 0000000..ffb1513
--- /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 0000000..39b5655
--- /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);
+ }
+}