Adds class to translate telephony Connections to Calls.

Adds primitive call model (int callId) translated from telephony
layer's Connection type.
Adds onDisconnect functionality to CallHandlerService.
Makes call Id functional in callHandlerService.

Change-Id: I4d54f4c3f606e5973cc316125486cc361c96d610
diff --git a/Android.mk b/Android.mk
index 9471b36..24d5b71 100644
--- a/Android.mk
+++ b/Android.mk
@@ -6,7 +6,8 @@
 
 LOCAL_JAVA_LIBRARIES := telephony-common voip-common
 LOCAL_STATIC_JAVA_LIBRARIES := com.android.phone.shared \
-        com.android.services.telephony.common
+        com.android.services.telephony.common \
+        guava
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 LOCAL_SRC_FILES += \
diff --git a/common/src/com/android/services/telephony/common/ICallHandlerService.aidl b/common/src/com/android/services/telephony/common/ICallHandlerService.aidl
index 665edc4..24378ef 100644
--- a/common/src/com/android/services/telephony/common/ICallHandlerService.aidl
+++ b/common/src/com/android/services/telephony/common/ICallHandlerService.aidl
@@ -40,4 +40,9 @@
      */
     void onIncomingCall(int callId);
 
+    /**
+     * Called when a call disconnects.
+     */
+    void onDisconnect(int callId);
+
 }
diff --git a/src/com/android/phone/CallHandlerServiceProxy.java b/src/com/android/phone/CallHandlerServiceProxy.java
index a9f6b21..f5247f5 100644
--- a/src/com/android/phone/CallHandlerServiceProxy.java
+++ b/src/com/android/phone/CallHandlerServiceProxy.java
@@ -34,7 +34,7 @@
 /**
  * This class is responsible for passing through call state changes to the CallHandlerService.
  */
