Add IncomingCallsMangager to perform the incoming call sequence.

IncomingCallsManager binds to the call service and asks it to confirm
the call. If the call service cannot be bound or it failed to confirm
within the timeout, we handle the incoming call as if it failed. The
code to do the actual confirmation is NYI.

Also add handleSuccess/Failed methods to Switchboard.
This still needs some changes to ICallService, ICallServiceAdapter, and
CallServiceWrapper before it will be fully functional.

Change-Id: Ibfd92ce17fb2054e2e0cb209738ad5375bf01492
diff --git a/src/com/android/telecomm/CallServiceRepository.java b/src/com/android/telecomm/CallServiceRepository.java
index eb31d87..23fffca 100644
--- a/src/com/android/telecomm/CallServiceRepository.java
+++ b/src/com/android/telecomm/CallServiceRepository.java
@@ -174,6 +174,31 @@
     }
 
     /**
+     * Creates and returns the call service for the specified {@link CallServiceInfo}. Inserts newly
+     * created entries into the cache, see {@link #mCallServiceCache}, or if a cached version
+     * already exists, returns that instead. All newly created instances will not yet be bound,
+     * however cached versions may or may not be bound.
+     *
+     * @param info The call service descriptor.
+     * @return The call service.
+     */
+    CallServiceWrapper getCallService(CallServiceInfo info) {
+        Preconditions.checkNotNull(info);
+
+        // TODO(santoscordon): Rename getServiceComponent to getComponentName.
+        ComponentName componentName = info.getServiceComponent();
+
+        CallServiceWrapper callService = mCallServiceCache.get(componentName);
+        if (callService == null) {
+            CallServiceAdapter adapter = new CallServiceAdapter(mOutgoingCallsManager);
+            callService = new CallServiceWrapper(info, adapter);
+            mCallServiceCache.put(componentName, callService);
+        }
+
+        return callService;
+    }
+
+    /**
      * Attempts to bind to the specified provider before continuing to {@link #processProvider}.
      *
      * @param componentName The component name of the relevant provider.
@@ -347,29 +372,4 @@
 
         return provider;
     }
-
-    /**
-     * Creates and returns the call service for the specified call-service info object. Inserts
-     * newly created entries into the cache, see {@link #mCallServiceCache}, or if a cached
-     * version already exists, returns that instead. All newly created instances will not yet
-     * be bound, however cached versions may or may not be bound.
-     *
-     * @param info The call service descriptor.
-     * @return The call service.
-     */
-    private CallServiceWrapper getCallService(CallServiceInfo info) {
-        Preconditions.checkNotNull(info);
-
-        // TODO(santoscordon): Rename getServiceComponent to getComponentName.
-        ComponentName componentName = info.getServiceComponent();
-
-        CallServiceWrapper callService = mCallServiceCache.get(componentName);
-        if (callService == null) {
-            CallServiceAdapter adapter = new CallServiceAdapter(mOutgoingCallsManager);
-            callService = new CallServiceWrapper(info, adapter);
-            mCallServiceCache.put(componentName, callService);
-        }
-
-        return callService;
-    }
 }
diff --git a/src/com/android/telecomm/CallsManager.java b/src/com/android/telecomm/CallsManager.java
index d69af53..ff224e5 100644
--- a/src/com/android/telecomm/CallsManager.java
+++ b/src/com/android/telecomm/CallsManager.java
@@ -17,6 +17,7 @@
 package com.android.telecomm;
 
 import android.content.Context;
+import android.telecomm.CallServiceInfo;
 import android.telecomm.CallState;
 import android.text.TextUtils;
 import android.util.Log;
