| /* |
| * 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.sip.demo; |
| |
| import android.app.AlertDialog; |
| import android.app.Dialog; |
| import android.content.BroadcastReceiver; |
| import android.content.Context; |
| import android.content.DialogInterface; |
| import android.content.Intent; |
| import android.content.IntentFilter; |
| import android.net.sip.ISipSession; |
| import android.net.sip.SipProfile; |
| import android.net.sip.SipAudioCall; |
| import android.net.sip.SipManager; |
| import android.net.sip.SipRegistrationListener; |
| import android.net.sip.SipSessionState; |
| import android.os.Bundle; |
| import android.os.RemoteException; |
| import android.preference.EditTextPreference; |
| import android.preference.Preference; |
| import android.preference.PreferenceActivity; |
| import android.preference.PreferenceScreen; |
| import android.preference.Preference.OnPreferenceClickListener; |
| import android.text.TextUtils; |
| import android.util.Log; |
| import android.view.Menu; |
| import android.view.MenuItem; |
| |
| import java.io.IOException; |
| import java.net.DatagramSocket; |
| import java.net.InetAddress; |
| import java.text.ParseException; |
| import javax.sip.SipException; |
| |
| /** |
| */ |
| public class SipMain extends PreferenceActivity |
| implements Preference.OnPreferenceChangeListener { |
| private static final String TAG = SipMain.class.getSimpleName(); |
| private static final String INCOMING_CALL_ACTION = SipMain.class.getName(); |
| private static final int EXPIRY_TIME = 3600; |
| private static final int MENU_REGISTER = Menu.FIRST; |
| private static final int MENU_CALL = Menu.FIRST + 1; |
| private static final int MENU_HANGUP = Menu.FIRST + 2; |
| private static final int MENU_SEND_DTMF_1 = Menu.FIRST + 3; |
| private static final int MENU_SPEAKER_MODE = Menu.FIRST + 4; |
| private static final int MENU_MUTE = Menu.FIRST + 5; |
| private static final int MENU_HOLD = Menu.FIRST + 6; |
| |
| private SipManager mSipManager; |
| private Preference mCallStatus; |
| private EditTextPreference mPeerUri; |
| private EditTextPreference mServerUri; |
| private EditTextPreference mPassword; |
| private EditTextPreference mDisplayName; |
| private EditTextPreference mOutboundProxy; |
| private Preference mMyIp; |
| |
| private SipProfile mLocalProfile; |
| private SipAudioCall mAudioCall; |
| private ISipSession mSipSession; |
| |
| private MyDialog mDialog; |
| private Throwable mError; |
| private boolean mChanged; |
| private boolean mSpeakerMode; |
| |
| private BroadcastReceiver mIncomingCallReceiver = |
| new IncomingCallReceiver(); |
| |
| @Override |
| protected void onCreate(Bundle savedInstanceState) { |
| super.onCreate(savedInstanceState); |
| addPreferencesFromResource(com.android.settings.sip.R.xml.dev_pref); |
| |
| mCallStatus = getPreferenceScreen().findPreference("call_status"); |
| mPeerUri = setupEditTextPreference("peer"); |
| mServerUri = setupEditTextPreference("server_address"); |
| mPassword = (EditTextPreference) |
| getPreferenceScreen().findPreference("password"); |
| mPassword.setOnPreferenceChangeListener(this); |
| mDisplayName = setupEditTextPreference("display_name"); |
| mOutboundProxy = setupEditTextPreference("proxy_address"); |
| mMyIp = getPreferenceScreen().findPreference("my_ip"); |
| mMyIp.setOnPreferenceClickListener( |
| new OnPreferenceClickListener() { |
| public boolean onPreferenceClick(Preference preference) { |
| // for testing convenience: copy my IP to server address |
| if (TextUtils.isEmpty(mServerUri.getText())) { |
| String myIp = mMyIp.getSummary().toString(); |
| String uri = "test@" + myIp + ":5060"; |
| mServerUri.setText(uri); |
| mServerUri.setSummary(uri); |
| } |
| return true; |
| } |
| }); |
| |
| mCallStatus.setOnPreferenceClickListener( |
| new OnPreferenceClickListener() { |
| public boolean onPreferenceClick(Preference preference) { |
| actOnCallStatus(); |
| return true; |
| } |
| }); |
| setCallStatus(); |
| |
| final Intent intent = getIntent(); |
| setIntent(null); |
| new Thread(new Runnable() { |
| public void run() { |
| final String localIp = getLocalIp(); |
| runOnUiThread(new Runnable() { |
| public void run() { |
| mMyIp.setSummary(localIp); |
| } |
| }); |
| |
| mSipManager = SipManager.getInstance(SipMain.this); |
| receiveCall(intent, "thread"); |
| } |
| }).start(); |
| |
| registerIncomingCallReceiver(mIncomingCallReceiver); |
| } |
| |
| @Override |
| protected void onNewIntent(Intent intent) { |
| Log.v(TAG, " onNewIntent(): " + intent); |
| setIntent(intent); |
| } |
| |
| @Override |
| protected void onResume() { |
| super.onResume(); |
| receiveCall(getIntent(), "onResume()"); |
| } |
| |
| private synchronized void receiveCall(Intent intent, String msg) { |
| Log.v(TAG, msg + ": receiveCall(): any call comes in? " + intent); |
| if (SipManager.isIncomingCallIntent(intent)) { |
| createSipAudioCall(intent); |
| setIntent(null); |
| } |
| } |
| |
| private void createSipAudioCall(Intent intent) { |
| // TODO: what happens if another call is going |
| try { |
| Log.v(TAG, "create SipAudioCall"); |
| mAudioCall = mSipManager.takeAudioCall(this, intent, |
| createListener()); |
| if (mAudioCall == null) { |
| throw new SipException("no session to handle audio call"); |
| } |
| } catch (SipException e) { |
| setCallStatus(e); |
| } |
| } |
| |
| private void makeAudioCall() throws Exception { |
| if ((mAudioCall == null) || mChanged) { |
| if (mChanged) register(); |
| closeAudioCall(); |
| mAudioCall = mSipManager.makeAudioCall(this, createLocalProfile(), |
| createPeerSipProfile(), createListener()); |
| Log.v(TAG, "info changed; recreate AudioCall isntance"); |
| } |
| } |
| |
| private EditTextPreference setupEditTextPreference(String key) { |
| EditTextPreference pref = (EditTextPreference) |
| getPreferenceScreen().findPreference(key); |
| pref.setOnPreferenceChangeListener(this); |
| pref.setSummary(pref.getText()); |
| return pref; |
| } |
| |
| @Override |
| protected void onDestroy() { |
| super.onDestroy(); |
| unregisterReceiver(mIncomingCallReceiver); |
| closeAudioCall(); |
| } |
| |
| public boolean onPreferenceChange(Preference pref, Object newValue) { |
| String value = (String) newValue; |
| if (value == null) value = ""; |
| if (pref != mPassword) pref.setSummary(value); |
| if ((pref != mPeerUri) |
| && !value.equals(((EditTextPreference) pref).getText())) { |
| mChanged = true; |
| } |
| return true; |
| } |
| |
| private String getText(EditTextPreference preference) { |
| CharSequence text = preference.getText(); |
| return ((text == null) ? "" : text.toString()); |
| } |
| |
| private SipProfile createLocalProfile() throws SipException { |
| try { |
| if ((mLocalProfile == null) || mChanged) { |
| String serverUri = getText(mServerUri); |
| if (TextUtils.isEmpty(serverUri)) { |
| throw new SipException("Server address missing"); |
| } |
| mLocalProfile = new SipProfile.Builder(serverUri) |
| .setPassword(getText(mPassword)) |
| .setDisplayName(getText(mDisplayName)) |
| .setOutboundProxy(getText(mOutboundProxy)) |
| .build(); |
| } |
| return mLocalProfile; |
| } catch (ParseException e) { |
| throw new SipException("createLoalSipProfile", e); |
| } |
| } |
| |
| private SipProfile createPeerSipProfile() { |
| try { |
| return new SipProfile.Builder(getPeerUri()).build(); |
| } catch (ParseException e) { |
| throw new RuntimeException(e); |
| } |
| } |
| |
| private void setCallStatus(Throwable e) { |
| mError = e; |
| setCallStatus(); |
| } |
| |
| private void setCallStatus() { |
| runOnUiThread(new Runnable() { |
| public void run() { |
| mCallStatus.setSummary(getCallStatus()); |
| mError = null; |
| } |
| }); |
| } |
| |
| private void showCallNotificationDialog(SipProfile caller) { |
| mDialog = new CallNotificationDialog(caller); |
| runOnUiThread(new Runnable() { |
| public void run() { |
| showDialog(mDialog.getId()); |
| } |
| }); |
| } |
| |
| private SipAudioCall.Listener createListener() { |
| return new SipAudioCall.Adapter() { |
| public void onChanged(SipAudioCall call) { |
| Log.v(TAG, "onChanged(): " + call + " <--> " + mAudioCall); |
| if (mAudioCall != call) return; |
| setCallStatus(); |
| } |
| |
| public void onCalling(SipAudioCall call) { |
| if (mAudioCall != call) return; |
| mSipSession = call.getSipSession(); |
| setCallStatus(); |
| } |
| |
| public void onRinging(SipAudioCall call, SipProfile caller) { |
| Log.v(TAG, "onRinging(): " + call + " <--> " + mAudioCall); |
| if (mAudioCall != null) return; |
| mSipSession = call.getSipSession(); |
| showCallNotificationDialog(caller); |
| setCallStatus(); |
| } |
| |
| public void onCallEstablished(SipAudioCall call) { |
| Log.v(TAG, "onCallEstablished(): " + call + " <--> " + mAudioCall); |
| if (mAudioCall != call) return; |
| setAllPreferencesEnabled(false); |
| setCallStatus(); |
| } |
| |
| public void onCallEnded(SipAudioCall call) { |
| Log.v(TAG, "onCallEnded(): " + call + " <--> " + mAudioCall); |
| if (mAudioCall != call) return; |
| if (mDialog != null) { |
| runOnUiThread(new Runnable() { |
| public void run() { |
| dismissDialog(mDialog.getId()); |
| } |
| }); |
| } |
| setCallStatus(); |
| mSipSession = null; |
| mAudioCall = null; |
| setAllPreferencesEnabled(true); |
| } |
| |
| public void onError(SipAudioCall call, String errorMessage) { |
| Log.v(TAG, "onError(): " + call + " <--> " + mAudioCall); |
| if (mAudioCall != call) return; |
| mError = new SipException(errorMessage); |
| setCallStatus(); |
| mSipSession = null; |
| mAudioCall = null; |
| setAllPreferencesEnabled(true); |
| } |
| }; |
| } |
| |
| private void closeAudioCall() { |
| if (mAudioCall != null) mAudioCall.close(); |
| } |
| |
| private SipRegistrationListener createRegistrationListener() { |
| return new SipRegistrationListener() { |
| public void onRegistrationDone(String uri, long expiryTime) { |
| setCallStatus(); |
| } |
| |
| public void onRegistrationFailed(String uri, String className, |
| String message) { |
| mError = new SipException("registration error: " + message); |
| setCallStatus(); |
| } |
| |
| public void onRegistering(String uri) { |
| setCallStatus(); |
| } |
| }; |
| } |
| |
| private void register() { |
| try { |
| if (mLocalProfile == null) createLocalProfile(); |
| if (mChanged) { |
| mSipManager.unregister(mLocalProfile, null); |
| } |
| mSipManager.register(createLocalProfile(), EXPIRY_TIME, |
| createRegistrationListener()); |
| mChanged = false; |
| setCallStatus(); |
| } catch (Exception e) { |
| Log.e(TAG, "register()", e); |
| setCallStatus(e); |
| } |
| } |
| |
| private void makeCall() { |
| try { |
| makeAudioCall(); |
| } catch (Exception e) { |
| Log.e(TAG, "makeCall()", e); |
| setCallStatus(e); |
| } |
| } |
| |
| private void endCall() { |
| try { |
| mAudioCall.endCall(); |
| mSpeakerMode = false; |
| } catch (SipException e) { |
| Log.e(TAG, "endCall()", e); |
| setCallStatus(e); |
| } |
| } |
| |
| private void holdOrEndCall() { |
| if (Math.random() > 0.4) { |
| holdCall(); |
| } else { |
| endCall(); |
| } |
| } |
| |
| private void answerCall() { |
| try { |
| mAudioCall.answerCall(); |
| } catch (SipException e) { |
| Log.e(TAG, "answerCall()", e); |
| setCallStatus(e); |
| } |
| } |
| |
| private void answerOrEndCall() { |
| if (Math.random() > 0) { |
| answerCall(); |
| } else { |
| endCall(); |
| } |
| } |
| |
| private void holdCall() { |
| try { |
| mAudioCall.holdCall(); |
| } catch (SipException e) { |
| Log.e(TAG, "holdCall()", e); |
| setCallStatus(e); |
| } |
| } |
| |
| private void continueCall() { |
| try { |
| mAudioCall.continueCall(); |
| } catch (SipException e) { |
| Log.e(TAG, "continueCall()", e); |
| setCallStatus(e); |
| } |
| } |
| |
| private boolean isOnHold() { |
| if (mAudioCall == null) return false; |
| return mAudioCall.isOnHold(); |
| } |
| |
| private void toggleOnHold() { |
| if (isOnHold()) { |
| continueCall(); |
| } else { |
| holdCall(); |
| } |
| } |
| |
| private void toggleMute() { |
| mAudioCall.toggleMute(); |
| } |
| |
| private void sendDtmf() { |
| mAudioCall.sendDtmf(1); |
| } |
| |
| private void setSpeakerMode() { |
| mAudioCall.setSpeakerMode(); |
| } |
| |
| private void setInCallMode() { |
| mAudioCall.setInCallMode(); |
| } |
| |
| private void setAllPreferencesEnabled(final boolean enabled) { |
| runOnUiThread(new Runnable() { |
| public void run() { |
| for (Preference preference : allPreferences()) { |
| preference.setEnabled(enabled); |
| } |
| } |
| }); |
| } |
| |
| private Preference[] allPreferences() { |
| return new Preference[] { |
| mCallStatus, mPeerUri, mServerUri, mPassword, mDisplayName, mOutboundProxy, mMyIp |
| }; |
| } |
| |
| @Override |
| public boolean onPrepareOptionsMenu(Menu menu) { |
| super.onPrepareOptionsMenu(menu); |
| SipSessionState state = ((mAudioCall == null) || mChanged) |
| ? SipSessionState.READY_TO_CALL |
| : getCallState(); |
| boolean muted = (mAudioCall == null) ? false : mAudioCall.isMuted(); |
| boolean onHold = isOnHold(); |
| |
| Log.v(TAG, "onPrepareOptionsMenu(), status=" + state); |
| menu.clear(); |
| switch (state) { |
| case READY_TO_CALL: |
| menu.add(0, MENU_REGISTER, 0, "Register"); |
| menu.add(0, MENU_CALL, 0, "Call"); |
| break; |
| case IN_CALL: |
| menu.add(0, MENU_SPEAKER_MODE, 0, (mSpeakerMode ? |
| "Speaker OFF" : "Speaker ON")); |
| menu.add(0, MENU_SEND_DTMF_1, 0, "Send DTMF_1"); |
| menu.add(0, MENU_MUTE, 0, |
| (muted ? "Unmute" : "Mute")); |
| menu.add(0, MENU_HOLD, 0, |
| (onHold ? "Unhold" : "Hold")); |
| /* pass through */ |
| default: |
| menu.add(0, MENU_HANGUP, 0, "Hang up"); |
| } |
| return true; |
| } |
| |
| @Override |
| public synchronized boolean onOptionsItemSelected(MenuItem item) { |
| switch (item.getItemId()) { |
| case MENU_REGISTER: |
| register(); |
| setCallStatus(); |
| return true; |
| |
| case MENU_CALL: |
| makeCall(); |
| return true; |
| |
| case MENU_HANGUP: |
| endCall(); |
| return true; |
| |
| case MENU_SPEAKER_MODE: |
| mSpeakerMode = !mSpeakerMode; |
| if (mSpeakerMode == true) { |
| setSpeakerMode(); |
| } else { |
| setInCallMode(); |
| } |
| return true; |
| |
| case MENU_SEND_DTMF_1: |
| sendDtmf(); |
| return true; |
| |
| case MENU_MUTE: |
| toggleMute(); |
| return true; |
| |
| case MENU_HOLD: |
| toggleOnHold(); |
| return true; |
| } |
| return super.onOptionsItemSelected(item); |
| } |
| |
| private String getPeerUri() { |
| return getText(mPeerUri); |
| } |
| |
| private SipSessionState getCallState() { |
| if (mSipSession == null) return SipSessionState.READY_TO_CALL; |
| try { |
| String state = mSipSession.getState(); |
| return Enum.valueOf(SipSessionState.class, state); |
| } catch (RemoteException e) { |
| return SipSessionState.REMOTE_ERROR; |
| } |
| } |
| |
| private String getCallStatus() { |
| if (mError != null) return mError.getMessage(); |
| if (mSipSession == null) return "Ready to call"; |
| switch (getCallState()) { |
| case REGISTERING: |
| return "Registering..."; |
| case READY_TO_CALL: |
| return "Ready to call"; |
| case INCOMING_CALL: |
| return "Ringing..."; |
| case INCOMING_CALL_ANSWERING: |
| return "Answering..."; |
| case OUTGOING_CALL: |
| return "Calling..."; |
| case OUTGOING_CALL_RING_BACK: |
| return "Ringing back..."; |
| case OUTGOING_CALL_CANCELING: |
| return "Cancelling..."; |
| case IN_CALL: |
| return (isOnHold() ? "On hold" : "Established"); |
| default: |
| return "Unknown"; |
| } |
| } |
| |
| private void actOnCallStatus() { |
| if ((mAudioCall == null) || mChanged) { |
| register(); |
| } else { |
| switch (getCallState()) { |
| case READY_TO_CALL: |
| makeCall(); |
| break; |
| case INCOMING_CALL: |
| answerOrEndCall(); |
| break; |
| case OUTGOING_CALL_RING_BACK: |
| case OUTGOING_CALL: |
| endCall(); |
| break; |
| case IN_CALL: |
| if (isOnHold()) { |
| continueCall(); |
| } else { |
| holdOrEndCall(); |
| } |
| break; |
| case OUTGOING_CALL_CANCELING: |
| case REGISTERING: |
| case INCOMING_CALL_ANSWERING: |
| default: |
| // do nothing |
| break; |
| } |
| } |
| |
| setCallStatus(); |
| } |
| |
| @Override |
| protected Dialog onCreateDialog (int id) { |
| return ((mDialog == null) ? null : mDialog.createDialog(id)); |
| } |
| |
| @Override |
| protected void onPrepareDialog (int id, Dialog dialog) { |
| if (mDialog != null) mDialog.prepareDialog(id, dialog); |
| } |
| |
| private class CallNotificationDialog implements MyDialog { |
| private SipProfile mCaller; |
| |
| CallNotificationDialog(SipProfile caller) { |
| mCaller = caller; |
| } |
| |
| public int getId() { |
| return 0; |
| } |
| |
| private String getCallerName() { |
| String name = mCaller.getDisplayName(); |
| if (TextUtils.isEmpty(name)) name = mCaller.getUri().toString(); |
| return name; |
| } |
| |
| public Dialog createDialog(int id) { |
| if (id != getId()) return null; |
| Log.d(TAG, "create call notification dialog"); |
| return new AlertDialog.Builder(SipMain.this) |
| .setTitle(getCallerName()) |
| .setIcon(android.R.drawable.ic_dialog_alert) |
| .setPositiveButton("Answer", |
| new DialogInterface.OnClickListener() { |
| public void onClick(DialogInterface dialog, int w) { |
| answerCall(); |
| } |
| }) |
| .setNegativeButton("Hang up", |
| new DialogInterface.OnClickListener() { |
| public void onClick(DialogInterface dialog, int w) { |
| endCall(); |
| } |
| }) |
| .setOnCancelListener(new DialogInterface.OnCancelListener() { |
| public void onCancel(DialogInterface dialog) { |
| endCall(); |
| } |
| }) |
| .create(); |
| } |
| |
| public void prepareDialog(int id, Dialog dialog) { |
| if (id != getId()) return; |
| dialog.setTitle(getCallerName()); |
| } |
| } |
| |
| private interface MyDialog { |
| int getId(); |
| Dialog createDialog(int id); |
| void prepareDialog(int id, Dialog dialog); |
| } |
| |
| private String getLocalIp() { |
| try { |
| DatagramSocket s = new DatagramSocket(); |
| s.connect(InetAddress.getByName("192.168.1.1"), 80); |
| return s.getLocalAddress().getHostAddress(); |
| } catch (IOException e) { |
| Log.w(TAG, "getLocalIp(): " + e); |
| return "127.0.0.1"; |
| } |
| } |
| |
| private void registerIncomingCallReceiver(BroadcastReceiver r) { |
| IntentFilter filter = new IntentFilter(); |
| filter.addAction(INCOMING_CALL_ACTION); |
| registerReceiver(r, filter); |
| } |
| |
| private class IncomingCallReceiver extends BroadcastReceiver { |
| @Override |
| public void onReceive(Context context, Intent intent) { |
| receiveCall(intent, "onReceive()"); |
| } |
| } |
| } |