Add support for remote incoming calls: impl

This CL changes how incoming calls are routed. We now
treat incoming calls the same as outgoing calls.
This allows a ConnectionService to attach to a incoming call
using a remote connection.

Change-Id: I5232d062ad3b559f4fe7c8224e7234b2c6bf8431
diff --git a/src/com/android/telecomm/CreateConnectionProcessor.java b/src/com/android/telecomm/CreateConnectionProcessor.java
new file mode 100644
index 0000000..631d6fe
--- /dev/null
+++ b/src/com/android/telecomm/CreateConnectionProcessor.java
@@ -0,0 +1,173 @@
+/*
+ * 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.content.ComponentName;
+import android.net.Uri;
+import android.telephony.DisconnectCause;
+import android.telephony.PhoneNumberUtils;
+import android.telecomm.ConnectionRequest;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * This class creates connections to place new outgoing calls to attached to an existing incoming
+ * call. In either case, this class cycles through a set of connection services until:
+ *   - a connection service returns a newly created connection in which case the call is displayed
+ *     to the user
+ *   - a connection service cancels the process, in which case the call is aborted
+ */
+final class CreateConnectionProcessor {
+    private final Call mCall;
+    private final ConnectionServiceRepository mRepository;
+    private List<ComponentName> mServiceComponentNames;
+    private Iterator<ComponentName> mServiceComponentNameIterator;
+    private CreateConnectionResponse mResponse;
+    private int mLastErrorCode = DisconnectCause.ERROR_UNSPECIFIED;
+    private String mLastErrorMsg;
+
+    CreateConnectionProcessor(
+            Call call, ConnectionServiceRepository repository, CreateConnectionResponse response) {
+        mCall = call;
+        mRepository = repository;
+        mResponse = response;
+    }
+
+    void process() {
+        Log.v(this, "process");
+
+        mServiceComponentNames = new ArrayList<>();
+
+        // TODO(sail): Remove once there's a way to pick the service.
+        ArrayList<ComponentName> priorityComponents = new ArrayList<>();
+        priorityComponents.add(new ComponentName("com.android.phone",
+                "com.android.services.telephony.sip.SipConnectionService"));
+        priorityComponents.add(new ComponentName("com.google.android.talk",
+                "com.google.android.apps.babel.telephony.TeleConnectionService"));
+        priorityComponents.add(new ComponentName("com.android.telecomm.tests",
+                "com.android.telecomm.testapps.TestConnectionService"));
+
+        for (ConnectionServiceWrapper service : mRepository.lookupServices()) {
+            ComponentName serviceName = service.getComponentName();
+            if (priorityComponents.contains(serviceName)) {
+                Log.i(this, "Moving connection service %s to top of list", serviceName);
+                mServiceComponentNames .add(0, serviceName);
+            } else {
+                mServiceComponentNames.add(serviceName);
+            }
+        }
+
+        adjustComponentNamesForEmergency();
+        mServiceComponentNameIterator = mServiceComponentNames.iterator();
+        attemptNextConnectionService();
+    }
+
+    void abort() {
+        Log.v(this, "abort");
+
+        // Clear the response first to prevent attemptNextConnectionService from attempting any
+        // more services.
+        CreateConnectionResponse response = mResponse;
+        mResponse = null;
+
+        ConnectionServiceWrapper service = mCall.getConnectionService();
+        if (service != null) {
+            service.abort(mCall);
+            mCall.clearConnectionService();
+        }
+        if (response != null) {
+            response.handleCreateConnectionCancelled();
+        }
+    }
+
+    private void attemptNextConnectionService() {
+        Log.v(this, "attemptNextConnectionService");
+
+        if (mResponse != null && mServiceComponentNameIterator.hasNext()) {
+            ComponentName component = mServiceComponentNameIterator.next();
+            ConnectionServiceWrapper service = mRepository.getService(component);
+            if (service == null) {
+                attemptNextConnectionService();
+            } else {
+                mCall.setConnectionService(service);
+                Log.i(this, "Attempting to call from %s", service.getComponentName());
+                service.createConnection(mCall, new Response(service));
+            }
+        } else {
+            Log.v(this, "attemptNextConnectionService, no more services, failing");
+            if (mResponse != null) {
+                mResponse.handleCreateConnectionFailed(mLastErrorCode, mLastErrorMsg);
+                mResponse = null;
+                mCall.clearConnectionService();
+            }
+        }
+    }
+
+    // If we are possibly attempting to call a local emergency number, ensure that the
+    // plain PSTN connection service, if it exists, is attempted first.
+    private void adjustComponentNamesForEmergency()  {
+        if (shouldProcessAsEmergency(mCall.getHandle())) {
+            for (int i = 0; i < mServiceComponentNames.size(); i++) {
+                if (TelephonyUtil.isPstnComponentName(mServiceComponentNames.get(i))) {
+                    mServiceComponentNames.add(0, mServiceComponentNames.remove(i));
+                    return;
+                }
+            }
+        }
+    }
+
+    private boolean shouldProcessAsEmergency(Uri handle) {
+        return handle != null && PhoneNumberUtils.isPotentialLocalEmergencyNumber(
+                TelecommApp.getInstance(), handle.getSchemeSpecificPart());
+    }
+
+    private class Response implements CreateConnectionResponse {
+        private final ConnectionServiceWrapper mService;
+
+        Response(ConnectionServiceWrapper service) {
+            mService = service;
+        }
+
+        @Override
+        public void handleCreateConnectionSuccessful(ConnectionRequest request) {
+            if (mResponse == null) {
+                mService.abort(mCall);
+            } else {
+                mResponse.handleCreateConnectionSuccessful(request);
+                mResponse= null;
+            }
+        }
+
+        @Override
+        public void handleCreateConnectionFailed(int code, String msg) {
+            mLastErrorCode = code;
+            mLastErrorMsg = msg;
+            attemptNextConnectionService();
+        }
+
+        @Override
+        public void handleCreateConnectionCancelled() {
+            if (mResponse != null) {
+                mResponse.handleCreateConnectionCancelled();
+                mResponse = null;
+            }
+        }
+    }
+}