Merge "Add handler parameter for callbacks." into mnc-dev
diff --git a/api/current.txt b/api/current.txt
index be01112..b41493a 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -30257,6 +30257,7 @@
     method public void playDtmfTone(char);
     method public void postDialContinue(boolean);
     method public void registerCallback(android.telecom.Call.Callback);
+    method public void registerCallback(android.telecom.Call.Callback, android.os.Handler);
     method public void reject(boolean, java.lang.String);
     method public void splitFromConference();
     method public void stopDtmfTone();
@@ -30564,6 +30565,7 @@
   public static abstract class InCallService.VideoCall {
     ctor public InCallService.VideoCall();
     method public abstract void registerCallback(android.telecom.InCallService.VideoCall.Callback);
+    method public abstract void registerCallback(android.telecom.InCallService.VideoCall.Callback, android.os.Handler);
     method public abstract void requestCallDataUsage();
     method public abstract void requestCameraCapabilities();
     method public abstract void sendSessionModifyRequest(android.telecom.VideoProfile);
@@ -30574,7 +30576,7 @@
     method public abstract void setPauseImage(java.lang.String);
     method public abstract void setPreviewSurface(android.view.Surface);
     method public abstract void setZoom(float);
-    method public abstract void unregisterCallback();
+    method public abstract void unregisterCallback(android.telecom.InCallService.VideoCall.Callback);
   }
 
   public static abstract class InCallService.VideoCall.Callback {
@@ -30662,6 +30664,7 @@
     method public void merge();
     method public void playDtmfTone(char);
     method public final void registerCallback(android.telecom.RemoteConference.Callback);
+    method public final void registerCallback(android.telecom.RemoteConference.Callback, android.os.Handler);
     method public void separate(android.telecom.RemoteConnection);
     method public void setAudioState(android.telecom.AudioState);
     method public void stopDtmfTone();
@@ -30701,6 +30704,7 @@
     method public void playDtmfTone(char);
     method public void postDialContinue(boolean);
     method public void registerCallback(android.telecom.RemoteConnection.Callback);
+    method public void registerCallback(android.telecom.RemoteConnection.Callback, android.os.Handler);
     method public void reject();
     method public void setAudioState(android.telecom.AudioState);
     method public void stopDtmfTone();
diff --git a/api/system-current.txt b/api/system-current.txt
index c4f96af..09411db 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -32371,6 +32371,7 @@
     method public void playDtmfTone(char);
     method public void postDialContinue(boolean);
     method public void registerCallback(android.telecom.Call.Callback);
+    method public void registerCallback(android.telecom.Call.Callback, android.os.Handler);
     method public void reject(boolean, java.lang.String);
     method public deprecated void removeListener(android.telecom.Call.Listener);
     method public void splitFromConference();
@@ -32687,6 +32688,7 @@
   public static abstract class InCallService.VideoCall {
     ctor public InCallService.VideoCall();
     method public abstract void registerCallback(android.telecom.InCallService.VideoCall.Callback);
+    method public abstract void registerCallback(android.telecom.InCallService.VideoCall.Callback, android.os.Handler);
     method public abstract void requestCallDataUsage();
     method public abstract void requestCameraCapabilities();
     method public abstract void sendSessionModifyRequest(android.telecom.VideoProfile);
@@ -32697,7 +32699,7 @@
     method public abstract void setPauseImage(java.lang.String);
     method public abstract void setPreviewSurface(android.view.Surface);
     method public abstract void setZoom(float);
-    method public abstract void unregisterCallback();
+    method public abstract void unregisterCallback(android.telecom.InCallService.VideoCall.Callback);
   }
 
   public static abstract class InCallService.VideoCall.Callback {
@@ -32805,6 +32807,7 @@
     method public void merge();
     method public void playDtmfTone(char);
     method public final void registerCallback(android.telecom.RemoteConference.Callback);
+    method public final void registerCallback(android.telecom.RemoteConference.Callback, android.os.Handler);
     method public void separate(android.telecom.RemoteConnection);
     method public void setAudioState(android.telecom.AudioState);
     method public void stopDtmfTone();
@@ -32844,6 +32847,7 @@
     method public void playDtmfTone(char);
     method public void postDialContinue(boolean);
     method public void registerCallback(android.telecom.RemoteConnection.Callback);
+    method public void registerCallback(android.telecom.RemoteConnection.Callback, android.os.Handler);
     method public void reject();
     method public void setAudioState(android.telecom.AudioState);
     method public void stopDtmfTone();
diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java
index d92c0c7..9273939 100644
--- a/telecomm/java/android/telecom/Call.java
+++ b/telecomm/java/android/telecom/Call.java
@@ -19,10 +19,12 @@
 import android.annotation.SystemApi;
 import android.net.Uri;
 import android.os.Bundle;
+import android.os.Handler;
 
 import java.lang.String;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
@@ -608,7 +610,7 @@
     private final List<String> mChildrenIds = new ArrayList<>();
     private final List<Call> mChildren = new ArrayList<>();
     private final List<Call> mUnmodifiableChildren = Collections.unmodifiableList(mChildren);
-    private final List<Callback> mCallbacks = new CopyOnWriteArrayList<>();
+    private final List<CallbackRecord<Callback>> mCallbackRecords = new CopyOnWriteArrayList<>();
     private final List<Call> mConferenceableCalls = new ArrayList<>();
     private final List<Call> mUnmodifiableConferenceableCalls =
             Collections.unmodifiableList(mConferenceableCalls);
@@ -850,7 +852,20 @@
      * @param callback A {@code Callback}.
      */
     public void registerCallback(Callback callback) {
-        mCallbacks.add(callback);
+        registerCallback(callback, new Handler());
+    }
+
+    /**
+     * Registers a callback to this {@code Call}.
+     *
+     * @param callback A {@code Callback}.
+     * @param handler A handler which command and status changes will be delivered to.
+     */
+    public void registerCallback(Callback callback, Handler handler) {
+        unregisterCallback(callback);
+        if (callback != null && handler != null) {
+            mCallbackRecords.add(new CallbackRecord<Callback>(callback, handler));
+        }
     }
 
     /**
@@ -860,7 +875,12 @@
      */
     public void unregisterCallback(Callback callback) {
         if (callback != null) {
-            mCallbacks.remove(callback);
+            for (CallbackRecord<Callback> record : mCallbackRecords) {
+                if (record.getCallback() == callback) {
+                    mCallbackRecords.remove(record);
+                    break;
+                }
+            }
         }
     }
 
@@ -1021,57 +1041,120 @@
         }
     }
 
