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
diff --git a/src/com/android/telecomm/Call.java b/src/com/android/telecomm/Call.java
index ea62da4..4576075 100644
--- a/src/com/android/telecomm/Call.java
+++ b/src/com/android/telecomm/Call.java
@@ -21,7 +21,10 @@
 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 @@
 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 @@
      */
     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 @@
      * @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 @@
         return mHandle;
     }
 
+    void setHandle(String handle) {
+        mHandle = handle;
+    }
+
     ContactInfo getContactInfo() {
         return mContactInfo;
     }
@@ -147,6 +152,36 @@
     }
 
     /**
+     * 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.
      */
     CallInfo toCallInfo() {
@@ -157,6 +192,18 @@
     }
 
     /**
+     * @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.
      */
     private void clearCallInfo() {
diff --git a/src/com/android/telecomm/CallServiceAdapter.java b/src/com/android/telecomm/CallServiceAdapter.java
index 9a9d0db..9e20d0c 100644
--- a/src/com/android/telecomm/CallServiceAdapter.java
+++ b/src/com/android/telecomm/CallServiceAdapter.java
@@ -47,13 +47,11 @@
     /** 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<String> mUnconfirmedIncomingCallIds = Sets.newHashSet();
+    private final Set<String> mPendingIncomingCallIds = Sets.newHashSet();
 
     /**
      * Persists the specified parameters.
@@ -69,15 +67,15 @@
     }
 
     /** {@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 @@
     }
 
     /**
-     * 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 941c6ee..5d2162a 100644
--- a/src/com/android/telecomm/CallServiceWrapper.java
+++ b/src/com/android/telecomm/CallServiceWrapper.java
@@ -55,7 +55,8 @@
     /**
      * 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 @@
 
     /** 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);
             }
-        } 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);
+            }
         }
     }
 
     /** 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);
             }
-        } catch (RemoteException e) {
-            Log.e(TAG, "Failed to confirmIncomingCall for call " + callId, e);
-            mAdapter.removeUnconfirmedIncomingCallId(callId);
+        }
+    }
+
+    /** 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);
+            }
         }
     }
 
     /**
-     * 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 @@
         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 aebee13..d79669a 100644
--- a/src/com/android/telecomm/CallsManager.java
+++ b/src/com/android/telecomm/CallsManager.java
@@ -94,6 +94,7 @@
      * @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 @@
      * @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 @@
      * @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 @@
      * @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 eb10318..9faa472 100644
--- a/src/com/android/telecomm/InCallAdapter.java
+++ b/src/com/android/telecomm/InCallAdapter.java
@@ -20,6 +20,7 @@
 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 @@
  * 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 @@
     /** {@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 @@
     /** {@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 d392a79..7c0d5c0 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.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;
 
 /**
@@ -44,11 +40,6 @@
     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 @@
      */
     private MediaPlayer mMediaPlayer;
 
-    /** Persists the specified parameters. */
-    public TestCallService(Context context) {
-        mContext = context;
-    }
-
     /** {@inheritDoc} */
     @Override
     public void setCallServiceAdapter(ICallServiceAdapter callServiceAdapter) {
@@ -78,7 +64,7 @@
         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 @@
 
     /** {@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);
         }
     }