@@ -86,6 +87,23 @@
     }
 
     /**
+     * Starts the incoming call sequence by having switchboard confirm with the specified call
+     * service that an incoming call actually exists for the specified call token. Upon success,
+     * execution returns to {@link #handleSuccessfulIncomingCall} to start the in-call UI.
+     *
+     * @param callServiceInfo The details of the call service to use for this incoming call.
+     * @param callToken The token used by the call service to identify the incoming call.
+     */
+    void processIncomingCallIntent(CallServiceInfo callServiceInfo, String callToken) {
+        // 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.
+        Call call = new Call(null, null);
+
+        mSwitchboard.confirmIncomingCall(call, callServiceInfo, callToken);
+    }
+
+    /**
      * Attempts to issue/connect the specified call.  From an (arbitrary) application standpoint,
      * all that is required to initiate this flow is to fire either of the CALL, CALL_PRIVILEGED,
      * and CALL_EMERGENCY intents. These are listened to by CallActivity.java which then invokes
@@ -123,6 +141,17 @@
         mInCallController.addCall(call.toCallInfo());
     }
 
+    /**
+     * Adds a new incoming call to the list of live calls and notifies the in-call app.
+     *
+     * @param call The new incoming call.
+     */
+    void handleSuccessfulIncomingCall(Call call) {
+        Preconditions.checkState(call.getState() == CallState.RINGING);
+        addCall(call);
+        mInCallController.addCall(call.toCallInfo());
+    }
+
     /*
      * Sends all the live calls to the in-call app if any exist. If there are no live calls, then
      * tells the in-call controller to unbind since it is not needed.
diff --git a/src/com/android/telecomm/IncomingCallsManager.java b/src/com/android/telecomm/IncomingCallsManager.java
new file mode 100644
index 0000000..941c248
--- /dev/null
+++ b/src/com/android/telecomm/IncomingCallsManager.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright 2014, 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;
+
+import android.os.Handler;
+import android.os.Looper;
+
+import com.android.telecomm.ServiceBinder.BindCallback;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Maps;
+
+import java.util.Map;
+
+/**
+ * Utility class to confirm the existence of an incoming call after receiving an incoming-call
+ * intent, see {@link TelecommReceiver}. Binds with the specified call services and requests
+ * confirmation of incoming calls using call tokens provided as part of the intent. Upon receipt of
+ * the confirmation, yields execution back to the switchboard to complete the incoming sequence. The
+ * entire process is timeboxed to protect against unresponsive call services.
+ */
+final class IncomingCallsManager {
+
+    /**
+     * The amount of time to wait for confirmation of an incoming call, in milliseconds.
+     * TODO(santoscordon): Likely needs adjustment.
+     */
+    private static final int INCOMING_CALL_TIMEOUT_MS = 1000;
+
+    private final Switchboard mSwitchboard;
+
+    private final Handler mHandler = new Handler(Looper.getMainLooper());
+
+    /** Maps incoming calls to their call services. */
+    private final Map<Call, CallServiceWrapper> mPendingIncomingCalls = Maps.newHashMap();
+
+    /**
+     * Persists the specified parameters.
+     *
+     * @param switchboard The switchboard.
+     */
+    IncomingCallsManager(Switchboard switchboard) {
+        mSwitchboard = switchboard;
+    }
+
+
+    /**
+     * Confirms the existence of an incoming call with the specified call service (asynchronously).
+     * Starts the timeout sequence in case the call service is unresponsive.
+     *
+     * @param call The call object.
+     * @param callService The call service.
+     * @param callToken The token used by the call service to identify the incoming call.
+     */
+    void confirmIncomingCall(
+            final Call call, final CallServiceWrapper callService, String callToken) {
+
+        ThreadUtil.checkOnMainThread();
+        // Just to be safe, lets make sure we're not already processing this call.
+        Preconditions.checkState(!mPendingIncomingCalls.containsKey(call));
+
+        mPendingIncomingCalls.put(call, callService);
+        startTimeoutForCall(call);
+
+        BindCallback callback = new BindCallback() {
+            @Override public void onSuccess() {
+                // TODO(santoscordon): ICallService needs to be updated with the following method.
+                // Confirmation won't work until this method is filled in.
+                // callService.confirmIncomingCall(call.toCallInfo(), callToken);
+            }
+            @Override public void onFailure() {
+                handleFailedIncomingCall(call);
+            }
+        };
+
+        callService.bind(callback);
+    }
+
+    /**
+     * Starts a timeout to timebox the confirmation of an incoming call. When the timeout expires,
+     * it will notify switchboard that the incoming call was not confirmed and thus does not exist
+     * as far as Telecomm is concerned.
+     *
+     * @param call The call.
+     */
+    void startTimeoutForCall(final Call call) {
+        Runnable timeoutCallback = new Runnable() {
+            @Override public void run() {
+                handleFailedIncomingCall(call);
+            }
+        };
+        mHandler.postDelayed(timeoutCallback, INCOMING_CALL_TIMEOUT_MS);
+    }
+
+    /**
+     * Notifies the switchboard of a successful incoming call after removing it from the pending
+     * list.
+     * TODO(santoscordon): Needs code in CallServiceAdapter to call this method.
+     *
+     * @param call The call.
+     */
+    void handleSuccessfulIncomingCall(Call call) {
+        ThreadUtil.checkOnMainThread();
+        if (mPendingIncomingCalls.remove(call) != null) {
+            mSwitchboard.handleSuccessfulIncomingCall(call);
+        }
+    }
+
+    /**
+     * Notifies switchboard of the failed incoming call after removing it from the pending list.
+     *
+     * @param call The call.
+     */
+    void handleFailedIncomingCall(Call call) {
+        ThreadUtil.checkOnMainThread();
+        if (mPendingIncomingCalls.remove(call) != null) {
+            // The call was found still waiting for confirmation. Consider it failed.
+            mSwitchboard.handleFailedIncomingCall(call);
+        }
+    }
+}
diff --git a/src/com/android/telecomm/Switchboard.java b/src/com/android/telecomm/Switchboard.java
index 32ef914..b5925cc 100644
--- a/src/com/android/telecomm/Switchboard.java
+++ b/src/com/android/telecomm/Switchboard.java
@@ -20,9 +20,9 @@
 import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
 