-    private void fireStateChanged(int newState) {
-        for (Callback callback : mCallbacks) {
-            callback.onStateChanged(this, newState);
+    private void fireStateChanged(final int newState) {
+        for (CallbackRecord<Callback> record : mCallbackRecords) {
+            final Call call = this;
+            final Callback callback = record.getCallback();
+            record.getHandler().post(new Runnable() {
+                @Override
+                public void run() {
+                    callback.onStateChanged(call, newState);
+                }
+            });
         }
     }
 
-    private void fireParentChanged(Call newParent) {
-        for (Callback callback : mCallbacks) {
-            callback.onParentChanged(this, newParent);
+    private void fireParentChanged(final Call newParent) {
+        for (CallbackRecord<Callback> record : mCallbackRecords) {
+            final Call call = this;
+            final Callback callback = record.getCallback();
+            record.getHandler().post(new Runnable() {
+                @Override
+                public void run() {
+                    callback.onParentChanged(call, newParent);
+                }
+            });
         }
     }
 
-    private void fireChildrenChanged(List<Call> children) {
-        for (Callback callback : mCallbacks) {
-            callback.onChildrenChanged(this, children);
+    private void fireChildrenChanged(final List<Call> children) {
+        for (CallbackRecord<Callback> record : mCallbackRecords) {
+            final Call call = this;
+            final Callback callback = record.getCallback();
+            record.getHandler().post(new Runnable() {
+                @Override
+                public void run() {
+                    callback.onChildrenChanged(call, children);
+                }
+            });
         }
     }
 
-    private void fireDetailsChanged(Details details) {
-        for (Callback callback : mCallbacks) {
-            callback.onDetailsChanged(this, details);
+    private void fireDetailsChanged(final Details details) {
+        for (CallbackRecord<Callback> record : mCallbackRecords) {
+            final Call call = this;
+            final Callback callback = record.getCallback();
+            record.getHandler().post(new Runnable() {
+                @Override
+                public void run() {
+                    callback.onDetailsChanged(call, details);
+                }
+            });
         }
     }
 
-    private void fireCannedTextResponsesLoaded(List<String> cannedTextResponses) {
-        for (Callback callback : mCallbacks) {
-            callback.onCannedTextResponsesLoaded(this, cannedTextResponses);
+    private void fireCannedTextResponsesLoaded(final List<String> cannedTextResponses) {
+        for (CallbackRecord<Callback> record : mCallbackRecords) {
+            final Call call = this;
+            final Callback callback = record.getCallback();
+            record.getHandler().post(new Runnable() {
+                @Override
+                public void run() {
+                    callback.onCannedTextResponsesLoaded(call, cannedTextResponses);
+                }
+            });
         }
     }
 
-    private void fireVideoCallChanged(InCallService.VideoCall videoCall) {
-        for (Callback callback : mCallbacks) {
-            callback.onVideoCallChanged(this, videoCall);
+    private void fireVideoCallChanged(final InCallService.VideoCall videoCall) {
+        for (CallbackRecord<Callback> record : mCallbackRecords) {
+            final Call call = this;
+            final Callback callback = record.getCallback();
+            record.getHandler().post(new Runnable() {
+                @Override
+                public void run() {
+                    callback.onVideoCallChanged(call, videoCall);
+                }
+            });
         }
     }
 
-    private void firePostDialWait(String remainingPostDialSequence) {
-        for (Callback callback : mCallbacks) {
-            callback.onPostDialWait(this, remainingPostDialSequence);
+    private void firePostDialWait(final String remainingPostDialSequence) {
+        for (CallbackRecord<Callback> record : mCallbackRecords) {
+            final Call call = this;
+            final Callback callback = record.getCallback();
+            record.getHandler().post(new Runnable() {
+                @Override
+                public void run() {
+                    callback.onPostDialWait(call, remainingPostDialSequence);
+                }
+            });
         }
     }
 
     private void fireCallDestroyed() {
-        for (Callback callback : mCallbacks) {
-            callback.onCallDestroyed(this);
+        for (CallbackRecord<Callback> record: mCallbackRecords) {
+            final Call call = this;
+            final Callback callback = record.getCallback();
+            record.getHandler().post(new Runnable() {
+                @Override
+                public void run() {
+                    callback.onCallDestroyed(call);
+                }
+            });
         }
     }
 
     private void fireConferenceableCallsChanged() {
-        for (Callback callback : mCallbacks) {
-            callback.onConferenceableCallsChanged(this, mUnmodifiableConferenceableCalls);
+        for (CallbackRecord<Callback> record : mCallbackRecords) {
+            final Call call = this;
+            final Callback callback = record.getCallback();
+            record.getHandler().post(new Runnable() {
+                @Override
+                public void run() {
+                    callback.onConferenceableCallsChanged(call, mUnmodifiableConferenceableCalls);
+                }
+            });
         }
     }
 }
diff --git a/telecomm/java/android/telecom/CallbackRecord.java b/telecomm/java/android/telecom/CallbackRecord.java
new file mode 100644
index 0000000..1a81925
--- /dev/null
+++ b/telecomm/java/android/telecom/CallbackRecord.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2015 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 android.telecom;
+
+import android.os.Handler;
+
+
+/**
+ * This class is used to associate a generic callback of type T with a handler to which commands and
+ * status updates will be delivered to.
+ *
+ * @hide
+ */
+class CallbackRecord<T> {
+    private final T mCallback;
+    private final Handler mHandler;
+
+    public CallbackRecord(T callback, Handler handler) {
+        mCallback = callback;
+        mHandler = handler;
+    }
+
+    public T getCallback() {
+        return mCallback;
+    }
+
+    public Handler getHandler() {
+        return mHandler;
+    }
+}
diff --git a/telecomm/java/android/telecom/InCallService.java b/telecomm/java/android/telecom/InCallService.java
index 2c8415a..3cb4e87 100644
--- a/telecomm/java/android/telecom/InCallService.java
+++ b/telecomm/java/android/telecom/InCallService.java
@@ -362,6 +362,9 @@
      */
     public static abstract class VideoCall {
 
+        /** @hide */
+        public abstract void destroy();
+
         /**
          * Registers a callback to receive commands and state changes for video calls.
          *
@@ -370,9 +373,17 @@
         public abstract void registerCallback(VideoCall.Callback callback);
 
         /**
+         * Registers a callback to receive commands and state changes for video calls.
+         *
+         * @param callback The video call callback.
+         * @param handler A handler which commands and status changes will be delivered to.
+         */
+        public abstract void registerCallback(VideoCall.Callback callback, Handler handler);
+
+        /**
          * Clears the video call listener set via {@link #registerCallback}.
          */
-        public abstract void unregisterCallback();
+        public abstract void unregisterCallback(VideoCall.Callback callback);
 
         /**
          * Sets the camera to be used for video recording in a video call.
diff --git a/telecomm/java/android/telecom/Phone.java b/telecomm/java/android/telecom/Phone.java
index 3d9acda..4cdfd2e 100644
--- a/telecomm/java/android/telecom/Phone.java
+++ b/telecomm/java/android/telecom/Phone.java
@@ -125,7 +125,7 @@
 
         InCallService.VideoCall videoCall = call.getVideoCall();
         if (videoCall != null) {
-            videoCall.unregisterCallback();
+            videoCall.destroy();
         }
         fireCallRemoved(call);
     }
@@ -174,7 +174,7 @@
         for (Call call : mCalls) {
             InCallService.VideoCall videoCall = call.getVideoCall();
             if (videoCall != null) {
-                videoCall.unregisterCallback();
+                videoCall.destroy();
             }
             if (call.getState() != Call.STATE_DISCONNECTED) {
                 call.internalSetDisconnected();
diff --git a/telecomm/java/android/telecom/RemoteConference.java b/telecomm/java/android/telecom/RemoteConference.java
index fba3ee3..a76bf59 100644
--- a/telecomm/java/android/telecom/RemoteConference.java
+++ b/telecomm/java/android/telecom/RemoteConference.java
@@ -18,6 +18,7 @@
 
 import com.android.internal.telecom.IConnectionService;
 
+import android.os.Handler;
 import android.os.RemoteException;
 
 import java.util.ArrayList;
@@ -49,7 +50,7 @@
     private final String mId;
     private final IConnectionService mConnectionService;
 
-    private final Set<Callback> mCallbacks = new CopyOnWriteArraySet<>();
+    private final Set<CallbackRecord<Callback>> mCallbackRecords = new CopyOnWriteArraySet<>();
     private final List<RemoteConnection> mChildConnections = new CopyOnWriteArrayList<>();
     private final List<RemoteConnection> mUnmodifiableChildConnections =
             Collections.unmodifiableList(mChildConnections);
@@ -77,13 +78,20 @@
         for (RemoteConnection connection : mChildConnections) {
             connection.setConference(null);
         }
-        for (Callback c : mCallbacks) {
-            c.onDestroyed(this);
+        for (CallbackRecord<Callback> record : mCallbackRecords) {
+            final RemoteConference conference = this;
+            final Callback callback = record.getCallback();
+            record.getHandler().post(new Runnable() {
+                @Override
+                public void run() {
+                    callback.onDestroyed(conference);
+                }
+            });
         }
     }
 
     /** {@hide} */
-    void setState(int newState) {
+    void setState(final int newState) {
         if (newState != Connection.STATE_ACTIVE &&
                 newState != Connection.STATE_HOLDING &&
                 newState != Connection.STATE_DISCONNECTED) {
@@ -93,42 +101,71 @@
         }
 
         if (mState != newState) {
-            int oldState = mState;
+            final int oldState = mState;
             mState = newState;
-            for (Callback c : mCallbacks) {
-                c.onStateChanged(this, oldState, newState);
+            for (CallbackRecord<Callback> record : mCallbackRecords) {
+                final RemoteConference conference = this;
+                final Callback callback = record.getCallback();
+                record.getHandler().post(new Runnable() {
+                    @Override
+                    public void run() {
+                        callback.onStateChanged(conference, oldState, newState);
+                    }
+                });
             }
         }
     }
 
     /** {@hide} */
-    void addConnection(RemoteConnection connection) {
+    void addConnection(final RemoteConnection connection) {
         if (!mChildConnections.contains(connection)) {
             mChildConnections.add(connection);
             connection.setConference(this);
-            for (Callback c : mCallbacks) {
-                c.onConnectionAdded(this, connection);
+            for (CallbackRecord<Callback> record : mCallbackRecords) {
+                final RemoteConference conference = this;
+                final Callback callback = record.getCallback();
+                record.getHandler().post(new Runnable() {
+                    @Override
+                    public void run() {
+                        callback.onConnectionAdded(conference, connection);
+                    }
+                });
             }
         }
     }
 
     /** {@hide} */
-    void removeConnection(RemoteConnection connection) {
+    void removeConnection(final RemoteConnection connection) {
         if (mChildConnections.contains(connection)) {
             mChildConnections.remove(connection);
             connection.setConference(null);
-            for (Callback c : mCallbacks) {
-                c.onConnectionRemoved(this, connection);
+            for (CallbackRecord<Callback> record : mCallbackRecords) {
+                final RemoteConference conference = this;
+                final Callback callback = record.getCallback();
+                record.getHandler().post(new Runnable() {
+                    @Override
+                    public void run() {
+                        callback.onConnectionRemoved(conference, connection);
+                    }
+                });
             }
         }
     }
 
     /** {@hide} */
-    void setConnectionCapabilities(int connectionCapabilities) {
+    void setConnectionCapabilities(final int connectionCapabilities) {
         if (mConnectionCapabilities != connectionCapabilities) {
             mConnectionCapabilities = connectionCapabilities;
-            for (Callback c : mCallbacks) {
-                c.onConnectionCapabilitiesChanged(this, mConnectionCapabilities);
+            for (CallbackRecord<Callback> record : mCallbackRecords) {
+                final RemoteConference conference = this;
+                final Callback callback = record.getCallback();
+                record.getHandler().post(new Runnable() {
+                    @Override
+                    public void run() {
+                        callback.onConnectionCapabilitiesChanged(
+                                conference, mConnectionCapabilities);
+                    }
+                });
             }
         }
     }
@@ -137,18 +174,33 @@
     void setConferenceableConnections(List<RemoteConnection> conferenceableConnections) {
         mConferenceableConnections.clear();
         mConferenceableConnections.addAll(conferenceableConnections);
-        for (Callback c : mCallbacks) {
-            c.onConferenceableConnectionsChanged(this, mUnmodifiableConferenceableConnections);
+        for (CallbackRecord<Callback> record : mCallbackRecords) {
+            final RemoteConference conference = this;
+            final Callback callback = record.getCallback();
+            record.getHandler().post(new Runnable() {
+                @Override
+                public void run() {
+                    callback.onConferenceableConnectionsChanged(
+                            conference, mUnmodifiableConferenceableConnections);
+                }
+            });
         }
     }
 
     /** {@hide} */
-    void setDisconnected(DisconnectCause disconnectCause) {
+    void setDisconnected(final DisconnectCause disconnectCause) {
         if (mState != Connection.STATE_DISCONNECTED) {
             mDisconnectCause = disconnectCause;
             setState(Connection.STATE_DISCONNECTED);
-            for (Callback c : mCallbacks) {
-                c.onDisconnected(this, disconnectCause);
+            for (CallbackRecord<Callback> record : mCallbackRecords) {
+                final RemoteConference conference = this;
+                final Callback callback = record.getCallback();
+                record.getHandler().post(new Runnable() {
+                    @Override
+                    public void run() {
+                        callback.onDisconnected(conference, disconnectCause);
+                    }
+                });
             }
         }
     }
@@ -239,10 +291,24 @@
     }
 
     public final void registerCallback(Callback callback) {
-        mCallbacks.add(callback);
+        registerCallback(callback, new Handler());
+    }
+
+    public final void registerCallback(Callback callback, Handler handler) {
+        unregisterCallback(callback);
+        if (callback != null && handler != null) {
+            mCallbackRecords.add(new CallbackRecord(callback, handler));
+        }
     }
 
     public final void unregisterCallback(Callback callback) {
-        mCallbacks.remove(callback);
+        if (callback != null) {
+            for (CallbackRecord<Callback> record : mCallbackRecords) {
+                if (record.getCallback() == callback) {
+                    mCallbackRecords.remove(record);
+                    break;
+                }
+            }
+        }
     }
 }
diff --git a/telecomm/java/android/telecom/RemoteConnection.java b/telecomm/java/android/telecom/RemoteConnection.java
index 4ecfd50..1493b20 100644
--- a/telecomm/java/android/telecom/RemoteConnection.java
+++ b/telecomm/java/android/telecom/RemoteConnection.java
@@ -21,6 +21,7 @@
 import com.android.internal.telecom.IVideoProvider;
 
 import android.net.Uri;
+import android.os.Handler;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.view.Surface;
@@ -392,8 +393,8 @@
      * load factor before resizing, 1 means we only expect a single thread to
      * access the map so make only a single shard
      */
-    private final Set<Callback> mCallbacks = Collections.newSetFromMap(
-            new ConcurrentHashMap<Callback, Boolean>(8, 0.9f, 1));
+    private final Set<CallbackRecord> mCallbackRecords = Collections.newSetFromMap(
+            new ConcurrentHashMap<CallbackRecord, Boolean>(8, 0.9f, 1));
     private final List<RemoteConnection> mConferenceableConnections = new ArrayList<>();
     private final List<RemoteConnection> mUnmodifiableconferenceableConnections =
             Collections.unmodifiableList(mConferenceableConnections);
@@ -470,7 +471,20 @@
      * @param callback A {@code Callback}.
      */
     public void registerCallback(Callback callback) {
-        mCallbacks.add(callback);
+        registerCallback(callback, new Handler());
+    }
+
+    /**
+     * Adds a callback to this {@code RemoteConnection}.
+     *
+     * @param callback A {@code Callback}.
+     * @param handler A {@code Handler} which command and status changes will be delivered to.
+     */
+    public void registerCallback(Callback callback, Handler handler) {
+        unregisterCallback(callback);
+        if (callback != null && handler != null) {
+            mCallbackRecords.add(new CallbackRecord(callback, handler));
+        }
     }
 
     /**
@@ -480,7 +494,12 @@
      */
     public void unregisterCallback(Callback callback) {
         if (callback != null) {
-            mCallbacks.remove(callback);
+            for (CallbackRecord record : mCallbackRecords) {
+                if (record.getCallback() == callback) {
+                    mCallbackRecords.remove(record);
+                    break;
+                }
+            }
         }
     }
 
@@ -800,11 +819,18 @@
     /**
      * @hide
      */
-    void setState(int state) {
+    void setState(final int state) {
         if (mState != state) {
             mState = state;
-            for (Callback c: mCallbacks) {
-                c.onStateChanged(this, state);
+            for (CallbackRecord record: mCallbackRecords) {
+                final RemoteConnection connection = this;
+                final Callback callback = record.getCallback();
+                record.getHandler().post(new Runnable() {
+                    @Override
+                    public void run() {
+                        callback.onStateChanged(connection, state);
+                    }
+                });
             }
         }
     }
@@ -812,13 +838,20 @@
     /**
      * @hide
      */
-    void setDisconnected(DisconnectCause disconnectCause) {
+    void setDisconnected(final DisconnectCause disconnectCause) {
         if (mState != Connection.STATE_DISCONNECTED) {
             mState = Connection.STATE_DISCONNECTED;
             mDisconnectCause = disconnectCause;
 
-            for (Callback c : mCallbacks) {
-                c.onDisconnected(this, mDisconnectCause);
+            for (CallbackRecord record : mCallbackRecords) {
+                final RemoteConnection connection = this;
+                final Callback callback = record.getCallback();
+                record.getHandler().post(new Runnable() {
+                    @Override
+                    public void run() {
+                        callback.onDisconnected(connection, disconnectCause);
+                    }
+                });
             }
         }
     }
@@ -826,11 +859,18 @@
     /**
      * @hide
      */
-    void setRingbackRequested(boolean ringback) {
+    void setRingbackRequested(final boolean ringback) {
         if (mRingbackRequested != ringback) {
             mRingbackRequested = ringback;
-            for (Callback c : mCallbacks) {
-                c.onRingbackRequested(this, ringback);
+            for (CallbackRecord record : mCallbackRecords) {
+                final RemoteConnection connection = this;
+                final Callback callback = record.getCallback();
+                record.getHandler().post(new Runnable() {
+                    @Override
+                    public void run() {
+                        callback.onRingbackRequested(connection, ringback);
+                    }
+                });
             }
         }
     }
@@ -838,10 +878,17 @@
     /**
      * @hide
      */
-    void setConnectionCapabilities(int connectionCapabilities) {
+    void setConnectionCapabilities(final int connectionCapabilities) {
         mConnectionCapabilities = connectionCapabilities;
-        for (Callback c : mCallbacks) {
-            c.onConnectionCapabilitiesChanged(this, connectionCapabilities);
+        for (CallbackRecord record : mCallbackRecords) {
+            final RemoteConnection connection = this;
+            final Callback callback = record.getCallback();
+            record.getHandler().post(new Runnable() {
+                @Override
+                public void run() {
+                    callback.onConnectionCapabilitiesChanged(connection, connectionCapabilities);
+                }
+            });
         }
     }
 
@@ -849,17 +896,24 @@
      * @hide
      */
     void setDestroyed() {
-        if (!mCallbacks.isEmpty()) {
+        if (!mCallbackRecords.isEmpty()) {
             // Make sure that the callbacks are notified that the call is destroyed first.
             if (mState != Connection.STATE_DISCONNECTED) {
                 setDisconnected(
                         new DisconnectCause(DisconnectCause.ERROR, "Connection destroyed."));
             }
 
-            for (Callback c : mCallbacks) {
-                c.onDestroyed(this);
+            for (CallbackRecord record : mCallbackRecords) {
+                final RemoteConnection connection = this;
+                final Callback callback = record.getCallback();
+                record.getHandler().post(new Runnable() {
+                    @Override
+                    public void run() {
+                        callback.onDestroyed(connection);
+                    }
+                });
             }
-            mCallbacks.clear();
+            mCallbackRecords.clear();
 
             mConnected = false;
         }
@@ -868,90 +922,162 @@
     /**
      * @hide
      */
-    void setPostDialWait(String remainingDigits) {
-        for (Callback c : mCallbacks) {
-            c.onPostDialWait(this, remainingDigits);
+    void setPostDialWait(final String remainingDigits) {
+        for (CallbackRecord record : mCallbackRecords) {
+            final RemoteConnection connection = this;
+            final Callback callback = record.getCallback();
+            record.getHandler().post(new Runnable() {
+                @Override
+                public void run() {
+                    callback.onPostDialWait(connection, remainingDigits);
+                }
+            });
         }
     }
 
     /**
      * @hide
      */
-    void onPostDialChar(char nextChar) {
-        for (Callback c : mCallbacks) {
-            c.onPostDialChar(this, nextChar);
+    void onPostDialChar(final char nextChar) {
+        for (CallbackRecord record : mCallbackRecords) {
+            final RemoteConnection connection = this;
+            final Callback callback = record.getCallback();
+            record.getHandler().post(new Runnable() {
+                @Override
+                public void run() {
+                    callback.onPostDialWait(connection, String.valueOf(nextChar));
+                }
+            });
         }
     }
 
     /**
      * @hide
      */
-    void setVideoState(int videoState) {
+    void setVideoState(final int videoState) {
         mVideoState = videoState;
-        for (Callback c : mCallbacks) {
-            c.onVideoStateChanged(this, videoState);
+        for (CallbackRecord record : mCallbackRecords) {
+            final RemoteConnection connection = this;
+            final Callback callback = record.getCallback();
+            record.getHandler().post(new Runnable() {
+                @Override
+                public void run() {
+                    callback.onVideoStateChanged(connection, videoState);
+                }
+            });
         }
     }
 
     /**
      * @hide
      */
-    void setVideoProvider(VideoProvider videoProvider) {
+    void setVideoProvider(final VideoProvider videoProvider) {
         mVideoProvider = videoProvider;
-        for (Callback c : mCallbacks) {
-            c.onVideoProviderChanged(this, videoProvider);
+        for (CallbackRecord record : mCallbackRecords) {
+            final RemoteConnection connection = this;
+            final Callback callback = record.getCallback();
+            record.getHandler().post(new Runnable() {
+                @Override
+                public void run() {
+                    callback.onVideoProviderChanged(connection, videoProvider);
+                }
+            });
         }
     }
 
     /** @hide */
-    void setIsVoipAudioMode(boolean isVoip) {
+    void setIsVoipAudioMode(final boolean isVoip) {
         mIsVoipAudioMode = isVoip;
-        for (Callback c : mCallbacks) {
-            c.onVoipAudioChanged(this, isVoip);
+        for (CallbackRecord record : mCallbackRecords) {
+            final RemoteConnection connection = this;
+            final Callback callback = record.getCallback();
+            record.getHandler().post(new Runnable() {
+                @Override
+                public void run() {
+                    callback.onVoipAudioChanged(connection, isVoip);
+                }
+            });
         }
     }
 
     /** @hide */
-    void setStatusHints(StatusHints statusHints) {
+    void setStatusHints(final StatusHints statusHints) {
         mStatusHints = statusHints;
-        for (Callback c : mCallbacks) {
-            c.onStatusHintsChanged(this, statusHints);
+        for (CallbackRecord record : mCallbackRecords) {
+            final RemoteConnection connection = this;
+            final Callback callback = record.getCallback();
+            record.getHandler().post(new Runnable() {
+                @Override
+                public void run() {
+                    callback.onStatusHintsChanged(connection, statusHints);
+                }
+            });
         }
     }
 
     /** @hide */
-    void setAddress(Uri address, int presentation) {
+    void setAddress(final Uri address, final int presentation) {
         mAddress = address;
         mAddressPresentation = presentation;
-        for (Callback c : mCallbacks) {
-            c.onAddressChanged(this, address, presentation);
+        for (CallbackRecord record : mCallbackRecords) {
+            final RemoteConnection connection = this;
+            final Callback callback = record.getCallback();
+            record.getHandler().post(new Runnable() {
+                @Override
+                public void run() {
+                    callback.onAddressChanged(connection, address, presentation);
+                }
+            });
         }
     }
 
     /** @hide */
-    void setCallerDisplayName(String callerDisplayName, int presentation) {
+    void setCallerDisplayName(final String callerDisplayName, final int presentation) {
         mCallerDisplayName = callerDisplayName;
         mCallerDisplayNamePresentation = presentation;
-        for (Callback c : mCallbacks) {
-            c.onCallerDisplayNameChanged(this, callerDisplayName, presentation);
+        for (CallbackRecord record : mCallbackRecords) {
+            final RemoteConnection connection = this;
+            final Callback callback = record.getCallback();
+            record.getHandler().post(new Runnable() {
+                @Override
+                public void run() {
+                    callback.onCallerDisplayNameChanged(
+                            connection, callerDisplayName, presentation);
+                }
+            });
         }
     }
 
     /** @hide */
-    void setConferenceableConnections(List<RemoteConnection> conferenceableConnections) {
+    void setConferenceableConnections(final List<RemoteConnection> conferenceableConnections) {
         mConferenceableConnections.clear();
         mConferenceableConnections.addAll(conferenceableConnections);
-        for (Callback c : mCallbacks) {
-            c.onConferenceableConnectionsChanged(this, mUnmodifiableconferenceableConnections);
+        for (CallbackRecord record : mCallbackRecords) {
+            final RemoteConnection connection = this;
+            final Callback callback = record.getCallback();
+            record.getHandler().post(new Runnable() {
+                @Override
+                public void run() {
+                    callback.onConferenceableConnectionsChanged(
+                            connection, mUnmodifiableconferenceableConnections);
+                }
+            });
         }
     }
 
     /** @hide */
-    void setConference(RemoteConference conference) {
+    void setConference(final RemoteConference conference) {
         if (mConference != conference) {
             mConference = conference;
-            for (Callback c : mCallbacks) {
-                c.onConferenceChanged(this, conference);
+            for (CallbackRecord record : mCallbackRecords) {
+                final RemoteConnection connection = this;
+                final Callback callback = record.getCallback();
+                record.getHandler().post(new Runnable() {
+                    @Override
+                    public void run() {
+                        callback.onConferenceChanged(connection, conference);
+                    }
+                });
             }
         }
     }
@@ -968,4 +1094,22 @@
     public static RemoteConnection failure(DisconnectCause disconnectCause) {
         return new RemoteConnection(disconnectCause);
     }
+
+    private static final class CallbackRecord extends Callback {
+        private final Callback mCallback;
+        private final Handler mHandler;
+
+        public CallbackRecord(Callback callback, Handler handler) {
+            mCallback = callback;
+            mHandler = handler;
+        }
+
+        public Callback getCallback() {
+            return mCallback;
+        }
+
+        public Handler getHandler() {
+            return mHandler;
+        }
+    }
 }
diff --git a/telecomm/java/android/telecom/VideoCallImpl.java b/telecomm/java/android/telecom/VideoCallImpl.java
index 3779d1a..7a82c1b 100644
--- a/telecomm/java/android/telecom/VideoCallImpl.java
+++ b/telecomm/java/android/telecom/VideoCallImpl.java
@@ -36,13 +36,6 @@
  * {@hide}
  */
 public class VideoCallImpl extends VideoCall {
-    private static final int MSG_RECEIVE_SESSION_MODIFY_REQUEST = 1;
-    private static final int MSG_RECEIVE_SESSION_MODIFY_RESPONSE = 2;
-    private static final int MSG_HANDLE_CALL_SESSION_EVENT = 3;
-    private static final int MSG_CHANGE_PEER_DIMENSIONS = 4;
-    private static final int MSG_CHANGE_CALL_DATA_USAGE = 5;
-    private static final int MSG_CHANGE_CAMERA_CAPABILITIES = 6;
-    private static final int MSG_CHANGE_VIDEO_QUALITY = 7;
 
     private final IVideoProvider mVideoProvider;
     private final VideoCallListenerBinder mBinder;
@@ -61,7 +54,7 @@
     private final class VideoCallListenerBinder extends IVideoCallback.Stub {
         @Override
         public void receiveSessionModifyRequest(VideoProfile videoProfile) {
-            mHandler.obtainMessage(MSG_RECEIVE_SESSION_MODIFY_REQUEST,
+            mHandler.obtainMessage(MessageHandler.MSG_RECEIVE_SESSION_MODIFY_REQUEST,
                     videoProfile).sendToTarget();
         }
 
@@ -72,12 +65,14 @@
             args.arg1 = status;
             args.arg2 = requestProfile;
             args.arg3 = responseProfile;
-            mHandler.obtainMessage(MSG_RECEIVE_SESSION_MODIFY_RESPONSE, args).sendToTarget();
+            mHandler.obtainMessage(MessageHandler.MSG_RECEIVE_SESSION_MODIFY_RESPONSE, args)
+                    .sendToTarget();
         }
 
         @Override
         public void handleCallSessionEvent(int event) {
-            mHandler.obtainMessage(MSG_HANDLE_CALL_SESSION_EVENT, event).sendToTarget();
+            mHandler.obtainMessage(MessageHandler.MSG_HANDLE_CALL_SESSION_EVENT, event)
+                    .sendToTarget();
         }
 
         @Override
@@ -85,28 +80,42 @@
             SomeArgs args = SomeArgs.obtain();
             args.arg1 = width;
             args.arg2 = height;
-            mHandler.obtainMessage(MSG_CHANGE_PEER_DIMENSIONS, args).sendToTarget();
+            mHandler.obtainMessage(MessageHandler.MSG_CHANGE_PEER_DIMENSIONS, args).sendToTarget();
         }
 
         @Override
         public void changeVideoQuality(int videoQuality) {
-            mHandler.obtainMessage(MSG_CHANGE_VIDEO_QUALITY, videoQuality, 0).sendToTarget();
+            mHandler.obtainMessage(MessageHandler.MSG_CHANGE_VIDEO_QUALITY, videoQuality, 0)
+                    .sendToTarget();
         }
 
         @Override
         public void changeCallDataUsage(long dataUsage) {
-            mHandler.obtainMessage(MSG_CHANGE_CALL_DATA_USAGE, dataUsage).sendToTarget();
+            mHandler.obtainMessage(MessageHandler.MSG_CHANGE_CALL_DATA_USAGE, dataUsage)
+                    .sendToTarget();
         }
 
         @Override
         public void changeCameraCapabilities(CameraCapabilities cameraCapabilities) {
-            mHandler.obtainMessage(MSG_CHANGE_CAMERA_CAPABILITIES,
+            mHandler.obtainMessage(MessageHandler.MSG_CHANGE_CAMERA_CAPABILITIES,
                     cameraCapabilities).sendToTarget();
         }
     }
 
     /** Default handler used to consolidate binder method calls onto a single thread. */
-    private final Handler mHandler = new Handler(Looper.getMainLooper()) {
+    private final class MessageHandler extends Handler {
+        private static final int MSG_RECEIVE_SESSION_MODIFY_REQUEST = 1;
+        private static final int MSG_RECEIVE_SESSION_MODIFY_RESPONSE = 2;
+        private static final int MSG_HANDLE_CALL_SESSION_EVENT = 3;
+        private static final int MSG_CHANGE_PEER_DIMENSIONS = 4;
+        private static final int MSG_CHANGE_CALL_DATA_USAGE = 5;
+        private static final int MSG_CHANGE_CAMERA_CAPABILITIES = 6;
+        private static final int MSG_CHANGE_VIDEO_QUALITY = 7;
+
+        public MessageHandler(Looper looper) {
+            super(looper);
+        }
+
         @Override
         public void handleMessage(Message msg) {
             if (mCallback == null) {
@@ -160,7 +169,8 @@
         }
     };
 
-    /** {@hide} */
+    private Handler mHandler;
+
     VideoCallImpl(IVideoProvider videoProvider) throws RemoteException {
         mVideoProvider = videoProvider;
         mVideoProvider.asBinder().linkToDeath(mDeathRecipient, 0);
@@ -169,13 +179,31 @@
         mVideoProvider.addVideoCallback(mBinder);
     }
 
-    /** {@inheritDoc} */
-    public void registerCallback(VideoCall.Callback callback) {
-        mCallback = callback;
+    public void destroy() {
+        unregisterCallback(mCallback);
     }
 
     /** {@inheritDoc} */
-    public void unregisterCallback() {
+    public void registerCallback(VideoCall.Callback callback) {
+        registerCallback(callback, null);
+    }
+
+    /** {@inheritDoc} */
+    public void registerCallback(VideoCall.Callback callback, Handler handler) {
+        mCallback = callback;
+        if (handler == null) {
+            mHandler = new MessageHandler(Looper.getMainLooper());
+        } else {
+            mHandler = new MessageHandler(handler.getLooper());
+        }
+    }
+
+    /** {@inheritDoc} */
+    public void unregisterCallback(VideoCall.Callback callback) {
+        if (callback != mCallback) {
+            return;
+        }
+
         mCallback = null;
         try {
             mVideoProvider.removeVideoCallback(mBinder);