-public class CallHandlerServiceProxy extends Handler {
+public class CallHandlerServiceProxy extends Handler implements CallModeler.Listener {
 
     private static final String TAG = CallHandlerServiceProxy.class.getSimpleName();
     private static final boolean DBG =
@@ -42,31 +42,42 @@
 
 
     private Context mContext;
-    private CallStateMonitor mCallStateMonitor;
+    private CallModeler mCallModeler;
     private ServiceConnection mConnection;
     private ICallHandlerService mCallHandlerService;
     private CallCommandService mCallCommandService;
 
-    public CallHandlerServiceProxy(Context context, CallStateMonitor callStateMonitor,
+    public CallHandlerServiceProxy(Context context, CallModeler callModeler,
             CallCommandService callCommandService) {
         mContext = context;
-        mCallStateMonitor = callStateMonitor;
         mCallCommandService = callCommandService;
+        mCallModeler = callModeler;
 
-        mCallStateMonitor.addListener(this);
+        mCallModeler.addListener(this);
         setupServiceConnection();
     }
 
     @Override
-    public void handleMessage(Message msg) {
-        switch (msg.what) {
-            case CallStateMonitor.PHONE_NEW_RINGING_CONNECTION:
-                onNewRingingConnection((AsyncResult) msg.obj);
-                break;
+    public void onNewCall(int callId) {
+        if (mCallHandlerService != null) {
+            try {
+                mCallHandlerService.onIncomingCall(callId);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Remote exception handling onIncomingCall", e);
+            }
+        } else {
+            Log.wtf(TAG, "Call handle service has not connected!  Cannot accept incoming call.");
+        }
+    }
 
-            default:
-                //super.handleMessage(msg);
-                break;
+    @Override
+    public void onDisconnect(int callId) {
+        if (mCallHandlerService != null) {
+            try {
+                mCallHandlerService.onDisconnect(callId);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Remote exception handling onDisconnect ", e);
+            }
         }
     }
 
@@ -103,28 +114,13 @@
     /**
      * Called when the in-call UI service is connected.  Send command interface to in-call.
      */
-    private void onCallHandlerServiceConnected(ICallHandlerService CallHandlerService) {
-        mCallHandlerService = CallHandlerService;
+    private void onCallHandlerServiceConnected(ICallHandlerService callHandlerService) {
+        mCallHandlerService = callHandlerService;
 
         try {
             mCallHandlerService.setCallCommandService(mCallCommandService);
         } catch (RemoteException e) {
-            Log.e(TAG, "Remote exception calling CallHandlerService::onConnected. " + e);
-        }
-    }
-
-    /**
-     * Send notification of a new incoming call.
-     */
-    private void onNewRingingConnection(AsyncResult result) {
-        if (mCallHandlerService != null) {
-            try {
-                mCallHandlerService.onIncomingCall(0);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Remote exception handling onIncomingCall:" + e);
-            }
-        } else {
-            Log.wtf(TAG, "Call handle service has not connected!  Cannot accept incoming call.");
+            Log.e(TAG, "Remote exception calling CallHandlerService::setCallCommandService", e);
         }
     }
 }
diff --git a/src/com/android/phone/CallModeler.java b/src/com/android/phone/CallModeler.java
new file mode 100644
index 0000000..018c771
--- /dev/null
+++ b/src/com/android/phone/CallModeler.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2013 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.phone;
+
+import com.google.android.collect.Lists;
+import com.google.android.collect.Maps;
+import com.google.common.base.Preconditions;
+
+import android.os.AsyncResult;
+import android.os.Handler;
+import android.os.Message;
+import android.util.Log;
+
+import com.android.internal.telephony.Connection;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Creates a Call model from Call state and data received from the telephony
+ * layer. The telephony layer maintains 3 conceptual objects: Phone, Call,
+ * Connection.
+ *
+ * Phone represents the radio and there is an implementation per technology
+ * type such as GSMPhone, SipPhone, CDMAPhone, etc. Generally, we will only ever
+ * deal with one instance of this object for the lifetime of this class.
+ *
+ * There are 3 Call instances that exist for the lifetime of this class which
+ * are created by CallTracker. The three are RingingCall, ForegroundCall, and
+ * BackgroundCall.
+ *
+ * A Connection most closely resembles what the layperson would consider a call.
+ * A Connection is created when a user dials and it is "owned" by one of the
+ * three Call instances.  Which of the three Calls owns the Connection changes
+ * as the Connection goes between ACTIVE, HOLD, RINGING, and other states.
+ *
+ * This class models a new Call class from Connection objects received from
+ * the telephony layer. We use Connection references as identifiers for a call;
+ * new reference = new call.
+ *
+ * TODO(klp): Create a new Call class to replace the simple call Id ints
+ * being used currently.
+ *
+ * The new Call models are parcellable for transfer via the CallHandlerService
+ * API.
+ */
+public class CallModeler extends Handler {
+
+    private static final String TAG = CallModeler.class.getSimpleName();
+
+    private static final int CALL_ID_START_VALUE = 1;
+    private static final int INVALID_CALL_ID = -1;
+
+    private CallStateMonitor mCallStateMonitor;
+    private HashMap<Connection, Integer> mCallIdMap = Maps.newHashMap();
+    private List<Listener> mListeners = Lists.newArrayList();
+    private AtomicInteger mNextCallId = new AtomicInteger(CALL_ID_START_VALUE);
+
+    public CallModeler(CallStateMonitor callStateMonitor) {
+        mCallStateMonitor = callStateMonitor;
+
+        mCallStateMonitor.addListener(this);
+    }
+
+    @Override
+    public void handleMessage(Message msg) {
+        switch(msg.what) {
+            case CallStateMonitor.PHONE_NEW_RINGING_CONNECTION:
+                onNewRingingConnection((AsyncResult) msg.obj);
+                break;
+            case CallStateMonitor.PHONE_DISCONNECT:
+                onDisconnect((AsyncResult) msg.obj);
+            default:
+                break;
+        }
+    }
+
+    public void addListener(Listener listener) {
+        Preconditions.checkNotNull(listener);
+
+        mListeners.add(listener);
+    }
+
+    private void onNewRingingConnection(AsyncResult r) {
+        final Connection conn = (Connection) r.result;
+        final int callId = getCallId(conn, true);
+
+        for (Listener l : mListeners) {
+            l.onNewCall(callId);
+        }
+    }
+
+    private void onDisconnect(AsyncResult r) {
+        final Connection conn = (Connection) r.result;
+        final int callId = getCallId(conn, false);
+
+        if (INVALID_CALL_ID != callId) {
+            mCallIdMap.remove(conn);
+
+            for (Listener l : mListeners) {
+                l.onDisconnect(callId);
+            }
+        }
+    }
+
+    /**
+     * Gets an existing callId for a connection, or creates one
+     * if none exists.
+     */
+    private int getCallId(Connection conn, boolean createIfMissing) {
+        int callId = INVALID_CALL_ID;
+
+        // Find the call id or create if missing and requested.
+        if (conn != null) {
+            if (mCallIdMap.containsKey(conn)) {
+                callId = mCallIdMap.get(conn).intValue();
+            } else if (createIfMissing) {
+                int newNextCallId;
+                do {
+                    callId = mNextCallId.get();
+
+                    // protect against overflow
+                    newNextCallId = (callId == Integer.MAX_VALUE ?
+                            CALL_ID_START_VALUE : callId + 1);
+
+                    // Keep looping if the change was not atomic OR the value is already taken.
+                    // The call to containsValue() is linear, however, most devices support a
+                    // maximum of 7 connections so it's not expensive.
+                } while (!mNextCallId.compareAndSet(callId, newNextCallId) ||
+                        mCallIdMap.containsValue(callId));
+
+                mCallIdMap.put(conn, callId);
+            }
+        }
+        return callId;
+    }
+
+    /**
+     * Listener interface for changes to Calls.
+     */
+    public interface Listener {
+        void onNewCall(int callId);
+        void onDisconnect(int callId);
+    }
+}
diff --git a/src/com/android/phone/PhoneGlobals.java b/src/com/android/phone/PhoneGlobals.java
index 2dbbd36..d96bfce 100644
--- a/src/com/android/phone/PhoneGlobals.java
+++ b/src/com/android/phone/PhoneGlobals.java
@@ -161,22 +161,26 @@
 
     // A few important fields we expose to the rest of the package
     // directly (rather than thru set/get methods) for efficiency.
-    Phone phone;
     CallController callController;
-    InCallUiState inCallUiState;
-    CallerInfoCache callerInfoCache;
-    CallNotifier notifier;
-    NotificationMgr notificationMgr;
-    Ringer ringer;
-    IBluetoothHeadsetPhone mBluetoothPhone;
-    PhoneInterfaceManager phoneMgr;
     CallManager mCM;
-    CallStateMonitor callStateMonitor;
-    CallHandlerServiceProxy CallHandlerServiceProxy;
-    CallCommandService callCommandService;
-    int mBluetoothHeadsetState = BluetoothProfile.STATE_DISCONNECTED;
-    int mBluetoothHeadsetAudioState = BluetoothHeadset.STATE_AUDIO_DISCONNECTED;
-    boolean mShowBluetoothIndication = false;
+    CallNotifier notifier;
+    CallerInfoCache callerInfoCache;
+    InCallUiState inCallUiState;
+    NotificationMgr notificationMgr;
+    Phone phone;
+    PhoneInterfaceManager phoneMgr;
+
+    private CallCommandService callCommandService;
+    private CallHandlerServiceProxy callHandlerServiceProxy;
+    private CallModeler callModeler;
+    private CallStateMonitor callStateMonitor;
+    private IBluetoothHeadsetPhone mBluetoothPhone;
+    private Ringer ringer;
+
+    private int mBluetoothHeadsetState = BluetoothProfile.STATE_DISCONNECTED;
+    private int mBluetoothHeadsetAudioState = BluetoothHeadset.STATE_AUDIO_DISCONNECTED;
+    private boolean mShowBluetoothIndication = false;
+
     static int mDockState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
     static boolean sVoiceCapable = true;
 
@@ -541,8 +545,11 @@
             // Service used by in-call UI to control calls
             callCommandService = new CallCommandService(this, mCM);
 
+            // Creates call models for use with CallHandlerService.
+            callModeler = new CallModeler(callStateMonitor);
+
             // Sends call state to the UI
-            CallHandlerServiceProxy = new CallHandlerServiceProxy(this, callStateMonitor,
+            callHandlerServiceProxy = new CallHandlerServiceProxy(this, callModeler,
                     callCommandService);
 
             // Create the CallNotifer singleton, which handles