-import android.content.Context;
 import android.os.Handler;
 import android.os.Looper;
+import android.telecomm.CallServiceInfo;
 import android.telecomm.ICallServiceSelector;
 
 import java.util.Collection;
@@ -47,6 +47,9 @@
     /** Used to place outgoing calls. */
     private final OutgoingCallsManager mOutgoingCallsManager;
 
+    /** Used to confirm incoming calls. */
+    private final IncomingCallsManager mIncomingCallsManager;
+
     private final CallServiceRepository mCallServiceRepository;
 
     private final CallServiceSelectorRepository mSelectorRepository;
@@ -100,6 +103,7 @@
     Switchboard(CallsManager callsManager) {
         mCallsManager = callsManager;
         mOutgoingCallsManager = new OutgoingCallsManager(this);
+        mIncomingCallsManager = new IncomingCallsManager(this);
         mCallServiceRepository = new CallServiceRepository(this, mOutgoingCallsManager);
         mSelectorRepository = new CallServiceSelectorRepository(this);
     }
@@ -123,6 +127,21 @@
     }
 
     /**
+     * Confirms with incoming call manager that an incoming call exists for the specified call
+     * service and call token. The incoming call manager will invoke either
+     * {@link #handleSuccessfulIncomingCall} or {@link #handleFailedIncomingCall} depending
+     * on the result.
+     *
+     * @param call The call object.
+     * @param callServiceInfo The details of the call service.
+     * @param callToken The token used by the call service to identify the incoming call.
+     */
+    void confirmIncomingCall(Call call, CallServiceInfo callServiceInfo, String callToken) {
+        CallServiceWrapper callService = mCallServiceRepository.getCallService(callServiceInfo);
+        mIncomingCallsManager.confirmIncomingCall(call, callService, callToken);
+    }
+
+    /**
      * Persists the specified set of call services and attempts to place any pending outgoing
      * calls.  Intended to be invoked by {@link CallServiceRepository} exclusively.
      *
@@ -181,6 +200,29 @@
     }
 
     /**
+     * Handles the case where we received confirmation of an incoming call. Hands the resulting
+     * call to {@link CallsManager} as the final step in the incoming sequence. At that point,
+     * {@link CallsManager} should bring up the incoming-call UI.
+     */
+    void handleSuccessfulIncomingCall(Call call) {
+        mCallsManager.handleSuccessfulIncomingCall(call);
+    }
+
+    /**
+     * Handles the case where we failed to confirm an incoming call after receiving an incoming-call
+     * intent via {@link TelecommReceiver}.
+     *
+     * @param call The call.
+     */
+    void handleFailedIncomingCall(Call call) {
+        // At the moment there is nothing to do if an incoming call is not confirmed. We may at a
+        // future date bind to the in-call app optimistically during the incoming-call sequence and
+        // this method could tell {@link CallsManager} to unbind from the in-call app if the
+        // incoming call was not confirmed. It's worth keeping this method for parity with the
+        // outgoing call sequence.
+    }
+
+    /**
      * @return True if ticking should continue (or be resumed) and false otherwise.
      */
     private boolean isTicking() {