Merge "Use the new incoming call confirmation APIs." into master-nova-dev
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/OutgoingCallProcessor.java b/src/com/android/telecomm/OutgoingCallProcessor.java
index 0a2df04..34649f0 100644
--- a/src/com/android/telecomm/OutgoingCallProcessor.java
+++ b/src/com/android/telecomm/OutgoingCallProcessor.java
@@ -23,12 +23,12 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.RemoteException;
-import android.telecomm.CallInfo;
 import android.telecomm.CallState;
 import android.telecomm.CallServiceInfo;
 import android.telecomm.ICallServiceSelectionResponse;
 import android.telecomm.ICallServiceSelector;
-import android.util.Log;
+
+import com.android.telecomm.ServiceBinder.BindCallback;
 
 import java.util.Iterator;
 import java.util.List;
@@ -62,9 +62,9 @@
     private final List<CallServiceInfo> mCallServiceInfos = Lists.newArrayList();
 
     /**
-     * The map of currently-available call-service implementations keyed by call-service infos.
+     * The map of currently-available call-service implementations keyed by call-service ID.
      */
-    private final Map<CallServiceInfo, CallServiceWrapper> mCallServicesByInfo = Maps.newHashMap();
+    private final Map<String, CallServiceWrapper> mCallServicesById = Maps.newHashMap();
 
     /**
      * The set of currently-available call-service selector implementations.
@@ -129,7 +129,7 @@
         for (CallServiceWrapper callService : callServices) {
             CallServiceInfo info = callService.getInfo();
             mCallServiceInfos.add(info);
-            mCallServicesByInfo.put(info, callService);
+            mCallServicesById.put(info.getCallServiceId(), callService);
         }
     }
 
@@ -253,11 +253,19 @@
 
         if (mCallServiceInfoIterator.hasNext()) {
             CallServiceInfo info = mCallServiceInfoIterator.next();
-            mCallService = mCallServicesByInfo.get(info);
+            mCallService = mCallServicesById.get(info.getCallServiceId());
             if (mCallService == null) {
                 attemptNextCallService();
             } else {
-                mCallService.call(mCall.toCallInfo());
+                BindCallback callback = new BindCallback() {
+                    @Override public void onSuccess() {
+                        mCallService.call(mCall.toCallInfo());
+                    }
+                    @Override public void onFailure() {
+                        attemptNextSelector();
+                    }
+                };
+                mCallService.bind(callback);
             }
         } else {
             mCallServiceInfoIterator = null;
diff --git a/src/com/android/telecomm/ServiceBinder.java b/src/com/android/telecomm/ServiceBinder.java
index c58791c..101b940 100644
--- a/src/com/android/telecomm/ServiceBinder.java
+++ b/src/com/android/telecomm/ServiceBinder.java
@@ -59,7 +59,8 @@
 
             mServiceConnection = this;
             mBinder = binder;
-            handleSuccessfulConnection(binder);
+            setServiceInterface(binder);
+            handleSuccessfulConnection();
         }
 
         @Override
@@ -140,7 +141,7 @@
             }
         } else {
             Preconditions.checkNotNull(mBinder);
-            handleSuccessfulConnection(mBinder);
+            handleSuccessfulConnection();
         }
 
         return true;
@@ -169,12 +170,8 @@
     /**
      * Notifies all the outstanding callbacks that the service is successfully bound. The list of
      * outstanding callbacks is cleared afterwards.
-     *
-     * @param binder The actual bound service implementation.
      */
-    private void handleSuccessfulConnection(IBinder binder) {
-        setServiceInterface(binder);
-
+    private void handleSuccessfulConnection() {
         for (BindCallback callback : mCallbacks) {
             callback.onSuccess();
         }
diff --git a/src/com/android/telecomm/Switchboard.java b/src/com/android/telecomm/Switchboard.java
index 32ef914..34398d2 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);
     }
@@ -114,15 +118,31 @@
     void placeOutgoingCall(Call call) {
         ThreadUtil.checkOnMainThread();
 
-        mLookupId++;
+        mNewOutgoingCalls.add(call);
 
         // We initialize a lookup every time because between calls the set of available call
         // services can change between calls.
+        mLookupId++;
         mCallServiceRepository.initiateLookup(mLookupId);
         mSelectorRepository.initiateLookup(mLookupId);
     }
 
     /**
+     * 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 +201,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() {
diff --git a/tests/src/com/android/telecomm/testcallservice/DummyCallServiceSelector.java b/tests/src/com/android/telecomm/testcallservice/DummyCallServiceSelector.java
index afc82f7..a11a35b 100644
--- a/tests/src/com/android/telecomm/testcallservice/DummyCallServiceSelector.java
+++ b/tests/src/com/android/telecomm/testcallservice/DummyCallServiceSelector.java
@@ -16,15 +16,8 @@
 
 package com.android.telecomm.testcallservice;
 
-import android.app.Service;
-import android.content.Intent;
-import android.os.IBinder;
-import android.os.RemoteException;
 import android.telecomm.CallInfo;
-import android.telecomm.CallServiceInfo;
-import android.telecomm.ICallServiceSelector;
-import android.telecomm.ICallServiceSelectionResponse;
-import android.telecomm.ICallSwitchabilityResponse;
+import android.telecomm.CallServiceSelector;
 
 import java.util.List;
 
@@ -32,36 +25,14 @@
  * Dummy call-service selector which returns the list of call services in the same order in which it
  * was given. Also returns false for every request on switchability.
  */
-public class DummyCallServiceSelector extends Service {
-
-    /**
-     * Actual Binder implementation of ICallServiceSelector.
-     */
-    private final IBinder mBinder = new ICallServiceSelector.Stub() {
-        /**
-         * Returns the unaltered list of call services.
-         *
-         * {@inheritDoc}
-         */
-        @Override public void select(
-                CallInfo callInfo,
-                List<CallServiceInfo> callServiceInfos,
-                ICallServiceSelectionResponse response) throws RemoteException {
-
-            response.setSelectedCallServiceInfos(callServiceInfos);
-        }
-
-        /** {@inheritDoc} */
-        @Override public void isSwitchable(CallInfo callInfo, ICallSwitchabilityResponse response)
-                throws RemoteException {
-
-            response.setIsSwitchable(false);
-        }
-    };
-
-    /** {@inheritDoc} */
+public class DummyCallServiceSelector extends CallServiceSelector {
     @Override
-    public IBinder onBind(Intent intent) {
-        return mBinder;
+    protected boolean isSwitchable(CallInfo callInfo) {
+        return false;
+    }
+
+    @Override
+    protected List<String> select(CallInfo callInfo, List<String> callServiceIds) {
+        return callServiceIds;
     }
 }