Use mapped IDs for calls

With this CL each client gets a unique ID to represent calls.
This has a couple of benefits:
  - avoids one client from modifying another clients call
  - allows for stricter validation of input
  - allows a call to handed off to a different call service
    (with a different call ID)

Bug: 13643568
Change-Id: I6e2039aead5723d01f9442a4e54f5e616711a3b3
diff --git a/src/com/android/telecomm/Call.java b/src/com/android/telecomm/Call.java
index 3e4895b..b57ec80 100644
--- a/src/com/android/telecomm/Call.java
+++ b/src/com/android/telecomm/Call.java
@@ -29,7 +29,6 @@
 import java.util.Date;
 import java.util.Locale;
 import java.util.Set;
-import java.util.UUID;
 
 /**
  *  Encapsulates all aspects of a given phone call throughout its lifecycle, starting
@@ -37,9 +36,6 @@
  *  connected etc).
  */
 final class Call {
-    /** Unique identifier for the call as a UUID string. */
-    private final String mId;
-
     /** Additional contact information beyond handle above, optional. */
     private final ContactInfo mContactInfo;
 
@@ -96,7 +92,7 @@
     private String mDisconnectMessage;
 
     /**
-     * Creates an empty call object with a unique call ID.
+     * Creates an empty call object.
      *
      * @param isIncoming True if this is an incoming call.
      */
@@ -113,7 +109,6 @@
      * @param isIncoming True if this is an incoming call.
      */
     Call(Uri handle, ContactInfo contactInfo, GatewayInfo gatewayInfo, boolean isIncoming) {
-        mId = UUID.randomUUID().toString();  // UUIDs should provide sufficient uniqueness.
         mState = CallState.NEW;
         setHandle(handle);
         mContactInfo = contactInfo;
@@ -125,15 +120,11 @@
 
     /** {@inheritDoc} */
     @Override public String toString() {
-        return String.format(Locale.US, "[%s, %s, %s, %s]", mId, mState,
+        return String.format(Locale.US, "[%s, %s, %s]", mState,
                 mCallService == null ? "<null>" : mCallService.getComponentName(),
                 Log.pii(mHandle));
     }
 
-    String getId() {
-        return mId;
-    }
-
     CallState getState() {
         return mState;
     }
@@ -237,6 +228,7 @@
 
         callService.incrementAssociatedCallCount();
         mCallService = callService;
+        mCallService.addCall(this);
     }
 
     /**
@@ -245,21 +237,27 @@
     void clearCallService() {
         if (mCallService != null) {
             decrementAssociatedCallCount(mCallService);
-            mCallService.cancelOutgoingCall(getId());
+            mCallService.removeCall(this);
             mCallService = null;
         }
     }
 
     void setCallServiceSelector(CallServiceSelectorWrapper selector) {
         Preconditions.checkNotNull(selector);
+
+        clearCallServiceSelector();
+
         mCallServiceSelector = selector;
+        mCallServiceSelector.addCall(this);
     }
 
     void clearCallServiceSelector() {
-        // TODO(gilad): Un-comment once selectors are converted into wrappers.
-        // decrementAssociatedCallCount(mCallServiceSelector);
-
-        mCallServiceSelector = null;
+        if (mCallServiceSelector != null) {
+            // TODO(sail): Stop leaking selectors.
+            // decrementAssociatedCallCount(mCallServiceSelector);
+            mCallServiceSelector.removeCall(this);
+            mCallServiceSelector = null;
+        }
     }
 
     /**
@@ -295,7 +293,7 @@
     void abort() {
         if (mState == CallState.NEW) {
             if (mCallService != null) {
-                mCallService.abort(mId);
+                mCallService.abort(this);
             }
             clearCallService();
             clearCallServiceSelector();
@@ -309,8 +307,8 @@
         if (mCallService == null) {
             Log.w(this, "playDtmfTone() request on a call without a call service.");
         } else {
-            Log.i(this, "Send playDtmfTone to call service for call with id %s", mId);
-            mCallService.playDtmfTone(mId, digit);
+            Log.i(this, "Send playDtmfTone to call service for call %s", this);
+            mCallService.playDtmfTone(this, digit);
         }
     }
 
@@ -321,8 +319,8 @@
         if (mCallService == null) {
             Log.w(this, "stopDtmfTone() request on a call without a call service.");
         } else {
-            Log.i(this, "Send stopDtmfTone to call service for call with id %s", mId);
-            mCallService.stopDtmfTone(mId);
+            Log.i(this, "Send stopDtmfTone to call service for call %s", this);
+            mCallService.stopDtmfTone(this);
         }
     }
 
@@ -333,11 +331,11 @@
         if (mCallService == null) {
             Log.w(this, "disconnect() request on a call without a call service.");
         } else {
-            Log.i(this, "Send disconnect to call service for call with id %s", mId);
+            Log.i(this, "Send disconnect to call service for call: %s", this);
             // The call isn't officially disconnected until the call service confirms that the call
             // was actually disconnected. Only then is the association between call and call service
             // severed, see {@link CallsManager#markCallAsDisconnected}.
-            mCallService.disconnect(mId);
+            mCallService.disconnect(this);
         }
     }
 
@@ -354,7 +352,7 @@
             // it will work. Instead, we wait until confirmation from the call service that the
             // call is in a non-RINGING state before changing the UI. See
             // {@link CallServiceAdapter#setActive} and other set* methods.
-            mCallService.answer(mId);
+            mCallService.answer(this);
         }
     }
 
@@ -367,7 +365,7 @@
         // Check to verify that the call is still in the ringing state. A call can change states
         // between the time the user hits 'reject' and Telecomm receives the command.
         if (isRinging("reject")) {
-            mCallService.reject(mId);
+            mCallService.reject(this);
         }
     }
 
@@ -378,7 +376,7 @@
         Preconditions.checkNotNull(mCallService);
 
         if (mState == CallState.ACTIVE) {
-            mCallService.hold(mId);
+            mCallService.hold(this);
         }
     }
 
@@ -389,15 +387,15 @@
         Preconditions.checkNotNull(mCallService);
 
         if (mState == CallState.ON_HOLD) {
-            mCallService.unhold(mId);
+            mCallService.unhold(this);
         }
     }
 
     /**
      * @return An object containing read-only information about this call.
      */
-    CallInfo toCallInfo() {
-        return new CallInfo(mId, mState, mHandle, mGatewayInfo);
+    CallInfo toCallInfo(String callId) {
+        return new CallInfo(callId, mState, mHandle, mGatewayInfo);
     }
 
     /** Checks if this is a live call or not. */
diff --git a/src/com/android/telecomm/CallAudioManager.java b/src/com/android/telecomm/CallAudioManager.java
index bf41f70..d9ff8a5 100644
--- a/src/com/android/telecomm/CallAudioManager.java
+++ b/src/com/android/telecomm/CallAudioManager.java
@@ -304,7 +304,7 @@
     private void updateAudioForForegroundCall() {
         Call call = CallsManager.getInstance().getForegroundCall();
         if (call != null && call.getCallService() != null) {
-            call.getCallService().onAudioStateChanged(call.getId(), mAudioState);
+            call.getCallService().onAudioStateChanged(call, mAudioState);
         }
     }
 }
diff --git a/src/com/android/telecomm/CallIdMapper.java b/src/com/android/telecomm/CallIdMapper.java
new file mode 100644
index 0000000..e5ed286
--- /dev/null
+++ b/src/com/android/telecomm/CallIdMapper.java
@@ -0,0 +1,77 @@
+/*
+ * 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 com.google.common.base.Preconditions;
+import com.google.common.collect.HashBiMap;
+
+import java.util.UUID;
+
+/** Utility to map {@link Call} objects to unique IDs. IDs are generated when a call is added. */
+class CallIdMapper {
+    private final HashBiMap<String, Call> mCalls = HashBiMap.create();
+    private final String mCallIdPrefix;
+
+    CallIdMapper(String callIdPrefix) {
+        ThreadUtil.checkOnMainThread();
+        mCallIdPrefix = callIdPrefix + "@";
+    }
+
+    void addCall(Call call) {
+        ThreadUtil.checkOnMainThread();
+        Preconditions.checkNotNull(call);
+        String callId = mCallIdPrefix + UUID.randomUUID();
+        mCalls.put(callId, call);
+    }
+
+    void removeCall(Call call) {
+        ThreadUtil.checkOnMainThread();
+        Preconditions.checkNotNull(call);
+        mCalls.inverse().remove(call);
+    }
+
+    String getCallId(Call call) {
+        ThreadUtil.checkOnMainThread();
+        Preconditions.checkNotNull(call);
+        return mCalls.inverse().get(call);
+    }
+
+    Call getCall(Object objId) {
+        ThreadUtil.checkOnMainThread();
+
+        String callId = null;
+        if (objId instanceof String) {
+            callId = (String) objId;
+        }
+        Preconditions.checkArgument(isValidCallId(callId));
+
+        return mCalls.get(callId);
+    }
+
+    void checkValidCallId(String callId) {
+        // Note, no need for thread check, this method is thread safe.
+        if (!isValidCallId(callId)) {
+            Log.wtf(this, "%s is not a valid call ID", callId);
+            throw new IllegalArgumentException("Invalid call ID.");
+        }
+    }
+
+    boolean isValidCallId(String callId) {
+        // Note, no need for thread check, this method is thread safe.
+        return callId != null && callId.startsWith(mCallIdPrefix);
+    }
+}
diff --git a/src/com/android/telecomm/CallServiceAdapter.java b/src/com/android/telecomm/CallServiceAdapter.java
index f61bce9..4bd7743 100644
--- a/src/com/android/telecomm/CallServiceAdapter.java
+++ b/src/com/android/telecomm/CallServiceAdapter.java
@@ -29,11 +29,8 @@
 import java.util.Set;
 
 /**
- * Used by call services in order to update state and control calls while the call service is bound
- * to Telecomm. Each call service is given its own instance for the lifetime of the binding between
- * Telecomm and the call service.
- * TODO(santoscordon): Whenever we get any method invocations from the call service, we need to
- * check that the invocation is expected from that call service.
+ * Used by call services to communicate with Telecomm. Each call service is given its own instance
+ * of the adapter for the lifetmie of the binding.
  * TODO(santoscordon): Do we need Binder.clear/restoreCallingIdentity() in the service methods?
  */
 public final class CallServiceAdapter extends ICallServiceAdapter.Stub {
@@ -50,44 +47,46 @@
     private final class CallServiceAdapterHandler extends Handler {
         @Override
         public void handleMessage(Message msg) {
-            String callId;
-
+            Call call;
             switch (msg.what) {
                 case MSG_SET_IS_COMPATIBLE_WITH:
-                    callId = (String) msg.obj;
-                    if (mPendingOutgoingCallIds.contains(callId)) {
-                        mOutgoingCallsManager.setIsCompatibleWith(callId,
+                    call = mCallIdMapper.getCall(msg.obj);
+                    if (call != null && !call.isIncoming()) {
+                        mOutgoingCallsManager.setIsCompatibleWith(call,
                                 msg.arg1 == 1 ? true : false);
                     } else {
-                        Log.wtf(this, "Unknown outgoing call: %s", callId);
+                        Log.w(this, "Unknown call: %s, id: %s", call, msg.obj);
                     }
                     break;
                 case MSG_NOTIFY_INCOMING_CALL:
-                    CallInfo callInfo = (CallInfo) msg.obj;
-                    if (mPendingIncomingCallIds.remove(callInfo.getId())) {
-                        mIncomingCallsManager.handleSuccessfulIncomingCall(callInfo);
+                    CallInfo clientCallInfo = (CallInfo) msg.obj;
+                    call = mCallIdMapper.getCall(clientCallInfo.getId());
+                    if (call != null && mPendingCalls.remove(call) && call.isIncoming()) {
+                        CallInfo callInfo = new CallInfo(null, clientCallInfo.getState(),
+                                clientCallInfo.getHandle());
+                        mIncomingCallsManager.handleSuccessfulIncomingCall(call, callInfo);
                     } else {
-                        Log.wtf(this, "Unknown incoming call: %s", callInfo);
+                        Log.w(this, "Unknown incoming call: %s, id: %s", call, msg.obj);
                     }
                     break;
                 case MSG_HANDLE_SUCCESSFUL_OUTGOING_CALL:
-                    callId = (String) msg.obj;
-                    if (mPendingOutgoingCallIds.remove(callId)) {
-                        mOutgoingCallsManager.handleSuccessfulCallAttempt(callId);
+                    call = mCallIdMapper.getCall(msg.obj);
+                    if (call != null && mPendingCalls.remove(call) && !call.isIncoming()) {
+                        mOutgoingCallsManager.handleSuccessfulCallAttempt(call);
                     } else {
                         // TODO(gilad): Figure out how to wire up the callService.abort() call.
-                        Log.wtf(this, "Unknown outgoing call: %s", callId);
+                        Log.w(this, "Unknown outgoing call: %s, id: %s", call, msg.obj);
                     }
                     break;
                 case MSG_HANDLE_FAILED_OUTGOING_CALL: {
                     SomeArgs args = (SomeArgs) msg.obj;
                     try {
-                        callId = (String) args.arg1;
+                        call = mCallIdMapper.getCall(args.arg1);
                         String reason = (String) args.arg2;
-                        if (mPendingOutgoingCallIds.remove(callId)) {
-                            mOutgoingCallsManager.handleFailedCallAttempt(callId, reason);
+                        if (call != null && mPendingCalls.remove(call) && !call.isIncoming()) {
+                            mOutgoingCallsManager.handleFailedCallAttempt(call, reason);
                         } else {
-                            Log.wtf(this, "Unknown outgoing call: %s", callId);
+                            Log.w(this, "Unknown outgoing call: %s, id: %s", call, msg.obj);
                         }
                     } finally {
                         args.recycle();
@@ -95,33 +94,53 @@
                     break;
                 }
                 case MSG_SET_ACTIVE:
-                    callId = (String) msg.obj;
-                    mCallsManager.markCallAsActive(callId);
+                    call = mCallIdMapper.getCall(msg.obj);
+                    if (call != null) {
+                        mCallsManager.markCallAsActive(call);
+                    } else {
+                        Log.w(this, "Unknown call id: %s", msg.obj);
+                    }
                     break;
                 case MSG_SET_RINGING:
-                    callId = (String) msg.obj;
-                    mCallsManager.markCallAsRinging(callId);
+                    call = mCallIdMapper.getCall(msg.obj);
+                    if (call != null) {
+                        mCallsManager.markCallAsRinging(call);
+                    } else {
+                        Log.w(this, "Unknown call id: %s", msg.obj);
+                    }
                     break;
                 case MSG_SET_DIALING:
-                    callId = (String) msg.obj;
-                    mCallsManager.markCallAsDialing(callId);
+                    call = mCallIdMapper.getCall(msg.obj);
+                    if (call != null) {
+                        mCallsManager.markCallAsDialing(call);
+                    } else {
+                        Log.w(this, "Unknown call id: %s", msg.obj);
+                    }
                     break;
                 case MSG_SET_DISCONNECTED: {
                     SomeArgs args = (SomeArgs) msg.obj;
                     try {
-                        callId = (String) args.arg1;
+                        call = mCallIdMapper.getCall(args.arg1);
                         String disconnectMessage = (String) args.arg2;
                         int disconnectCause = args.argi1;
-                        mCallsManager.markCallAsDisconnected(callId, disconnectCause,
-                                disconnectMessage);
+                        if (call != null) {
+                            mCallsManager.markCallAsDisconnected(call, disconnectCause,
+                                    disconnectMessage);
+                        } else {
+                            Log.w(this, "Unknown call id: %s", msg.obj);
+                        }
                     } finally {
                         args.recycle();
                     }
                     break;
                 }
                 case MSG_SET_ON_HOLD:
-                    callId = (String) msg.obj;
-                    mCallsManager.markCallAsOnHold(callId);
+                    call = mCallIdMapper.getCall(msg.obj);
+                    if (call != null) {
+                        mCallsManager.markCallAsOnHold(call);
+                    } else {
+                        Log.w(this, "Unknown call id: %s", msg.obj);
+                    }
                     break;
             }
         }
@@ -131,19 +150,8 @@
     private final OutgoingCallsManager mOutgoingCallsManager;
     private final IncomingCallsManager mIncomingCallsManager;
     private final Handler mHandler = new CallServiceAdapterHandler();
-
-    /**
-     * The set of pending outgoing call IDs.  Any {@link #handleSuccessfulOutgoingCall} and
-     * {@link #handleFailedOutgoingCall} invocations with a call ID that is not in this set
-     * are ignored.
-     */
-    private final Set<String> mPendingOutgoingCallIds = Sets.newHashSet();
-
-    /**
-     * The set of pending incoming call IDs.  Any {@link #handleIncomingCall} invocations with
-     * a call ID not in this set are ignored.
-     */
-    private final Set<String> mPendingIncomingCallIds = Sets.newHashSet();
+    private final CallIdMapper mCallIdMapper;
+    private final Set<Call> mPendingCalls = Sets.newHashSet();
 
     /**
      * Persists the specified parameters.
@@ -152,17 +160,21 @@
      * @param incomingCallsManager Manages the incoming call initialization flow.
      */
     CallServiceAdapter(
-            OutgoingCallsManager outgoingCallsManager, IncomingCallsManager incomingCallsManager) {
+            OutgoingCallsManager outgoingCallsManager,
+            IncomingCallsManager incomingCallsManager,
+            CallIdMapper callIdMapper) {
         ThreadUtil.checkOnMainThread();
         mCallsManager = CallsManager.getInstance();
         mOutgoingCallsManager = outgoingCallsManager;
         mIncomingCallsManager = incomingCallsManager;
+        mCallIdMapper = callIdMapper;
     }
 
     /** {@inheritDoc} */
     @Override
     public void setIsCompatibleWith(String callId, boolean isCompatible) {
-        checkValidCallId(callId);
+        Log.v(this, "setIsCompatibleWith id: %d, isCompatible: %b", callId, isCompatible);
+        mCallIdMapper.checkValidCallId(callId);
         mHandler.obtainMessage(MSG_SET_IS_COMPATIBLE_WITH, isCompatible ? 1 : 0, 0, callId).
                 sendToTarget();
     }
@@ -170,21 +182,21 @@
     /** {@inheritDoc} */
     @Override
     public void notifyIncomingCall(CallInfo callInfo) {
-        checkValidCallId(callInfo.getId());
+        mCallIdMapper.checkValidCallId(callInfo.getId());
         mHandler.obtainMessage(MSG_NOTIFY_INCOMING_CALL, callInfo).sendToTarget();
     }
 
     /** {@inheritDoc} */
     @Override
     public void handleSuccessfulOutgoingCall(String callId) {
-        checkValidCallId(callId);
+        mCallIdMapper.checkValidCallId(callId);
         mHandler.obtainMessage(MSG_HANDLE_SUCCESSFUL_OUTGOING_CALL, callId).sendToTarget();
     }
 
     /** {@inheritDoc} */
     @Override
     public void handleFailedOutgoingCall(String callId, String reason) {
-        checkValidCallId(callId);
+        mCallIdMapper.checkValidCallId(callId);
         SomeArgs args = SomeArgs.obtain();
         args.arg1 = callId;
         args.arg2 = reason;
@@ -194,31 +206,29 @@
     /** {@inheritDoc} */
     @Override
     public void setActive(String callId) {
-        checkValidCallId(callId);
+        mCallIdMapper.checkValidCallId(callId);
         mHandler.obtainMessage(MSG_SET_ACTIVE, callId).sendToTarget();
     }
 
     /** {@inheritDoc} */
     @Override
     public void setRinging(String callId) {
-        checkValidCallId(callId);
+        mCallIdMapper.checkValidCallId(callId);
         mHandler.obtainMessage(MSG_SET_RINGING, callId).sendToTarget();
     }
 
     /** {@inheritDoc} */
     @Override
     public void setDialing(String callId) {
-        checkValidCallId(callId);
+        mCallIdMapper.checkValidCallId(callId);
         mHandler.obtainMessage(MSG_SET_DIALING, callId).sendToTarget();
     }
 
     /** {@inheritDoc} */
-    // TODO(gilad): Ensure that any communication from the underlying ICallService
-    // implementation is expected (or otherwise suppressed at the adapter level).
     @Override
     public void setDisconnected(
             String callId, int disconnectCause, String disconnectMessage) {
-        checkValidCallId(callId);
+        mCallIdMapper.checkValidCallId(callId);
         SomeArgs args = SomeArgs.obtain();
         args.arg1 = callId;
         args.arg2 = disconnectMessage;
@@ -229,86 +239,47 @@
     /** {@inheritDoc} */
     @Override
     public void setOnHold(String callId) {
-        checkValidCallId(callId);
+        mCallIdMapper.checkValidCallId(callId);
         mHandler.obtainMessage(MSG_SET_ON_HOLD, callId).sendToTarget();
     }
 
     /**
-     * Adds the specified call ID to the list of pending outgoing call IDs.
-     * TODO(gilad): Consider passing the call processor (instead of the ID) both here and in the
-     * remove case (same for incoming) such that the detour via the *CallsManager can be avoided.
-     *
-     * @param callId The ID of the call.
+     * Adds the specified call to the list of pending calls. Only calls in this list which are
+     * outgoing will be handled by {@link #isCompatibleWith}, {@link handleSuccessfulOutgoingCall},
+     * and {@link handleFailedOutgoingCall}. Similarly, only calls in this list which are incoming
+     * will be handled by {@link notifyIncomingCall}.
      */
-    void addPendingOutgoingCallId(String callId) {
-        mPendingOutgoingCallIds.add(callId);
+    void addPendingCall(Call call) {
+        mPendingCalls.add(call);
     }
 
     /**
-     * Removes the specified call ID from the list of pending outgoing call IDs.
-     *
-     * @param callId The ID of the call.
+     * Removes the specified call from the list of pending calls.
      */
-    void removePendingOutgoingCallId(String callId) {
-        mPendingOutgoingCallIds.remove(callId);
-    }
-
-    /**
-     * Adds a call ID to the list of pending incoming call IDs. Only calls with call IDs in the
-     * list will be handled by {@link #handleIncomingCall}.
-     *
-     * @param callId The ID of the call.
-     */
-    void addPendingIncomingCallId(String callId) {
-        mPendingIncomingCallIds.add(callId);
-    }
-
-    /**
-     * Removes the specified call ID from the list of pending incoming call IDs.
-     *
-     * @param callId The ID of the call.
-     */
-    void removePendingIncomingCallId(String callId) {
-        mPendingIncomingCallIds.remove(callId);
+    void removePendingCall(Call call) {
+        mPendingCalls.remove(call);
     }
 
     /**
      * Called when the associated call service dies.
      */
     void handleCallServiceDeath() {
-        if (!mPendingIncomingCallIds.isEmpty()) {
-            // Here and in the for loop below, we need to iterate through a copy because the code
-            // inside the loop will modify the original list.
-            for (String callId : ImmutableList.copyOf(mPendingIncomingCallIds)) {
-                mIncomingCallsManager.handleFailedIncomingCall(callId);
+        if (!mPendingCalls.isEmpty()) {
+            // Iterate through a copy because the code inside the loop will modify the original
+            // list.
+            for (Call call : ImmutableList.copyOf(mPendingCalls)) {
+                if (call.isIncoming()) {
+                    mIncomingCallsManager.handleFailedIncomingCall(call);
+                } else {
+                    mOutgoingCallsManager.handleFailedCallAttempt(call,
+                            "Call service disconnected.");
+                }
             }
 
-            if (!mPendingIncomingCallIds.isEmpty()) {
-                Log.wtf(this, "Pending incoming calls did not get cleared.");
-                mPendingIncomingCallIds.clear();
+            if (!mPendingCalls.isEmpty()) {
+                Log.wtf(this, "Pending calls did not get cleared.");
+                mPendingCalls.clear();
             }
         }
-
-        if (!mPendingOutgoingCallIds.isEmpty()) {
-            for (String callId : ImmutableList.copyOf(mPendingOutgoingCallIds)) {
-                mOutgoingCallsManager.handleFailedCallAttempt(callId, "Call service disconnected.");
-            }
-
-            if (!mPendingOutgoingCallIds.isEmpty()) {
-                Log.wtf(this, "Pending outgoing calls did not get cleared.");
-                mPendingOutgoingCallIds.clear();
-            }
-        }
-    }
-
-    /**
-     * Throws an IllegalArgumentException if the specified call ID is invalid.
-     *
-     * @param callId The call ID to check.
-     */
-    private void checkValidCallId(String callId) {
-        if (Strings.isNullOrEmpty(callId)) {
-            throw new IllegalArgumentException("Invalid call ID.");
-        }
     }
 }
diff --git a/src/com/android/telecomm/CallServiceRepository.java b/src/com/android/telecomm/CallServiceRepository.java
index 40a1e7b..7247259 100644
--- a/src/com/android/telecomm/CallServiceRepository.java
+++ b/src/com/android/telecomm/CallServiceRepository.java
@@ -281,9 +281,8 @@
 
         CallServiceWrapper callService = mCallServices.get(callServiceName);
         if (callService == null) {
-            CallServiceAdapter adapter =
-                    new CallServiceAdapter(mOutgoingCallsManager, mIncomingCallsManager);
-            mCallServices.put(callServiceName, new CallServiceWrapper(descriptor, adapter));
+            mCallServices.put(callServiceName, new CallServiceWrapper(descriptor,
+                    mOutgoingCallsManager, mIncomingCallsManager));
         }
     }
 
diff --git a/src/com/android/telecomm/CallServiceSelectorWrapper.java b/src/com/android/telecomm/CallServiceSelectorWrapper.java
index a535798..6db5c8f 100644
--- a/src/com/android/telecomm/CallServiceSelectorWrapper.java
+++ b/src/com/android/telecomm/CallServiceSelectorWrapper.java
@@ -22,8 +22,9 @@
 import android.os.RemoteException;
 import android.telecomm.CallInfo;
 import android.telecomm.CallServiceDescriptor;
-import android.telecomm.TelecommConstants;
+import android.telecomm.CallServiceSelector;
 import android.telecomm.CallServiceSelector.CallServiceSelectionResponse;
+import android.telecomm.TelecommConstants;
 
 import com.google.common.base.Preconditions;
 import com.android.internal.telecomm.ICallServiceSelectionResponse;
@@ -56,10 +57,9 @@
     }
 
     private ICallServiceSelector mSelectorInterface;
-
-    private Binder mBinder = new Binder();
-
-    private Handler mHandler = new Handler();
+    private final Binder mBinder = new Binder();
+    private final Handler mHandler = new Handler();
+    private final CallIdMapper mCallIdMapper = new CallIdMapper("CallServiceSelector");
 
     /**
      * Creates a call-service selector for the specified component.
@@ -74,22 +74,22 @@
      * Retrieves the sorted set of call services that are preferred by this selector. Upon failure,
      * the error callback is invoked. Can be invoked even when the call service is unbound.
      *
-     * @param callInfo The details of the call.
      * @param selectionResponse The selection response callback to invoke upon success.
      * @param errorCallback The callback to invoke upon failure.
      */
-    void select(final CallInfo callInfo, final List<CallServiceDescriptor> callServiceDescriptors,
+    void select(final Call call, final List<CallServiceDescriptor> callServiceDescriptors,
             final CallServiceSelectionResponse selectionResponse, final Runnable errorCallback) {
         BindCallback callback = new BindCallback() {
             @Override
             public void onSuccess() {
                 if (isServiceValid("select")) {
                     try {
+                        CallInfo callInfo = call.toCallInfo(mCallIdMapper.getCallId(call));
                         mSelectorInterface.select(callInfo, callServiceDescriptors,
                                 new SelectionResponseImpl(selectionResponse));
                     } catch (RemoteException e) {
-                        Log.e(CallServiceSelectorWrapper.this, e, "Failed calling select for selector: %s.",
-                                getComponentName());
+                        Log.e(CallServiceSelectorWrapper.this, e,
+                                "Failed calling select for selector: %s.", getComponentName());
                     }
                 }
             }
@@ -103,6 +103,14 @@
         mBinder.bind(callback);
     }
 
+    void addCall(Call call) {
+        mCallIdMapper.addCall(call);
+    }
+
+    void removeCall(Call call) {
+        mCallIdMapper.removeCall(call);
+    }
+
     /** {@inheritDoc} */
     @Override
     protected void setServiceInterface(IBinder binder) {
diff --git a/src/com/android/telecomm/CallServiceWrapper.java b/src/com/android/telecomm/CallServiceWrapper.java
index 4475b2d..007d35a 100644
--- a/src/com/android/telecomm/CallServiceWrapper.java
+++ b/src/com/android/telecomm/CallServiceWrapper.java
@@ -48,18 +48,25 @@
     private ICallService mServiceInterface;
 
     private Binder mBinder = new Binder();
+    private final CallIdMapper mCallIdMapper;
 
     /**
-     * Creates a call-service provider for the specified descriptor.
+     * Creates a call-service for the specified descriptor.
      *
      * @param descriptor The call-service descriptor from
      *         {@link ICallServiceProvider#lookupCallServices}.
-     * @param adapter The call-service adapter.
+     * @param outgoingCallsManager Manages the placing of outgoing calls.
+     * @param incomingCallsManager Manages the incoming call initialization flow.
      */
-    CallServiceWrapper(CallServiceDescriptor descriptor, CallServiceAdapter adapter) {
+    CallServiceWrapper(
+            CallServiceDescriptor descriptor,
+            OutgoingCallsManager outgoingCallsManager,
+            IncomingCallsManager incomingCallsManager) {
         super(TelecommConstants.ACTION_CALL_SERVICE, descriptor.getServiceComponent());
         mDescriptor = descriptor;
-        mAdapter = adapter;
+        mCallIdMapper = new CallIdMapper("CallService");
+        mAdapter = new CallServiceAdapter(outgoingCallsManager, incomingCallsManager,
+                mCallIdMapper);
     }
 
     CallServiceDescriptor getDescriptor() {
@@ -67,12 +74,11 @@
     }
 
     /** See {@link ICallService#setCallServiceAdapter}. */
-    void setCallServiceAdapter(ICallServiceAdapter callServiceAdapter) {
+    private void setCallServiceAdapter(ICallServiceAdapter callServiceAdapter) {
         if (isServiceValid("setCallServiceAdapter")) {
             try {
                 mServiceInterface.setCallServiceAdapter(callServiceAdapter);
             } catch (RemoteException e) {
-                Log.e(this, e, "Failed to setCallServiceAdapter.");
             }
         }
     }
@@ -82,20 +88,18 @@
      * see {@link ICallService#isCompatibleWith}.  Upon failure, the specified error callback is
      * invoked. Can be invoked even when the call service is unbound.
      *
-     * @param callInfo The details of the call.
      * @param errorCallback The callback to invoke upon failure.
      */
-    void isCompatibleWith(final CallInfo callInfo, final Runnable errorCallback) {
-        Log.d(this, "isCompatibleWith(%s) via %s.", callInfo, getComponentName());
+    void isCompatibleWith(final Call call, final Runnable errorCallback) {
+        Log.d(this, "isCompatibleWith(%s) via %s.", call, getComponentName());
         BindCallback callback = new BindCallback() {
             @Override public void onSuccess() {
                 if (isServiceValid("isCompatibleWith")) {
                     try {
-                        mAdapter.addPendingOutgoingCallId(callInfo.getId());
+                        mAdapter.addPendingCall(call);
+                        CallInfo callInfo = call.toCallInfo(mCallIdMapper.getCallId(call));
                         mServiceInterface.isCompatibleWith(callInfo);
                     } catch (RemoteException e) {
-                        mAdapter.removePendingOutgoingCallId(callInfo.getId());
-                        Log.e(CallServiceWrapper.this, e, "Failed checking isCompatibleWith.");
                     }
                 }
             }
@@ -110,72 +114,56 @@
     /**
      * Attempts to place the specified call, see {@link ICallService#call}.  Upon failure, the
      * specified error callback is invoked. Can be invoked even when the call service is unbound.
-     *
-     * @param callInfo The details of the call.
-     * @param errorCallback The callback to invoke upon failure.
      */
-    void call(final CallInfo callInfo, final Runnable errorCallback) {
-        Log.d(this, "call(%s) via %s.", callInfo, getComponentName());
-        BindCallback callback = new BindCallback() {
-            @Override public void onSuccess() {
-                String callId = callInfo.getId();
-                if (isServiceValid("call")) {
-                    try {
-                        mServiceInterface.call(callInfo);
-                    } catch (RemoteException e) {
-                        Log.e(CallServiceWrapper.this, e, "Failed to place call %s", callId);
-                    }
-                }
+    void call(Call call) {
+        Log.d(this, "call(%s) via %s.", call, getComponentName());
+        if (isServiceValid("call")) {
+            try {
+                CallInfo callInfo = call.toCallInfo(mCallIdMapper.getCallId(call));
+                mServiceInterface.call(callInfo);
+            } catch (RemoteException e) {
             }
-            @Override public void onFailure() {
-                errorCallback.run();
-            }
-        };
-
-         mBinder.bind(callback);
+        }
     }
 
     /** @see CallService#abort(String) */
-    void abort(String callId) {
-        mAdapter.removePendingOutgoingCallId(callId);
+    void abort(Call call) {
+        mAdapter.removePendingCall(call);
         if (isServiceValid("abort")) {
             try {
-                mServiceInterface.abort(callId);
+                mServiceInterface.abort(mCallIdMapper.getCallId(call));
             } catch (RemoteException e) {
-                Log.e(this, e, "Failed to abort call %s", callId);
             }
         }
     }
 
     /** @see CallService#hold(String) */
-    void hold(String callId) {
+    void hold(Call call) {
         if (isServiceValid("hold")) {
             try {
-                mServiceInterface.hold(callId);
+                mServiceInterface.hold(mCallIdMapper.getCallId(call));
             } catch (RemoteException e) {
-                Log.e(this, e, "Failed to put on hold for call %s", callId);
             }
         }
     }
 
     /** @see CallService#unhold(String) */
-    void unhold(String callId) {
+    void unhold(Call call) {
         if (isServiceValid("unhold")) {
             try {
-                mServiceInterface.unhold(callId);
+                mServiceInterface.unhold(mCallIdMapper.getCallId(call));
             } catch (RemoteException e) {
-                Log.e(this, e, "Failed to remove from hold for call %s", callId);
             }
         }
     }
 
     /** @see CallService#onAudioStateChanged(String,CallAudioState) */
-    void onAudioStateChanged(String activeCallId, CallAudioState audioState) {
+    void onAudioStateChanged(Call activeCall, CallAudioState audioState) {
         if (isServiceValid("onAudioStateChanged")) {
             try {
-                mServiceInterface.onAudioStateChanged(activeCallId, audioState);
+                mServiceInterface.onAudioStateChanged(mCallIdMapper.getCallId(activeCall),
+                        audioState);
             } catch (RemoteException e) {
-                Log.e(this, e, "Failed to update audio state for call %s", activeCallId);
             }
         }
     }
@@ -186,26 +174,20 @@
      * is invoked. Can be invoked even when the call service is unbound.
      * See {@link ICallService#setIncomingCallId}.
      *
-     * @param callId The call ID used for the incoming call.
+     * @param call The call used for the incoming call.
      * @param extras The {@link CallService}-provided extras which need to be sent back.
      * @param errorCallback The callback to invoke upon failure.
      */
-    void setIncomingCallId(
-            final String callId,
-            final Bundle extras,
-            final Runnable errorCallback) {
-
-        Log.d(this, "setIncomingCall(%s) via %s.", callId, getComponentName());
+    void setIncomingCallId(final Call call, final Bundle extras, final Runnable errorCallback) {
+        Log.d(this, "setIncomingCall(%s) via %s.", call, getComponentName());
         BindCallback callback = new BindCallback() {
             @Override public void onSuccess() {
                 if (isServiceValid("setIncomingCallId")) {
-                    mAdapter.addPendingIncomingCallId(callId);
+                    mAdapter.addPendingCall(call);
                     try {
-                        mServiceInterface.setIncomingCallId(callId, extras);
+                        mServiceInterface.setIncomingCallId(mCallIdMapper.getCallId(call),
+                                extras);
                     } catch (RemoteException e) {
-                        Log.e(CallServiceWrapper.this, e,
-                                "Failed to setIncomingCallId for call %s", callId);
-                        mAdapter.removePendingIncomingCallId(callId);
                     }
                 }
             }
@@ -218,78 +200,62 @@
     }
 
     /** @see CallService#disconnect(String) */
-    void disconnect(String callId) {
+    void disconnect(Call call) {
         if (isServiceValid("disconnect")) {
             try {
-                mServiceInterface.disconnect(callId);
+                mServiceInterface.disconnect(mCallIdMapper.getCallId(call));
             } catch (RemoteException e) {
-                Log.e(this, e, "Failed to disconnect call %s", callId);
             }
         }
     }
 
     /** @see CallService#answer(String) */
-    void answer(String callId) {
+    void answer(Call call) {
         if (isServiceValid("answer")) {
             try {
-                mServiceInterface.answer(callId);
+                mServiceInterface.answer(mCallIdMapper.getCallId(call));
             } catch (RemoteException e) {
-                Log.e(this, e, "Failed to answer call %s", callId);
             }
         }
     }
 
     /** @see CallService#reject(String) */
-    void reject(String callId) {
+    void reject(Call call) {
         if (isServiceValid("reject")) {
             try {
-                mServiceInterface.reject(callId);
+                mServiceInterface.reject(mCallIdMapper.getCallId(call));
             } catch (RemoteException e) {
-                Log.e(this, e, "Failed to reject call %s", callId);
             }
         }
     }
 
     /** @see CallService#playDtmfTone(String,char) */
-    void playDtmfTone(String callId, char digit) {
+    void playDtmfTone(Call call, char digit) {
         if (isServiceValid("playDtmfTone")) {
             try {
-                mServiceInterface.playDtmfTone(callId, digit);
+                mServiceInterface.playDtmfTone(mCallIdMapper.getCallId(call), digit);
             } catch (RemoteException e) {
-                Log.e(this, e, "Failed to play DTMF tone for call %s", callId);
             }
         }
     }
 
     /** @see CallService#stopDtmfTone(String) */
-    void stopDtmfTone(String callId) {
+    void stopDtmfTone(Call call) {
         if (isServiceValid("stopDtmfTone")) {
             try {
-                mServiceInterface.stopDtmfTone(callId);
+                mServiceInterface.stopDtmfTone(mCallIdMapper.getCallId(call));
             } catch (RemoteException e) {
-                Log.e(this, e, "Failed to stop DTMF tone for call %s", callId);
             }
         }
     }
 
-    /**
-     * Cancels the incoming call for the specified call ID.
-     * TODO(santoscordon): This method should be called by IncomingCallsManager when the incoming
-     * call has failed.
-     *
-     * @param callId The ID of the call.
-     */
-    void cancelIncomingCall(String callId) {
-        mAdapter.removePendingIncomingCallId(callId);
+    void addCall(Call call) {
+        mCallIdMapper.addCall(call);
     }
 
-    /**
-     * Cancels the outgoing call for the specified call ID.
-     *
-     * @param callId The ID of the call.
-     */
-    void cancelOutgoingCall(String callId) {
-        mAdapter.removePendingOutgoingCallId(callId);
+    void removeCall(Call call) {
+        mAdapter.removePendingCall(call);
+        mCallIdMapper.removeCall(call);
     }
 
     /** {@inheritDoc} */
diff --git a/src/com/android/telecomm/CallsManager.java b/src/com/android/telecomm/CallsManager.java
index 3821426..0387fab 100644
--- a/src/com/android/telecomm/CallsManager.java
+++ b/src/com/android/telecomm/CallsManager.java
@@ -62,12 +62,10 @@
     private final Switchboard mSwitchboard;
 
     /**
-     * The main call repository. Keeps an instance of all live calls keyed by call ID. New incoming
-     * and outgoing calls are added to the map and removed when the calls move to the disconnected
-     * state.
-     * TODO(santoscordon): Add new CallId class and use it in place of String.
+     * The main call repository. Keeps an instance of all live calls. New incoming and outgoing
+     * calls are added to the map and removed when the calls move to the disconnected state.
      */
-    private final Map<String, Call> mCalls = Maps.newHashMap();
+    private final Set<Call> mCalls = Sets.newLinkedHashSet();
 
     /**
      * The call the user is currently interacting with. This is the call that should have audio
@@ -104,7 +102,7 @@
     }
 
     ImmutableCollection<Call> getCalls() {
-        return ImmutableList.copyOf(mCalls.values());
+        return ImmutableList.copyOf(mCalls);
     }
 
     Call getForegroundCall() {
@@ -112,7 +110,7 @@
     }
 
     boolean hasEmergencyCall() {
-        for (Call call : mCalls.values()) {
+        for (Call call : mCalls) {
             if (call.isEmergencyCall()) {
                 return true;
             }
@@ -135,8 +133,8 @@
     void processIncomingCallIntent(CallServiceDescriptor descriptor, Bundle extras) {
         Log.d(this, "processIncomingCallIntent");
         // 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.
+        // additional information from the call service, but for now we just need one to pass
+        // around.
         Call call = new Call(true /* isIncoming */);
 
         mSwitchboard.retrieveIncomingCall(call, descriptor, extras);
@@ -238,7 +236,7 @@
             removeCall(call);
         } else {
             // TODO: Replace disconnect cause with more specific disconnect causes.
-            markCallAsDisconnected(call.getId(), DisconnectCause.ERROR_UNSPECIFIED, null);
+            markCallAsDisconnected(call, DisconnectCause.ERROR_UNSPECIFIED, null);
         }
     }
 
@@ -246,22 +244,17 @@
      * Instructs Telecomm to answer the specified call. Intended to be invoked by the in-call
      * app through {@link InCallAdapter} after Telecomm notifies it of an incoming call followed by
      * the user opting to answer said call.
-     *
-     * @param callId The ID of the call.
      */
-    void answerCall(String callId) {
-        Call call = mCalls.get(callId);
-        if (call == null) {
-            Log.i(this, "Request to answer a non-existent call %s", callId);
+    void answerCall(Call call) {
+        if (!mCalls.contains(call)) {
+            Log.i(this, "Request to answer a non-existent call %s", call);
         } else {
             for (CallsManagerListener listener : mListeners) {
                 listener.onIncomingCallAnswered(call);
             }
 
             // We do not update the UI until we get confirmation of the answer() through
-            // {@link #markCallAsActive}. However, if we ever change that to look more responsive,
-            // then we need to make sure we add a timeout for the answer() in case the call never
-            // comes out of RINGING.
+            // {@link #markCallAsActive}.
             call.answer();
         }
     }
@@ -270,18 +263,14 @@
      * Instructs Telecomm to reject the specified call. Intended to be invoked by the in-call
      * app through {@link InCallAdapter} after Telecomm notifies it of an incoming call followed by
      * the user opting to reject said call.
-     *
-     * @param callId The ID of the call.
      */
-    void rejectCall(String callId) {
-        Call call = mCalls.get(callId);
-        if (call == null) {
-            Log.i(this, "Request to reject a non-existent call %s", callId);
+    void rejectCall(Call call) {
+        if (!mCalls.contains(call)) {
+            Log.i(this, "Request to reject a non-existent call %s", call);
         } else {
             for (CallsManagerListener listener : mListeners) {
                 listener.onIncomingCallRejected(call);
             }
-
             call.reject();
         }
     }
@@ -289,13 +278,11 @@
     /**
      * Instructs Telecomm to play the specified DTMF tone within the specified call.
      *
-     * @param callId The ID of the call.
      * @param digit The DTMF digit to play.
      */
-    void playDtmfTone(String callId, char digit) {
-        Call call = mCalls.get(callId);
-        if (call == null) {
-            Log.i(this, "Request to play DTMF in a non-existent call %s", callId);
+    void playDtmfTone(Call call, char digit) {
+        if (!mCalls.contains(call)) {
+            Log.i(this, "Request to play DTMF in a non-existent call %s", call);
         } else {
             call.playDtmfTone(digit);
         }
@@ -303,13 +290,10 @@
 
     /**
      * Instructs Telecomm to stop the currently playing DTMF tone, if any.
-     *
-     * @param callId The ID of the call.
      */
-    void stopDtmfTone(String callId) {
-        Call call = mCalls.get(callId);
-        if (call == null) {
-            Log.i(this, "Request to stop DTMF in a non-existent call %s", callId);
+    void stopDtmfTone(Call call) {
+        if (!mCalls.contains(call)) {
+            Log.i(this, "Request to stop DTMF in a non-existent call %s", call);
         } else {
             call.stopDtmfTone();
         }
@@ -317,13 +301,10 @@
 
     /**
      * Instructs Telecomm to continue the current post-dial DTMF string, if any.
-     *
-     * @param callId The ID of the call.
      */
-    void postDialContinue(String callId) {
-        Call call = mCalls.get(callId);
-        if (call == null) {
-            Log.i(this, "Request to continue post-dial string in a non-existent call %s", callId);
+    void postDialContinue(Call call) {
+        if (!mCalls.contains(call)) {
+            Log.i(this, "Request to continue post-dial string in a non-existent call %s", call);
         } else {
             // TODO(ihab): Implement this from this level on downwards
             // call.postDialContinue();
@@ -335,13 +316,10 @@
      * Instructs Telecomm to disconnect the specified call. Intended to be invoked by the
      * in-call app through {@link InCallAdapter} for an ongoing call. This is usually triggered by
      * the user hitting the end-call button.
-     *
-     * @param callId The ID of the call.
      */
-    void disconnectCall(String callId) {
-        Call call = mCalls.get(callId);
-        if (call == null) {
-            Log.w(this, "Unknown call (%s) asked to disconnect", callId);
+    void disconnectCall(Call call) {
+        if (!mCalls.contains(call)) {
+            Log.w(this, "Unknown call (%s) asked to disconnect", call);
         } else {
             call.disconnect();
         }
@@ -351,15 +329,12 @@
      * Instructs Telecomm to put the specified call on hold. Intended to be invoked by the
      * in-call app through {@link InCallAdapter} for an ongoing call. This is usually triggered by
      * the user hitting the hold button during an active call.
-     *
-     * @param callId The ID of the call.
      */
-    void holdCall(String callId) {
-        Call call = mCalls.get(callId);
-        if (call == null) {
-            Log.w(this, "Unknown call (%s) asked to be put on hold", callId);
+    void holdCall(Call call) {
+        if (!mCalls.contains(call)) {
+            Log.w(this, "Unknown call (%s) asked to be put on hold", call);
         } else {
-            Log.d(this, "Putting call on hold: (%s)", callId);
+            Log.d(this, "Putting call on hold: (%s)", call);
             call.hold();
         }
     }
@@ -368,15 +343,12 @@
      * Instructs Telecomm to release the specified call from hold. Intended to be invoked by
      * the in-call app through {@link InCallAdapter} for an ongoing call. This is usually triggered
      * by the user hitting the hold button during a held call.
-     *
-     * @param callId The ID of the call
      */
-    void unholdCall(String callId) {
-        Call call = mCalls.get(callId);
-        if (call == null) {
-            Log.w(this, "Unknown call (%s) asked to be removed from hold", callId);
+    void unholdCall(Call call) {
+        if (!mCalls.contains(call)) {
+            Log.w(this, "Unknown call (%s) asked to be removed from hold", call);
         } else {
-            Log.d(this, "Removing call from hold: (%s)", callId);
+            Log.d(this, "Removing call from hold: (%s)", call);
             call.unhold();
         }
     }
@@ -402,37 +374,33 @@
         }
     }
 
-    void markCallAsRinging(String callId) {
-        setCallState(callId, CallState.RINGING);
+    void markCallAsRinging(Call call) {
+        setCallState(call, CallState.RINGING);
     }
 
-    void markCallAsDialing(String callId) {
-        setCallState(callId, CallState.DIALING);
+    void markCallAsDialing(Call call) {
+        setCallState(call, CallState.DIALING);
     }
 
-    void markCallAsActive(String callId) {
-        setCallState(callId, CallState.ACTIVE);
+    void markCallAsActive(Call call) {
+        setCallState(call, CallState.ACTIVE);
     }
 
-    void markCallAsOnHold(String callId) {
-        setCallState(callId, CallState.ON_HOLD);
+    void markCallAsOnHold(Call call) {
+        setCallState(call, CallState.ON_HOLD);
     }
 
     /**
      * 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.
      * @param disconnectCause The disconnect reason, see {@link android.telephony.DisconnectCause}.
      * @param disconnectMessage Optional call-service-provided message about the disconnect.
      */
-    void markCallAsDisconnected(String callId, int disconnectCause, String disconnectMessage) {
-        Call call = mCalls.get(callId);
-        if (call != null) {
-            call.setDisconnectCause(disconnectCause, disconnectMessage);
-            setCallState(callId, CallState.DISCONNECTED);
-            removeCall(call);
-        }
+    void markCallAsDisconnected(Call call, int disconnectCause, String disconnectMessage) {
+        call.setDisconnectCause(disconnectCause, disconnectMessage);
+        setCallState(call, CallState.DISCONNECTED);
+        removeCall(call);
     }
 
     /**
@@ -443,9 +411,9 @@
      */
     void handleCallServiceDeath(CallServiceWrapper callService) {
         Preconditions.checkNotNull(callService);
-        for (Call call : ImmutableList.copyOf(mCalls.values())) {
+        for (Call call : ImmutableList.copyOf(mCalls)) {
             if (call.getCallService() == callService) {
-                markCallAsDisconnected(call.getId(), DisconnectCause.ERROR_UNSPECIFIED, null);
+                markCallAsDisconnected(call, DisconnectCause.ERROR_UNSPECIFIED, null);
             }
         }
     }
@@ -456,7 +424,7 @@
      * @param call The call to add.
      */
     private void addCall(Call call) {
-        mCalls.put(call.getId(), call);
+        mCalls.add(call);
         for (CallsManagerListener listener : mListeners) {
             listener.onCallAdded(call);
         }
@@ -464,30 +432,21 @@
     }
 
     private void removeCall(Call call) {
-        mCalls.remove(call.getId());
         call.clearCallService();
-        for (CallsManagerListener listener : mListeners) {
-            listener.onCallRemoved(call);
+        call.clearCallServiceSelector();
+
+        boolean shouldNotify = false;
+        if (mCalls.contains(call)) {
+            mCalls.remove(call);
+            shouldNotify = true;
         }
-        updateForegroundCall();
-    }
 
-    /**
-     * Sets the specified state on the specified call.
-     *
-     * @param callId The ID of the call to update.
-     * @param newState The new state of the call.
-     */
-    private void setCallState(String callId, CallState newState) {
-        Preconditions.checkState(!Strings.isNullOrEmpty(callId));
-        Preconditions.checkNotNull(newState);
-
-        Call call = mCalls.get(callId);
-        if (call == null) {
-            Log.w(this, "Call %s was not found while attempting to update the state to %s.",
-                    callId, newState );
-        } else {
-            setCallState(call, newState);
+        // Only broadcast changes for calls that are being tracked.
+        if (shouldNotify) {
+            for (CallsManagerListener listener : mListeners) {
+                listener.onCallRemoved(call);
+            }
+            updateForegroundCall();
         }
     }
 
@@ -498,6 +457,7 @@
      * @param newState The new state of the call.
      */
     private void setCallState(Call call, CallState newState) {
+        Preconditions.checkNotNull(newState);
         CallState oldState = call.getState();
         if (newState != oldState) {
             // Unfortunately, in the telephony world the radio is king. So if the call notifies
@@ -510,7 +470,7 @@
             call.setState(newState);
 
             // Only broadcast state change for calls that are being tracked.
-            if (mCalls.containsKey(call.getId())) {
+            if (mCalls.contains(call)) {
                 for (CallsManagerListener listener : mListeners) {
                     listener.onCallStateChanged(call, oldState, newState);
                 }
@@ -524,7 +484,7 @@
      */
     private void updateForegroundCall() {
         Call newForegroundCall = null;
-        for (Call call : mCalls.values()) {
+        for (Call call : mCalls) {
             // Incoming ringing calls have priority.
             if (call.getState() == CallState.RINGING) {
                 newForegroundCall = call;
diff --git a/src/com/android/telecomm/InCallAdapter.java b/src/com/android/telecomm/InCallAdapter.java
index c0dbb7b..14669c6 100644
--- a/src/com/android/telecomm/InCallAdapter.java
+++ b/src/com/android/telecomm/InCallAdapter.java
@@ -41,30 +41,39 @@
     private final class InCallAdapterHandler extends Handler {
         @Override
         public void handleMessage(Message msg) {
+            Call call = null;
+            if (msg.obj != null) {
+                call = mCallIdMapper.getCall(msg.obj);
+                if (call == null) {
+                    Log.w(this, "Unknown call id: %s, msg: %d", msg.obj, msg.what);
+                    return;
+                }
+            }
+
             switch (msg.what) {
                 case MSG_ANSWER_CALL:
-                    mCallsManager.answerCall((String) msg.obj);
+                    mCallsManager.answerCall(call);
                     break;
                 case MSG_REJECT_CALL:
-                    mCallsManager.rejectCall((String) msg.obj);
+                    mCallsManager.rejectCall(call);
                     break;
                 case MSG_PLAY_DTMF_TONE:
-                    mCallsManager.playDtmfTone((String) msg.obj, (char) msg.arg1);
+                    mCallsManager.playDtmfTone(call, (char) msg.arg1);
                     break;
                 case MSG_STOP_DTMF_TONE:
-                    mCallsManager.stopDtmfTone((String) msg.obj);
+                    mCallsManager.stopDtmfTone(call);
                     break;
                 case MSG_POST_DIAL_CONTINUE:
-                    mCallsManager.postDialContinue((String) msg.obj);
+                    mCallsManager.postDialContinue(call);
                     break;
                 case MSG_DISCONNECT_CALL:
-                    mCallsManager.disconnectCall((String) msg.obj);
+                    mCallsManager.disconnectCall(call);
                     break;
                 case MSG_HOLD_CALL:
-                    mCallsManager.holdCall((String) msg.obj);
+                    mCallsManager.holdCall(call);
                     break;
                 case MSG_UNHOLD_CALL:
-                    mCallsManager.unholdCall((String) msg.obj);
+                    mCallsManager.unholdCall(call);
                     break;
                 case MSG_MUTE:
                     mCallsManager.mute(msg.arg1 == 1 ? true : false);
@@ -78,17 +87,20 @@
 
     private final CallsManager mCallsManager;
     private final Handler mHandler = new InCallAdapterHandler();
+    private final CallIdMapper mCallIdMapper;
 
     /** Persists the specified parameters. */
-    public InCallAdapter(CallsManager callsManager) {
+    public InCallAdapter(CallsManager callsManager, CallIdMapper callIdMapper) {
         ThreadUtil.checkOnMainThread();
         mCallsManager = callsManager;
+        mCallIdMapper = callIdMapper;
     }
 
     /** {@inheritDoc} */
     @Override
     public void answerCall(String callId) {
         Log.d(this, "answerCall(%s)", callId);
+        mCallIdMapper.checkValidCallId(callId);
         mHandler.obtainMessage(MSG_ANSWER_CALL, callId).sendToTarget();
     }
 
@@ -96,6 +108,7 @@
     @Override
     public void rejectCall(String callId) {
         Log.d(this, "rejectCall(%s)", callId);
+        mCallIdMapper.checkValidCallId(callId);
         mHandler.obtainMessage(MSG_REJECT_CALL, callId).sendToTarget();
     }
 
@@ -103,6 +116,7 @@
     @Override
     public void playDtmfTone(String callId, char digit) {
         Log.d(this, "playDtmfTone(%s,%c)", callId, digit);
+        mCallIdMapper.checkValidCallId(callId);
         mHandler.obtainMessage(MSG_PLAY_DTMF_TONE, (int) digit, 0, callId).sendToTarget();
     }
 
@@ -110,6 +124,7 @@
     @Override
     public void stopDtmfTone(String callId) {
         Log.d(this, "stopDtmfTone(%s)", callId);
+        mCallIdMapper.checkValidCallId(callId);
         mHandler.obtainMessage(MSG_STOP_DTMF_TONE, callId).sendToTarget();
     }
 
@@ -117,24 +132,29 @@
     @Override
     public void postDialContinue(String callId) {
         Log.d(this, "postDialContinue(%s)", callId);
+        mCallIdMapper.checkValidCallId(callId);
         mHandler.obtainMessage(MSG_POST_DIAL_CONTINUE, callId).sendToTarget();
     }
 
     /** {@inheritDoc} */
     @Override
     public void disconnectCall(String callId) {
+        Log.v(this, "disconnectCall: %s", callId);
+        mCallIdMapper.checkValidCallId(callId);
         mHandler.obtainMessage(MSG_DISCONNECT_CALL, callId).sendToTarget();
     }
 
     /** {@inheritDoc} */
     @Override
     public void holdCall(String callId) {
+        mCallIdMapper.checkValidCallId(callId);
         mHandler.obtainMessage(MSG_HOLD_CALL, callId).sendToTarget();
     }
 
     /** {@inheritDoc} */
     @Override
     public void unholdCall(String callId) {
+        mCallIdMapper.checkValidCallId(callId);
         mHandler.obtainMessage(MSG_UNHOLD_CALL, callId).sendToTarget();
     }
 
diff --git a/src/com/android/telecomm/InCallController.java b/src/com/android/telecomm/InCallController.java
index 5fbf1f8..d975b0b 100644
--- a/src/com/android/telecomm/InCallController.java
+++ b/src/com/android/telecomm/InCallController.java
@@ -32,16 +32,12 @@
 /**
  * Binds to {@link IInCallService} and provides the service to {@link CallsManager} through which it
  * can send updates to the in-call app. This class is created and owned by CallsManager and retains
- * a binding to the {@link IInCallService} (implemented by the in-call app) until CallsManager
- * explicitly disconnects it. CallsManager starts the connection by calling {@link #connect} and
- * retains the connection as long as it has calls which need UI. When all calls are disconnected,
- * CallsManager will invoke {@link #disconnect} to sever the binding until the in-call UI is needed
- * again.
+ * a binding to the {@link IInCallService} (implemented by the in-call app).
  */
 public final class InCallController extends CallsManagerListenerBase {
     /**
      * Used to bind to the in-call app and triggers the start of communication between
-     * CallsManager and in-call app.
+     * this class and in-call app.
      */
     private class InCallServiceConnection implements ServiceConnection {
         /** {@inheritDoc} */
@@ -75,8 +71,8 @@
     /** The in-call app implementation, see {@link IInCallService}. */
     private IInCallService mInCallService;
 
-    // TODO(santoscordon): May be better to expose the IInCallService methods directly from this
-    // class as its own method to make the CallsManager code easier to read.
+    private final CallIdMapper mCallIdMapper = new CallIdMapper("InCall");
+
     IInCallService getService() {
         return mInCallService;
     }
@@ -87,10 +83,11 @@
             bind();
         } else {
             Log.i(this, "Adding call: %s", call);
+            mCallIdMapper.addCall(call);
+            CallInfo callInfo = call.toCallInfo(mCallIdMapper.getCallId(call));
             try {
-                mInCallService.addCall(call.toCallInfo());
+                mInCallService.addCall(callInfo);
             } catch (RemoteException e) {
-                Log.e(this, e, "Exception attempting to addCall.");
             }
         }
     }
@@ -101,6 +98,7 @@
             // TODO(sail): Wait for all messages to be delivered to the service before unbinding.
             unbind();
         }
+        mCallIdMapper.removeCall(call);
     }
 
     @Override
@@ -109,29 +107,27 @@
             return;
         }
 
+        String callId = mCallIdMapper.getCallId(call);
         switch (newState) {
             case ACTIVE:
-                Log.i(this, "Mark call as ACTIVE: %s", call.getId());
+                Log.i(this, "Mark call as ACTIVE: %s", callId);
                 try {
-                    mInCallService.setActive(call.getId());
+                    mInCallService.setActive(callId);
                 } catch (RemoteException e) {
-                    Log.e(this, e, "Exception attempting to call setActive.");
                 }
                 break;
             case ON_HOLD:
-                Log.i(this, "Mark call as HOLD: %s", call.getId());
+                Log.i(this, "Mark call as HOLD: %s", callId);
                 try {
-                    mInCallService.setOnHold(call.getId());
+                    mInCallService.setOnHold(callId);
                 } catch (RemoteException e) {
-                    Log.e(this, e, "Exception attempting to call setOnHold.");
                 }
                 break;
             case DISCONNECTED:
-                Log.i(this, "Mark call as DISCONNECTED: %s", call.getId());
+                Log.i(this, "Mark call as DISCONNECTED: %s", callId);
                 try {
-                    mInCallService.setDisconnected(call.getId(), call.getDisconnectCause());
+                    mInCallService.setDisconnected(callId, call.getDisconnectCause());
                 } catch (RemoteException e) {
-                    Log.e(this, e, "Exception attempting to call setDisconnected.");
                 }
                 break;
             default:
@@ -147,7 +143,6 @@
             try {
                 mInCallService.onAudioStateChanged(newAudioState);
             } catch (RemoteException e) {
-                Log.e(this, e, "Exception attempting to update audio state.");
             }
         }
     }
@@ -189,7 +184,7 @@
 
     /**
      * 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
+     * this class and in-call app by sending the first update to in-call app. This method is
      * called after a successful binding connection is established.
      *
      * @param service The {@link IInCallService} implementation.
@@ -199,7 +194,8 @@
         mInCallService = IInCallService.Stub.asInterface(service);
 
         try {
-            mInCallService.setInCallAdapter(new InCallAdapter(CallsManager.getInstance()));
+            mInCallService.setInCallAdapter(new InCallAdapter(CallsManager.getInstance(),
+                    mCallIdMapper));
         } catch (RemoteException e) {
             Log.e(this, e, "Failed to set the in-call adapter.");
             mInCallService = null;
diff --git a/src/com/android/telecomm/IncomingCallsManager.java b/src/com/android/telecomm/IncomingCallsManager.java
index 0d98dc4..41b98f2 100644
--- a/src/com/android/telecomm/IncomingCallsManager.java
+++ b/src/com/android/telecomm/IncomingCallsManager.java
@@ -22,8 +22,9 @@
 
 import com.google.common.base.Preconditions;
 import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
 
-import java.util.Map;
+import java.util.Set;
 
 /**
  * Used to retrieve details about an incoming call. This is invoked after an incoming call intent.
@@ -31,9 +32,7 @@
 final class IncomingCallsManager {
 
     private final Switchboard mSwitchboard;
-
-    /** Maps call ID to the call. */
-    private final Map<String, Call> mPendingIncomingCalls = Maps.newHashMap();
+    private final Set<Call> mPendingIncomingCalls = Sets.newLinkedHashSet();
 
     /**
      * Persists the specified parameters.
@@ -55,19 +54,18 @@
         ThreadUtil.checkOnMainThread();
         Log.d(this, "retrieveIncomingCall");
 
-        final String callId = call.getId();
         // Just to be safe, lets make sure we're not already processing this call.
-        Preconditions.checkState(!mPendingIncomingCalls.containsKey(callId));
+        Preconditions.checkState(!mPendingIncomingCalls.contains(call));
 
-        mPendingIncomingCalls.put(callId, call);
+        mPendingIncomingCalls.add(call);
 
         Runnable errorCallback = new Runnable() {
             @Override public void run() {
-                handleFailedIncomingCall(callId);
+                handleFailedIncomingCall(call);
             }
         };
 
-        call.getCallService().setIncomingCallId(callId, extras, errorCallback);
+        call.getCallService().setIncomingCallId(call, extras, errorCallback);
     }
 
     /**
@@ -76,27 +74,25 @@
      *
      * @param callInfo The details of the call.
      */
-    void handleSuccessfulIncomingCall(CallInfo callInfo) {
+    void handleSuccessfulIncomingCall(Call call, CallInfo callInfo) {
         ThreadUtil.checkOnMainThread();
 
-        Call call = mPendingIncomingCalls.remove(callInfo.getId());
-        if (call != null) {
-            Log.d(this, "Incoming call %s found.", call.getId());
+        if (mPendingIncomingCalls.contains(call)) {
+            Log.d(this, "Incoming call %s found.", call);
+            mPendingIncomingCalls.remove(call);
             mSwitchboard.handleSuccessfulIncomingCall(call, callInfo);
         }
     }
 
     /**
      * Notifies switchboard of the failed incoming call after removing it from the pending list.
-     *
-     * @param callId The ID of the call.
      */
-    void handleFailedIncomingCall(String callId) {
+    void handleFailedIncomingCall(Call call) {
         ThreadUtil.checkOnMainThread();
 
-        Call call = mPendingIncomingCalls.remove(callId);
-        if (call != null) {
+        if (mPendingIncomingCalls.contains(call)) {
             Log.i(this, "Failed to get details for incoming call %s", call);
+            mPendingIncomingCalls.remove(call);
             // The call was found still waiting for details. Consider it failed.
             mSwitchboard.handleFailedIncomingCall(call);
         }
diff --git a/src/com/android/telecomm/OutgoingCallProcessor.java b/src/com/android/telecomm/OutgoingCallProcessor.java
index 3d41fe7..ca4b33e 100644
--- a/src/com/android/telecomm/OutgoingCallProcessor.java
+++ b/src/com/android/telecomm/OutgoingCallProcessor.java
@@ -164,15 +164,14 @@
      * TODO(gilad): Consider making this class stateful, potentially rejecting out-of-order/
      * unexpected invocations (i.e. beyond checking for unexpected call IDs).
      *
-     * @param callId The ID of the call.
      * @param isCompatible True if the call-service is compatible with the corresponding call and
      *     false otherwise.
      */
-    void setIsCompatibleWith(String callId, boolean isCompatible) {
-        Log.v(this, "setIsCompatibleWith, callId: %s, isCompatible: %b", callId, isCompatible);
-        if (!callId.equals(mCall.getId())) {
-            Log.wtf(this, "setIsCompatibleWith invoked with unexpected call ID: %s - expected call"
-                  + " ID: %s", callId, mCall.getId());
+    void setIsCompatibleWith(Call call, boolean isCompatible) {
+        Log.v(this, "setIsCompatibleWith, call: %s, isCompatible: %b", call, isCompatible);
+        if (call != mCall) {
+            Log.wtf(this, "setIsCompatibleWith invoked with unexpected call: %s, expected call: %s",
+                    call, mCall);
             return;
         }
 
@@ -180,7 +179,7 @@
             CallServiceWrapper callService = mCall.getCallService();
             if (callService != null) {
                 if (isCompatible) {
-                    callService.call(mCall.toCallInfo(), mNextCallServiceCallback);
+                    callService.call(mCall);
                     return;
                 }
                 mIncompatibleCallServices.add(callService);
@@ -198,6 +197,7 @@
         ThreadUtil.checkOnMainThread();
         if (!mIsAborted) {
             mIsAborted = true;
+            // This will also clear the call's call service and selector.
             mOutgoingCallsManager.handleFailedOutgoingCall(mCall, true /* isAborted */);
         }
     }
@@ -232,9 +232,6 @@
         Log.v(this, "handleFailedCallAttempt");
         if (!mIsAborted) {
             ThreadUtil.checkOnMainThread();
-
-            mCall.clearCallService();
-            mCall.clearCallServiceSelector();
             attemptNextCallService();
         }
     }
@@ -259,10 +256,11 @@
                         processSelectedCallServiceDescriptors(callServices);
                     }
                 };
-            selector.select(mCall.toCallInfo(), mCallServiceDescriptors, responseCallback,
+            selector.select(mCall, mCallServiceDescriptors, responseCallback,
                     mNextSelectorCallback);
         } else {
             Log.v(this, "attemptNextSelector, no more selectors, failing");
+            mCall.clearCallServiceSelector();
             mOutgoingCallsManager.handleFailedOutgoingCall(mCall, false /* isAborted */);
         }
     }
@@ -310,10 +308,11 @@
             } else {
                 mAttemptedCallServices.add(callService);
                 mCall.setCallService(callService);
-                callService.isCompatibleWith(mCall.toCallInfo(), mNextCallServiceCallback);
+                callService.isCompatibleWith(mCall, mNextCallServiceCallback);
             }
         } else {
             mCallServiceDescriptorIterator = null;
+            mCall.clearCallService();
             attemptNextSelector();
         }
     }
diff --git a/src/com/android/telecomm/OutgoingCallsManager.java b/src/com/android/telecomm/OutgoingCallsManager.java
index 50c24ea..1a955ce 100644
--- a/src/com/android/telecomm/OutgoingCallsManager.java
+++ b/src/com/android/telecomm/OutgoingCallsManager.java
@@ -26,7 +26,7 @@
 /**
  * Responsible for placing all outgoing calls. For each outgoing call, this class creates an
  * instance of {@link OutgoingCallProcessor} which handles the details of connecting to the
- * appropriate call service and placing the call. This class maintains a mapping from call ID
+ * appropriate call service and placing the call. This class maintains a mapping from call
  * to {@link OutgoingCallProcessor} so that other classes (Switchboard, CallServiceAdapter, etc),
  * can simply call into this class instead of individual OutgoingCallProcessors.
  */
@@ -34,9 +34,9 @@
     private final Switchboard mSwitchboard;
 
     /**
-     * Maps call IDs to {@link OutgoingCallProcessor}s.
+     * Maps call to {@link OutgoingCallProcessor}s.
      */
-    private Map<String, OutgoingCallProcessor> mOutgoingCallProcessors = Maps.newHashMap();
+    private Map<Call, OutgoingCallProcessor> mOutgoingCallProcessors = Maps.newHashMap();
 
     /** Persists specified parameters. */
     OutgoingCallsManager(Switchboard switchboard) {
@@ -58,7 +58,7 @@
             Set<CallServiceWrapper> callServices,
             Collection<CallServiceSelectorWrapper> selectors) {
 
-        Log.i(this, "Placing an outgoing call (%s)", call.getId());
+        Log.i(this, "Placing an outgoing call: %s", call);
 
         // Create the processor for this (outgoing) call and store it in a map such that call
         // attempts can be aborted etc.
@@ -66,26 +66,24 @@
         OutgoingCallProcessor processor =
                 new OutgoingCallProcessor(call, callServices, selectors, this, mSwitchboard);
 
-        mOutgoingCallProcessors.put(call.getId(), processor);
+        mOutgoingCallProcessors.put(call, processor);
         processor.process();
     }
 
     /**
-     * Forwards the compatibility status from the call-service implementation to the corresponding
-     * outgoing-call processor.
+     * Forwards the compatibility status from the call-service to the corresponding outgoing-call
+     * processor.
      *
-     * @param callId The ID of the call.
-     * @param isCompatible True if the call-service is compatible with the corresponding call and
-     *     false otherwise.
+     * @param isCompatible True if the call-service is compatible with the call.
      */
-    void setIsCompatibleWith(String callId, boolean isCompatible) {
-        Log.v(this, "setIsCompatibleWith, callId %s, isCompatible: %b", callId, isCompatible);
-        OutgoingCallProcessor processor = mOutgoingCallProcessors.get(callId);
+    void setIsCompatibleWith(Call call, boolean isCompatible) {
+        Log.v(this, "setIsCompatibleWith, call %s, isCompatible: %b", call, isCompatible);
+        OutgoingCallProcessor processor = mOutgoingCallProcessors.get(call);
         if (processor == null) {
             // Shouldn't happen, so log a wtf if it does.
             Log.wtf(this, "Received unexpected setCompatibleWith notification.");
         } else {
-            processor.setIsCompatibleWith(callId, isCompatible);
+            processor.setIsCompatibleWith(call, isCompatible);
         }
     }
 
@@ -93,12 +91,10 @@
      * Removes the outgoing call processor mapping for the successful call and returns execution to
      * the switchboard. This method is invoked from {@link CallServiceAdapter} after a call service
      * has notified Telecomm that it successfully placed the call.
-     *
-     * @param callId The ID of the call.
      */
-    void handleSuccessfulCallAttempt(String callId) {
-        Log.v(this, "handleSuccessfulCallAttempt, callId: %s", callId);
-        OutgoingCallProcessor processor = mOutgoingCallProcessors.remove(callId);
+    void handleSuccessfulCallAttempt(Call call) {
+        Log.v(this, "handleSuccessfulCallAttempt, call: %s", call);
+        OutgoingCallProcessor processor = mOutgoingCallProcessors.remove(call);
 
         if (processor == null) {
             // Shouldn't happen, so log a wtf if it does.
@@ -114,14 +110,12 @@
      * service. This method is called from {@link CallServiceAdapter} after a call service has
      * notified Telecomm that it could not place the call.
      *
-     * @param callId The ID of the failed outgoing call.
      * @param reason The call-service supplied reason for the failed call attempt.
      */
-    void handleFailedCallAttempt(String callId, String reason) {
-        Log.v(this, "handleFailedCallAttempt, callId: %s, reason: %s", callId, reason);
-        OutgoingCallProcessor processor = mOutgoingCallProcessors.get(callId);
+    void handleFailedCallAttempt(Call call, String reason) {
+        Log.v(this, "handleFailedCallAttempt, call: %s, reason: %s", call, reason);
+        OutgoingCallProcessor processor = mOutgoingCallProcessors.get(call);
 
-        // TODO(santoscordon): Consider combining the check here and in handleSuccessfulCallAttempt.
         if (processor == null) {
             // Shouldn't happen, so log a wtf if it does.
             Log.wtf(this, "Received an unexpected failed-call notification.");
@@ -142,7 +136,7 @@
      */
     void handleFailedOutgoingCall(Call call, boolean isAborted) {
         Log.v(this, "handleFailedOutgoingCall, call: %s", call);
-        mOutgoingCallProcessors.remove(call.getId());
+        mOutgoingCallProcessors.remove(call);
         mSwitchboard.handleFailedOutgoingCall(call, isAborted);
     }
 
@@ -153,7 +147,7 @@
      */
     void abort(Call call) {
         Log.v(this, "abort, call: %s", call);
-        OutgoingCallProcessor processor = mOutgoingCallProcessors.remove(call.getId());
+        OutgoingCallProcessor processor = mOutgoingCallProcessors.remove(call);
         if (processor != null) {
             processor.abort();
         }
diff --git a/src/com/android/telecomm/PhoneStateBroadcaster.java b/src/com/android/telecomm/PhoneStateBroadcaster.java
index 5728802..ff0a836 100644
--- a/src/com/android/telecomm/PhoneStateBroadcaster.java
+++ b/src/com/android/telecomm/PhoneStateBroadcaster.java
@@ -46,7 +46,7 @@
                 break;
             default:
                 Log.w(this, "Call is in an unknown state (%s), not broadcasting: %s",
-                        newState, call.getId());
+                        newState, call);
                 return;
         }
         sendPhoneStateChangedBroadcast(call, phoneState);
@@ -57,8 +57,6 @@
 
         Intent intent = new Intent(TelephonyManager.ACTION_PHONE_STATE_CHANGED);
         intent.putExtra(TelephonyManager.EXTRA_STATE, phoneState);
-        // TODO: See if we can add this (the current API doesn't have a callId).
-        intent.putExtra(TelecommConstants.EXTRA_CALL_ID, call.getId());
 
         // Populate both, since the original API was needlessly complicated.
         String callHandle = call.getHandle().getSchemeSpecificPart();
diff --git a/src/com/android/telecomm/RingbackPlayer.java b/src/com/android/telecomm/RingbackPlayer.java
index 5e0834f..df4d861 100644
--- a/src/com/android/telecomm/RingbackPlayer.java
+++ b/src/com/android/telecomm/RingbackPlayer.java
@@ -33,9 +33,9 @@
     private final InCallTonePlayer.Factory mPlayerFactory;
 
     /**
-     * The ID of the current call for which the ringback tone is being played.
+     * The current call for which the ringback tone is being played.
      */
-    private String mCallId;
+    private Call mCall;
 
     /**
      * The currently active player.
@@ -83,13 +83,13 @@
         Preconditions.checkState(call.getState() == CallState.DIALING);
         ThreadUtil.checkOnMainThread();
 
-        if (mCallId != null) {
+        if (mCall != null) {
             // We only get here for the foreground call so, there's no reason why there should
-            // exist a current dialing call ID.
+            // exist a current dialing call.
             Log.wtf(this, "Ringback player thinks there are two foreground-dialing calls.");
         }
 
-        mCallId = call.getId();
+        mCall = call;
         if (mTonePlayer == null) {
             Log.d(this, "Playing the ringback tone.");
             mTonePlayer = mPlayerFactory.createPlayer(InCallTonePlayer.TONE_RING_BACK);
@@ -105,10 +105,10 @@
     private void stopRingbackForCall(Call call) {
         ThreadUtil.checkOnMainThread();
 
-        if (mCallId != null && mCallId.equals(call.getId())) {
+        if (mCall == call) {
             // The foreground call is no longer dialing or is no longer the foreground call. In
             // either case, stop the ringback tone.
-            mCallId = null;
+            mCall = null;
 
             if (mTonePlayer == null) {
                 Log.w(this, "No player found to stop.");
diff --git a/src/com/android/telecomm/Ringer.java b/src/com/android/telecomm/Ringer.java
index dbf76d5..bb32cc3 100644
--- a/src/com/android/telecomm/Ringer.java
+++ b/src/com/android/telecomm/Ringer.java
@@ -34,7 +34,7 @@
      * Used to keep ordering of unanswered incoming calls. There can easily exist multiple incoming
      * calls and explicit ordering is useful for maintaining the proper state of the ringer.
      */
-    private final List<String> mUnansweredCallIds = Lists.newLinkedList();
+    private final List<Call> mUnansweredCalls = Lists.newLinkedList();
 
     private final CallAudioManager mCallAudioManager;
 
@@ -45,11 +45,11 @@
     @Override
     public void onCallAdded(Call call) {
         if (call.isIncoming() && call.getState() == CallState.RINGING) {
-            if (mUnansweredCallIds.contains(call.getId())) {
+            if (mUnansweredCalls.contains(call)) {
                 Log.wtf(this, "New ringing call is already in list of unanswered calls");
             }
-            mUnansweredCallIds.add(call.getId());
-            if (mUnansweredCallIds.size() == 1) {
+            mUnansweredCalls.add(call);
+            if (mUnansweredCalls.size() == 1) {
                 // Start the ringer if we are the top-most incoming call (the only one in this
                 // case).
                 startRinging();
@@ -59,13 +59,13 @@
 
     @Override
     public void onCallRemoved(Call call) {
-        removeFromUnansweredCallIds(call.getId());
+        removeFromUnansweredCall(call);
     }
 
     @Override
     public void onCallStateChanged(Call call, CallState oldState, CallState newState) {
         if (newState != CallState.RINGING) {
-            removeFromUnansweredCallIds(call.getId());
+            removeFromUnansweredCall(call);
         }
     }
 
@@ -81,21 +81,19 @@
 
     private void onRespondedToIncomingCall(Call call) {
         // Only stop the ringer if this call is the top-most incoming call.
-        if (!mUnansweredCallIds.isEmpty() && mUnansweredCallIds.get(0).equals(call.getId())) {
+        if (!mUnansweredCalls.isEmpty() && mUnansweredCalls.get(0) == call) {
             stopRinging();
         }
     }
 
     /**
      * Removes the specified call from the list of unanswered incoming calls and updates the ringer
-     * based on the new state of {@link #mUnansweredCallIds}. Safe to call with a call ID that
-     * is not present in the list of incoming calls.
-     *
-     * @param callId The ID of the call.
+     * based on the new state of {@link #mUnansweredCalls}. Safe to call with a call that is not
+     * present in the list of incoming calls.
      */
-    private void removeFromUnansweredCallIds(String callId) {
-        if (mUnansweredCallIds.remove(callId)) {
-            if (mUnansweredCallIds.isEmpty()) {
+    private void removeFromUnansweredCall(Call call) {
+        if (mUnansweredCalls.remove(call)) {
+            if (mUnansweredCalls.isEmpty()) {
                 stopRinging();
             } else {
                 startRinging();
diff --git a/src/com/android/telecomm/Switchboard.java b/src/com/android/telecomm/Switchboard.java
index d8c57fb..5a23f88 100644
--- a/src/com/android/telecomm/Switchboard.java
+++ b/src/com/android/telecomm/Switchboard.java
@@ -334,7 +334,7 @@
         while (iterator.hasNext()) {
             Call call = iterator.next();
             if (call.getAgeInMilliseconds() >= newCallTimeoutMs) {
-                Log.d(this, "Call %s timed out.", call.getId());
+                Log.d(this, "Call %s timed out.", call);
                 mOutgoingCallsManager.abort(call);
                 calls.remove(call);