Merge "Adding answer/reject API support to Telecomm." into master-nova

This commit is contained in:
Santos Cordon 2014-02-25 02:37:14 +00:00 committed by Android (Google) Code Review
commit 83a4aa7d71
6 changed files with 188 additions and 93 deletions

View File

@ -21,7 +21,10 @@ import android.telecomm.CallInfo;
import android.telecomm.CallState; import android.telecomm.CallState;
import android.util.Log; import android.util.Log;
import com.google.common.base.Preconditions;
import java.util.Date; import java.util.Date;
import java.util.UUID;
/** /**
* Encapsulates all aspects of a given phone call throughout its lifecycle, starting * Encapsulates all aspects of a given phone call throughout its lifecycle, starting
@ -31,19 +34,9 @@ import java.util.Date;
final class Call { final class Call {
private static final String TAG = Call.class.getSimpleName(); private static final String TAG = Call.class.getSimpleName();
/** /** Unique identifier for the call as a UUID string. */
* Unique identifier for the call as a UUID string.
*/
private final String mId; private final String mId;
/**
* The state of the call.
*/
private CallState mState;
/** The handle with which to establish this call. */
private final String mHandle;
/** Additional contact information beyond handle above, optional. */ /** Additional contact information beyond handle above, optional. */
private final ContactInfo mContactInfo; private final ContactInfo mContactInfo;
@ -55,15 +48,19 @@ final class Call {
*/ */
private final Date mCreationTime; private final Date mCreationTime;
/** The state of the call. */
private CallState mState;
/** The handle with which to establish this call. */
private String mHandle;
/** /**
* The call service which is currently connecting this call, null as long as the call is not * The call service which is currently connecting this call, null as long as the call is not
* connected. * connected.
*/ */
private CallServiceWrapper mCallService; private CallServiceWrapper mCallService;
/** /** Read-only and parcelable version of this call. */
* Read-only and parcelable version of this call.
*/
private CallInfo mCallInfo; private CallInfo mCallInfo;
/** /**
@ -73,14 +70,18 @@ final class Call {
* @param contactInfo Information about the entity being called. * @param contactInfo Information about the entity being called.
*/ */
Call(String handle, ContactInfo contactInfo) { Call(String handle, ContactInfo contactInfo) {
// TODO(gilad): Pass this in etc. mId = UUID.randomUUID().toString(); // UUIDs should provide sufficient uniqueness.
mId = "dummy";
mState = CallState.NEW; mState = CallState.NEW;
mHandle = handle; mHandle = handle;
mContactInfo = contactInfo; mContactInfo = contactInfo;
mCreationTime = new Date(); mCreationTime = new Date();
} }
/** {@inheritDoc} */
@Override public String toString() {
return "[" + mId + ", " + mState + ", " + mCallService.getComponentName() + "]";
}
String getId() { String getId() {
return mId; return mId;
} }
@ -104,6 +105,10 @@ final class Call {
return mHandle; return mHandle;
} }
void setHandle(String handle) {
mHandle = handle;
}
ContactInfo getContactInfo() { ContactInfo getContactInfo() {
return mContactInfo; return mContactInfo;
} }
@ -146,6 +151,36 @@ final class Call {
} }
} }
/**
* Answers the call if it is ringing.
*/
void answer() {
Preconditions.checkNotNull(mCallService);
// Check to verify that the call is still in the ringing state. A call can change states
// between the time the user hits 'answer' and Telecomm receives the command.
if (isRinging("answer")) {
// At this point, we are asking the call service to answer but we don't assume that
// it will work. Instead, we wait until confirmation from the call service that the
// call is in a non-RINGING state before changing the UI. See
// {@link CallServiceAdapter#setActive} and other set* methods.
mCallService.answer(mId);
}
}
/**
* Rejects the call if it is ringing.
*/
void reject() {
Preconditions.checkNotNull(mCallService);
// Check to verify that the call is still in the ringing state. A call can change states
// between the time the user hits 'reject' and Telecomm receives the command.
if (isRinging("reject")) {
mCallService.reject(mId);
}
}
/** /**
* @return An object containing read-only information about this call. * @return An object containing read-only information about this call.
*/ */
@ -156,6 +191,18 @@ final class Call {
return mCallInfo; return mCallInfo;
} }
/**
* @return True if the call is ringing, else logs the action name.
*/
private boolean isRinging(String actionName) {
if (mState == CallState.RINGING) {
return true;
}
Log.i(TAG, "Request to " + actionName + " a non-ringing call " + this);
return false;
}
/** /**
* Resets the cached read-only version of this call. * Resets the cached read-only version of this call.
*/ */

