blob: d35d78997fd572c6a88efbf88c790e8405b1ab90 [file] [log] [blame]
/*
* Copyright (C) 2013 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.telecomm.testapps;
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Intent;
import android.media.MediaPlayer;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.telecomm.AudioState;
import android.telecomm.Conference;
import android.telecomm.Connection;
import android.telecomm.PhoneAccount;
import android.telecomm.PhoneCapabilities;
import android.telecomm.ConnectionRequest;
import android.telecomm.ConnectionService;
import android.telecomm.PhoneAccountHandle;
import android.telecomm.RemoteConnection;
import android.telecomm.StatusHints;
import android.telecomm.TelecommManager;
import android.telecomm.VideoProfile;
import android.telephony.DisconnectCause;
import android.util.Log;
import com.android.telecomm.tests.R;
import java.lang.String;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
/**
* Service which provides fake calls to test the ConnectionService interface.
* TODO: Rename all classes in the directory to Dummy* (e.g., DummyConnectionService).
*/
public class TestConnectionService extends ConnectionService {
/**
* Intent extra used to pass along whether a call is video or audio based on the user's choice
* in the notification.
*/
public static final String EXTRA_IS_VIDEO_CALL = "extra_is_video_call";
public static final String EXTRA_HANDLE = "extra_handle";
/**
* Random number generator used to generate phone numbers.
*/
private Random mRandom = new Random();
private final class TestConference extends Conference {
private final Connection.Listener mConnectionListener = new Connection.Listener() {
@Override
public void onDestroyed(Connection c) {
removeConnection(c);
if (getConnections().size() == 0) {
setDisconnected(DisconnectCause.NORMAL, null);
destroy();
}
}
};
public TestConference(Connection a, Connection b) {
super(null);
addConnection(a);
addConnection(b);
a.addConnectionListener(mConnectionListener);
b.addConnectionListener(mConnectionListener);
a.setConference(this);
b.setConference(this);
setActive();
}
@Override
public void onDisconnect() {
for (Connection c : getConnections()) {
c.setDisconnected(DisconnectCause.NORMAL, null);
c.destroy();
}
}
@Override
public void onSeparate(Connection connection) {
if (getConnections().contains(connection)) {
connection.setConference(null);
removeConnection(connection);
connection.removeConnectionListener(mConnectionListener);
}
}
@Override
public void onHold() {
for (Connection c : getConnections()) {
c.setOnHold();
}
setOnHold();
}
@Override
public void onUnhold() {
for (Connection c : getConnections()) {
c.setActive();
}
setActive();
}
}
private final class TestConnection extends Connection {
private final boolean mIsIncoming;
/** Used to cleanup camera and media when done with connection. */
private TestVideoProvider mTestVideoCallProvider;
TestConnection(boolean isIncoming) {
mIsIncoming = isIncoming;
// Assume all calls are video capable.
int capabilities = getCallCapabilities();
capabilities |= PhoneCapabilities.SUPPORTS_VT_LOCAL;
capabilities |= PhoneCapabilities.ADD_CALL;
capabilities |= PhoneCapabilities.MUTE;
capabilities |= PhoneCapabilities.SUPPORT_HOLD;
capabilities |= PhoneCapabilities.HOLD;
setCallCapabilities(capabilities);
}
void startOutgoing() {
setDialing();
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
setActive();
activateCall(TestConnection.this);
}
}, 4000);
}
/** ${inheritDoc} */
@Override
public void onAbort() {
destroyCall(this);
destroy();
}
/** ${inheritDoc} */
@Override
public void onAnswer(int videoState) {
setVideoState(videoState);
activateCall(this);
setActive();
updateConferenceable();
}
/** ${inheritDoc} */
@Override
public void onPlayDtmfTone(char c) {
if (c == '1') {
setDialing();
}
}
/** ${inheritDoc} */
@Override
public void onStopDtmfTone() { }
/** ${inheritDoc} */
@Override
public void onDisconnect() {
setDisconnected(DisconnectCause.LOCAL, null);
destroyCall(this);
destroy();
}
/** ${inheritDoc} */
@Override
public void onHold() {
setOnHold();
}
/** ${inheritDoc} */
@Override
public void onReject() {
setDisconnected(DisconnectCause.INCOMING_REJECTED, null);
destroyCall(this);
destroy();
}
/** ${inheritDoc} */
@Override
public void onUnhold() {
setActive();
}
@Override
public void onAudioStateChanged(AudioState state) { }
public void setTestVideoCallProvider(TestVideoProvider testVideoCallProvider) {
mTestVideoCallProvider = testVideoCallProvider;
}
/**
* Stops playback of test videos.
*/
private void stopAndCleanupMedia() {
if (mTestVideoCallProvider != null) {
mTestVideoCallProvider.stopAndCleanupMedia();
mTestVideoCallProvider.stopCamera();
}
}
}
private final List<TestConnection> mCalls = new ArrayList<>();
private final Handler mHandler = new Handler();
/** Used to play an audio tone during a call. */
private MediaPlayer mMediaPlayer;
@Override
public boolean onUnbind(Intent intent) {
log("onUnbind");
mMediaPlayer = null;
return super.onUnbind(intent);
}
@Override
public void onConference(Connection a, Connection b) {
addConference(new TestConference(a, b));
}
@Override
public Connection onCreateOutgoingConnection(
PhoneAccountHandle connectionManagerAccount,
final ConnectionRequest originalRequest) {
final Uri handle = originalRequest.getAddress();
String number = originalRequest.getAddress().getSchemeSpecificPart();
log("call, number: " + number);
// Crash on 555-DEAD to test call service crashing.
if ("5550340".equals(number)) {
throw new RuntimeException("Goodbye, cruel world.");
}
Bundle extras = originalRequest.getExtras();
String gatewayPackage = extras.getString(TelecommManager.GATEWAY_PROVIDER_PACKAGE);
Uri originalHandle = extras.getParcelable(TelecommManager.GATEWAY_ORIGINAL_ADDRESS);
log("gateway package [" + gatewayPackage + "], original handle [" +
originalHandle + "]");
final TestConnection connection = new TestConnection(false /* isIncoming */);
// If the number starts with 555, then we handle it ourselves. If not, then we
// use a remote connection service.
// TODO: Have a special phone number to test the account-picker dialog flow.
if (number != null && number.startsWith("555")) {
// Normally we would use the original request as is, but for testing purposes, we are
// adding ".." to the end of the number to follow its path more easily through the logs.
final ConnectionRequest request = new ConnectionRequest(
originalRequest.getAccountHandle(),
Uri.fromParts(handle.getScheme(),
handle.getSchemeSpecificPart() + "..", ""),
originalRequest.getExtras(),
originalRequest.getVideoState());
addCall(connection);
connection.startOutgoing();
for (Connection c : getAllConnections()) {
c.setOnHold();
}
} else {
log("Not a test number");
}
return connection;
}
@Override
public Connection onCreateIncomingConnection(
PhoneAccountHandle connectionManagerAccount,
final ConnectionRequest request) {
PhoneAccountHandle accountHandle = request.getAccountHandle();
ComponentName componentName = new ComponentName(this, TestConnectionService.class);
if (accountHandle != null && componentName.equals(accountHandle.getComponentName())) {
final TestConnection connection = new TestConnection(true);
// Get the stashed intent extra that determines if this is a video call or audio call.
Bundle extras = request.getExtras();
boolean isVideoCall = extras.getBoolean(EXTRA_IS_VIDEO_CALL);
Uri providedHandle = extras.getParcelable(EXTRA_HANDLE);
// Use dummy number for testing incoming calls.
Uri address = providedHandle == null ?
Uri.fromParts(PhoneAccount.SCHEME_TEL, getDummyNumber(isVideoCall), null)
: providedHandle;
if (isVideoCall) {
TestVideoProvider testVideoCallProvider =
new TestVideoProvider(getApplicationContext());
connection.setVideoProvider(testVideoCallProvider);
// Keep reference to original so we can clean up the media players later.
connection.setTestVideoCallProvider(testVideoCallProvider);
}
int videoState = isVideoCall ?
VideoProfile.VideoState.BIDIRECTIONAL :
VideoProfile.VideoState.AUDIO_ONLY;
connection.setVideoState(videoState);
connection.setAddress(address, TelecommManager.PRESENTATION_ALLOWED);
addCall(connection);
ConnectionRequest newRequest = new ConnectionRequest(
request.getAccountHandle(),
address,
request.getExtras(),
videoState);
connection.setVideoState(videoState);
return connection;
} else {
return Connection.createFailedConnection(
DisconnectCause.NOT_VALID,
"Invalid inputs: " + accountHandle + " " + componentName);
}
}
private void activateCall(TestConnection connection) {
if (mMediaPlayer == null) {
mMediaPlayer = createMediaPlayer();
}
if (!mMediaPlayer.isPlaying()) {
mMediaPlayer.start();
}
}
private void destroyCall(TestConnection connection) {
mCalls.remove(connection);
// Ensure any playing media and camera resources are released.
connection.stopAndCleanupMedia();
// Stops audio if there are no more calls.
if (mCalls.isEmpty() && mMediaPlayer != null && mMediaPlayer.isPlaying()) {
mMediaPlayer.stop();
mMediaPlayer.release();
mMediaPlayer = createMediaPlayer();
}
updateConferenceable();
}
private void addCall(TestConnection connection) {
mCalls.add(connection);
updateConferenceable();
}
private void updateConferenceable() {
List<Connection> freeConnections = new ArrayList<>();
freeConnections.addAll(mCalls);
for (int i = 0; i < freeConnections.size(); i++) {
if (freeConnections.get(i).getConference() != null) {
freeConnections.remove(i);
}
}
for (int i = 0; i < freeConnections.size(); i++) {
Connection c = freeConnections.remove(i);
c.setConferenceableConnections(freeConnections);
freeConnections.add(i, c);
}
}
private MediaPlayer createMediaPlayer() {
// Prepare the media player to play a tone when there is a call.
MediaPlayer mediaPlayer = MediaPlayer.create(getApplicationContext(), R.raw.beep_boop);
mediaPlayer.setLooping(true);
return mediaPlayer;
}
private static void log(String msg) {
Log.w("telecomtestcs", "[TestConnectionService] " + msg);
}
/**
* Generates a random phone number of format 555YXXX. Where Y will be {@code 1} if the
* phone number is for a video call and {@code 0} for an audio call. XXX is a randomly
* generated phone number.
*
* @param isVideo {@code True} if the call is a video call.
* @return The phone number.
*/
private String getDummyNumber(boolean isVideo) {
int videoDigit = isVideo ? 1 : 0;
int number = mRandom.nextInt(999);
return String.format("555%s%03d", videoDigit, number);
}
}