Change sequence of call removal from Phone's db.

Since the Call API's callbacks are now fired from handlers, they end up
changing the order in which the callbacks from the Phone and Call API's
are fired. To preserve the below ordering, we move the call removal from
Phone's db to after all the onCallDestoryedcallbacks have executed.
1. Call->onStateChanged
2. Call->onDetailsChanged
3. Call->onCallDestroyed
4. Phone->onCallRemoved

BUG: 22127504
Change-Id: Ice17f727decb516baabbe69adae598ebdf370094
diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java
index e756a57..4569549 100644
--- a/telecomm/java/android/telecom/Call.java
+++ b/telecomm/java/android/telecom/Call.java
@@ -921,7 +921,8 @@
      */
     public void registerCallback(Callback callback, Handler handler) {
         unregisterCallback(callback);
-        if (callback != null && handler != null) {
+        // Don't allow new callback registration if the call is already being destroyed.
+        if (callback != null && handler != null && mState != STATE_DISCONNECTED) {
             mCallbackRecords.add(new CallbackRecord<Callback>(callback, handler));
         }
     }
@@ -932,7 +933,8 @@
      * @param callback A {@code Callback}.
      */
     public void unregisterCallback(Callback callback) {
-        if (callback != null) {
+        // Don't allow callback deregistration if the call is already being destroyed.
+        if (callback != null && mState != STATE_DISCONNECTED) {
             for (CallbackRecord<Callback> record : mCallbackRecords) {
                 if (record.getCallback() == callback) {
                     mCallbackRecords.remove(record);
@@ -1080,7 +1082,6 @@
         // DISCONNECTED Call while still relying on the existence of that Call in the Phone's list.
         if (mState == STATE_DISCONNECTED) {
             fireCallDestroyed();
-            mPhone.internalRemoveCall(this);
         }
     }
 
@@ -1096,7 +1097,6 @@
             mState = Call.STATE_DISCONNECTED;
             fireStateChanged(mState);
             fireCallDestroyed();
-            mPhone.internalRemoveCall(this);
         }
     }
 
@@ -1192,13 +1192,42 @@
     }
 
     private void fireCallDestroyed() {
-        for (CallbackRecord<Callback> record: mCallbackRecords) {
-            final Call call = this;
+        /**
+         * To preserve the ordering of the Call's onCallDestroyed callback and Phone's
+         * onCallRemoved callback, we remove this call from the Phone's record
+         * only once all of the registered onCallDestroyed callbacks are executed.
+         * All the callbacks get removed from our records as a part of this operation
+         * since onCallDestroyed is the final callback.
+         */
+        final Call call = this;
+        if (mCallbackRecords.isEmpty()) {
+            // No callbacks registered, remove the call from Phone's record.
+            mPhone.internalRemoveCall(call);
+        }
+        for (final CallbackRecord<Callback> record : mCallbackRecords) {
             final Callback callback = record.getCallback();
             record.getHandler().post(new Runnable() {
                 @Override
                 public void run() {
-                    callback.onCallDestroyed(call);
+                    boolean isFinalRemoval = false;
+                    RuntimeException toThrow = null;
+                    try {
+                        callback.onCallDestroyed(call);
+                    } catch (RuntimeException e) {
+                            toThrow = e;
+                    }
+                    synchronized(Call.this) {
+                        mCallbackRecords.remove(record);
+                        if (mCallbackRecords.isEmpty()) {
+                            isFinalRemoval = true;
+                        }
+                    }
+                    if (isFinalRemoval) {
+                        mPhone.internalRemoveCall(call);
+                    }
+                    if (toThrow != null) {
+                        throw toThrow;
+                    }
                 }
             });
         }