blob: 2b5a2e3b637c55f7347b23d054f5b5a31085fb35 [file] [log] [blame]
/*
* 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 com.android.settings.sip.R;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.ContentValues;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.net.sip.SipProfile;
import android.net.sip.SipAudioCall;
import android.net.sip.SipManager;
import android.net.sip.SipSessionState;
import android.os.Bundle;
import android.provider.CallLog;
import android.provider.CallLog.Calls;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import java.io.IOException;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.Date;
import javax.sip.SipException;
/**
*/
public class SipCallUi extends Activity implements OnClickListener,
SipAudioCall.Listener {
private static final String TAG = SipCallUi.class.getSimpleName();
private TextView mPeerBox;
private TextView mMyIp;
private TextView mCallStatus;
private Button mEndButton;
private Button mMuteButton;
private Button mHoldButton;
private Button mDtmfButton;
private Button mModeButton;
private SipManager mSipManager;
private SipAudioCall mAudioCall;
private MyDialog mDialog;
private Throwable mError;
private boolean mSpeakerMode;
private long mCallTime = 0;
private String mCallee;
private String mCaller;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.call_ui);
mPeerBox = (TextView) findViewById(R.id.caller);
mCallStatus = (TextView) findViewById(R.id.call_status);
mMyIp = (TextView) findViewById(R.id.local_ip);
mEndButton = (Button) findViewById(R.id.hang_up_btn);
mMuteButton = (Button) findViewById(R.id.mute_btn);
mHoldButton = (Button) findViewById(R.id.hold_btn);
mDtmfButton = (Button) findViewById(R.id.dtmf_btn);
mModeButton = (Button) findViewById(R.id.mode_btn);
((TextView) findViewById(R.id.local_ip_title)).setText("Local IP");
((TextView) findViewById(R.id.call_status_title)).setText("Call status");
mEndButton.setText("End call");
mDtmfButton.setText("DTMF 1");
mPeerBox.setText("...");
mEndButton.setOnClickListener(this);
mMuteButton.setOnClickListener(this);
mHoldButton.setOnClickListener(this);
mDtmfButton.setOnClickListener(this);
mModeButton.setOnClickListener(this);
setCallStatus();
final Intent intent = getIntent();
setIntent(null);
new Thread(new Runnable() {
public void run() {
setText(mMyIp, getLocalIp());
mSipManager = SipManager.getInstance(SipCallUi.this);
if (SipManager.isIncomingCallIntent(intent)) {
receiveCall(intent);
} else {
makeCall(intent);
}
}
}).start();
}
@Override
protected void onResume() {
super.onResume();
enableProximitySensor();
if (mAudioCall != null) continueCall();
}
@Override
protected void onPause() {
super.onPause();
disableProximitySensor();
if (mAudioCall != null) holdCall();
}
@Override
protected void onDestroy() {
super.onDestroy();
closeAudioCall();
}
private void receiveCall(Intent intent) {
Log.v(TAG, "receiveCall(): any call comes in? " + intent);
if (SipManager.isIncomingCallIntent(intent)) {
createSipAudioCall(intent);
setIntent(null);
}
}
private void makeCall(Intent intent) {
if ("call".equals(intent.getAction())) {
SipProfile caller = (SipProfile)
intent.getParcelableExtra("caller");
mCallee = intent.getStringExtra("callee");
Log.v(TAG, "call from " + caller + " to " + mCallee);
try {
makeAudioCall(caller, mCallee);
setText(mPeerBox, "Dialing " + mCallee + "...");
setAllButtonsEnabled(true, true, false);
} catch (Exception e) {
Log.e(TAG, "makeCall()", e);
setCallStatus(e);
}
}
}
private void createSipAudioCall(Intent intent) {
try {
Log.v(TAG, "create SipAudioCall");
mAudioCall = mSipManager.takeAudioCall(this, intent, null);
mAudioCall.setListener(this, true);
if (mAudioCall == null) {
throw new SipException("no session to handle audio call");
}
} catch (SipException e) {
setCallStatus(e);
}
}
private void makeAudioCall(SipProfile caller, String calleeUri)
throws Exception {
if (mAudioCall != null) {
Log.v(TAG, "a call is ongoing; no new call is made");
return;
}
SipProfile callee = new SipProfile.Builder(calleeUri).build();
mAudioCall = mSipManager.makeAudioCall(this, caller, callee, this);
}
private void setCallStatus(Throwable e) {
mError = e;
setCallStatus();
}
private void setCallStatus() {
setText(mCallStatus, getCallStatus());
if (mError != null) {
setText(mPeerBox, mError.getMessage());
}
mError = null;
}
private void showCallNotificationDialog(SipProfile caller) {
mDialog = new CallNotificationDialog(caller);
runOnUiThread(new Runnable() {
public void run() {
showDialog(mDialog.getId());
}
});
}
public synchronized void onChanged(SipAudioCall call) {
Log.v(TAG, "onChanged(): " + call + " <--> " + mAudioCall);
if (mAudioCall != call) return;
setCallStatus();
}
public synchronized void onCalling(SipAudioCall call) {
if (mAudioCall != call) return;
setCallStatus();
showToast("Dialing...");
}
public synchronized void onRinging(SipAudioCall call, SipProfile caller) {
Log.v(TAG, "onRinging(): " + call + " <--> " + mAudioCall);
if (mAudioCall != call) return;
mCaller = caller.getUserName() + '@' + caller.getSipDomain();
showCallNotificationDialog(caller);
setCallStatus();
}
public void onRingingBack(SipAudioCall call) {
}
public void onReadyToCall(SipAudioCall call) {
}
public synchronized void onCallEstablished(SipAudioCall call) {
Log.v(TAG, "onCallEstablished(): " + call + " <--> " + mAudioCall);
mCallTime = new Date().getTime();
if (mAudioCall != call) return;
setCallStatus();
setText(mPeerBox, getDisplayName(call.getPeerProfile()));
showToast("Call established");
setAllButtonsEnabled(true, true, true);
}
public void onCallHeld(SipAudioCall call) {
}
public void onCallBusy(SipAudioCall call) {
if (mAudioCall != call) return;
mError = new SipException("Line busy");
setCallStatus();
mAudioCall = null;
setAllButtonsEnabled(false, false, false);
showToast("Line busy");
}
public synchronized 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();
mAudioCall = null;
addCallLog();
setAllButtonsEnabled(false, false, false);
setText(mPeerBox, "...");
showToast("Call ended");
finish();
}
public synchronized void onError(SipAudioCall call, String errorMessage) {
Log.v(TAG, "onError(): " + call + " <--> " + mAudioCall);
if (mAudioCall != call) return;
mError = new SipException(errorMessage);
setCallStatus();
mAudioCall = null;
setAllButtonsEnabled(false, false, false);
showToast("Call ended");
}
private void closeAudioCall() {
if (mAudioCall != null) mAudioCall.close();
}
private void endCall() {
try {
mAudioCall.endCall();
mSpeakerMode = false;
addCallLog();
} catch (SipException e) {
Log.e(TAG, "endCall()", e);
setCallStatus(e);
}
}
private void addCallLog() {
if (mCallee != null) addOutgoingCallLog(mCallee);
if (mCaller != null) addIncomingCallLog(mCaller);
mCaller = mCallee = null;
mCallTime = 0;
}
private void addOutgoingCallLog(String callee) {
addCallRecord(Calls.OUTGOING_TYPE, callee);
}
private void addIncomingCallLog(String caller) {
addCallRecord((mCallTime != 0) ? Calls.INCOMING_TYPE:
Calls.MISSED_TYPE, caller);
}
private void addCallRecord(int callType, String address) {
long insertDate = new Date().getTime();
ContentValues value = new ContentValues();
//value.put(Calls.NUMBER, address.substring(0, address.indexOf('@')));
value.put(Calls.NUMBER, address);
value.put(Calls.DATE, insertDate);
value.put(Calls.DURATION,
(mCallTime != 0) ? (insertDate - mCallTime)/1000 : 0);
value.put(Calls.TYPE, callType);
value.put(Calls.NEW, 0);
try {
getContentResolver().acquireProvider(
CallLog.AUTHORITY).insert(Calls.CONTENT_URI, value);
} catch (android.os.RemoteException e) {
Log.e(TAG, "cannot add calllog", e);
}
}
private void answerCall() {
try {
mAudioCall.answerCall();
} catch (SipException e) {
Log.e(TAG, "answerCall()", e);
setCallStatus(e);
}
}
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 sendDtmf() {
mAudioCall.sendDtmf(1);
}
private void setSpeakerMode() {
mAudioCall.setSpeakerMode();
}
private void setInCallMode() {
mAudioCall.setInCallMode();
}
public synchronized void onClick(View v) {
if (mEndButton == v) {
endCall();
} else if (mModeButton == v) {
mSpeakerMode = !mSpeakerMode;
if (mSpeakerMode) {
setSpeakerMode();
} else {
setInCallMode();
}
} else if (mDtmfButton == v) {
sendDtmf();
} else if (mMuteButton == v) {
mAudioCall.toggleMute();
} else if (mHoldButton == v) {
if (isOnHold()) {
continueCall();
} else {
holdCall();
}
}
}
private SipSessionState getCallState() {
if (mAudioCall == null) return SipSessionState.READY_TO_CALL;
return mAudioCall.getState();
}
private String getCallStatus() {
if (mError != null) return "Error!";
if (mAudioCall == null) return "Ready to call";
switch (getCallState()) {
case READY_TO_CALL:
return "Call ended";
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" : "In call");
default:
return "Unknown";
}
}
private void setText(final TextView view, final String text) {
runOnUiThread(new Runnable() {
public void run() {
view.setText(text);
}
});
}
private void setAllButtonsEnabled(final boolean endButton,
final boolean modeButton, final boolean others) {
runOnUiThread(new Runnable() {
public void run() {
for (Button button : otherButtons()) {
button.setEnabled(others);
}
mEndButton.setEnabled(endButton);
mModeButton.setEnabled(modeButton);
}
});
}
private Button[] otherButtons() {
return new Button[] {
mMuteButton, mHoldButton, mDtmfButton
};
}
@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 String getDisplayName(SipProfile profile) {
String name = profile.getDisplayName();
if (TextUtils.isEmpty(name)) {
name = profile.getUserName() + "@" + profile.getSipDomain();
}
return name;
}
private class CallNotificationDialog implements MyDialog {
private SipProfile mCaller;
CallNotificationDialog(SipProfile caller) {
mCaller = caller;
}
public int getId() {
return 0;
}
public Dialog createDialog(int id) {
if (id != getId()) return null;
Log.d(TAG, "create call notification dialog");
return new AlertDialog.Builder(SipCallUi.this)
.setTitle(getDisplayName(mCaller))
.setIcon(android.R.drawable.ic_dialog_alert)
.setPositiveButton("Answer",
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int w) {
answerCall();
mPeerBox.setText(getDisplayName(mCaller));
}
})
.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(getDisplayName(mCaller));
}
}
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 showToast(final String message) {
runOnUiThread(new Runnable() {
public void run() {
Toast.makeText(SipCallUi.this, message, Toast.LENGTH_SHORT)
.show();
}
});
}
private void enableProximitySensor() {
SensorManager sensorManager = (SensorManager)
getSystemService(Context.SENSOR_SERVICE);
Sensor sensor = sensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY);
sensorManager.registerListener(mProximityListener, sensor,
SensorManager.SENSOR_DELAY_NORMAL);
}
private void disableProximitySensor() {
((SensorManager) getSystemService(Context.SENSOR_SERVICE))
.unregisterListener(mProximityListener);
}
private synchronized void onProximityChanged(float[] values) {
if ((mAudioCall == null) || !mAudioCall.isInCall()) return;
StringBuilder b = new StringBuilder();
for (float f : values) {
b.append(", " + f);
}
Log.v("Proximity", "onSensorChanged: " + b);
boolean far = (values[0] > 1f);
setAllButtonsEnabled(far, far, far);
}
SensorEventListener mProximityListener = new SensorEventListener() {
public void onSensorChanged(SensorEvent event) {
onProximityChanged(event.values);
}
public void onAccuracyChanged(Sensor sensor, int accuracy) {
}
};
}