1 /* 2 * Copyright (C) 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License 15 */ 16 17 package com.android.server.telecom; 18 19 import android.net.Uri; 20 import android.os.IBinder; 21 import android.os.Looper; 22 import android.os.RemoteException; 23 import android.telecom.Connection; 24 import android.telecom.InCallService; 25 import android.telecom.VideoProfile; 26 import android.view.Surface; 27 28 import com.android.internal.telecom.IVideoCallback; 29 import com.android.internal.telecom.IVideoProvider; 30 31 import java.util.Collections; 32 import java.util.Set; 33 import java.util.concurrent.ConcurrentHashMap; 34 35 /** 36 * Proxies video provider messages from {@link InCallService.VideoCall} 37 * implementations to the underlying {@link Connection.VideoProvider} implementation. Also proxies 38 * callbacks from the {@link Connection.VideoProvider} to {@link InCallService.VideoCall} 39 * implementations. 40 * 41 * Also provides a means for Telecom to send and receive these messages. 42 */ 43 public class VideoProviderProxy extends Connection.VideoProvider { 44 45 /** 46 * Listener for Telecom components interested in callbacks from the video provider. 47 */ 48 interface Listener { 49 void onSessionModifyRequestReceived(Call call, VideoProfile videoProfile); 50 } 51 52 /** 53 * Set of listeners on this VideoProviderProxy. 54 * 55 * ConcurrentHashMap constructor params: 8 is initial table size, 0.9f is 56 * load factor before resizing, 1 means we only expect a single thread to 57 * access the map so make only a single shard 58 */ 59 private final Set<Listener> mListeners = Collections.newSetFromMap( 60 new ConcurrentHashMap<Listener, Boolean>(8, 0.9f, 1)); 61 62 /** The TelecomSystem SyncRoot used for synchronized operations. */ 63 private final TelecomSystem.SyncRoot mLock; 64 65 /** 66 * The {@link android.telecom.Connection.VideoProvider} implementation residing with the 67 * {@link android.telecom.ConnectionService} which is being wrapped by this 68 * {@link VideoProviderProxy}. 69 */ 70 private final IVideoProvider mConectionServiceVideoProvider; 71 72 /** 73 * Binder used to bind to the {@link android.telecom.ConnectionService}'s 74 * {@link com.android.internal.telecom.IVideoCallback}. 75 */ 76 private final VideoCallListenerBinder mVideoCallListenerBinder; 77 78 /** 79 * The Telecom {@link Call} this {@link VideoProviderProxy} is associated with. 80 */ 81 private Call mCall; 82 83 private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() { 84 @Override 85 public void binderDied() { 86 mConectionServiceVideoProvider.asBinder().unlinkToDeath(this, 0); 87 } 88 }; 89 90 /** 91 * Creates a new instance of the {@link VideoProviderProxy}, binding it to the passed in 92 * {@code videoProvider} residing with the {@link android.telecom.ConnectionService}. 93 * 94 * 95 * @param lock 96 * @param videoProvider The {@link android.telecom.ConnectionService}'s video provider. 97 * @param call The current call. 98 * @throws RemoteException Remote exception. 99 */ 100 VideoProviderProxy(TelecomSystem.SyncRoot lock, 101 IVideoProvider videoProvider, Call call) throws RemoteException { 102 103 super(Looper.getMainLooper()); 104 105 mLock = lock; 106 107 mConectionServiceVideoProvider = videoProvider; 108 mConectionServiceVideoProvider.asBinder().linkToDeath(mDeathRecipient, 0); 109 110 mVideoCallListenerBinder = new VideoCallListenerBinder(); 111 mConectionServiceVideoProvider.addVideoCallback(mVideoCallListenerBinder); 112 mCall = call; 113 } 114 115 /** 116 * IVideoCallback stub implementation. An instance of this class receives callbacks from the 117 * {@code ConnectionService}'s video provider. 118 */ 119 private final class VideoCallListenerBinder extends IVideoCallback.Stub { 120 /** 121 * Proxies a request from the {@link #mConectionServiceVideoProvider} to the 122 * {@link InCallService} when a session modification request is received. 123 * 124 * @param videoProfile The requested video profile. 125 */ 126 @Override 127 public void receiveSessionModifyRequest(VideoProfile videoProfile) { 128 try { 129 Log.startSession("VPP.rSMR"); 130 synchronized (mLock) { 131 logFromVideoProvider("receiveSessionModifyRequest: " + videoProfile); 132 Log.event(mCall, Log.Events.RECEIVE_VIDEO_REQUEST, 133 VideoProfile.videoStateToString(videoProfile.getVideoState())); 134 135 mCall.getAnalytics().addVideoEvent( 136 Analytics.RECEIVE_REMOTE_SESSION_MODIFY_REQUEST, 137 videoProfile.getVideoState()); 138 139 if (!mCall.isVideoCallingSupported() && 140 VideoProfile.isVideo(videoProfile.getVideoState())) { 141 // If video calling is not supported by the phone account, and we receive 142 // a request to upgrade to video, automatically reject it without informing 143 // the InCallService. 144 145 Log.event(mCall, Log.Events.SEND_VIDEO_RESPONSE, "video not supported"); 146 VideoProfile responseProfile = new VideoProfile( 147 VideoProfile.STATE_AUDIO_ONLY); 148 try { 149 mConectionServiceVideoProvider.sendSessionModifyResponse( 150 responseProfile); 151 } catch (RemoteException e) { 152 } 153 154 // Don't want to inform listeners of the request as we've just rejected it. 155 return; 156 } 157 158 // Inform other Telecom components of the session modification request. 159 for (Listener listener : mListeners) { 160 listener.onSessionModifyRequestReceived(mCall, videoProfile); 161 } 162 163 VideoProviderProxy.this.receiveSessionModifyRequest(videoProfile); 164 } 165 } finally { 166 Log.endSession(); 167 } 168 } 169 170 /** 171 * Proxies a request from the {@link #mConectionServiceVideoProvider} to the 172 * {@link InCallService} when a session modification response is received. 173 * 174 * @param status The status of the response. 175 * @param requestProfile The requested video profile. 176 * @param responseProfile The response video profile. 177 */ 178 @Override 179 public void receiveSessionModifyResponse(int status, VideoProfile requestProfile, 180 VideoProfile responseProfile) { 181 logFromVideoProvider("receiveSessionModifyResponse: status=" + status + 182 " requestProfile=" + requestProfile + " responseProfile=" + responseProfile); 183 String eventMessage = "Status Code : " + status + " Video State: " + 184 (responseProfile != null ? responseProfile.getVideoState() : "null"); 185 Log.event(mCall, Log.Events.RECEIVE_VIDEO_RESPONSE, eventMessage); 186 synchronized (mLock) { 187 if (status == Connection.VideoProvider.SESSION_MODIFY_REQUEST_SUCCESS) { 188 mCall.getAnalytics().addVideoEvent( 189 Analytics.RECEIVE_REMOTE_SESSION_MODIFY_RESPONSE, 190 requestProfile.getVideoState()); 191 } 192 VideoProviderProxy.this.receiveSessionModifyResponse(status, requestProfile, 193 responseProfile); 194 } 195 } 196 197 /** 198 * Proxies a request from the {@link #mConectionServiceVideoProvider} to the 199 * {@link InCallService} when a call session event occurs. 200 * 201 * @param event The call session event. 202 */ 203 @Override 204 public void handleCallSessionEvent(int event) { 205 synchronized (mLock) { 206 logFromVideoProvider("handleCallSessionEvent: " + event); 207 VideoProviderProxy.this.handleCallSessionEvent(event); 208 } 209 } 210 211 /** 212 * Proxies a request from the {@link #mConectionServiceVideoProvider} to the 213 * {@link InCallService} when the peer dimensions change. 214 * 215 * @param width The width of the peer's video. 216 * @param height The height of the peer's video. 217 */ 218 @Override 219 public void changePeerDimensions(int width, int height) { 220 synchronized (mLock) { 221 logFromVideoProvider("changePeerDimensions: width=" + width + " height=" + 222 height); 223 VideoProviderProxy.this.changePeerDimensions(width, height); 224 } 225 } 226 227 /** 228 * Proxies a request from the {@link #mConectionServiceVideoProvider} to the 229 * {@link InCallService} when the video quality changes. 230 * 231 * @param videoQuality The video quality. 232 */ 233 @Override 234 public void changeVideoQuality(int videoQuality) { 235 synchronized (mLock) { 236 logFromVideoProvider("changeVideoQuality: " + videoQuality); 237 VideoProviderProxy.this.changeVideoQuality(videoQuality); 238 } 239 } 240 241 /** 242 * Proxies a request from the {@link #mConectionServiceVideoProvider} to the 243 * {@link InCallService} when the call data usage changes. 244 * 245 * Also tracks the current call data usage on the {@link Call} for use when writing to the 246 * call log. 247 * 248 * @param dataUsage The data usage. 249 */ 250 @Override 251 public void changeCallDataUsage(long dataUsage) { 252 synchronized (mLock) { 253 logFromVideoProvider("changeCallDataUsage: " + dataUsage); 254 VideoProviderProxy.this.setCallDataUsage(dataUsage); 255 mCall.setCallDataUsage(dataUsage); 256 } 257 } 258 259 /** 260 * Proxies a request from the {@link #mConectionServiceVideoProvider} to the 261 * {@link InCallService} when the camera capabilities change. 262 * 263 * @param cameraCapabilities The camera capabilities. 264 */ 265 @Override 266 public void changeCameraCapabilities(VideoProfile.CameraCapabilities cameraCapabilities) { 267 synchronized (mLock) { 268 logFromVideoProvider("changeCameraCapabilities: " + cameraCapabilities); 269 VideoProviderProxy.this.changeCameraCapabilities(cameraCapabilities); 270 } 271 } 272 } 273 274 /** 275 * Proxies a request from the {@link InCallService} to the 276 * {@link #mConectionServiceVideoProvider} to change the camera. 277 * 278 * @param cameraId The id of the camera. 279 */ 280 @Override 281 public void onSetCamera(String cameraId) { 282 synchronized (mLock) { 283 logFromInCall("setCamera: " + cameraId); 284 try { 285 mConectionServiceVideoProvider.setCamera(cameraId); 286 } catch (RemoteException e) { 287 } 288 } 289 } 290 291 /** 292 * Proxies a request from the {@link InCallService} to the 293 * {@link #mConectionServiceVideoProvider} to set the preview surface. 294 * 295 * @param surface The surface. 296 */ 297 @Override 298 public void onSetPreviewSurface(Surface surface) { 299 synchronized (mLock) { 300 logFromInCall("setPreviewSurface"); 301 try { 302 mConectionServiceVideoProvider.setPreviewSurface(surface); 303 } catch (RemoteException e) { 304 } 305 } 306 } 307 308 /** 309 * Proxies a request from the {@link InCallService} to the 310 * {@link #mConectionServiceVideoProvider} to change the display surface. 311 * 312 * @param surface The surface. 313 */ 314 @Override 315 public void onSetDisplaySurface(Surface surface) { 316 synchronized (mLock) { 317 logFromInCall("setDisplaySurface"); 318 try { 319 mConectionServiceVideoProvider.setDisplaySurface(surface); 320 } catch (RemoteException e) { 321 } 322 } 323 } 324 325 /** 326 * Proxies a request from the {@link InCallService} to the 327 * {@link #mConectionServiceVideoProvider} to change the device orientation. 328 * 329 * @param rotation The device orientation, in degrees. 330 */ 331 @Override 332 public void onSetDeviceOrientation(int rotation) { 333 synchronized (mLock) { 334 logFromInCall("setDeviceOrientation: " + rotation); 335 try { 336 mConectionServiceVideoProvider.setDeviceOrientation(rotation); 337 } catch (RemoteException e) { 338 } 339 } 340 } 341 342 /** 343 * Proxies a request from the {@link InCallService} to the 344 * {@link #mConectionServiceVideoProvider} to change the camera zoom ratio. 345 * 346 * @param value The camera zoom ratio. 347 */ 348 @Override 349 public void onSetZoom(float value) { 350 synchronized (mLock) { 351 logFromInCall("setZoom: " + value); 352 try { 353 mConectionServiceVideoProvider.setZoom(value); 354 } catch (RemoteException e) { 355 } 356 } 357 } 358 359 /** 360 * Proxies a request from the {@link InCallService} to the 361 * {@link #mConectionServiceVideoProvider} to provide a response to a session modification 362 * request. 363 * 364 * @param fromProfile The video properties prior to the request. 365 * @param toProfile The video properties with the requested changes made. 366 */ 367 @Override 368 public void onSendSessionModifyRequest(VideoProfile fromProfile, VideoProfile toProfile) { 369 synchronized (mLock) { 370 logFromInCall("sendSessionModifyRequest: from=" + fromProfile + " to=" + toProfile); 371 Log.event(mCall, Log.Events.SEND_VIDEO_REQUEST, 372 VideoProfile.videoStateToString(toProfile.getVideoState())); 373 mCall.getAnalytics().addVideoEvent( 374 Analytics.SEND_LOCAL_SESSION_MODIFY_REQUEST, 375 toProfile.getVideoState()); 376 try { 377 mConectionServiceVideoProvider.sendSessionModifyRequest(fromProfile, toProfile); 378 } catch (RemoteException e) { 379 } 380 } 381 } 382 383 /** 384 * Proxies a request from the {@link InCallService} to the 385 * {@link #mConectionServiceVideoProvider} to send a session modification request. 386 * 387 * @param responseProfile The response connection video properties. 388 */ 389 @Override 390 public void onSendSessionModifyResponse(VideoProfile responseProfile) { 391 synchronized (mLock) { 392 logFromInCall("sendSessionModifyResponse: " + responseProfile); 393 Log.event(mCall, Log.Events.SEND_VIDEO_RESPONSE, 394 VideoProfile.videoStateToString(responseProfile.getVideoState())); 395 mCall.getAnalytics().addVideoEvent( 396 Analytics.SEND_LOCAL_SESSION_MODIFY_RESPONSE, 397 responseProfile.getVideoState()); 398 try { 399 mConectionServiceVideoProvider.sendSessionModifyResponse(responseProfile); 400 } catch (RemoteException e) { 401 } 402 } 403 } 404 405 /** 406 * Proxies a request from the {@link InCallService} to the 407 * {@link #mConectionServiceVideoProvider} to request the camera capabilities. 408 */ 409 @Override 410 public void onRequestCameraCapabilities() { 411 synchronized (mLock) { 412 logFromInCall("requestCameraCapabilities"); 413 try { 414 mConectionServiceVideoProvider.requestCameraCapabilities(); 415 } catch (RemoteException e) { 416 } 417 } 418 } 419 420 /** 421 * Proxies a request from the {@link InCallService} to the 422 * {@link #mConectionServiceVideoProvider} to request the connection data usage. 423 */ 424 @Override 425 public void onRequestConnectionDataUsage() { 426 synchronized (mLock) { 427 logFromInCall("requestCallDataUsage"); 428 try { 429 mConectionServiceVideoProvider.requestCallDataUsage(); 430 } catch (RemoteException e) { 431 } 432 } 433 } 434 435 /** 436 * Proxies a request from the {@link InCallService} to the 437 * {@link #mConectionServiceVideoProvider} to set the pause image. 438 * 439 * @param uri URI of image to display. 440 */ 441 @Override 442 public void onSetPauseImage(Uri uri) { 443 synchronized (mLock) { 444 logFromInCall("setPauseImage: " + uri); 445 try { 446 mConectionServiceVideoProvider.setPauseImage(uri); 447 } catch (RemoteException e) { 448 } 449 } 450 } 451 452 /** 453 * Add a listener to this {@link VideoProviderProxy}. 454 * 455 * @param listener The listener. 456 */ 457 public void addListener(Listener listener) { 458 mListeners.add(listener); 459 } 460 461 /** 462 * Remove a listener from this {@link VideoProviderProxy}. 463 * 464 * @param listener The listener. 465 */ 466 public void removeListener(Listener listener) { 467 if (listener != null) { 468 mListeners.remove(listener); 469 } 470 } 471 472 /** 473 * Logs a message originating from the {@link InCallService}. 474 * 475 * @param toLog The message to log. 476 */ 477 private void logFromInCall(String toLog) { 478 Log.v(this, "IC->VP: " + toLog); 479 } 480 481 /** 482 * Logs a message originating from the {@link android.telecom.ConnectionService}'s 483 * {@link Connection.VideoProvider}. 484 * 485 * @param toLog The message to log. 486 */ 487 private void logFromVideoProvider(String toLog) { 488 Log.v(this, "VP->IC: " + toLog); 489 } 490 } 491