Merge "Play tone when video upgrade request is received." into mnc-dev
diff --git a/src/com/android/server/telecom/Call.java b/src/com/android/server/telecom/Call.java
index cf123e7..dfe5849 100644
--- a/src/com/android/server/telecom/Call.java
+++ b/src/com/android/server/telecom/Call.java
@@ -23,11 +23,13 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Looper;
+import android.os.RemoteException;
 import android.os.Trace;
 import android.provider.ContactsContract.Contacts;
 import android.telecom.DisconnectCause;
 import android.telecom.Connection;
 import android.telecom.GatewayInfo;
+import android.telecom.InCallService.VideoCall;
 import android.telecom.ParcelableConnection;
 import android.telecom.PhoneAccount;
 import android.telecom.PhoneAccountHandle;
@@ -306,6 +308,7 @@
     private boolean mCannedSmsResponsesLoadingStarted = false;
 
     private IVideoProvider mVideoProvider;
+    private VideoProviderProxy mVideoProviderProxy;
 
     private boolean mIsVoipAudioMode;
     private StatusHints mStatusHints;
@@ -1506,17 +1509,41 @@
      * Sets a video call provider for the call.
      */
     public void setVideoProvider(IVideoProvider videoProvider) {
+        Log.v(this, "setVideoProvider");
+
+        if (videoProvider != null ) {
+            try {
+                mVideoProviderProxy = new VideoProviderProxy(mLock, videoProvider, this);
+            } catch (RemoteException ignored) {
+                // Ignore RemoteException.
+            }
+        } else {
+            mVideoProviderProxy = null;
+        }
+
         mVideoProvider = videoProvider;
+
         for (Listener l : mListeners) {
             l.onVideoCallProviderChanged(Call.this);
         }
     }
 
     /**
-     * @return Return the {@link Connection.VideoProvider} binder.
+     * @return The {@link Connection.VideoProvider} binder.
      */
     public IVideoProvider getVideoProvider() {
-        return mVideoProvider;
+        if (mVideoProviderProxy == null) {
+            return null;
+        }
+
+        return mVideoProviderProxy.getInterface();
+    }
+
+    /**
+     * @return The {@link VideoProviderProxy} for this call.
+     */
+    public VideoProviderProxy getVideoProviderProxy() {
+        return mVideoProviderProxy;
     }
 
     /**
diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java
index 636f721..a53f888 100644
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -59,7 +59,7 @@
  * beyond the com.android.server.telecom package boundary.
  */
 @VisibleForTesting
