Fully connect CallsManager with InCallController for outgoing calls.

Change-Id: Ic28fc5ea1e4a76be32fc7bd2d29f9690da959c96
diff --git a/src/com/android/telecomm/Call.java b/src/com/android/telecomm/Call.java
index 9f9a692..905fa6a 100644
--- a/src/com/android/telecomm/Call.java
+++ b/src/com/android/telecomm/Call.java
@@ -16,9 +16,11 @@
 
 package com.android.telecomm;
 
+import android.os.RemoteException;
 import android.telecomm.CallInfo;
 import android.telecomm.CallState;
 import android.telecomm.ICallService;
+import android.util.Log;
 
 import java.util.Date;
 
@@ -28,6 +30,7 @@
  *  connected etc).
  */
 final class Call {
+    private static final String TAG = Call.class.getSimpleName();
 
     /**
      * Unique identifier for the call as a UUID string.
@@ -129,6 +132,22 @@
         setCallService(null);
     }
 
+    /*
+     * Attempts to disconnect the call through the call service.
+     */
+    void disconnect() {
+        if (mCallService == null) {
+            Log.w(TAG, "disconnect() request on a call without a call service.");
+        } else {
+            try {
+                Log.i(TAG, "Send disconnect to call service for call with id " + mId);
+                mCallService.disconnect(mId);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Disconnect attempt failed.", e);
+            }
+        }
+    }
+
     /**
      * @return An object containing read-only information about this call.
      */
diff --git a/src/com/android/telecomm/CallsManager.java b/src/com/android/telecomm/CallsManager.java
index 332eb0a..2a621aa 100644
--- a/src/com/android/telecomm/CallsManager.java
+++ b/src/com/android/telecomm/CallsManager.java
@@ -117,7 +117,23 @@
         Preconditions.checkState(call.getState() == CallState.DIALING);
 
         addCall(call);
-        // TODO(santoscordon): Notify in-call UI.
+
+        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.
+     */
+    void updateInCall() {
+        if (mCalls.isEmpty()) {
+            mInCallController.unbind();
+            return;
+        }
+
+        for (Call call : mCalls.values()) {
+            mInCallController.addCall(call.toCallInfo());
+        }
     }
 
     /**
@@ -150,7 +166,13 @@
      * @param callId The ID of the call.
      */
     void disconnectCall(String callId) {
-        // TODO(santoscordon): fill in and check that the call is in the active state.
+        Call call = mCalls.get(callId);
+        if (call == null) {
+            Log.e(TAG, "Unknown call (" + callId + ") asked to disconnect");
+        } else {
+            call.disconnect();
+        }
+
     }
 
     void markCallAsRinging(String callId) {
@@ -165,9 +187,21 @@
         setCallState(callId, CallState.ACTIVE);
     }
 
+    /**
+     * Marks the specified call as DISCONNECTED and notifies the in-call app. If this was the last
+     * live call, then also disconnect from the in-call controller.
+     *
+     * @param callId The ID of the call.
+     */
     void markCallAsDisconnected(String callId) {
         setCallState(callId, CallState.DISCONNECTED);
         mCalls.remove(callId);
+
+        // Notify the in-call UI
+        mInCallController.markCallAsDisconnected(callId);
+        if (mCalls.isEmpty()) {
+            mInCallController.unbind();
+        }
     }
 
     /**
diff --git a/src/com/android/telecomm/InCallController.java b/src/com/android/telecomm/InCallController.java
index f597030..dedc21c 100644
--- a/src/com/android/telecomm/InCallController.java
+++ b/src/com/android/telecomm/InCallController.java
@@ -22,6 +22,7 @@
 import android.content.ServiceConnection;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.telecomm.CallInfo;
 import android.telecomm.IInCallService;
 import android.util.Log;
 
@@ -64,7 +65,7 @@
     /**
      * Class name of the component within in-call app which implements {@link IInCallService}.
      */
-    private static final String IN_CALL_SERVICE_CLASS_NAME = "com.android.incall.InCallService";
+    private static final String IN_CALL_SERVICE_CLASS_NAME = "com.android.incallui.InCallService";
 
     /** Maintains a binding connection to the in-call app. */
     private final InCallServiceConnection mConnection = new InCallServiceConnection();
@@ -88,42 +89,95 @@
     }
 
     /**
-     * Binds to the in-call app if not already connected by binding directly to the saved
-     * component name of the {@link IInCallService} implementation.
+     * Indicates to the in-call app that a new call has been created and an appropriate
+     * user-interface should be built and shown to notify the user.  Information about the call
+     * including its current state is passed in through the callInfo object.
      *
-     * @param context The application context.
+     * @param callInfo Details about the new call.
      */
