Merge commit '7595842' into merge2

Change-Id: I7e11e98e6d59562374195a8761d64a79dc0268e8
diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java
index 2a30384..d92c0c7 100644
--- a/telecomm/java/android/telecom/Call.java
+++ b/telecomm/java/android/telecom/Call.java
@@ -933,7 +933,8 @@
                     Collections.unmodifiableList(parcelableCall.getCannedSmsResponses());
         }
 
-        boolean videoCallChanged = !Objects.equals(mVideoCall, parcelableCall.getVideoCall());
+        boolean videoCallChanged = parcelableCall.isVideoCallProviderChanged() &&
+                !Objects.equals(mVideoCall, parcelableCall.getVideoCall());
         if (videoCallChanged) {
             mVideoCall = parcelableCall.getVideoCall();
         }
diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java
index e79584f..cd10050 100644
--- a/telecomm/java/android/telecom/Connection.java
+++ b/telecomm/java/android/telecom/Connection.java
@@ -28,6 +28,7 @@
 
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
@@ -402,7 +403,7 @@
          */
         public static final int SESSION_MODIFY_REQUEST_REJECTED_BY_REMOTE = 5;
 
-        private static final int MSG_SET_VIDEO_CALLBACK = 1;
+        private static final int MSG_ADD_VIDEO_CALLBACK = 1;
         private static final int MSG_SET_CAMERA = 2;
         private static final int MSG_SET_PREVIEW_SURFACE = 3;
         private static final int MSG_SET_DISPLAY_SURFACE = 4;
@@ -413,11 +414,16 @@
         private static final int MSG_REQUEST_CAMERA_CAPABILITIES = 9;
         private static final int MSG_REQUEST_CONNECTION_DATA_USAGE = 10;
         private static final int MSG_SET_PAUSE_IMAGE = 11;
+        private static final int MSG_REMOVE_VIDEO_CALLBACK = 12;
 
         private final VideoProvider.VideoProviderHandler
                 mMessageHandler = new VideoProvider.VideoProviderHandler();
         private final VideoProvider.VideoProviderBinder mBinder;