-public class CallsManager extends Call.ListenerBase {
+public class CallsManager extends Call.ListenerBase implements VideoProviderProxy.Listener {
 
     // TODO: Consider renaming this CallsManagerPlugin.
     interface CallsManagerListener {
@@ -79,6 +79,7 @@
         void onIsVoipAudioModeChanged(Call call);
         void onVideoStateChanged(Call call);
         void onCanAddCallChanged(boolean canAddCall);
+        void onSessionModifyRequestReceived(Call call, VideoProfile videoProfile);
     }
 
     private static final String TAG = "CallsManager";
@@ -372,6 +373,45 @@
         return true;
     }
 
+    /**
+     * Handles changes to the {@link Connection.VideoProvider} for a call.  Adds the
+     * {@link CallsManager} as a listener for the {@link VideoProviderProxy} which is created
+     * in {@link Call#setVideoProvider(IVideoProvider)}.  This allows the {@link CallsManager} to
+     * respond to callbacks from the {@link VideoProviderProxy}.
+     *
+     * @param call The call.
+     */
+    @Override
+    public void onVideoCallProviderChanged(Call call) {
+        VideoProviderProxy videoProviderProxy = call.getVideoProviderProxy();
+
+        if (videoProviderProxy == null) {
+            return;
+        }
+
+        videoProviderProxy.addListener(this);
+    }
+
+    /**
+     * Handles session modification requests received via the {@link TelecomVideoCallCallback} for
+     * a call.  Notifies listeners of the {@link CallsManager.CallsManagerListener} of the session
+     * modification request.
+     *
+     * @param call The call.
+     * @param videoProfile The {@link VideoProfile}.
+     */
+    @Override
+    public void onSessionModifyRequestReceived(Call call, VideoProfile videoProfile) {
+        int videoState = videoProfile != null ? videoProfile.getVideoState() :
+                VideoProfile.STATE_AUDIO_ONLY;
+        Log.v(TAG, "onSessionModifyRequestReceived : videoProfile = " + VideoProfile
+                .videoStateToString(videoState));
+
+        for (CallsManagerListener listener : mListeners) {
+            listener.onSessionModifyRequestReceived(call, videoProfile);
+        }
+    }
+
     Collection<Call> getCalls() {
         return Collections.unmodifiableCollection(mCalls);
     }
diff --git a/src/com/android/server/telecom/CallsManagerListenerBase.java b/src/com/android/server/telecom/CallsManagerListenerBase.java
index 129f9ae..58085a0 100644
--- a/src/com/android/server/telecom/CallsManagerListenerBase.java
+++ b/src/com/android/server/telecom/CallsManagerListenerBase.java
@@ -18,6 +18,7 @@
 
 import android.telecom.AudioState;
 import android.telecom.CallAudioState;
+import android.telecom.VideoProfile;
 
 /**
  * Provides a default implementation for listeners of CallsManager.
@@ -78,4 +79,9 @@
     @Override
     public void onCanAddCallChanged(boolean canAddCall) {
     }
+
+    @Override
+    public void onSessionModifyRequestReceived(Call call, VideoProfile videoProfile) {
+
+    }
 }
diff --git a/src/com/android/server/telecom/InCallToneMonitor.java b/src/com/android/server/telecom/InCallToneMonitor.java
index e25d207..afe0f06 100644
--- a/src/com/android/server/telecom/InCallToneMonitor.java
+++ b/src/com/android/server/telecom/InCallToneMonitor.java
@@ -17,6 +17,8 @@
 package com.android.server.telecom;
 
 import android.media.ToneGenerator;
+import android.telecom.Connection;
+import android.telecom.VideoProfile;
 
 import java.util.Collection;
 
@@ -77,4 +79,36 @@
             }
         }
     }
+
+    /**
+     * Handles requests received via the {@link VideoProviderProxy} requesting a change in the video
+     * state of the call by the peer.  If the request involves the peer turning their camera on,
+     * the call waiting tone is played to inform the user of the incoming request.
+     *
+     * @param call The call.
+     * @param videoProfile The requested video profile.
+     */
+    @Override
+    public void onSessionModifyRequestReceived(Call call, VideoProfile videoProfile) {
+        if (videoProfile == null) {
+            return;
+        }
+
+        if (mCallsManager.getForegroundCall() != call) {
+            // We only play tones for foreground calls.
+            return;
+        }
+
+        int previousVideoState = call.getVideoState();
+        int newVideoState = videoProfile.getVideoState();
+        Log.v(this, "onSessionModifyRequestReceived : videoProfile = " + VideoProfile
+                .videoStateToString(newVideoState));
+
+        boolean isUpgradeRequest = !VideoProfile.isReceptionEnabled(previousVideoState) &&
+                VideoProfile.isReceptionEnabled(newVideoState);
+
+        if (isUpgradeRequest) {
+            mPlayerFactory.createPlayer(InCallTonePlayer.TONE_VIDEO_UPGRADE).startTone();
+        }
+    }
 }
diff --git a/src/com/android/server/telecom/InCallTonePlayer.java b/src/com/android/server/telecom/InCallTonePlayer.java
index f51a639..0d2e3c4 100644
--- a/src/com/android/server/telecom/InCallTonePlayer.java
+++ b/src/com/android/server/telecom/InCallTonePlayer.java
@@ -60,6 +60,7 @@
     public static final int TONE_RING_BACK = 11;
     public static final int TONE_UNOBTAINABLE_NUMBER = 12;
     public static final int TONE_VOICE_PRIVACY = 13;
+    public static final int TONE_VIDEO_UPGRADE = 14;
 
     private static final int RELATIVE_VOLUME_EMERGENCY = 100;
     private static final int RELATIVE_VOLUME_HIPRI = 80;
@@ -183,6 +184,12 @@
                 case TONE_VOICE_PRIVACY:
                     // TODO: fill in.
                     throw new IllegalStateException("Voice privacy tone NYI.");
+                case TONE_VIDEO_UPGRADE:
+                    // Similar to the call waiting tone, but does not repeat.
+                    toneType = ToneGenerator.TONE_SUP_CALL_WAITING;
+                    toneVolume = RELATIVE_VOLUME_HIPRI;
+                    toneLengthMillis = 4000;
+                    break;
                 default:
                     throw new IllegalStateException("Bad toneId: " + mToneId);
             }