-    void connect(Context context) {
-        ThreadUtil.checkOnMainThread();
-        if (mInCallService == null) {
-            ComponentName component =
-                    new ComponentName(IN_CALL_PACKAGE_NAME, IN_CALL_SERVICE_CLASS_NAME);
-
-            Intent serviceIntent = new Intent(IInCallService.class.getName());
-            serviceIntent.setComponent(component);
-
-            if (!context.bindService(serviceIntent, mConnection, Context.BIND_AUTO_CREATE)) {
-                Log.e(TAG, "Could not connect to the in-call app (" + component + ")");
-
-                // TODO(santoscordon): Implement retry or fall-back-to-default logic.
+    void addCall(CallInfo callInfo) {
+        try {
+            if (mInCallService == null) {
+                bind();
+            } else {
+                // TODO(santoscordon): Protect against logging phone number.
+                Log.i(TAG, "Adding call: " + callInfo);
+                mInCallService.addCall(callInfo);
             }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Exception attempting to addCall.", e);
+        }
+    }
+
+    /**
+     * Indicates to the in-call app that a call has moved to the active state.
+     *
+     * @param callId The identifier of the call that became active.
+     */
+    void markCallAsActive(String callId) {
+        try {
+            if (mInCallService != null) {
+                Log.i(TAG, "Mark call as ACTIVE: " + callId);
+                mInCallService.setActive(callId);
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Exception attempting to markCallAsActive.", e);
+        }
+    }
+
+    /**
+     * Indicates to the in-call app that a call has been disconnected and the user should be
+     * notified.
+     *
+     * @param callId The identifier of the call that was disconnected.
+     */
+    void markCallAsDisconnected(String callId) {
+        try {
+            if (mInCallService != null) {
+                Log.i(TAG, "Mark call as DISCONNECTED: " + callId);
+                mInCallService.setDisconnected(callId);
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Exception attempting to markCallAsDisconnected.", e);
         }
     }
 
     /**
      * Unbinds an existing bound connection to the in-call app.
-     *
-     * @param context The application context.
      */
-    void disconnect(Context context) {
+    void unbind() {
         ThreadUtil.checkOnMainThread();
         if (mInCallService != null) {
-            context.unbindService(mConnection);
+            Log.i(TAG, "Unbinding from InCallService");
+            TelecommApp.getInstance().unbindService(mConnection);
             mInCallService = null;
         }
     }
 
     /**
+     * Binds to the in-call app if not already connected by binding directly to the saved
+     * component name of the {@link IInCallService} implementation.
+     */
+    private void bind() {
+        ThreadUtil.checkOnMainThread();
+        if (mInCallService == null) {
+            ComponentName component =
+                    new ComponentName(IN_CALL_PACKAGE_NAME, IN_CALL_SERVICE_CLASS_NAME);
+            Log.i(TAG, "Attempting to bind to InCallService: " + component);
+
+            Intent serviceIntent = new Intent(IInCallService.class.getName());
+            serviceIntent.setComponent(component);
+
+            Context context = TelecommApp.getInstance();
+            if (!context.bindService(serviceIntent, mConnection, Context.BIND_AUTO_CREATE)) {
+                Log.e(TAG, "Could not connect to the in-call app (" + component + ")");
+
+                // TODO(santoscordon): Implement retry or fall-back-to-default logic.
+            }
+        }
+    }
+
+    /**
      * Persists the {@link IInCallService} instance and starts the communication between
      * CallsManager and in-call app by sending the first update to in-call app. This method is
      * called after a successful binding connection is established.
@@ -141,7 +195,11 @@
             mInCallService = null;
         }
 
-        update();
+        // Upon successful connection, send the state of the world to the in-call app.
+        if (mInCallService != null) {
+            mCallsManager.updateInCall();
+        }
+
     }
 
     /**
@@ -151,11 +209,4 @@
         ThreadUtil.checkOnMainThread();
         mInCallService = null;
     }
-
-    /**
-     * Gathers the list of current calls from CallsManager and sends them to the in-call app.
-     */
-    private void update() {
-        // TODO(santoscordon): mInCallService.sendCalls(CallsManager.getCallList());
-    }
 }
diff --git a/src/com/android/telecomm/TelecommApp.java b/src/com/android/telecomm/TelecommApp.java
new file mode 100644
index 0000000..e0a3bf6
--- /dev/null
+++ b/src/com/android/telecomm/TelecommApp.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 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.app.Application;
+
+/**
+ * Top-level Application class for Telecomm.
+ */
+public final class TelecommApp extends Application {
+
+    // Singleton instance of TelecommApp.
+    private static TelecommApp sInstance;
+
+    /** {@inheritDoc} */
+    @Override public void onCreate() {
+        super.onCreate();
+        sInstance = this;
+    }
+
+    public static TelecommApp getInstance() {
+        if (null == sInstance) {
+            throw new IllegalStateException("No TelecommApp running.");
+        }
+        return sInstance;
+    }
+}