blob: d4521727bb455bc6c4e6c0769a2436f117ba2934 [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 android.telecomm;
import android.app.Service;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import com.android.internal.os.SomeArgs;
import com.android.internal.telecomm.ICallService;
import com.android.internal.telecomm.ICallServiceAdapter;
import java.util.List;
/**
* Base implementation of CallService which can be used to provide calls for the system
* in-call UI. CallService is a one-way service from the framework's CallsManager to any app
* that would like to provide calls managed by the default system in-call user interface.
* When the service is bound by the framework, CallsManager will call setCallServiceAdapter
* which will provide CallService with an instance of {@link CallServiceAdapter} to be used
* for communicating back to CallsManager. Subsequently, more specific methods of the service
* will be called to perform various call actions including making an outgoing call and
* disconnected existing calls.
* TODO(santoscordon): Needs more about AndroidManifest.xml service registrations before
* we can unhide this API.
*
* Most public methods of this function are backed by a one-way AIDL interface which precludes
* synchronous responses. As a result, most responses are handled by (or have TODOs to handle)
* response objects instead of return values.
* TODO(santoscordon): Improve paragraph above once the final design is in place.
*/
public abstract class CallService extends Service {
private static final int MSG_SET_CALL_SERVICE_ADAPTER = 1;
private static final int MSG_IS_COMPATIBLE_WITH = 2;
private static final int MSG_CALL = 3;
private static final int MSG_ABORT = 4;
private static final int MSG_SET_INCOMING_CALL_ID = 5;
private static final int MSG_ANSWER = 6;
private static final int MSG_REJECT = 7;
private static final int MSG_DISCONNECT = 8;
private static final int MSG_HOLD = 9;
private static final int MSG_UNHOLD = 10;
private static final int MSG_ON_AUDIO_STATE_CHANGED = 11;
private static final int MSG_PLAY_DTMF_TONE = 12;
private static final int MSG_STOP_DTMF_TONE = 13;
private static final int MSG_ADD_TO_CONFERENCE = 14;
private static final int MSG_SPLIT_FROM_CONFERENCE = 15;
/**
* Default Handler used to consolidate binder method calls onto a single thread.
*/
private final class CallServiceMessageHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_SET_CALL_SERVICE_ADAPTER:
mAdapter = new CallServiceAdapter((ICallServiceAdapter) msg.obj);
onAdapterAttached(mAdapter);
break;
case MSG_IS_COMPATIBLE_WITH:
isCompatibleWith((CallInfo) msg.obj);
break;
case MSG_CALL:
call((CallInfo) msg.obj);
break;
case MSG_ABORT:
abort((String) msg.obj);
break;
case MSG_SET_INCOMING_CALL_ID: {
SomeArgs args = (SomeArgs) msg.obj;
try {
String callId = (String) args.arg1;
Bundle extras = (Bundle) args.arg2;
setIncomingCallId(callId, extras);
} finally {
args.recycle();
}
break;
}
case MSG_ANSWER:
answer((String) msg.obj);
break;
case MSG_REJECT:
reject((String) msg.obj);
break;
case MSG_DISCONNECT:
disconnect((String) msg.obj);
break;
case MSG_HOLD:
hold((String) msg.obj);
break;
case MSG_UNHOLD:
unhold((String) msg.obj);
break;
case MSG_ON_AUDIO_STATE_CHANGED: {
SomeArgs args = (SomeArgs) msg.obj;
try {
String callId = (String) args.arg1;
CallAudioState audioState = (CallAudioState) args.arg2;
onAudioStateChanged(callId, audioState);
} finally {
args.recycle();
}
break;
}
case MSG_PLAY_DTMF_TONE:
playDtmfTone((String) msg.obj, (char) msg.arg1);
break;
case MSG_STOP_DTMF_TONE:
stopDtmfTone((String) msg.obj);
break;
case MSG_ADD_TO_CONFERENCE: {
SomeArgs args = (SomeArgs) msg.obj;
try {
@SuppressWarnings("unchecked")
List<String> callIds = (List<String>) args.arg2;
String conferenceCallId = (String) args.arg1;
addToConference(conferenceCallId, callIds);
} finally {
args.recycle();
}
break;
}
case MSG_SPLIT_FROM_CONFERENCE: {
SomeArgs args = (SomeArgs) msg.obj;
try {
String conferenceCallId = (String) args.arg1;
String callId = (String) args.arg2;
splitFromConference(conferenceCallId, callId);
} finally {
args.recycle();
}
break;
}
default:
break;
}
}
}
/**
* Default ICallService implementation provided to CallsManager via {@link #onBind}.
*/
private final class CallServiceBinder extends ICallService.Stub {
@Override
public void setCallServiceAdapter(ICallServiceAdapter callServiceAdapter) {
mMessageHandler.obtainMessage(MSG_SET_CALL_SERVICE_ADAPTER, callServiceAdapter)
.sendToTarget();
}
@Override
public void isCompatibleWith(CallInfo callInfo) {
mMessageHandler.obtainMessage(MSG_IS_COMPATIBLE_WITH, callInfo).sendToTarget();
}
@Override
public void call(CallInfo callInfo) {
mMessageHandler.obtainMessage(MSG_CALL, callInfo).sendToTarget();
}
@Override
public void abort(String callId) {
mMessageHandler.obtainMessage(MSG_ABORT, callId).sendToTarget();
}
@Override
public void setIncomingCallId(String callId, Bundle extras) {
SomeArgs args = SomeArgs.obtain();
args.arg1 = callId;
args.arg2 = extras;
mMessageHandler.obtainMessage(MSG_SET_INCOMING_CALL_ID, args).sendToTarget();
}
@Override
public void answer(String callId) {
mMessageHandler.obtainMessage(MSG_ANSWER, callId).sendToTarget();
}
@Override
public void reject(String callId) {
mMessageHandler.obtainMessage(MSG_REJECT, callId).sendToTarget();
}
@Override
public void disconnect(String callId) {
mMessageHandler.obtainMessage(MSG_DISCONNECT, callId).sendToTarget();
}
@Override
public void hold(String callId) {
mMessageHandler.obtainMessage(MSG_HOLD, callId).sendToTarget();
}
@Override
public void unhold(String callId) {
mMessageHandler.obtainMessage(MSG_UNHOLD, callId).sendToTarget();
}
@Override
public void playDtmfTone(String callId, char digit) {
mMessageHandler.obtainMessage(MSG_PLAY_DTMF_TONE, digit, 0, callId).sendToTarget();
}
@Override
public void stopDtmfTone(String callId) {
mMessageHandler.obtainMessage(MSG_STOP_DTMF_TONE, callId).sendToTarget();
}
@Override
public void onAudioStateChanged(String callId, CallAudioState audioState) {
SomeArgs args = SomeArgs.obtain();
args.arg1 = callId;
args.arg2 = audioState;
mMessageHandler.obtainMessage(MSG_ON_AUDIO_STATE_CHANGED, args).sendToTarget();
}
@Override
public void addToConference(String conferenceCallId, List<String> callsToConference) {
SomeArgs args = SomeArgs.obtain();
args.arg1 = conferenceCallId;
args.arg2 = callsToConference;
mMessageHandler.obtainMessage(MSG_ADD_TO_CONFERENCE, args).sendToTarget();
}
@Override
public void splitFromConference(String conferenceCallId, String callId) {
SomeArgs args = SomeArgs.obtain();
args.arg1 = conferenceCallId;
args.arg2 = callId;
mMessageHandler.obtainMessage(MSG_SPLIT_FROM_CONFERENCE, args).sendToTarget();
}
}
/**
* Message handler for consolidating binder callbacks onto a single thread.
* See {@link CallServiceMessageHandler}.
*/
private final CallServiceMessageHandler mMessageHandler = new CallServiceMessageHandler();
/**
* Default binder implementation of {@link ICallService} interface.
*/
private final CallServiceBinder mBinder = new CallServiceBinder();
private CallServiceAdapter mAdapter = null;
/** {@inheritDoc} */
@Override
public final IBinder onBind(Intent intent) {
return getBinder();
}
/**
* Returns binder object which can be used across IPC methods.
*/
public final IBinder getBinder() {
return mBinder;
}
/**
* @return The attached {@link CallServiceAdapter} if the service is bound, null otherwise.
*/
protected final CallServiceAdapter getAdapter() {
return mAdapter;
}
/**
* Lifecycle callback which is called when this {@link CallService} has been attached to a
* {@link CallServiceAdapter}, indicating {@link #getAdapter()} is now safe to use.
*
* @param adapter The adapter now attached to this call service.
*/
protected void onAdapterAttached(CallServiceAdapter adapter) {
}
/**
* Determines if the CallService can place the specified call. Response is sent via
* {@link CallServiceAdapter#setIsCompatibleWith}. When responding, the correct call ID must be
* specified. Only used in the context of outgoing calls and call switching (handoff).
*
* @param callInfo The details of the relevant call.
*/
public abstract void isCompatibleWith(CallInfo callInfo);
/**
* Attempts to call the relevant party using the specified call's handle, be it a phone number,
* SIP address, or some other kind of user ID. Note that the set of handle types is
* dynamically extensible since call providers should be able to implement arbitrary
* handle-calling systems. See {@link #isCompatibleWith}. It is expected that the
* call service respond via {@link CallServiceAdapter#handleSuccessfulOutgoingCall(String)}
* if it can successfully make the call. Only used in the context of outgoing calls.
*
* @param callInfo The details of the relevant call.
*/
public abstract void call(CallInfo callInfo);
/**
* Aborts the outgoing call attempt. Invoked in the unlikely event that Telecomm decides to
* abort an attempt to place a call. Only ever be invoked after {@link #call} invocations.
* After this is invoked, Telecomm does not expect any more updates about the call and will
* actively ignore any such update. This is different from {@link #disconnect} where Telecomm
* expects confirmation via CallServiceAdapter.markCallAsDisconnected.
*
* @param callId The identifier of the call to abort.
*/
public abstract void abort(String callId);
/**
* Receives a new call ID to use with an incoming call. Invoked by Telecomm after it is notified
* that this call service has a pending incoming call, see
* {@link TelecommConstants#ACTION_INCOMING_CALL}. The call service must first give Telecomm
* additional information about the call through {@link CallServiceAdapter#notifyIncomingCall}.
* Following that, the call service can update the call at will using the specified call ID.
*
* If a {@link Bundle} was passed (via {@link TelecommConstants#EXTRA_INCOMING_CALL_EXTRAS}) in
* with the {@link TelecommConstants#ACTION_INCOMING_CALL} intent, <code>extras</code> will be
* populated with this {@link Bundle}. Otherwise, an empty Bundle will be returned.
*
* @param callId The ID of the call.
* @param extras The optional extras which were passed in with the intent, or an empty Bundle.
*/
public abstract void setIncomingCallId(String callId, Bundle extras);
/**
* Answers a ringing call identified by callId. Telecomm invokes this method as a result of the
* user hitting the "answer" button in the incoming call screen.
*
* @param callId The ID of the call.
*/
public abstract void answer(String callId);
/**
* Rejects a ringing call identified by callId. Telecomm invokes this method as a result of the
* user hitting the "reject" button in the incoming call screen.
*
* @param callId The ID of the call.
*/
public abstract void reject(String callId);
/**
* Disconnects the specified call.
*
* @param callId The ID of the call to disconnect.
*/
public abstract void disconnect(String callId);
/**
* Puts the specified call on hold.
*
* @param callId The ID of the call to put on hold.
*/
public abstract void hold(String callId);
/**
* Removes the specified call from hold.
*
* @param callId The ID of the call to unhold.
*/
public abstract void unhold(String callId);
/**
* Plays a dual-tone multi-frequency signaling (DTMF) tone in a call.
*
* @param callId The unique ID of the call in which the tone will be played.
* @param digit A character representing the DTMF digit for which to play the tone. This
* value must be one of {@code '0'} through {@code '9'}, {@code '*'} or {@code '#'}.
*/
public abstract void playDtmfTone(String callId, char digit);
/**
* Stops any dual-tone multi-frequency sinaling (DTMF) tone currently playing.
*
* DTMF tones are played by calling {@link #playDtmfTone(String,char)}. If no DTMF tone is
* currently playing, this method will do nothing.
*
* @param callId The unique ID of the call in which any currently playing tone will be stopped.
*/
public abstract void stopDtmfTone(String callId);
/**
* Called when the audio state changes.
*
* @param activeCallId The identifier of the call that was active during the state change.
* @param audioState The new {@link CallAudioState}.
*/
public abstract void onAudioStateChanged(String activeCallId, CallAudioState audioState);
/**
* Adds the specified calls to the specified conference call.
*
* @param conferenceCallId The unique ID of the conference call onto which the specified calls
* should be added.
* @param callIds The calls to add to the conference call.
* @hide
*/
public abstract void addToConference(String conferenceCallId, List<String> callIds);
/**
* Removes the specified call from the specified conference call. This is a no-op if the call
* is not already part of the conference call.
*
* @param conferenceCallId The conference call.
* @param callId The call to remove from the conference call
* @hide
*/
public abstract void splitFromConference(String conferenceCallId, String callId);
}