diff --git a/src/com/android/server/telecom/VideoProviderProxy.java b/src/com/android/server/telecom/VideoProviderProxy.java
new file mode 100644
index 0000000..7dcfdfb
--- /dev/null
+++ b/src/com/android/server/telecom/VideoProviderProxy.java
@@ -0,0 +1,440 @@
+/*
+ * 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 com.android.server.telecom;
+
+import android.net.Uri;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.telecom.Connection;
+import android.telecom.InCallService;
+import android.telecom.VideoProfile;
+import android.view.Surface;
+
+import com.android.internal.telecom.IVideoCallback;
+import com.android.internal.telecom.IVideoProvider;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * Proxies video provider messages from {@link InCallService.VideoCall}
+ * implementations to the underlying {@link Connection.VideoProvider} implementation.  Also proxies
+ * callbacks from the {@link Connection.VideoProvider} to {@link InCallService.VideoCall}
+ * implementations.
+ *
+ * Also provides a means for Telecom to send and receive these messages.
+ */
+public class VideoProviderProxy extends Connection.VideoProvider {
+
+    /**
+     * Listener for Telecom components interested in callbacks from the video provider.
+     */
+    interface Listener {
+        void onSessionModifyRequestReceived(Call call, VideoProfile videoProfile);
+    }
+
+    /**
+     * Set of listeners on this VideoProviderProxy.
+     *
+     * 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));
+
+    /** The TelecomSystem SyncRoot used for synchronized operations. */
+    private final TelecomSystem.SyncRoot mLock;
+
+    /**
+     * The {@link android.telecom.Connection.VideoProvider} implementation residing with the
+     * {@link android.telecom.ConnectionService} which is being wrapped by this
+     * {@link VideoProviderProxy}.
+     */
+    private final IVideoProvider mConectionServiceVideoProvider;
+
+    /**
+     * Binder used to bind to the {@link android.telecom.ConnectionService}'s
+     * {@link com.android.internal.telecom.IVideoCallback}.
+     */
+    private final VideoCallListenerBinder mVideoCallListenerBinder;
+
+    /**
+     * The Telecom {@link Call} this {@link VideoProviderProxy} is associated with.
+     */
+    private Call mCall;
+
+    private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
+        @Override
+        public void binderDied() {
+            mConectionServiceVideoProvider.asBinder().unlinkToDeath(this, 0);
+        }
+    };
+
+    /**
+     * Creates a new instance of the {@link VideoProviderProxy}, binding it to the passed in
+     * {@code videoProvider} residing with the {@link android.telecom.ConnectionService}.
+     *
+     *
+     * @param lock
+     * @param videoProvider The {@link android.telecom.ConnectionService}'s video provider.
+     * @param call The current call.
+     * @throws RemoteException Remote exception.
+     */
+    VideoProviderProxy(TelecomSystem.SyncRoot lock,
+            IVideoProvider videoProvider, Call call) throws RemoteException {
+
+        super(Looper.getMainLooper());
+
+        mLock = lock;
+
+        mConectionServiceVideoProvider = videoProvider;
+        mConectionServiceVideoProvider.asBinder().linkToDeath(mDeathRecipient, 0);
+
+        mVideoCallListenerBinder = new VideoCallListenerBinder();
+        mConectionServiceVideoProvider.addVideoCallback(mVideoCallListenerBinder);
+        mCall = call;
+    }
+
+    /**
+     * IVideoCallback stub implementation.  An instance of this class receives callbacks from the
+     * {@code ConnectionService}'s video provider.
+     */
+    private final class VideoCallListenerBinder extends IVideoCallback.Stub {
+        /**
+         * Proxies a request from the {@link #mConectionServiceVideoProvider} to the
+         * {@link InCallService} when a session modification request is received.
+         *
+         * @param videoProfile The requested video profile.
+         */
+        @Override
+        public void receiveSessionModifyRequest(VideoProfile videoProfile) {
+            synchronized (mLock) {
+                logFromVideoProvider("receiveSessionModifyRequest: " + videoProfile);
+
+                // Inform other Telecom components of the session modification request.
+                for (Listener listener : mListeners) {
+                    listener.onSessionModifyRequestReceived(mCall, videoProfile);
+                }
+
+                VideoProviderProxy.this.receiveSessionModifyRequest(videoProfile);
+            }
+        }
+
+        /**
+         * Proxies a request from the {@link #mConectionServiceVideoProvider} to the
+         * {@link InCallService} when a session modification response is received.
+         *
+         * @param status The status of the response.
+         * @param requestProfile The requested video profile.
+         * @param responseProfile The response video profile.
+         */
+        @Override
+        public void receiveSessionModifyResponse(int status, VideoProfile requestProfile,
+                VideoProfile responseProfile) {
+            synchronized (mLock) {
+                logFromVideoProvider("receiveSessionModifyResponse: status=" + status +
+                        " requestProfile=" + requestProfile + " responseProfile=" +
+                        responseProfile);
+                VideoProviderProxy.this.receiveSessionModifyResponse(status, requestProfile,
+                        responseProfile);
+            }
+        }
+
+        /**
+         * Proxies a request from the {@link #mConectionServiceVideoProvider} to the
+         * {@link InCallService} when a call session event occurs.
+         *
+         * @param event The call session event.
+         */
+        @Override
+        public void handleCallSessionEvent(int event) {
+            synchronized (mLock) {
+                logFromVideoProvider("handleCallSessionEvent: " + event);
+                VideoProviderProxy.this.handleCallSessionEvent(event);
+            }
+        }
+
+        /**
+         * Proxies a request from the {@link #mConectionServiceVideoProvider} to the
+         * {@link InCallService} when the peer dimensions change.
+         *
+         * @param width The width of the peer's video.
+         * @param height The height of the peer's video.
+         */
+        @Override
+        public void changePeerDimensions(int width, int height) {
+            synchronized (mLock) {
+                logFromVideoProvider("changePeerDimensions: width=" + width + " height=" +
+                        height);
+                VideoProviderProxy.this.changePeerDimensions(width, height);
+            }
+        }
+
+        /**
+         * Proxies a request from the {@link #mConectionServiceVideoProvider} to the
+         * {@link InCallService} when the video quality changes.
+         *
+         * @param videoQuality The video quality.
+         */
+        @Override
+        public void changeVideoQuality(int videoQuality) {
+            synchronized (mLock) {
+                logFromVideoProvider("changeVideoQuality: " + videoQuality);
+                VideoProviderProxy.this.changeVideoQuality(videoQuality);
+            }
+        }
+
+        /**
+         * Proxies a request from the {@link #mConectionServiceVideoProvider} to the
+         * {@link InCallService} when the call data usage changes.
+         *
+         * @param dataUsage The data usage.
+         */
+        @Override
+        public void changeCallDataUsage(long dataUsage) {
+            synchronized (mLock) {
+                logFromVideoProvider("changeCallDataUsage: " + dataUsage);
+                VideoProviderProxy.this.setCallDataUsage(dataUsage);
+            }
+        }
+
+        /**
+         * Proxies a request from the {@link #mConectionServiceVideoProvider} to the
+         * {@link InCallService} when the camera capabilities change.
+         *
+         * @param cameraCapabilities The camera capabilities.
+         */
+        @Override
+        public void changeCameraCapabilities(VideoProfile.CameraCapabilities cameraCapabilities) {
+            synchronized (mLock) {
+                logFromVideoProvider("changeCameraCapabilities: " + cameraCapabilities);
+                VideoProviderProxy.this.changeCameraCapabilities(cameraCapabilities);
+            }
+        }
+    }
+
+    /**
+     * Proxies a request from the {@link InCallService} to the
+     * {@link #mConectionServiceVideoProvider} to change the camera.
+     *
+     * @param cameraId The id of the camera.
+     */
+    @Override
+    public void onSetCamera(String cameraId) {
+        synchronized (mLock) {
+            logFromInCall("setCamera: " + cameraId);
+            try {
+                mConectionServiceVideoProvider.setCamera(cameraId);
+            } catch (RemoteException e) {
+            }
+        }
+    }
+
+    /**
+     * Proxies a request from the {@link InCallService} to the
+     * {@link #mConectionServiceVideoProvider} to set the preview surface.
+     *
+     * @param surface The surface.
+     */
+    @Override
+    public void onSetPreviewSurface(Surface surface) {
+        synchronized (mLock) {
+            logFromInCall("setPreviewSurface");
+            try {
+                mConectionServiceVideoProvider.setPreviewSurface(surface);
+            } catch (RemoteException e) {
+            }
+        }
+    }
+
+    /**
+     * Proxies a request from the {@link InCallService} to the
+     * {@link #mConectionServiceVideoProvider} to change the display surface.
+     *
+     * @param surface The surface.
+     */
+    @Override
+    public void onSetDisplaySurface(Surface surface) {
+        synchronized (mLock) {
+            logFromInCall("setDisplaySurface");
+            try {
+                mConectionServiceVideoProvider.setDisplaySurface(surface);
+            } catch (RemoteException e) {
+            }
+        }
+    }
+
+    /**
+     * Proxies a request from the {@link InCallService} to the
+     * {@link #mConectionServiceVideoProvider} to change the device orientation.
+     *
+     * @param rotation The device orientation, in degrees.
+     */
+    @Override
+    public void onSetDeviceOrientation(int rotation) {
+        synchronized (mLock) {
+            logFromInCall("setDeviceOrientation: " + rotation);
+            try {
+                mConectionServiceVideoProvider.setDeviceOrientation(rotation);
+            } catch (RemoteException e) {
+            }
+        }
+    }
+
+    /**
+     * Proxies a request from the {@link InCallService} to the
+     * {@link #mConectionServiceVideoProvider} to change the camera zoom ratio.
+     *
+     * @param value The camera zoom ratio.
+     */
+    @Override
+    public void onSetZoom(float value) {
+        synchronized (mLock) {
+            logFromInCall("setZoom: " + value);
+            try {
+                mConectionServiceVideoProvider.setZoom(value);
+            } catch (RemoteException e) {
+            }
+        }
+    }
+
+    /**
+     * Proxies a request from the {@link InCallService} to the
+     * {@link #mConectionServiceVideoProvider} to provide a response to a session modification
+     * request.
+     *
+     * @param fromProfile The video properties prior to the request.
+     * @param toProfile The video properties with the requested changes made.
+     */
+    @Override
+    public void onSendSessionModifyRequest(VideoProfile fromProfile, VideoProfile toProfile) {
+        synchronized (mLock) {
+            logFromInCall("sendSessionModifyRequest: from=" + fromProfile + " to=" + toProfile);
+            try {
+                mConectionServiceVideoProvider.sendSessionModifyRequest(fromProfile, toProfile);
+            } catch (RemoteException e) {
+            }
+        }
+    }
+
+    /**
+     * Proxies a request from the {@link InCallService} to the
+     * {@link #mConectionServiceVideoProvider} to send a session modification request.
+     *
+     * @param responseProfile The response connection video properties.
+     */
+    @Override
+    public void onSendSessionModifyResponse(VideoProfile responseProfile) {
+        synchronized (mLock) {
+            logFromInCall("sendSessionModifyResponse: " + responseProfile);
+            try {
+                mConectionServiceVideoProvider.sendSessionModifyResponse(responseProfile);
+            } catch (RemoteException e) {
+            }
+        }
+    }
+
+    /**
+     * Proxies a request from the {@link InCallService} to the
+     * {@link #mConectionServiceVideoProvider} to request the camera capabilities.
+     */
+    @Override
+    public void onRequestCameraCapabilities() {
+        synchronized (mLock) {
+            logFromInCall("requestCameraCapabilities");
+            try {
+                mConectionServiceVideoProvider.requestCameraCapabilities();
+            } catch (RemoteException e) {
+            }
+        }
+    }
+
+    /**
+     * Proxies a request from the {@link InCallService} to the
+     * {@link #mConectionServiceVideoProvider} to request the connection data usage.
+     */
+    @Override
+    public void onRequestConnectionDataUsage() {
+        synchronized (mLock) {
+            logFromInCall("requestCallDataUsage");
+            try {
+                mConectionServiceVideoProvider.requestCallDataUsage();
+            } catch (RemoteException e) {
+            }
+        }
+    }
+
+    /**
+     * Proxies a request from the {@link InCallService} to the
+     * {@link #mConectionServiceVideoProvider} to set the pause image.
+     *
+     * @param uri URI of image to display.
+     */
+    @Override
+    public void onSetPauseImage(Uri uri) {
+        synchronized (mLock) {
+            logFromInCall("setPauseImage: " + uri);
+            try {
+                mConectionServiceVideoProvider.setPauseImage(uri);
+            } catch (RemoteException e) {
+            }
+        }
+    }
+
+    /**
+     * Add a listener to this {@link VideoProviderProxy}.
+     *
+     * @param listener The listener.
+     */
+    public void addListener(Listener listener) {
+        mListeners.add(listener);
+    }
+
+    /**
+     * Remove a listener from this {@link VideoProviderProxy}.
+     *
+     * @param listener The listener.
+     */
+    public void removeListener(Listener listener) {
+        if (listener != null) {
+            mListeners.remove(listener);
+        }
+    }
+
+    /**
+     * Logs a message originating from the {@link InCallService}.
+     *
+     * @param toLog The message to log.
+     */
+    private void logFromInCall(String toLog) {
+        Log.v(this, "IC->VP: " + toLog);
+    }
+
+    /**
+     * Logs a message originating from the {@link android.telecom.ConnectionService}'s
+     * {@link Connection.VideoProvider}.
+     *
+     * @param toLog The message to log.
+     */
+    private void logFromVideoProvider(String toLog) {
+        Log.v(this, "VP->IC: " + toLog);
+    }
+}
diff --git a/testapps/AndroidManifest.xml b/testapps/AndroidManifest.xml
index 8b9827f..df333a5 100644
--- a/testapps/AndroidManifest.xml
+++ b/testapps/AndroidManifest.xml
@@ -59,6 +59,7 @@
                  android:process="com.android.server.telecom.testapps.TestInCallService" >
             <intent-filter>
                 <action android:name="android.server.telecom.testapps.ACTION_SEND_UPDATE_REQUEST_FROM_TEST_INCALL_SERVICE"/>
