diff --git a/build/sdk.atree b/build/sdk.atree
index cb15d5a95..35d34dd6d 100644
--- a/build/sdk.atree
+++ b/build/sdk.atree
@@ -160,6 +160,7 @@ development/samples/MultiResolution samples/${PLATFORM_NAME}/MultiResol
development/samples/NotePad samples/${PLATFORM_NAME}/NotePad
development/samples/SampleSyncAdapter samples/${PLATFORM_NAME}/SampleSyncAdapter
development/samples/SearchableDictionary samples/${PLATFORM_NAME}/SearchableDictionary
+development/samples/SipDemo samples/${PLATFORM_NAME}/SipDemo
development/samples/SkeletonApp samples/${PLATFORM_NAME}/SkeletonApp
development/samples/Snake samples/${PLATFORM_NAME}/Snake
development/samples/SoftKeyboard samples/${PLATFORM_NAME}/SoftKeyboard
diff --git a/samples/SipDemo/Android.mk b/samples/SipDemo/Android.mk
new file mode 100644
index 000000000..6363727ce
--- /dev/null
+++ b/samples/SipDemo/Android.mk
@@ -0,0 +1,16 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := samples
+
+# Only compile source java files in this apk.
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := SipDemo
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_PACKAGE)
+
+# Use the following include to make our test apk.
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/samples/SipDemo/AndroidManifest.xml b/samples/SipDemo/AndroidManifest.xml
new file mode 100644
index 000000000..8fb767569
--- /dev/null
+++ b/samples/SipDemo/AndroidManifest.xml
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/SipDemo/_index.html b/samples/SipDemo/_index.html
new file mode 100644
index 000000000..dbc90373f
--- /dev/null
+++ b/samples/SipDemo/_index.html
@@ -0,0 +1,52 @@
+
This is a demo application highlighting how to make internet-based calls with
+the SIP API. The application uses a walkie-talkie style interface, allowing you
+to only be heard when the button is pushed down.
+
+The source code for this demo app shows how to accomplish three key things
+with SIP: Make a call, receive a call, and signal to the Android platform that
+your app wants to receive incoming SIP calls, so that they can be handled from
+within the application.
+
+
+The application includes:
+
SipSettings
+ — a PreferenceActivity that supplies basic settings for SIP
+ authentication.
+ IncomingCallReceiver — a BroadcastReceiver
+ that listens for incoming SIP calls and passes them to
+ WalkieTalkieActivity for handling. WalkieTalkieActivity
+ — a activity that login to SIP provider and registers the device to
+ receive incoming SIP, handles incoming calls and makes outgoing calls, managing
+ UI during the call.
+If you are developing an application that uses the SIP API, remember that the
+feature is supported only on Android 2.3 (API level 9) and higher versions of
+the platform. Also, among devices running Android 2.3 (API level 9) or higher,
+not all devices will offer SIP support. To ensure that your application can only
+be installed on devices that are capable of supporting SIP, remember to add the
+following to the application's manifest before publishing to Android Market:
+ <uses-sdk android:minSdkVersion="9" />, which
+ indicates to Android Market and the platform that your application requires
+ Android 2.3 or higher. For more information, see API Levels and the
+ documentation for the <uses-sdk>
+ element.
To control how Android Market filters your application
+from devices that do not support SIP, remember to add the following to the
+application's manifest
<uses-feature
+ android:name="android.hardware.sip.voip" />, which tells Android
+ Market that your application uses the SIP API. The declaration should include
+ an android:required attribute that indicates whether you want
+ Android Market to filter the application from devices that do not offer SIP
+ support. Other <uses-feature> declarations may also be
+ needed, depending on your implementation. For more information, see the
+ documentation for the <uses-feature>
+ element.
+For more information about using the SIP API, see the android.net.sip
+documentation.
+
+
diff --git a/samples/SipDemo/res/drawable/btn_record.xml b/samples/SipDemo/res/drawable/btn_record.xml
new file mode 100644
index 000000000..a803da11c
--- /dev/null
+++ b/samples/SipDemo/res/drawable/btn_record.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
diff --git a/samples/SipDemo/res/drawable/btn_speak_normal.png b/samples/SipDemo/res/drawable/btn_speak_normal.png
new file mode 100644
index 000000000..fb86ceba1
Binary files /dev/null and b/samples/SipDemo/res/drawable/btn_speak_normal.png differ
diff --git a/samples/SipDemo/res/drawable/btn_speak_pressed.png b/samples/SipDemo/res/drawable/btn_speak_pressed.png
new file mode 100644
index 000000000..cffdf918c
Binary files /dev/null and b/samples/SipDemo/res/drawable/btn_speak_pressed.png differ
diff --git a/samples/SipDemo/res/drawable/btn_speak_selected.png b/samples/SipDemo/res/drawable/btn_speak_selected.png
new file mode 100644
index 000000000..99a1a6f57
Binary files /dev/null and b/samples/SipDemo/res/drawable/btn_speak_selected.png differ
diff --git a/samples/SipDemo/res/drawable/icon.png b/samples/SipDemo/res/drawable/icon.png
new file mode 100644
index 000000000..64e3601c2
Binary files /dev/null and b/samples/SipDemo/res/drawable/icon.png differ
diff --git a/samples/SipDemo/res/layout/call_address_dialog.xml b/samples/SipDemo/res/layout/call_address_dialog.xml
new file mode 100644
index 000000000..2bd75565e
--- /dev/null
+++ b/samples/SipDemo/res/layout/call_address_dialog.xml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/SipDemo/res/layout/walkietalkie.xml b/samples/SipDemo/res/layout/walkietalkie.xml
new file mode 100644
index 000000000..bdf634c84
--- /dev/null
+++ b/samples/SipDemo/res/layout/walkietalkie.xml
@@ -0,0 +1,49 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/SipDemo/res/values/strings.xml b/samples/SipDemo/res/values/strings.xml
new file mode 100644
index 000000000..a0d49bc0a
--- /dev/null
+++ b/samples/SipDemo/res/values/strings.xml
@@ -0,0 +1,22 @@
+
+
+
+
+ SIP Demo
+ SIP Address to contact
+ Talk
+
diff --git a/samples/SipDemo/res/xml/preferences.xml b/samples/SipDemo/res/xml/preferences.xml
new file mode 100644
index 000000000..590e0924c
--- /dev/null
+++ b/samples/SipDemo/res/xml/preferences.xml
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
diff --git a/samples/SipDemo/src/com/example/android/sip/IncomingCallReceiver.java b/samples/SipDemo/src/com/example/android/sip/IncomingCallReceiver.java
new file mode 100644
index 000000000..ea27a0a85
--- /dev/null
+++ b/samples/SipDemo/src/com/example/android/sip/IncomingCallReceiver.java
@@ -0,0 +1,72 @@
+/*
+ * 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.example.android.sip;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.net.sip.*;
+import android.util.Log;
+
+/**
+ * Listens for incoming SIP calls, intercepts and hands them off to WalkieTalkieActivity.
+ */
+public class IncomingCallReceiver extends BroadcastReceiver {
+ /**
+ * Processes the incoming call, answers it, and hands it over to the
+ * WalkieTalkieActivity.
+ * @param context The context under which the receiver is running.
+ * @param intent The intent being received.
+ */
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ SipAudioCall incomingCall = null;
+ try {
+
+ SipAudioCall.Listener listener = new SipAudioCall.Listener() {
+ @Override
+ public void onRinging(SipAudioCall call, SipProfile caller) {
+ try {
+ call.answerCall(30);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ };
+
+ WalkieTalkieActivity wtActivity = (WalkieTalkieActivity) context;
+
+ incomingCall = wtActivity.manager.takeAudioCall(intent, listener);
+ incomingCall.answerCall(30);
+ incomingCall.startAudio();
+ incomingCall.setSpeakerMode(true);
+ if(incomingCall.isMuted()) {
+ incomingCall.toggleMute();
+ }
+
+ wtActivity.call = incomingCall;
+
+ wtActivity.updateStatus(incomingCall);
+
+ } catch (Exception e) {
+ if (incomingCall != null) {
+ incomingCall.close();
+ }
+ }
+ }
+
+}
diff --git a/samples/SipDemo/src/com/example/android/sip/SipSettings.java b/samples/SipDemo/src/com/example/android/sip/SipSettings.java
new file mode 100644
index 000000000..2f0624bbf
--- /dev/null
+++ b/samples/SipDemo/src/com/example/android/sip/SipSettings.java
@@ -0,0 +1,34 @@
+/*
+ * 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.example.android.sip;
+
+import android.os.Bundle;
+import android.preference.PreferenceActivity;
+
+/**
+ * Handles SIP authentication settings for the Walkie Talkie app.
+ */
+public class SipSettings extends PreferenceActivity {
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ // Note that none of the preferences are actually defined here.
+ // They're all in the XML file res/xml/preferences.xml.
+ super.onCreate(savedInstanceState);
+ addPreferencesFromResource(R.xml.preferences);
+ }
+}
diff --git a/samples/SipDemo/src/com/example/android/sip/WalkieTalkieActivity.java b/samples/SipDemo/src/com/example/android/sip/WalkieTalkieActivity.java
new file mode 100644
index 000000000..4c187eddc
--- /dev/null
+++ b/samples/SipDemo/src/com/example/android/sip/WalkieTalkieActivity.java
@@ -0,0 +1,358 @@
+/*
+ * 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.example.android.sip;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.PendingIntent;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.preference.PreferenceManager;
+import android.util.Log;
+import android.view.*;
+import android.net.sip.*;
+import android.widget.EditText;
+import android.widget.TextView;
+import android.widget.ToggleButton;
+
+import java.text.ParseException;
+
+/**
+ * Handles all calling, receiving calls, and UI interaction in the WalkieTalkie app.
+ */
+public class WalkieTalkieActivity extends Activity implements View.OnTouchListener {
+
+ public String sipAddress = null;
+
+ public SipManager manager = null;
+ public SipProfile me = null;
+ public SipAudioCall call = null;
+ public IncomingCallReceiver callReceiver;
+
+ private static final int CALL_ADDRESS = 1;
+ private static final int SET_AUTH_INFO = 2;
+ private static final int UPDATE_SETTINGS_DIALOG = 3;
+ private static final int HANG_UP = 4;
+
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.walkietalkie);
+
+ ToggleButton pushToTalkButton = (ToggleButton) findViewById(R.id.pushToTalk);
+ pushToTalkButton.setOnTouchListener(this);
+
+ // Set up the intent filter. This will be used to fire an
+ // IncomingCallReceiver when someone calls the SIP address used by this
+ // application.
+ IntentFilter filter = new IntentFilter();
+ filter.addAction("android.SipDemo.INCOMING_CALL");
+ callReceiver = new IncomingCallReceiver();
+ this.registerReceiver(callReceiver, filter);
+
+ // "Push to talk" can be a serious pain when the screen keeps turning off.
+ // Let's prevent that.
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+
+ initializeManager();
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ // When we get back from the preference setting Activity, assume
+ // settings have changed, and re-login with new auth info.
+ initializeManager();
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ if (call != null) {
+ call.close();
+ }
+
+ closeLocalProfile();
+
+ if (callReceiver != null) {
+ this.unregisterReceiver(callReceiver);
+ }
+ }
+
+ public void initializeManager() {
+ if(manager == null) {
+ manager = SipManager.newInstance(this);
+ }
+
+ initializeLocalProfile();
+ }
+
+ /**
+ * Logs you into your SIP provider, registering this device as the location to
+ * send SIP calls to for your SIP address.
+ */
+ public void initializeLocalProfile() {
+ if (manager == null) {
+ return;
+ }
+
+ if (me != null) {
+ closeLocalProfile();
+ }
+
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getBaseContext());
+ String username = prefs.getString("namePref", "");
+ String domain = prefs.getString("domainPref", "");
+ String password = prefs.getString("passPref", "");
+
+ if (username.length() == 0 || domain.length() == 0 || password.length() == 0) {
+ showDialog(UPDATE_SETTINGS_DIALOG);
+ return;
+ }
+
+ try {
+ SipProfile.Builder builder = new SipProfile.Builder(username, domain);
+ builder.setPassword(password);
+ me = builder.build();
+
+ Intent i = new Intent();
+ i.setAction("android.SipDemo.INCOMING_CALL");
+ PendingIntent pi = PendingIntent.getBroadcast(this, 0, i, Intent.FILL_IN_DATA);
+ manager.open(me, pi, null);
+
+
+ // This listener must be added AFTER manager.open is called,
+ // Otherwise the methods aren't guaranteed to fire.
+
+ manager.setRegistrationListener(me.getUriString(), new SipRegistrationListener() {
+ public void onRegistering(String localProfileUri) {
+ updateStatus("Registering with SIP Server...");
+ }
+
+ public void onRegistrationDone(String localProfileUri, long expiryTime) {
+ updateStatus("Ready");
+ }
+
+ public void onRegistrationFailed(String localProfileUri, int errorCode,
+ String errorMessage) {
+ updateStatus("Registration failed. Please check settings.");
+ }
+ });
+ } catch (ParseException pe) {
+ updateStatus("Connection Error.");
+ } catch (SipException se) {
+ updateStatus("Connection error.");
+ }
+ }
+
+ /**
+ * Closes out your local profile, freeing associated objects into memory
+ * and unregistering your device from the server.
+ */
+ public void closeLocalProfile() {
+ if (manager == null) {
+ return;
+ }
+ try {
+ if (me != null) {
+ manager.close(me.getUriString());
+ }
+ } catch (Exception ee) {
+ Log.d("WalkieTalkieActivity/onDestroy", "Failed to close local profile.", ee);
+ }
+ }
+
+ /**
+ * Make an outgoing call.
+ */
+ public void initiateCall() {
+
+ updateStatus(sipAddress);
+
+ try {
+ SipAudioCall.Listener listener = new SipAudioCall.Listener() {
+ // Much of the client's interaction with the SIP Stack will
+ // happen via listeners. Even making an outgoing call, don't
+ // forget to set up a listener to set things up once the call is established.
+ @Override
+ public void onCallEstablished(SipAudioCall call) {
+ call.startAudio();
+ call.setSpeakerMode(true);
+ call.toggleMute();
+ updateStatus(call);
+ }
+
+ @Override
+ public void onCallEnded(SipAudioCall call) {
+ updateStatus("Ready.");
+ }
+ };
+
+ call = manager.makeAudioCall(me.getUriString(), sipAddress, listener, 30);
+
+ }
+ catch (Exception e) {
+ Log.i("WalkieTalkieActivity/InitiateCall", "Error when trying to close manager.", e);
+ if (me != null) {
+ try {
+ manager.close(me.getUriString());
+ } catch (Exception ee) {
+ Log.i("WalkieTalkieActivity/InitiateCall",
+ "Error when trying to close manager.", ee);
+ ee.printStackTrace();
+ }
+ }
+ if (call != null) {
+ call.close();
+ }
+ }
+ }
+
+ /**
+ * Updates the status box at the top of the UI with a messege of your choice.
+ * @param status The String to display in the status box.
+ */
+ public void updateStatus(final String status) {
+ // Be a good citizen. Make sure UI changes fire on the UI thread.
+ this.runOnUiThread(new Runnable() {
+ public void run() {
+ TextView labelView = (TextView) findViewById(R.id.sipLabel);
+ labelView.setText(status);
+ }
+ });
+ }
+
+ /**
+ * Updates the status box with the SIP address of the current call.
+ * @param call The current, active call.
+ */
+ public void updateStatus(SipAudioCall call) {
+ String useName = call.getPeerProfile().getDisplayName();
+ if(useName == null) {
+ useName = call.getPeerProfile().getUserName();
+ }
+ updateStatus(useName + "@" + call.getPeerProfile().getSipDomain());
+ }
+
+ /**
+ * Updates whether or not the user's voice is muted, depending on whether the button is pressed.
+ * @param v The View where the touch event is being fired.
+ * @param event The motion to act on.
+ * @return boolean Returns false to indicate that the parent view should handle the touch event
+ * as it normally would.
+ */
+ public boolean onTouch(View v, MotionEvent event) {
+ if (call == null) {
+ return false;
+ } else if (event.getAction() == MotionEvent.ACTION_DOWN && call != null && call.isMuted()) {
+ call.toggleMute();
+ } else if (event.getAction() == MotionEvent.ACTION_UP && !call.isMuted()) {
+ call.toggleMute();
+ }
+ return false;
+ }
+
+ public boolean onCreateOptionsMenu(Menu menu) {
+ menu.add(0, CALL_ADDRESS, 0, "Call someone");
+ menu.add(0, SET_AUTH_INFO, 0, "Edit your SIP Info.");
+ menu.add(0, HANG_UP, 0, "End Current Call.");
+
+ return true;
+ }
+
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case CALL_ADDRESS:
+ showDialog(CALL_ADDRESS);
+ break;
+ case SET_AUTH_INFO:
+ updatePreferences();
+ break;
+ case HANG_UP:
+ if(call != null) {
+ try {
+ call.endCall();
+ } catch (SipException se) {
+ Log.d("WalkieTalkieActivity/onOptionsItemSelected",
+ "Error ending call.", se);
+ }
+ call.close();
+ }
+ break;
+ }
+ return true;
+ }
+
+ @Override
+ protected Dialog onCreateDialog(int id) {
+ switch (id) {
+ case CALL_ADDRESS:
+
+ LayoutInflater factory = LayoutInflater.from(this);
+ final View textBoxView = factory.inflate(R.layout.call_address_dialog, null);
+ return new AlertDialog.Builder(this)
+ .setTitle("Call Someone.")
+ .setView(textBoxView)
+ .setPositiveButton(
+ android.R.string.ok, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int whichButton) {
+ EditText textField = (EditText)
+ (textBoxView.findViewById(R.id.calladdress_edit));
+ sipAddress = textField.getText().toString();
+ initiateCall();
+
+ }
+ })
+ .setNegativeButton(
+ android.R.string.cancel, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int whichButton) {
+ // Noop.
+ }
+ })
+ .create();
+
+ case UPDATE_SETTINGS_DIALOG:
+ return new AlertDialog.Builder(this)
+ .setMessage("Please update your SIP Account Settings.")
+ .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int whichButton) {
+ updatePreferences();
+ }
+ })
+ .setNegativeButton(
+ android.R.string.cancel, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int whichButton) {
+ // Noop.
+ }
+ })
+ .create();
+ }
+ return null;
+ }
+
+ public void updatePreferences() {
+ Intent settingsActivity = new Intent(getBaseContext(),
+ SipSettings.class);
+ startActivity(settingsActivity);
+ }
+}