From 61d0f70cf45036f9cdeb41b96538f792b7c9764b Mon Sep 17 00:00:00 2001 From: Santos Cordon Date: Wed, 19 Feb 2014 02:52:23 -0800 Subject: [PATCH] Adding answer/reject API support to Telecomm. Adds answer/reject overridden methods to CallServiceWrapper. Adds direct answer/reject to Call.java Fills in existing answer/reject in CallsManager.java. Change-Id: Ifd3a65230661b94f9dd99aabb2d2083684e2fc7c --- src/com/android/telecomm/Call.java | 79 +++++++++++--- .../android/telecomm/CallServiceAdapter.java | 30 +++--- .../android/telecomm/CallServiceWrapper.java | 102 +++++++++++------- src/com/android/telecomm/CallsManager.java | 20 +++- src/com/android/telecomm/InCallAdapter.java | 6 ++ .../testcallservice/TestCallService.java | 44 ++++---- 6 files changed, 188 insertions(+), 93 deletions(-) diff --git a/src/com/android/telecomm/Call.java b/src/com/android/telecomm/Call.java index ea62da498..4576075c7 100644 --- a/src/com/android/telecomm/Call.java +++ b/src/com/android/telecomm/Call.java @@ -21,7 +21,10 @@ import android.telecomm.CallInfo; import android.telecomm.CallState; import android.util.Log; +import com.google.common.base.Preconditions; + import java.util.Date; +import java.util.UUID; /** * Encapsulates all aspects of a given phone call throughout its lifecycle, starting @@ -31,19 +34,9 @@ import java.util.Date; final class Call { 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; - /** - * 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. */ private final ContactInfo mContactInfo; @@ -55,15 +48,19 @@ final class Call { */ 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 * connected. */ private CallServiceWrapper mCallService; - /** - * Read-only and parcelable version of this call. - */ + /** Read-only and parcelable version of this call. */ private CallInfo mCallInfo; /** @@ -73,14 +70,18 @@ final class Call { * @param contactInfo Information about the entity being called. */ Call(String handle, ContactInfo contactInfo) { - // TODO(gilad): Pass this in etc. - mId = "dummy"; + mId = UUID.randomUUID().toString(); // UUIDs should provide sufficient uniqueness. mState = CallState.NEW; mHandle = handle; mContactInfo = contactInfo; mCreationTime = new Date(); } + /** {@inheritDoc} */ + @Override public String toString() { + return "[" + mId + ", " + mState + ", " + mCallService.getComponentName() + "]"; + } + String getId() { return mId; } @@ -104,6 +105,10 @@ final class Call { return mHandle; } + void setHandle(String handle) { + mHandle = handle; + } + ContactInfo getContactInfo() { 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. */ @@ -156,6 +191,18 @@ final class Call { 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. */ diff --git a/src/com/android/telecomm/CallServiceAdapter.java b/src/com/android/telecomm/CallServiceAdapter.java index 9a9d0dbbb..9e20d0cb4 100644 --- a/src/com/android/telecomm/CallServiceAdapter.java +++ b/src/com/android/telecomm/CallServiceAdapter.java @@ -47,13 +47,11 @@ public final class CallServiceAdapter extends ICallServiceAdapter.Stub { /** Used to run code (e.g. messages, Runnables) on the main (UI) thread. */ private final Handler mHandler = new Handler(Looper.getMainLooper()); - /** The list of unconfirmed incoming call IDs. Contains only IDs for incoming calls which are - * pending confirmation from the call service. Entries are added by the call service when a - * confirmation request is sent and removed when the confirmation is received or it times out. - * See {@link IncomingCallsManager} for more information about the incoming sequence and its - * timeouts. + /** The set of pending incoming call IDs. Contains the call IDs for which we are expecting + * details via {@link #handleIncomingCall}. If {@link #handleIncomingCall} is invoked for a call + * ID that is not in this set, it will be ignored. */ - private final Set mUnconfirmedIncomingCallIds = Sets.newHashSet(); + private final Set mPendingIncomingCallIds = Sets.newHashSet(); /** * Persists the specified parameters. @@ -69,15 +67,15 @@ public final class CallServiceAdapter extends ICallServiceAdapter.Stub { } /** {@inheritDoc} */ - @Override public void handleConfirmedIncomingCall(final CallInfo callInfo) { + @Override public void handleIncomingCall(final CallInfo callInfo) { checkValidCallId(callInfo.getId()); mHandler.post(new Runnable() { @Override public void run() { - if (mUnconfirmedIncomingCallIds.remove(callInfo.getId())) { + if (mPendingIncomingCallIds.remove(callInfo.getId())) { // TODO(santoscordon): Uncomment when ready. // mIncomingCallsManager.handleSuccessfulIncomingCall(callInfo); } 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 - * list will be handled by {@link #handleConfirmedIncomingCall}. + * 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 #handleIncomingCall}. * * @param callId The ID of the call. */ - void addUnconfirmedIncomingCallId(String callId) { - mUnconfirmedIncomingCallIds.add(callId); + void addPendingIncomingCallId(String 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. */ - void removeUnconfirmedIncomingCallId(String callId) { - mUnconfirmedIncomingCallIds.remove(callId); + void removePendingIncomingCallId(String callId) { + mPendingIncomingCallIds.remove(callId); } /** diff --git a/src/com/android/telecomm/CallServiceWrapper.java b/src/com/android/telecomm/CallServiceWrapper.java index 941c6eebd..5d2162a7d 100644 --- a/src/com/android/telecomm/CallServiceWrapper.java +++ b/src/com/android/telecomm/CallServiceWrapper.java @@ -55,7 +55,8 @@ public class CallServiceWrapper extends ServiceBinder { /** * 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. */ public CallServiceWrapper(CallServiceDescriptor descriptor, CallServiceAdapter adapter) { @@ -70,80 +71,92 @@ public class CallServiceWrapper extends ServiceBinder { /** See {@link ICallService#setCallServiceAdapter}. */ public void setCallServiceAdapter(ICallServiceAdapter callServiceAdapter) { - try { - if (mServiceInterface == null) { - Log.wtf(TAG, "setCallServiceAdapter() invoked while the service is unbound."); - } else { + if (isServiceValid("setCallServiceAdapter")) { + try { 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}. */ public void isCompatibleWith(CallInfo callInfo) { - try { - if (mServiceInterface == null) { - Log.wtf(TAG, "isCompatibleWith() invoked while the service is unbound."); - } else { + if (isServiceValid("isCompatibleWith")) { + try { 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}. */ public void call(CallInfo callInfo) { - try { - if (mServiceInterface == null) { - Log.wtf(TAG, "call() invoked while the service is unbound."); - } else { + if (isServiceValid("call")) { + try { 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}. */ public void disconnect(String callId) { - try { - if (mServiceInterface == null) { - Log.wtf(TAG, "disconnect() invoked while the service is unbound."); - } else { + if (isServiceValid("disconnect")) { + try { 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}. */ - public void confirmIncomingCall(String callId, String callToken) { - try { - if (mServiceInterface == null) { - Log.wtf(TAG, "confirmIncomingCall() invoked while service in unbound."); - } else { - mAdapter.addUnconfirmedIncomingCallId(callId); - mServiceInterface.confirmIncomingCall(callId, callToken); + /** See {@link ICallService#answer}. */ + public void answer(String callId) { + if (isServiceValid("answer")) { + try { + mServiceInterface.answer(callId); + } catch (RemoteException e) { + Log.e(TAG, "Failed to answer call " + callId, e); + } + } + } + + /** 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. - * TODO(santoscordon): This method should be called by IncomingCallManager when the incoming - * call confirmation has failed. + * Cancels the incoming call for the specified call ID. + * TODO(santoscordon): This method should be called by IncomingCallsManager when the incoming + * call has failed. * * @param callId The ID of the call. */ void cancelIncomingCall(String callId) { - mAdapter.removeUnconfirmedIncomingCallId(callId); + mAdapter.removePendingIncomingCallId(callId); } /** {@inheritDoc} */ @@ -151,4 +164,13 @@ public class CallServiceWrapper extends ServiceBinder { mServiceInterface = ICallService.Stub.asInterface(binder); setCallServiceAdapter(mAdapter); } + + private boolean isServiceValid(String actionName) { + if (mServiceInterface != null) { + return true; + } + + Log.wtf(TAG, actionName + " invoked while service is unbound"); + return false; + } } diff --git a/src/com/android/telecomm/CallsManager.java b/src/com/android/telecomm/CallsManager.java index aebee13b5..d79669aea 100644 --- a/src/com/android/telecomm/CallsManager.java +++ b/src/com/android/telecomm/CallsManager.java @@ -94,6 +94,7 @@ public final class CallsManager { * @param callToken The token used by the call service to identify the incoming call. */ void processIncomingCallIntent(CallServiceDescriptor descriptor, String callToken) { + Log.d(TAG, "processIncomingCallIntent"); // 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 // with a unique call ID. @@ -146,6 +147,7 @@ public final class CallsManager { * @param call The new incoming call. */ void handleSuccessfulIncomingCall(Call call) { + Log.d(TAG, "handleSuccessfulIncomingCall"); Preconditions.checkState(call.getState() == CallState.RINGING); addCall(call); mInCallController.addCall(call.toCallInfo()); @@ -174,7 +176,16 @@ public final class CallsManager { * @param callId The ID of the call. */ 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(); + } } /** @@ -185,7 +196,12 @@ public final class CallsManager { * @param callId The ID of the call. */ 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(); + } } /** diff --git a/src/com/android/telecomm/InCallAdapter.java b/src/com/android/telecomm/InCallAdapter.java index eb10318bf..9faa472c5 100644 --- a/src/com/android/telecomm/InCallAdapter.java +++ b/src/com/android/telecomm/InCallAdapter.java @@ -20,6 +20,7 @@ import android.os.Handler; import android.os.Looper; import android.os.RemoteException; import android.telecomm.IInCallAdapter; +import android.util.Log; /** * 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. */ class InCallAdapter extends IInCallAdapter.Stub { + + private static final String TAG = InCallAdapter.class.getSimpleName(); + private final CallsManager mCallsManager; private final Handler mHandler = new Handler(Looper.getMainLooper()); @@ -39,6 +43,7 @@ class InCallAdapter extends IInCallAdapter.Stub { /** {@inheritDoc} */ @Override public void answerCall(final String callId) throws RemoteException { + Log.d(TAG, "answerCall(" + callId + ")"); mHandler.post(new Runnable() { @Override public void run() { mCallsManager.answerCall(callId); @@ -49,6 +54,7 @@ class InCallAdapter extends IInCallAdapter.Stub { /** {@inheritDoc} */ @Override public void rejectCall(final String callId) throws RemoteException { + Log.d(TAG, "rejectCall(" + callId + ")"); mHandler.post(new Runnable() { @Override public void run() { mCallsManager.rejectCall(callId); diff --git a/tests/src/com/android/telecomm/testcallservice/TestCallService.java b/tests/src/com/android/telecomm/testcallservice/TestCallService.java index d392a7910..7c0d5c0f2 100644 --- a/tests/src/com/android/telecomm/testcallservice/TestCallService.java +++ b/tests/src/com/android/telecomm/testcallservice/TestCallService.java @@ -21,19 +21,15 @@ import com.google.common.base.Preconditions; import com.google.common.base.Strings; import com.google.common.collect.Sets; -import java.util.Date; import java.util.Set; -import android.content.Context; import android.content.Intent; -import android.media.AudioManager; import android.media.MediaPlayer; import android.os.RemoteException; import android.telecomm.CallInfo; import android.telecomm.CallService; import android.telecomm.CallState; import android.telecomm.ICallServiceAdapter; -import android.text.TextUtils; import android.util.Log; /** @@ -43,11 +39,6 @@ import android.util.Log; public class TestCallService extends CallService { 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. * TODO(santoscordon): Reference CallState javadoc when available for the different call states. @@ -64,11 +55,6 @@ public class TestCallService extends CallService { */ private MediaPlayer mMediaPlayer; - /** Persists the specified parameters. */ - public TestCallService(Context context) { - mContext = context; - } - /** {@inheritDoc} */ @Override public void setCallServiceAdapter(ICallServiceAdapter callServiceAdapter) { @@ -78,7 +64,7 @@ public class TestCallService extends CallService { mLiveCallIds = Sets.newHashSet(); // 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); // TODO(santoscordon): Re-enable audio through voice-call stream. @@ -144,17 +130,37 @@ public class TestCallService extends CallService { /** {@inheritDoc} */ @Override - public void confirmIncomingCall(String callId, String callToken) { - Log.i(TAG, "confirmIncomingCall(" + callId + ", " + callToken + ")"); + public void setIncomingCallId(String callId) { + Log.i(TAG, "setIncomingCallId(" + callId + ")"); // Use dummy number for testing incoming calls. String handle = "5551234"; CallInfo callInfo = new CallInfo(callId, CallState.RINGING, handle); try { - mCallsManagerAdapter.handleConfirmedIncomingCall(callInfo); + mCallsManagerAdapter.handleIncomingCall(callInfo); } 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); } }