Enable (@hidden) video connection support for RemoteConnections

Bug: 16958934

Change-Id: I2c618743a689303f1bec190da53a1ec42d759370
diff --git a/telecomm/java/android/telecomm/Connection.java b/telecomm/java/android/telecomm/Connection.java
index 6df117e..03db1c3 100644
--- a/telecomm/java/android/telecomm/Connection.java
+++ b/telecomm/java/android/telecomm/Connection.java
@@ -139,7 +139,7 @@
          */
         public static final int SESSION_MODIFY_REQUEST_INVALID = 3;
 
-        private static final int MSG_SET_VIDEO_LISTENER = 1;
+        private static final int MSG_SET_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;
@@ -154,7 +154,7 @@
         private final VideoProvider.VideoProviderHandler
                 mMessageHandler = new VideoProvider.VideoProviderHandler();
         private final VideoProvider.VideoProviderBinder mBinder;
-        private IVideoCallback mVideoListener;
+        private IVideoCallback mVideoCallback;
 
         /**
          * Default handler used to consolidate binder method calls onto a single thread.
@@ -163,8 +163,8 @@
             @Override
             public void handleMessage(Message msg) {
                 switch (msg.what) {
-                    case MSG_SET_VIDEO_LISTENER:
-                        mVideoListener = IVideoCallback.Stub.asInterface((IBinder) msg.obj);
+                    case MSG_SET_VIDEO_CALLBACK:
+                        mVideoCallback = IVideoCallback.Stub.asInterface((IBinder) msg.obj);
                         break;
                     case MSG_SET_CAMERA:
                         onSetCamera((String) msg.obj);
@@ -206,9 +206,9 @@
          * IVideoProvider stub implementation.
          */
         private final class VideoProviderBinder extends IVideoProvider.Stub {
-            public void setVideoListener(IBinder videoListenerBinder) {
+            public void setVideoCallback(IBinder videoCallbackBinder) {
                 mMessageHandler.obtainMessage(
-                        MSG_SET_VIDEO_LISTENER, videoListenerBinder).sendToTarget();
+                        MSG_SET_VIDEO_CALLBACK, videoCallbackBinder).sendToTarget();
             }
 
             public void setCamera(String cameraId) {
@@ -350,9 +350,9 @@
          * @param videoProfile The requested video call profile.
          */
         public void receiveSessionModifyRequest(VideoProfile videoProfile) {
-            if (mVideoListener != null) {
+            if (mVideoCallback != null) {
                 try {
-                    mVideoListener.receiveSessionModifyRequest(videoProfile);
+                    mVideoCallback.receiveSessionModifyRequest(videoProfile);
                 } catch (RemoteException ignored) {
                 }
             }
@@ -370,9 +370,9 @@
          */
         public void receiveSessionModifyResponse(int status,
                 VideoProfile requestedProfile, VideoProfile responseProfile) {
-            if (mVideoListener != null) {
+            if (mVideoCallback != null) {
                 try {
-                    mVideoListener.receiveSessionModifyResponse(
+                    mVideoCallback.receiveSessionModifyResponse(
                             status, requestedProfile, responseProfile);
                 } catch (RemoteException ignored) {
                 }
@@ -390,9 +390,9 @@
          * @param event The event.
          */
         public void handleCallSessionEvent(int event) {
-            if (mVideoListener != null) {
+            if (mVideoCallback != null) {
                 try {
-                    mVideoListener.handleCallSessionEvent(event);
+                    mVideoCallback.handleCallSessionEvent(event);
                 } catch (RemoteException ignored) {
                 }
             }
@@ -405,9 +405,9 @@
          * @param height The updated peer video height.
          */
         public void changePeerDimensions(int width, int height) {
-            if (mVideoListener != null) {
+            if (mVideoCallback != null) {
                 try {
-                    mVideoListener.changePeerDimensions(width, height);
+                    mVideoCallback.changePeerDimensions(width, height);
                 } catch (RemoteException ignored) {
                 }
             }
@@ -419,9 +419,9 @@
          * @param dataUsage The updated data usage.
          */
         public void changeCallDataUsage(int dataUsage) {
-            if (mVideoListener != null) {
+            if (mVideoCallback != null) {
                 try {
-                    mVideoListener.changeCallDataUsage(dataUsage);
+                    mVideoCallback.changeCallDataUsage(dataUsage);
                 } catch (RemoteException ignored) {
                 }
             }
@@ -433,9 +433,9 @@
          * @param cameraCapabilities The changed camera capabilities.
          */
         public void changeCameraCapabilities(CameraCapabilities cameraCapabilities) {
-            if (mVideoListener != null) {
+            if (mVideoCallback != null) {
                 try {
-                    mVideoListener.changeCameraCapabilities(cameraCapabilities);
+                    mVideoCallback.changeCameraCapabilities(cameraCapabilities);
                 } catch (RemoteException ignored) {
                 }
             }
diff --git a/telecomm/java/android/telecomm/RemoteConnection.java b/telecomm/java/android/telecomm/RemoteConnection.java
index 31afb4b..8ad8d19 100644
--- a/telecomm/java/android/telecomm/RemoteConnection.java
+++ b/telecomm/java/android/telecomm/RemoteConnection.java
@@ -17,15 +17,18 @@
 package android.telecomm;
 
 import com.android.internal.telecomm.IConnectionService;
+import com.android.internal.telecomm.IVideoCallback;
+import com.android.internal.telecomm.IVideoProvider;
 
 import android.app.PendingIntent;
 import android.net.Uri;
+import android.os.IBinder;
 import android.os.RemoteException;
 import android.telephony.DisconnectCause;
+import android.view.Surface;
 
 import java.util.ArrayList;
 import java.util.Collections;
-import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
@@ -183,6 +186,18 @@
                 List<RemoteConnection> conferenceableConnections) {}
 
         /**
+         * Indicates that the {@code VideoProvider} associated with this {@code RemoteConnection}
+         * has changed.
+         *
+         * @param connection The {@code RemoteConnection} invoking this method.
+         * @param videoProvider The new {@code VideoProvider} associated with this
+         *         {@code RemoteConnection}.
+         * @hide
+         */
+        public void onVideoProviderChanged(
+                RemoteConnection connection, VideoProvider videoProvider) {}
+
+        /**
          * Indicates that the {@code RemoteConference} that this {@code RemoteConnection} is a part
          * of has changed.
          *
@@ -195,6 +210,185 @@
                 RemoteConference conference) {}
     }
 
+    /** {@hide} */
+    public static class VideoProvider {
+
+        public abstract static class Listener {
+            public void onReceiveSessionModifyRequest(
+                    VideoProvider videoProvider,
+                    VideoProfile videoProfile) {}
+
+            public void onReceiveSessionModifyResponse(
+                    VideoProvider videoProvider,
+                    int status,
+                    VideoProfile requestedProfile,
+                    VideoProfile responseProfile) {}
+
+            public void onHandleCallSessionEvent(VideoProvider videoProvider, int event) {}
+
+            public void onPeerDimensionsChanged(VideoProvider videoProvider, int width, int height) {}
+
+            public void onCallDataUsageChanged(VideoProvider videoProvider, int dataUsage) {}
+
+            public void onCameraCapabilitiesChanged(
+                    VideoProvider videoProvider,
+                    CameraCapabilities cameraCapabilities) {}
+        }
+
+        private final IVideoCallback mVideoCallbackDelegate = new IVideoCallback() {
+            @Override
+            public void receiveSessionModifyRequest(VideoProfile videoProfile) {
+                for (Listener l : mListeners) {
+                    l.onReceiveSessionModifyRequest(VideoProvider.this, videoProfile);
+                }
+            }
+
+            @Override
+            public void receiveSessionModifyResponse(int status, VideoProfile requestedProfile,
+                    VideoProfile responseProfile) {
+                for (Listener l : mListeners) {
+                    l.onReceiveSessionModifyResponse(
+                            VideoProvider.this,
+                            status,
+                            requestedProfile,
+                            responseProfile);
+                }
+            }
+
+            @Override
+            public void handleCallSessionEvent(int event) {
+                for (Listener l : mListeners) {
+                    l.onHandleCallSessionEvent(VideoProvider.this, event);
+                }
+            }
+
+            @Override
+            public void changePeerDimensions(int width, int height) {
+                for (Listener l : mListeners) {
+                    l.onPeerDimensionsChanged(VideoProvider.this, width, height);
+                }
+            }
+
+            @Override
+            public void changeCallDataUsage(int dataUsage) {
+                for (Listener l : mListeners) {
+                    l.onCallDataUsageChanged(VideoProvider.this, dataUsage);
+                }
+            }
+
+            @Override
+            public void changeCameraCapabilities(CameraCapabilities cameraCapabilities) {
+                for (Listener l : mListeners) {
+                    l.onCameraCapabilitiesChanged(VideoProvider.this, cameraCapabilities);
+                }
+            }
+
+            @Override
+            public IBinder asBinder() {
+                return null;
+            }
+        };
+
+        private final VideoCallbackServant mVideoCallbackServant =
+                new VideoCallbackServant(mVideoCallbackDelegate);
+
+        private final IVideoProvider mVideoProviderBinder;
+
+        /**
+         * ConcurrentHashMap constructor params: 8 is initial table size, 0.9f is
+         * 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<Listener> mListeners = Collections.newSetFromMap(
+                new ConcurrentHashMap<Listener, Boolean>(8, 0.9f, 1));
+
+        public VideoProvider(IVideoProvider videoProviderBinder) {
+            mVideoProviderBinder = videoProviderBinder;
+            try {
+                mVideoProviderBinder.setVideoCallback(mVideoCallbackServant.getStub().asBinder());
+            } catch (RemoteException e) {
+            }
+        }
+
+        public void addListener(Listener l) {
+            mListeners.add(l);
+        }
+
+        public void removeListener(Listener l) {
+            mListeners.remove(l);
+        }
+
+        public void setCamera(String cameraId) {
+            try {
+                mVideoProviderBinder.setCamera(cameraId);
+            } catch (RemoteException e) {
+            }
+        }
+
+        public void setPreviewSurface(Surface surface) {
+            try {
+                mVideoProviderBinder.setPreviewSurface(surface);
+            } catch (RemoteException e) {
+            }
+        }
+
+        public void setDisplaySurface(Surface surface) {
+            try {
+                mVideoProviderBinder.setDisplaySurface(surface);
+            } catch (RemoteException e) {
+            }
+        }
+
+        public void setDeviceOrientation(int rotation) {
+            try {
+                mVideoProviderBinder.setDeviceOrientation(rotation);
+            } catch (RemoteException e) {
+            }
+        }
+
+        public void setZoom(float value) {
+            try {
+                mVideoProviderBinder.setZoom(value);
+            } catch (RemoteException e) {
+            }
+        }
+
+        public void sendSessionModifyRequest(VideoProfile reqProfile) {
+            try {
+                mVideoProviderBinder.sendSessionModifyRequest(reqProfile);
+            } catch (RemoteException e) {
+            }
+        }
+
+        public void sendSessionModifyResponse(VideoProfile responseProfile) {
+            try {
+                mVideoProviderBinder.sendSessionModifyResponse(responseProfile);
+            } catch (RemoteException e) {
+            }
+        }
+
+        public void requestCameraCapabilities() {
+            try {
+                mVideoProviderBinder.requestCameraCapabilities();
+            } catch (RemoteException e) {
+            }
+        }
+
+        public void requestCallDataUsage() {
+            try {
+                mVideoProviderBinder.requestCallDataUsage();
+            } catch (RemoteException e) {
+            }
+        }
+
+        public void setPauseImage(String uri) {
+            try {
+                mVideoProviderBinder.setPauseImage(uri);
+            } catch (RemoteException e) {
+            }
+        }
+    }
+
     private IConnectionService mConnectionService;
     private final String mConnectionId;
     /**
@@ -215,6 +409,7 @@
     private boolean mConnected;
     private int mCallCapabilities;
     private int mVideoState;
+    private VideoProvider mVideoProvider;
     private boolean mAudioModeIsVoip;
     private StatusHints mStatusHints;
     private Uri mHandle;
@@ -380,6 +575,14 @@
     }
 
     /**
+     * @return The video provider associated with this {@code RemoteConnection}.
+     * @hide
+     */
+    public final VideoProvider getVideoProvider() {
+        return mVideoProvider;
+    }
+
+    /**
      * @return The failure code ({@see DisconnectCause}) associated with this failed
      * {@code RemoteConnection}.
      */
@@ -684,6 +887,16 @@
         }
     }
 
+    /**
+     * @hide
+     */
+    void setVideoProvider(VideoProvider videoProvider) {
+        mVideoProvider = videoProvider;
+        for (Listener l : mListeners) {
+            l.onVideoProviderChanged(this, videoProvider);
+        }
+    }
+
     /** @hide */
     void setAudioModeIsVoip(boolean isVoip) {
         mAudioModeIsVoip = isVoip;
diff --git a/telecomm/java/android/telecomm/RemoteConnectionService.java b/telecomm/java/android/telecomm/RemoteConnectionService.java
index 8ad0ad0..348c36c 100644
--- a/telecomm/java/android/telecomm/RemoteConnectionService.java
+++ b/telecomm/java/android/telecomm/RemoteConnectionService.java
@@ -74,7 +74,7 @@
                     }
                 }
                 connection.setConferenceableConnections(conferenceable);
-                // TODO: Do we need to support video providers for remote connections?
+                connection.setVideoState(parcel.getVideoState());
                 if (connection.getState() == Connection.STATE_DISCONNECTED) {
                     // ... then, if it was created in a disconnected state, that indicates
                     // failure on the providing end, so immediately mark it destroyed
@@ -226,7 +226,8 @@
 
         @Override
         public void setVideoProvider(String callId, IVideoProvider videoProvider) {
-            // not supported for remote connections.
+            findConnectionForAction(callId, "setVideoProvider")
+                    .setVideoProvider(new RemoteConnection.VideoProvider(videoProvider));
         }
 
         @Override
diff --git a/telecomm/java/android/telecomm/VideoCallImpl.java b/telecomm/java/android/telecomm/VideoCallImpl.java
index c10865f..d33a351 100644
--- a/telecomm/java/android/telecomm/VideoCallImpl.java
+++ b/telecomm/java/android/telecomm/VideoCallImpl.java
@@ -157,7 +157,7 @@
         mVideoProvider.asBinder().linkToDeath(mDeathRecipient, 0);
 
         mBinder = new VideoCallListenerBinder();
-        mVideoProvider.setVideoListener(mBinder);
+        mVideoProvider.setVideoCallback(mBinder);
     }
 
     /** {@inheritDoc} */
diff --git a/telecomm/java/android/telecomm/VideoCallbackServant.java b/telecomm/java/android/telecomm/VideoCallbackServant.java
new file mode 100644
index 0000000..060b8a9
--- /dev/null
+++ b/telecomm/java/android/telecomm/VideoCallbackServant.java
@@ -0,0 +1,160 @@
+/*
+ * 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
+ R* limitations under the License.
+ */
+
+package android.telecomm;
+
+import com.android.internal.os.SomeArgs;
+import com.android.internal.telecomm.IVideoCallback;
+
+import android.os.Handler;
+import android.os.Message;
+import android.os.RemoteException;
+
+/**
+ * A component that provides an RPC servant implementation of {@link IVideoCallback},
+ * posting incoming messages on the main thread on a client-supplied delegate object.
+ *
+ * TODO: Generate this and similar classes using a compiler starting from AIDL interfaces.
+ *
+ * @hide
+ */
+final class VideoCallbackServant {
+    private static final int MSG_RECEIVE_SESSION_MODIFY_REQUEST = 0;
+    private static final int MSG_RECEIVE_SESSION_MODIFY_RESPONSE = 1;
+    private static final int MSG_HANDLE_CALL_SESSION_EVENT = 2;
+    private static final int MSG_CHANGE_PEER_DIMENSIONS = 3;
+    private static final int MSG_CHANGE_CALL_DATA_USAGE = 4;
+    private static final int MSG_CHANGE_CAMERA_CAPABILITIES = 5;
+
+    private final IVideoCallback mDelegate;
+
+    private final Handler mHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            try {
+                internalHandleMessage(msg);
+            } catch (RemoteException e) {
+            }
+        }
+
+        // Internal method defined to centralize handling of RemoteException
+        private void internalHandleMessage(Message msg) throws RemoteException {
+            switch (msg.what) {
+                case MSG_RECEIVE_SESSION_MODIFY_REQUEST: {
+                    mDelegate.receiveSessionModifyRequest((VideoProfile) msg.obj);
+                    break;
+                }
+                case MSG_RECEIVE_SESSION_MODIFY_RESPONSE: {
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    try {
+                        mDelegate.receiveSessionModifyResponse(
+                                args.argi1,
+                                (VideoProfile) args.arg1,
+                                (VideoProfile) args.arg2);
+                    } finally {
+                        args.recycle();
+                    }
+                    break;
+                }
+                case MSG_HANDLE_CALL_SESSION_EVENT: {
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    try {
+                        mDelegate.handleCallSessionEvent(args.argi1);
+                    } finally {
+                        args.recycle();
+                    }
+                    break;
+                }
+                case MSG_CHANGE_PEER_DIMENSIONS: {
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    try {
+                        mDelegate.changePeerDimensions(args.argi1, args.argi2);
+                    } finally {
+                        args.recycle();
+                    }
+                    break;
+                }
+                case MSG_CHANGE_CALL_DATA_USAGE: {
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    try {
+                        mDelegate.changeCallDataUsage(args.argi1);
+                    } finally {
+                        args.recycle();
+                    }
+                    break;
+                }
+                case MSG_CHANGE_CAMERA_CAPABILITIES: {
+                    mDelegate.changeCameraCapabilities((CameraCapabilities) msg.obj);
+                    break;
+                }
+            }
+        }
+    };
+
+    private final IVideoCallback mStub = new IVideoCallback.Stub() {
+        @Override
+        public void receiveSessionModifyRequest(VideoProfile videoProfile) throws RemoteException {
+            mHandler.obtainMessage(MSG_RECEIVE_SESSION_MODIFY_REQUEST, videoProfile).sendToTarget();
+        }
+
+        @Override
+        public void receiveSessionModifyResponse(int status, VideoProfile requestedProfile,
+                VideoProfile responseProfile) throws RemoteException {
+            SomeArgs args = SomeArgs.obtain();
+            args.argi1 = status;
+            args.arg1 = requestedProfile;
+            args.arg2 = responseProfile;
+            mHandler.obtainMessage(MSG_RECEIVE_SESSION_MODIFY_RESPONSE, args).sendToTarget();
+        }
+
+        @Override
+        public void handleCallSessionEvent(int event) throws RemoteException {
+            SomeArgs args = SomeArgs.obtain();
+            args.argi1 = event;
+            mHandler.obtainMessage(MSG_HANDLE_CALL_SESSION_EVENT, args).sendToTarget();
+        }
+
+        @Override
+        public void changePeerDimensions(int width, int height) throws RemoteException {
+            SomeArgs args = SomeArgs.obtain();
+            args.argi1 = width;
+            args.argi2 = height;
+            mHandler.obtainMessage(MSG_CHANGE_PEER_DIMENSIONS, args).sendToTarget();
+        }
+
+        @Override
+        public void changeCallDataUsage(int dataUsage) throws RemoteException {
+            SomeArgs args = SomeArgs.obtain();
+            args.argi1 = dataUsage;
+            mHandler.obtainMessage(MSG_CHANGE_CALL_DATA_USAGE, args).sendToTarget();
+        }
+
+        @Override
+        public void changeCameraCapabilities(CameraCapabilities cameraCapabilities)
+                throws RemoteException {
+            mHandler.obtainMessage(MSG_CHANGE_CAMERA_CAPABILITIES, cameraCapabilities)
+                    .sendToTarget();
+        }
+    };
+
+    public VideoCallbackServant(IVideoCallback delegate) {
+        mDelegate = delegate;
+    }
+
+    public IVideoCallback getStub() {
+        return mStub;
+    }
+}