View File

@ -47,13 +47,11 @@ public final class CallServiceAdapter extends ICallServiceAdapter.Stub {
/** Used to run code (e.g. messages, Runnables) on the main (UI) thread. */ /** Used to run code (e.g. messages, Runnables) on the main (UI) thread. */
private final Handler mHandler = new Handler(Looper.getMainLooper()); private final Handler mHandler = new Handler(Looper.getMainLooper());
/** The list of unconfirmed incoming call IDs. Contains only IDs for incoming calls which are /** The set of pending incoming call IDs. Contains the call IDs for which we are expecting
* pending confirmation from the call service. Entries are added by the call service when a * details via {@link #handleIncomingCall}. If {@link #handleIncomingCall} is invoked for a call
* confirmation request is sent and removed when the confirmation is received or it times out. * ID that is not in this set, it will be ignored.
* See {@link IncomingCallsManager} for more information about the incoming sequence and its
* timeouts.
*/ */
private final Set<String> mUnconfirmedIncomingCallIds = Sets.newHashSet(); private final Set<String> mPendingIncomingCallIds = Sets.newHashSet();
/** /**
* Persists the specified parameters. * Persists the specified parameters.
@ -69,15 +67,15 @@ public final class CallServiceAdapter extends ICallServiceAdapter.Stub {
} }
/** {@inheritDoc} */ /** {@inheritDoc} */
@Override public void handleConfirmedIncomingCall(final CallInfo callInfo) { @Override public void handleIncomingCall(final CallInfo callInfo) {
checkValidCallId(callInfo.getId()); checkValidCallId(callInfo.getId());
mHandler.post(new Runnable() { mHandler.post(new Runnable() {
@Override public void run() { @Override public void run() {
if (mUnconfirmedIncomingCallIds.remove(callInfo.getId())) { if (mPendingIncomingCallIds.remove(callInfo.getId())) {
// TODO(santoscordon): Uncomment when ready. // TODO(santoscordon): Uncomment when ready.
// mIncomingCallsManager.handleSuccessfulIncomingCall(callInfo); // mIncomingCallsManager.handleSuccessfulIncomingCall(callInfo);
} else { } else {
Log.wtf(TAG, "Call service confirming unknown incoming call " + callInfo); Log.wtf(TAG, "Received details for an unknown incoming call " + callInfo);
} }
} }
}); });
@ -144,22 +142,22 @@ public final class CallServiceAdapter extends ICallServiceAdapter.Stub {
} }
/** /**
* Adds a call ID to the list of unconfirmed incoming call IDs. Only calls with call IDs in the * Adds a call ID to the list of pending incoming call IDs. Only calls with call IDs in the
* list will be handled by {@link #handleConfirmedIncomingCall}. * list will be handled by {@link #handleIncomingCall}.
* *
* @param callId The ID of the call. * @param callId The ID of the call.
*/ */
void addUnconfirmedIncomingCallId(String callId) { void addPendingIncomingCallId(String callId) {
mUnconfirmedIncomingCallIds.add(callId); mPendingIncomingCallIds.add(callId);
} }
/** /**
* Removed a call ID from the list of unconfirmed incoming call IDs. * Removed a call ID from the list of pending incoming call IDs.
* *
* @param callId The ID of the call. * @param callId The ID of the call.
*/ */
void removeUnconfirmedIncomingCallId(String callId) { void removePendingIncomingCallId(String callId) {
mUnconfirmedIncomingCallIds.remove(callId); mPendingIncomingCallIds.remove(callId);
} }
/** /**

View File

@ -55,7 +55,8 @@ public class CallServiceWrapper extends ServiceBinder<ICallService> {
/** /**
* Creates a call-service provider for the specified component. * Creates a call-service provider for the specified component.
* *
* @param descriptor The call-service descriptor from {@link ICallServiceProvider#lookupCallServices}. * @param descriptor The call-service descriptor from
* {@link ICallServiceProvider#lookupCallServices}.
* @param adapter The call-service adapter. * @param adapter The call-service adapter.
*/ */
public CallServiceWrapper(CallServiceDescriptor descriptor, CallServiceAdapter adapter) { public CallServiceWrapper(CallServiceDescriptor descriptor, CallServiceAdapter adapter) {
@ -70,80 +71,92 @@ public class CallServiceWrapper extends ServiceBinder<ICallService> {
/** See {@link ICallService#setCallServiceAdapter}. */ /** See {@link ICallService#setCallServiceAdapter}. */
public void setCallServiceAdapter(ICallServiceAdapter callServiceAdapter) { public void setCallServiceAdapter(ICallServiceAdapter callServiceAdapter) {
try { if (isServiceValid("setCallServiceAdapter")) {
if (mServiceInterface == null) { try {
Log.wtf(TAG, "setCallServiceAdapter() invoked while the service is unbound.");
} else {
mServiceInterface.setCallServiceAdapter(callServiceAdapter); mServiceInterface.setCallServiceAdapter(callServiceAdapter);
} catch (RemoteException e) {
Log.e(TAG, "Failed to setCallServiceAdapter.", e);
} }
} catch (RemoteException e) {
Log.e(TAG, "Failed to setCallServiceAdapter.", e);
} }
} }
/** See {@link ICallService#isCompatibleWith}. */ /** See {@link ICallService#isCompatibleWith}. */
public void isCompatibleWith(CallInfo callInfo) { public void isCompatibleWith(CallInfo callInfo) {
try { if (isServiceValid("isCompatibleWith")) {
if (mServiceInterface == null) { try {
Log.wtf(TAG, "isCompatibleWith() invoked while the service is unbound.");
} else {
mServiceInterface.isCompatibleWith(callInfo); mServiceInterface.isCompatibleWith(callInfo);
} catch (RemoteException e) {
Log.e(TAG, "Failed checking isCompatibleWith.", e);
} }
} catch (RemoteException e) {
Log.e(TAG, "Failed checking isCompatibleWith.", e);
} }
} }
/** See {@link ICallService#call}. */ /** See {@link ICallService#call}. */
public void call(CallInfo callInfo) { public void call(CallInfo callInfo) {
try { if (isServiceValid("call")) {
if (mServiceInterface == null) { try {
Log.wtf(TAG, "call() invoked while the service is unbound.");
} else {
mServiceInterface.call(callInfo); mServiceInterface.call(callInfo);
} catch (RemoteException e) {
Log.e(TAG, "Failed to place call " + callInfo.getId() + ".", e);
}
}
}
/** See {@link ICallService#setIncomingCallId}. */
public void setIncomingCallId(String callId) {
if (isServiceValid("setIncomingCallId")) {
mAdapter.addPendingIncomingCallId(callId);
try {
mServiceInterface.setIncomingCallId(callId);
} catch (RemoteException e) {
Log.e(TAG, "Failed to setIncomingCallId for call " + callId, e);
mAdapter.removePendingIncomingCallId(callId);
} }
} catch (RemoteException e) {
Log.e(TAG, "Failed to place call " + callInfo.getId() + ".", e);
} }
} }
/** See {@link ICallService#disconnect}. */ /** See {@link ICallService#disconnect}. */
public void disconnect(String callId) { public void disconnect(String callId) {
try { if (isServiceValid("disconnect")) {
if (mServiceInterface == null) { try {
Log.wtf(TAG, "disconnect() invoked while the service is unbound.");
} else {
mServiceInterface.disconnect(callId); mServiceInterface.disconnect(callId);
} catch (RemoteException e) {
Log.e(TAG, "Failed to disconnect call " + callId + ".", e);
} }
} catch (RemoteException e) {
Log.e(TAG, "Failed to disconnect call " + callId + ".", e);
} }
} }
/** See {@link ICallService#confirmIncomingCall}. */ /** See {@link ICallService#answer}. */
public void confirmIncomingCall(String callId, String callToken) { public void answer(String callId) {
try { if (isServiceValid("answer")) {
if (mServiceInterface == null) { try {
Log.wtf(TAG, "confirmIncomingCall() invoked while service in unbound."); mServiceInterface.answer(callId);
} else { } catch (RemoteException e) {
mAdapter.addUnconfirmedIncomingCallId(callId); Log.e(TAG, "Failed to answer call " + callId, e);
mServiceInterface.confirmIncomingCall(callId, callToken); }
}
}
/** See {@link ICallService#reject}. */
public void reject(String callId) {
if (isServiceValid("reject")) {
try {
mServiceInterface.reject(callId);
} catch (RemoteException e) {
Log.e(TAG, "Failed to reject call " + callId, e);
} }
} catch (RemoteException e) {
Log.e(TAG, "Failed to confirmIncomingCall for call " + callId, e);
mAdapter.removeUnconfirmedIncomingCallId(callId);
} }
} }
/** /**
* Cancels the an incoming call confirmation for the specified call ID. * Cancels the incoming call for the specified call ID.
* TODO(santoscordon): This method should be called by IncomingCallManager when the incoming * TODO(santoscordon): This method should be called by IncomingCallsManager when the incoming
* call confirmation has failed. * call has failed.
* *
* @param callId The ID of the call. * @param callId The ID of the call.
*/ */
void cancelIncomingCall(String callId) { void cancelIncomingCall(String callId) {
mAdapter.removeUnconfirmedIncomingCallId(callId); mAdapter.removePendingIncomingCallId(callId);
} }
/** {@inheritDoc} */ /** {@inheritDoc} */
@ -151,4 +164,13 @@ public class CallServiceWrapper extends ServiceBinder<ICallService> {
mServiceInterface = ICallService.Stub.asInterface(binder); mServiceInterface = ICallService.Stub.asInterface(binder);
setCallServiceAdapter(mAdapter); setCallServiceAdapter(mAdapter);
} }
private boolean isServiceValid(String actionName) {
if (mServiceInterface != null) {
return true;
}
Log.wtf(TAG, actionName + " invoked while service is unbound");
return false;
}
} }

View File

@ -93,6 +93,7 @@ public final class CallsManager {
* @param callToken The token used by the call service to identify the incoming call. * @param callToken The token used by the call service to identify the incoming call.
*/ */
void processIncomingCallIntent(CallServiceDescriptor descriptor, String callToken) { void processIncomingCallIntent(CallServiceDescriptor descriptor, String callToken) {
Log.d(TAG, "processIncomingCallIntent");
// Create a call with no handle. Eventually, switchboard will update the call with // Create a call with no handle. Eventually, switchboard will update the call with
// additional information from the call service, but for now we just need one to pass around // additional information from the call service, but for now we just need one to pass around
// with a unique call ID. // with a unique call ID.
@ -144,6 +145,7 @@ public final class CallsManager {
* @param call The new incoming call. * @param call The new incoming call.
*/ */
void handleSuccessfulIncomingCall(Call call) { void handleSuccessfulIncomingCall(Call call) {
Log.d(TAG, "handleSuccessfulIncomingCall");
Preconditions.checkState(call.getState() == CallState.RINGING); Preconditions.checkState(call.getState() == CallState.RINGING);
addCall(call); addCall(call);
mInCallController.addCall(call.toCallInfo()); mInCallController.addCall(call.toCallInfo());
@ -172,7 +174,16 @@ public final class CallsManager {
* @param callId The ID of the call. * @param callId The ID of the call.
*/ */
void answerCall(String callId) { void answerCall(String callId) {
// TODO(santoscordon): fill in and check that it is in the ringing state. Call call = mCalls.get(callId);
if (call == null) {
Log.i(TAG, "Request to answer a non-existent call " + callId);
} else {
// We do not update the UI until we get confirmation of the answer() through
// {@link #markCallAsActive}. However, if we ever change that to look more responsive,
// then we need to make sure we add a timeout for the answer() in case the call never
// comes out of RINGING.
call.answer();
}
} }
/** /**
@ -183,7 +194,12 @@ public final class CallsManager {
* @param callId The ID of the call. * @param callId The ID of the call.
*/ */
void rejectCall(String callId) { void rejectCall(String callId) {
// TODO(santoscordon): fill in and check that it is in the ringing state. Call call = mCalls.get(callId);
if (call == null) {
Log.i(TAG, "Request to reject a non-existent call " + callId);
} else {
call.reject();
}
} }
/** /**

View File

@ -20,6 +20,7 @@ import android.os.Handler;
import android.os.Looper; import android.os.Looper;
import android.os.RemoteException; import android.os.RemoteException;
import android.telecomm.IInCallAdapter; import android.telecomm.IInCallAdapter;
import android.util.Log;
/** /**
* Receives call commands and updates from in-call app and passes them through to CallsManager. * Receives call commands and updates from in-call app and passes them through to CallsManager.
@ -27,6 +28,9 @@ import android.telecomm.IInCallAdapter;
* binding to it. This adapter can receive commands and updates until the in-call app is unbound. * binding to it. This adapter can receive commands and updates until the in-call app is unbound.
*/ */
class InCallAdapter extends IInCallAdapter.Stub { class InCallAdapter extends IInCallAdapter.Stub {
private static final String TAG = InCallAdapter.class.getSimpleName();
private final CallsManager mCallsManager; private final CallsManager mCallsManager;
private final Handler mHandler = new Handler(Looper.getMainLooper()); private final Handler mHandler = new Handler(Looper.getMainLooper());
@ -39,6 +43,7 @@ class InCallAdapter extends IInCallAdapter.Stub {
/** {@inheritDoc} */ /** {@inheritDoc} */
@Override @Override
public void answerCall(final String callId) throws RemoteException { public void answerCall(final String callId) throws RemoteException {
Log.d(TAG, "answerCall(" + callId + ")");
mHandler.post(new Runnable() { mHandler.post(new Runnable() {
@Override public void run() { @Override public void run() {
mCallsManager.answerCall(callId); mCallsManager.answerCall(callId);
@ -49,6 +54,7 @@ class InCallAdapter extends IInCallAdapter.Stub {
/** {@inheritDoc} */ /** {@inheritDoc} */
@Override @Override
public void rejectCall(final String callId) throws RemoteException { public void rejectCall(final String callId) throws RemoteException {
Log.d(TAG, "rejectCall(" + callId + ")");
mHandler.post(new Runnable() { mHandler.post(new Runnable() {
@Override public void run() { @Override public void run() {
mCallsManager.rejectCall(callId); mCallsManager.rejectCall(callId);

View File

@ -21,19 +21,15 @@ import com.google.common.base.Preconditions;
import com.google.common.base.Strings; import com.google.common.base.Strings;
import com.google.common.collect.Sets; import com.google.common.collect.Sets;
import java.util.Date;
import java.util.Set; import java.util.Set;
import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.media.AudioManager;
import android.media.MediaPlayer; import android.media.MediaPlayer;
import android.os.RemoteException; import android.os.RemoteException;
import android.telecomm.CallInfo; import android.telecomm.CallInfo;
import android.telecomm.CallService; import android.telecomm.CallService;
import android.telecomm.CallState; import android.telecomm.CallState;
import android.telecomm.ICallServiceAdapter; import android.telecomm.ICallServiceAdapter;
import android.text.TextUtils;
import android.util.Log; import android.util.Log;
/** /**
@ -43,11 +39,6 @@ import android.util.Log;
public class TestCallService extends CallService { public class TestCallService extends CallService {
private static final String TAG = TestCallService.class.getSimpleName(); private static final String TAG = TestCallService.class.getSimpleName();
/**
* The application context.
*/
private final Context mContext;
/** /**
* Set of call IDs for live (active, ringing, dialing) calls. * Set of call IDs for live (active, ringing, dialing) calls.
* TODO(santoscordon): Reference CallState javadoc when available for the different call states. * TODO(santoscordon): Reference CallState javadoc when available for the different call states.
@ -64,11 +55,6 @@ public class TestCallService extends CallService {
*/ */
private MediaPlayer mMediaPlayer; private MediaPlayer mMediaPlayer;
/** Persists the specified parameters. */
public TestCallService(Context context) {
mContext = context;
}
/** {@inheritDoc} */ /** {@inheritDoc} */
@Override @Override
public void setCallServiceAdapter(ICallServiceAdapter callServiceAdapter) { public void setCallServiceAdapter(ICallServiceAdapter callServiceAdapter) {
@ -78,7 +64,7 @@ public class TestCallService extends CallService {
mLiveCallIds = Sets.newHashSet(); mLiveCallIds = Sets.newHashSet();
// Prepare the media player to play a tone when there is a call. // Prepare the media player to play a tone when there is a call.
mMediaPlayer = MediaPlayer.create(mContext, R.raw.beep_boop); mMediaPlayer = MediaPlayer.create(getApplicationContext(), R.raw.beep_boop);
mMediaPlayer.setLooping(true); mMediaPlayer.setLooping(true);
// TODO(santoscordon): Re-enable audio through voice-call stream. // TODO(santoscordon): Re-enable audio through voice-call stream.
@ -144,17 +130,37 @@ public class TestCallService extends CallService {
/** {@inheritDoc} */ /** {@inheritDoc} */
@Override @Override
public void confirmIncomingCall(String callId, String callToken) { public void setIncomingCallId(String callId) {
Log.i(TAG, "confirmIncomingCall(" + callId + ", " + callToken + ")"); Log.i(TAG, "setIncomingCallId(" + callId + ")");
// Use dummy number for testing incoming calls. // Use dummy number for testing incoming calls.
String handle = "5551234"; String handle = "5551234";
CallInfo callInfo = new CallInfo(callId, CallState.RINGING, handle); CallInfo callInfo = new CallInfo(callId, CallState.RINGING, handle);
try { try {
mCallsManagerAdapter.handleConfirmedIncomingCall(callInfo); mCallsManagerAdapter.handleIncomingCall(callInfo);
} catch (RemoteException e) { } catch (RemoteException e) {
Log.e(TAG, "Failed to handleConfirmedIncomingCall().", e); Log.e(TAG, "Failed to handleIncomingCall().", e);
}
}
/** {@inheritDoc} */
@Override
public void answer(String callId) {
try {
mCallsManagerAdapter.setActive(callId);
} catch (RemoteException e) {
Log.e(TAG, "Failed to setActive the call " + callId);
}
}
/** {@inheritDoc} */
@Override
public void reject(String callId) {
try {
mCallsManagerAdapter.setDisconnected(callId);
} catch (RemoteException e) {
Log.e(TAG, "Failed to setDisconnected the call " + callId);
} }
} }