-        private IVideoCallback mVideoCallback;
+
+        /**
+         * Stores a list of the video callbacks, keyed by IBinder.
+         */
+        private HashMap<IBinder, IVideoCallback> mVideoCallbacks = new HashMap<>();
 
         /**
          * Default handler used to consolidate binder method calls onto a single thread.
@@ -426,9 +432,29 @@
             @Override
             public void handleMessage(Message msg) {
                 switch (msg.what) {
-                    case MSG_SET_VIDEO_CALLBACK:
-                        mVideoCallback = IVideoCallback.Stub.asInterface((IBinder) msg.obj);
+                    case MSG_ADD_VIDEO_CALLBACK: {
+                        IBinder binder = (IBinder) msg.obj;
+                        IVideoCallback callback = IVideoCallback.Stub
+                                .asInterface((IBinder) msg.obj);
+                        if (mVideoCallbacks.containsKey(binder)) {
+                            Log.i(this, "addVideoProvider - skipped; already present.");
+                            break;
+                        }
+                        mVideoCallbacks.put(binder, callback);
+                        Log.i(this, "addVideoProvider  "+ mVideoCallbacks.size());
                         break;
+                    }
+                    case MSG_REMOVE_VIDEO_CALLBACK: {
+                        IBinder binder = (IBinder) msg.obj;
+                        IVideoCallback callback = IVideoCallback.Stub
+                                .asInterface((IBinder) msg.obj);
+                        if (!mVideoCallbacks.containsKey(binder)) {
+                            Log.i(this, "removeVideoProvider - skipped; not present.");
+                            break;
+                        }
+                        mVideoCallbacks.remove(binder);
+                        break;
+                    }
                     case MSG_SET_CAMERA:
                         onSetCamera((String) msg.obj);
                         break;
@@ -469,9 +495,14 @@
          * IVideoProvider stub implementation.
          */
         private final class VideoProviderBinder extends IVideoProvider.Stub {
-            public void setVideoCallback(IBinder videoCallbackBinder) {
+            public void addVideoCallback(IBinder videoCallbackBinder) {
                 mMessageHandler.obtainMessage(
-                        MSG_SET_VIDEO_CALLBACK, videoCallbackBinder).sendToTarget();
+                        MSG_ADD_VIDEO_CALLBACK, videoCallbackBinder).sendToTarget();
+            }
+
+            public void removeVideoCallback(IBinder videoCallbackBinder) {
+                mMessageHandler.obtainMessage(
+                        MSG_REMOVE_VIDEO_CALLBACK, videoCallbackBinder).sendToTarget();
             }
 
             public void setCamera(String cameraId) {
@@ -609,21 +640,23 @@
         public abstract void onSetPauseImage(String uri);
 
         /**
-         * Invokes callback method defined in In-Call UI.
+         * Invokes callback method defined in listening {@link InCallService} implementations.
          *
          * @param videoProfile The requested video connection profile.
          */
         public void receiveSessionModifyRequest(VideoProfile videoProfile) {
-            if (mVideoCallback != null) {
+            if (mVideoCallbacks != null) {
                 try {
-                    mVideoCallback.receiveSessionModifyRequest(videoProfile);
+                    for (IVideoCallback callback : mVideoCallbacks.values()) {
+                        callback.receiveSessionModifyRequest(videoProfile);
+                    }
                 } catch (RemoteException ignored) {
                 }
             }
         }
 
         /**
-         * Invokes callback method defined in In-Call UI.
+         * Invokes callback method defined in listening {@link InCallService} implementations.
          *
          * @param status Status of the session modify request.  Valid values are
          *               {@link VideoProvider#SESSION_MODIFY_REQUEST_SUCCESS},
@@ -634,17 +667,19 @@
          */
         public void receiveSessionModifyResponse(int status,
                 VideoProfile requestedProfile, VideoProfile responseProfile) {
-            if (mVideoCallback != null) {
+            if (mVideoCallbacks != null) {
                 try {
-                    mVideoCallback.receiveSessionModifyResponse(
-                            status, requestedProfile, responseProfile);
+                    for (IVideoCallback callback : mVideoCallbacks.values()) {
+                        callback.receiveSessionModifyResponse(status, requestedProfile,
+                                responseProfile);
+                    }
                 } catch (RemoteException ignored) {
                 }
             }
         }
 
         /**
-         * Invokes callback method defined in In-Call UI.
+         * Invokes callback method defined in listening {@link InCallService} implementations.
          *
          * Valid values are: {@link VideoProvider#SESSION_EVENT_RX_PAUSE},
          * {@link VideoProvider#SESSION_EVENT_RX_RESUME},
@@ -654,66 +689,76 @@
          * @param event The event.
          */
         public void handleCallSessionEvent(int event) {
-            if (mVideoCallback != null) {
+            if (mVideoCallbacks != null) {
                 try {
-                    mVideoCallback.handleCallSessionEvent(event);
+                    for (IVideoCallback callback : mVideoCallbacks.values()) {
+                        callback.handleCallSessionEvent(event);
+                    }
                 } catch (RemoteException ignored) {
                 }
             }
         }
 
         /**
-         * Invokes callback method defined in In-Call UI.
+         * Invokes callback method defined in listening {@link InCallService} implementations.
          *
          * @param width  The updated peer video width.
          * @param height The updated peer video height.
          */
         public void changePeerDimensions(int width, int height) {
-            if (mVideoCallback != null) {
+            if (mVideoCallbacks != null) {
                 try {
-                    mVideoCallback.changePeerDimensions(width, height);
+                    for (IVideoCallback callback : mVideoCallbacks.values()) {
+                        callback.changePeerDimensions(width, height);
+                    }
                 } catch (RemoteException ignored) {
                 }
             }
         }
 
         /**
-         * Invokes callback method defined in In-Call UI.
+         * Invokes callback method defined in listening {@link InCallService} implementations.
          *
          * @param dataUsage The updated data usage.
          */
         public void changeCallDataUsage(long dataUsage) {
-            if (mVideoCallback != null) {
+            if (mVideoCallbacks != null) {
                 try {
-                    mVideoCallback.changeCallDataUsage(dataUsage);
+                    for (IVideoCallback callback : mVideoCallbacks.values()) {
+                        callback.changeCallDataUsage(dataUsage);
+                    }
                 } catch (RemoteException ignored) {
                 }
             }
         }
 
         /**
-         * Invokes callback method defined in In-Call UI.
+         * Invokes callback method defined in listening {@link InCallService} implementations.
          *
          * @param cameraCapabilities The changed camera capabilities.
          */
         public void changeCameraCapabilities(CameraCapabilities cameraCapabilities) {
-            if (mVideoCallback != null) {
+            if (mVideoCallbacks != null) {
                 try {
-                    mVideoCallback.changeCameraCapabilities(cameraCapabilities);
+                    for (IVideoCallback callback : mVideoCallbacks.values()) {
+                        callback.changeCameraCapabilities(cameraCapabilities);
+                    }
                 } catch (RemoteException ignored) {
                 }
             }
         }
 
         /**
-         * Invokes callback method defined in In-Call UI.
+         * Invokes callback method defined in listening {@link InCallService} implementations.
          *
          * @param videoQuality The updated video quality.
          */
         public void changeVideoQuality(int videoQuality) {
-            if (mVideoCallback != null) {
+            if (mVideoCallbacks != null) {
                 try {
-                    mVideoCallback.changeVideoQuality(videoQuality);
+                    for (IVideoCallback callback : mVideoCallbacks.values()) {
+                        callback.changeVideoQuality(videoQuality);
+                    }
                 } catch (RemoteException ignored) {
                 }
             }
diff --git a/telecomm/java/android/telecom/InCallService.java b/telecomm/java/android/telecom/InCallService.java
index 7cbc0fc..1cdddd4 100644
--- a/telecomm/java/android/telecom/InCallService.java
+++ b/telecomm/java/android/telecom/InCallService.java
@@ -370,6 +370,11 @@
         public abstract void registerCallback(VideoCall.Callback callback);
 
         /**
+         * Clears the video call listener set via {@link #setVideoCallListener(Listener)}.
+         */
+        public abstract void removeVideoCallListener();
+
+        /**
          * Sets the camera to be used for video recording in a video call.
          *
          * @param cameraId The id of the camera.
diff --git a/telecomm/java/android/telecom/ParcelableCall.java b/telecomm/java/android/telecom/ParcelableCall.java
index c5c3d11..1a30910 100644
--- a/telecomm/java/android/telecom/ParcelableCall.java
+++ b/telecomm/java/android/telecom/ParcelableCall.java
@@ -46,6 +46,7 @@
     private final int mCallerDisplayNamePresentation;
     private final GatewayInfo mGatewayInfo;
     private final PhoneAccountHandle mAccountHandle;
+    private final boolean mIsVideoCallProviderChanged;
     private final IVideoProvider mVideoCallProvider;
     private InCallService.VideoCall mVideoCall;
     private final String mParentCallId;
@@ -69,6 +70,7 @@
             int callerDisplayNamePresentation,
             GatewayInfo gatewayInfo,
             PhoneAccountHandle accountHandle,
+            boolean isVideoCallProviderChanged,
             IVideoProvider videoCallProvider,
             String parentCallId,
             List<String> childCallIds,
@@ -89,6 +91,7 @@
         mCallerDisplayNamePresentation = callerDisplayNamePresentation;
         mGatewayInfo = gatewayInfo;
         mAccountHandle = accountHandle;
+        mIsVideoCallProviderChanged = isVideoCallProviderChanged;
         mVideoCallProvider = videoCallProvider;
         mParentCallId = parentCallId;
         mChildCallIds = childCallIds;
@@ -232,6 +235,18 @@
         return mExtras;
     }
 
+    /**
+     * Indicates to the receiver of the {@link ParcelableCall} whether a change has occurred in the
+     * {@link android.telecom.InCallService.VideoCall} associated with this call.  Since
+     * {@link #getVideoCall()} creates a new {@link VideoCallImpl}, it is useful to know whether
+     * the provider has changed (which can influence whether it is accessed).
+     *
+     * @return {@code true} if the video call changed, {@code false} otherwise.
+     */
+    public boolean isVideoCallProviderChanged() {
+        return mIsVideoCallProviderChanged;
+    }
+
     /** Responsible for creating ParcelableCall objects for deserialized Parcels. */
     public static final Parcelable.Creator<ParcelableCall> CREATOR =
             new Parcelable.Creator<ParcelableCall> () {
@@ -252,6 +267,7 @@
             int callerDisplayNamePresentation = source.readInt();
             GatewayInfo gatewayInfo = source.readParcelable(classLoader);
             PhoneAccountHandle accountHandle = source.readParcelable(classLoader);
+            boolean isVideoCallProviderChanged = source.readByte() == 1;
             IVideoProvider videoCallProvider =
                     IVideoProvider.Stub.asInterface(source.readStrongBinder());
             String parentCallId = source.readString();
@@ -276,6 +292,7 @@
                     callerDisplayNamePresentation,
                     gatewayInfo,
                     accountHandle,
+                    isVideoCallProviderChanged,
                     videoCallProvider,
                     parentCallId,
                     childCallIds,
@@ -313,6 +330,7 @@
         destination.writeInt(mCallerDisplayNamePresentation);
         destination.writeParcelable(mGatewayInfo, 0);
         destination.writeParcelable(mAccountHandle, 0);
+        destination.writeByte((byte) (mIsVideoCallProviderChanged ? 1 : 0));
         destination.writeStrongBinder(
                 mVideoCallProvider != null ? mVideoCallProvider.asBinder() : null);
         destination.writeString(mParentCallId);
diff --git a/telecomm/java/android/telecom/Phone.java b/telecomm/java/android/telecom/Phone.java
index c1c1129..9f47898 100644
--- a/telecomm/java/android/telecom/Phone.java
+++ b/telecomm/java/android/telecom/Phone.java
@@ -122,6 +122,11 @@
     final void internalRemoveCall(Call call) {
         mCallByTelecomCallId.remove(call.internalGetCallId());
         mCalls.remove(call);
+
+        InCallService.VideoCall videoCall = call.getVideoCall();
+        if (videoCall != null) {
+            videoCall.removeVideoCallListener();
+        }
         fireCallRemoved(call);
     }
 
@@ -167,6 +172,10 @@
      */
     final void destroy() {
         for (Call call : mCalls) {
+            InCallService.VideoCall videoCall = call.getVideoCall();
+            if (videoCall != null) {
+                videoCall.removeVideoCallListener();
+            }
             if (call.getState() != Call.STATE_DISCONNECTED) {
                 call.internalSetDisconnected();
             }
diff --git a/telecomm/java/android/telecom/RemoteConnection.java b/telecomm/java/android/telecom/RemoteConnection.java
index 4c423f2..4ecfd50 100644
--- a/telecomm/java/android/telecom/RemoteConnection.java
+++ b/telecomm/java/android/telecom/RemoteConnection.java
@@ -301,7 +301,7 @@
         public VideoProvider(IVideoProvider videoProviderBinder) {
             mVideoProviderBinder = videoProviderBinder;
             try {
-                mVideoProviderBinder.setVideoCallback(mVideoCallbackServant.getStub().asBinder());
+                mVideoProviderBinder.addVideoCallback(mVideoCallbackServant.getStub().asBinder());
             } catch (RemoteException e) {
             }
         }
diff --git a/telecomm/java/android/telecom/VideoCallImpl.java b/telecomm/java/android/telecom/VideoCallImpl.java
index 7bef688..ba064cf 100644
--- a/telecomm/java/android/telecom/VideoCallImpl.java
+++ b/telecomm/java/android/telecom/VideoCallImpl.java
@@ -166,7 +166,7 @@
         mVideoProvider.asBinder().linkToDeath(mDeathRecipient, 0);
 
         mBinder = new VideoCallListenerBinder();
-        mVideoProvider.setVideoCallback(mBinder);
+        mVideoProvider.addVideoCallback(mBinder);
     }
 
     /** {@inheritDoc} */
@@ -175,6 +175,15 @@
     }
 
     /** {@inheritDoc} */
+    public void removeVideoCallListener() {
+        mVideoCallListener = null;
+        try {
+            mVideoProvider.removeVideoCallback(mBinder);
+        } catch (RemoteException e) {
+        }
+    }
+
+    /** {@inheritDoc} */
     public void setCamera(String cameraId) {
         try {
             mVideoProvider.setCamera(cameraId);
diff --git a/telecomm/java/com/android/internal/telecom/IVideoProvider.aidl b/telecomm/java/com/android/internal/telecom/IVideoProvider.aidl
index e96d9d3..bff3865 100644
--- a/telecomm/java/com/android/internal/telecom/IVideoProvider.aidl
+++ b/telecomm/java/com/android/internal/telecom/IVideoProvider.aidl
@@ -25,7 +25,9 @@
  * @hide
  */
 oneway interface IVideoProvider {
-    void setVideoCallback(IBinder videoCallbackBinder);
+    void addVideoCallback(IBinder videoCallbackBinder);
+
+    void removeVideoCallback(IBinder videoCallbackBinder);
 
     void setCamera(String cameraId);