Remove orphaned Calls as part of doUpdate().

When a connection is no longer valid in telephony (for whatever reason),
we do not clear it from our list of Calls.  This results in the UI
becoming non-dismissable.

We do not expect this condition but have seen cases where connection
objects become orphaned (like when device switches technologies
GSM->CDMA and back).  See bug for more information.

Could not repro this case.  Did testing by simulating orphaned
connection objects and making sure the UI goes away.

bug:11580121
Change-Id: Iaa4264713bbab29545dff4b85d74d0879366b751
diff --git a/src/com/android/phone/CallModeler.java b/src/com/android/phone/CallModeler.java
index 9f4bb76..e4ed147 100644
--- a/src/com/android/phone/CallModeler.java
+++ b/src/com/android/phone/CallModeler.java
@@ -36,6 +36,7 @@
 import com.android.services.telephony.common.Call.State;
 
 import com.google.android.collect.Maps;
+import com.google.android.collect.Sets;
 import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSortedSet;
@@ -46,6 +47,7 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map.Entry;
+import java.util.Set;
 import java.util.concurrent.atomic.AtomicInteger;
 
 /**
@@ -346,10 +348,14 @@
         PhoneGlobals.getInstance().updateWakeState();
     }
 
-
     /**
      * Go through the Calls from CallManager and return the list of calls that were updated.
-     * Or, the full list if requested.
+     * Method also finds any orphaned Calls (Connection objects no longer returned by telephony as
+     * either ringing, foreground, or background).  For each orphaned call, it sets the call state
+     * to IDLE and adds it to the list of calls to update.
+     *
+     * @param fullUpdate Add all calls to out parameter including those that have no updates.
+     * @param out List to populate with Calls that have been updated.
      */
     private void doUpdate(boolean fullUpdate, List<Call> out) {
         final List<com.android.internal.telephony.Call> telephonyCalls = Lists.newArrayList();
@@ -357,6 +363,14 @@
         telephonyCalls.addAll(mCallManager.getForegroundCalls());
         telephonyCalls.addAll(mCallManager.getBackgroundCalls());
 
+        // orphanedConnections starts out including all connections we know about.
+        // As we iterate through the connections we get from the telephony layer we
+        // prune this Set down to only the connections we have but telephony no longer
+        // recognizes.
+        final Set<Connection> orphanedConnections = Sets.newHashSet();
+        orphanedConnections.addAll(mCallMap.keySet());
+        orphanedConnections.addAll(mConfCallMap.keySet());
+
         // Cycle through all the Connections on all the Calls. Update our Call objects
         // to reflect any new state and send the updated Call objects to the handler service.
         for (com.android.internal.telephony.Call telephonyCall : telephonyCalls) {
@@ -364,6 +378,10 @@
             for (Connection connection : telephonyCall.getConnections()) {
                 if (DBG) Log.d(TAG, "connection: " + connection + connection.getState());
 
+                if (orphanedConnections.contains(connection)) {
+                    orphanedConnections.remove(connection);
+                }
+
                 // We only send updates for live calls which are not incoming (ringing).
                 // Disconnected and incoming calls are handled by onDisconnect and
                 // onNewRingingConnection.
@@ -404,7 +422,26 @@
             for (Connection connection : telephonyCall.getConnections()) {
                 updateForConferenceCalls(connection, out);
             }
+        }
 
+        // Iterate through orphaned connections, set them to idle, and remove
+        // them from our internal structures.
+        for (Connection orphanedConnection : orphanedConnections) {
+            if (mCallMap.containsKey(orphanedConnection)) {
+                final Call call = mCallMap.get(orphanedConnection);
+                call.setState(Call.State.IDLE);
+                out.add(call);
+
+                mCallMap.remove(orphanedConnection);
+            }
+
+            if (mConfCallMap.containsKey(orphanedConnection)) {
+                final Call call = mCallMap.get(orphanedConnection);
+                call.setState(Call.State.IDLE);
+                out.add(call);
+
+                mConfCallMap.remove(orphanedConnection);
+            }
         }
     }