+                <action android:name="android.server.telecom.testapps.ACTION_SEND_UPGRADE_RESPONSE"/>
                 <data android:scheme="int" />
             </intent-filter>
         </receiver>
diff --git a/testapps/src/com/android/server/telecom/testapps/TestCallList.java b/testapps/src/com/android/server/telecom/testapps/TestCallList.java
index d6ed306..a16c4e2 100644
--- a/testapps/src/com/android/server/telecom/testapps/TestCallList.java
+++ b/testapps/src/com/android/server/telecom/testapps/TestCallList.java
@@ -144,6 +144,26 @@
         }
     }
 
+    /**
+     * For any video calls which are active, sends an upgrade to video response with the specified
+     * video state.
+     *
+     * @param videoState The video state to respond with.
+     */
+    public void sendUpgradeToVideoResponse(int videoState) {
+        Log.v(TAG, "sendUpgradeToVideoResponse : videoState = " + videoState);
+
+        for (Call call : mCalls) {
+            InCallService.VideoCall videoCall = call.getVideoCall();
+            if (videoCall == null) {
+                continue;
+            }
+
+            Log.v(TAG, "send upgrade to video response for call: " + call);
+            videoCall.sendSessionModifyResponse(new VideoProfile(videoState));
+        }
+    }
+
     @Override
     public void onVideoCallChanged(Call call, InCallService.VideoCall videoCall) {
         Log.v(TAG, "onVideoCallChanged: call = " + call + " " + System.identityHashCode(this));
diff --git a/testapps/src/com/android/server/telecom/testapps/TestInCallServiceBroadcastReceiver.java b/testapps/src/com/android/server/telecom/testapps/TestInCallServiceBroadcastReceiver.java
index 790c9eb..b6902bf 100644
--- a/testapps/src/com/android/server/telecom/testapps/TestInCallServiceBroadcastReceiver.java
+++ b/testapps/src/com/android/server/telecom/testapps/TestInCallServiceBroadcastReceiver.java
@@ -34,6 +34,12 @@
             "android.server.telecom.testapps.ACTION_SEND_UPDATE_REQUEST_FROM_TEST_INCALL_SERVICE";
 
     /**
+     * Sends an a response to an upgrade to video request.
+     */
+    public static final String ACTION_SEND_UPGRADE_RESPONSE =
+            "android.server.telecom.testapps.ACTION_SEND_UPGRADE_RESPONSE";
+
+    /**
      * Handles broadcasts directed at the {@link TestInCallServiceImpl}.
      *
      * @param context The Context in which the receiver is running.
@@ -47,6 +53,9 @@
         if (ACTION_SEND_UPDATE_REQUEST_FROM_TEST_INCALL_SERVICE.equals(action)) {
             final int videoState = Integer.parseInt(intent.getData().getSchemeSpecificPart());
             TestCallList.getInstance().sendUpgradeToVideoRequest(videoState);
+        } else if (ACTION_SEND_UPGRADE_RESPONSE.equals(action)) {
+            final int videoState = Integer.parseInt(intent.getData().getSchemeSpecificPart());
+            TestCallList.getInstance().sendUpgradeToVideoResponse(videoState);
         }
     }
 }