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 { onSessionModifyRequestReceived(Call call, VideoProfile videoProfile)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 */ VideoProviderProxy(TelecomSystem.SyncRoot lock, IVideoProvider videoProvider, Call call)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 receiveSessionModifyRequest(VideoProfile videoProfile)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 receiveSessionModifyResponse(int status, VideoProfile requestProfile, VideoProfile responseProfile)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 responseProfile == null ? 191 VideoProfile.STATE_AUDIO_ONLY : 192 responseProfile.getVideoState()); 193 } 194 VideoProviderProxy.this.receiveSessionModifyResponse(status, requestProfile, 195 responseProfile); 196 } 197 } 198 199 /** 200 * Proxies a request from the {@link #mConectionServiceVideoProvider} to the 201 * {@link InCallService} when a call session event occurs. 202 * 203 * @param event The call session event. 204 */ 205 @Override handleCallSessionEvent(int event)206 public void handleCallSessionEvent(int event) { 207 synchronized (mLock) { 208 logFromVideoProvider("handleCallSessionEvent: " + 209 Connection.VideoProvider.sessionEventToString(event)); 210 VideoProviderProxy.this.handleCallSessionEvent(event); 211 } 212 } 213 214 /** 215 * Proxies a request from the {@link #mConectionServiceVideoProvider} to the 216 * {@link InCallService} when the peer dimensions change. 217 * 218 * @param width The width of the peer's video. 219 * @param height The height of the peer's video. 220 */ 221 @Override changePeerDimensions(int width, int height)222 public void changePeerDimensions(int width, int height) { 223 synchronized (mLock) { 224 logFromVideoProvider("changePeerDimensions: width=" + width + " height=" + 225 height); 226 VideoProviderProxy.this.changePeerDimensions(width, height); 227 } 228 } 229 230 /** 231 * Proxies a request from the {@link #mConectionServiceVideoProvider} to the 232 * {@link InCallService} when the video quality changes. 233 * 234 * @param videoQuality The video quality. 235 */ 236 @Override changeVideoQuality(int videoQuality)237 public void changeVideoQuality(int videoQuality) { 238 synchronized (mLock) { 239 logFromVideoProvider("changeVideoQuality: " + videoQuality); 240 VideoProviderProxy.this.changeVideoQuality(videoQuality); 241 } 242 } 243 244 /** 245 * Proxies a request from the {@link #mConectionServiceVideoProvider} to the 246 * {@link InCallService} when the call data usage changes. 247 * 248 * Also tracks the current call data usage on the {@link Call} for use when writing to the 249 * call log. 250 * 251 * @param dataUsage The data usage. 252 */ 253 @Override changeCallDataUsage(long dataUsage)254 public void changeCallDataUsage(long dataUsage) { 255 synchronized (mLock) { 256 logFromVideoProvider("changeCallDataUsage: " + dataUsage); 257 VideoProviderProxy.this.setCallDataUsage(dataUsage); 258 mCall.setCallDataUsage(dataUsage); 259 } 260 } 261 262 /** 263 * Proxies a request from the {@link #mConectionServiceVideoProvider} to the 264 * {@link InCallService} when the camera capabilities change. 265 * 266 * @param cameraCapabilities The camera capabilities. 267 */ 268 @Override changeCameraCapabilities(VideoProfile.CameraCapabilities cameraCapabilities)269 public void changeCameraCapabilities(VideoProfile.CameraCapabilities cameraCapabilities) { 270 synchronized (mLock) { 271 logFromVideoProvider("changeCameraCapabilities: " + cameraCapabilities); 272 VideoProviderProxy.this.changeCameraCapabilities(cameraCapabilities); 273 } 274 } 275 } 276 277 /** 278 * Proxies a request from the {@link InCallService} to the 279 * {@link #mConectionServiceVideoProvider} to change the camera. 280 * 281 * @param cameraId The id of the camera. 282 */ 283 @Override onSetCamera(String cameraId)284 public void onSetCamera(String cameraId) { 285 synchronized (mLock) { 286 logFromInCall("setCamera: " + cameraId); 287 try { 288 mConectionServiceVideoProvider.setCamera(cameraId); 289 } catch (RemoteException e) { 290 } 291 } 292 } 293 294 /** 295 * Proxies a request from the {@link InCallService} to the 296 * {@link #mConectionServiceVideoProvider} to set the preview surface. 297 * 298 * @param surface The surface. 299 */ 300 @Override onSetPreviewSurface(Surface surface)301 public void onSetPreviewSurface(Surface surface) { 302 synchronized (mLock) { 303 logFromInCall("setPreviewSurface"); 304 try { 305 mConectionServiceVideoProvider.setPreviewSurface(surface); 306 } catch (RemoteException e) { 307 } 308 } 309 } 310 311 /** 312 * Proxies a request from the {@link InCallService} to the 313 * {@link #mConectionServiceVideoProvider} to change the display surface. 314 * 315 * @param surface The surface. 316 */ 317 @Override onSetDisplaySurface(Surface surface)318 public void onSetDisplaySurface(Surface surface) { 319 synchronized (mLock) { 320 logFromInCall("setDisplaySurface"); 321 try { 322 mConectionServiceVideoProvider.setDisplaySurface(surface); 323 } catch (RemoteException e) { 324 } 325 } 326 } 327 328 /** 329 * Proxies a request from the {@link InCallService} to the 330 * {@link #mConectionServiceVideoProvider} to change the device orientation. 331 * 332 * @param rotation The device orientation, in degrees. 333 */ 334 @Override onSetDeviceOrientation(int rotation)335 public void onSetDeviceOrientation(int rotation) { 336 synchronized (mLock) { 337 logFromInCall("setDeviceOrientation: " + rotation); 338 try { 339 mConectionServiceVideoProvider.setDeviceOrientation(rotation); 340 } catch (RemoteException e) { 341 } 342 } 343 } 344 345 /** 346 * Proxies a request from the {@link InCallService} to the 347 * {@link #mConectionServiceVideoProvider} to change the camera zoom ratio. 348 * 349 * @param value The camera zoom ratio. 350 */ 351 @Override onSetZoom(float value)352 public void onSetZoom(float value) { 353 synchronized (mLock) { 354 logFromInCall("setZoom: " + value); 355 try { 356 mConectionServiceVideoProvider.setZoom(value); 357 } catch (RemoteException e) { 358 } 359 } 360 } 361 362 /** 363 * Proxies a request from the {@link InCallService} to the 364 * {@link #mConectionServiceVideoProvider} to provide a response to a session modification 365 * request. 366 * 367 * @param fromProfile The video properties prior to the request. 368 * @param toProfile The video properties with the requested changes made. 369 */ 370 @Override onSendSessionModifyRequest(VideoProfile fromProfile, VideoProfile toProfile)371 public void onSendSessionModifyRequest(VideoProfile fromProfile, VideoProfile toProfile) { 372 synchronized (mLock) { 373 logFromInCall("sendSessionModifyRequest: from=" + fromProfile + " to=" + toProfile); 374 Log.event(mCall, Log.Events.SEND_VIDEO_REQUEST, 375 VideoProfile.videoStateToString(toProfile.getVideoState())); 376 mCall.getAnalytics().addVideoEvent( 377 Analytics.SEND_LOCAL_SESSION_MODIFY_REQUEST, 378 toProfile.getVideoState()); 379 try { 380 mConectionServiceVideoProvider.sendSessionModifyRequest(fromProfile, toProfile); 381 } catch (RemoteException e) { 382 } 383 } 384 } 385 386 /** 387 * Proxies a request from the {@link InCallService} to the 388 * {@link #mConectionServiceVideoProvider} to send a session modification request. 389 * 390 * @param responseProfile The response connection video properties. 391 */ 392 @Override onSendSessionModifyResponse(VideoProfile responseProfile)393 public void onSendSessionModifyResponse(VideoProfile responseProfile) { 394 synchronized (mLock) { 395 logFromInCall("sendSessionModifyResponse: " + responseProfile); 396 Log.event(mCall, Log.Events.SEND_VIDEO_RESPONSE, 397 VideoProfile.videoStateToString(responseProfile.getVideoState())); 398 mCall.getAnalytics().addVideoEvent( 399 Analytics.SEND_LOCAL_SESSION_MODIFY_RESPONSE, 400 responseProfile.getVideoState()); 401 try { 402 mConectionServiceVideoProvider.sendSessionModifyResponse(responseProfile); 403 } catch (RemoteException e) { 404 } 405 } 406 } 407 408 /** 409 * Proxies a request from the {@link InCallService} to the 410 * {@link #mConectionServiceVideoProvider} to request the camera capabilities. 411 */ 412 @Override onRequestCameraCapabilities()413 public void onRequestCameraCapabilities() { 414 synchronized (mLock) { 415 logFromInCall("requestCameraCapabilities"); 416 try { 417 mConectionServiceVideoProvider.requestCameraCapabilities(); 418 } catch (RemoteException e) { 419 } 420 } 421 } 422 423 /** 424 * Proxies a request from the {@link InCallService} to the 425 * {@link #mConectionServiceVideoProvider} to request the connection data usage. 426 */ 427 @Override onRequestConnectionDataUsage()428 public void onRequestConnectionDataUsage() { 429 synchronized (mLock) { 430 logFromInCall("requestCallDataUsage"); 431 try { 432 mConectionServiceVideoProvider.requestCallDataUsage(); 433 } catch (RemoteException e) { 434 } 435 } 436 } 437 438 /** 439 * Proxies a request from the {@link InCallService} to the 440 * {@link #mConectionServiceVideoProvider} to set the pause image. 441 * 442 * @param uri URI of image to display. 443 */ 444 @Override onSetPauseImage(Uri uri)445 public void onSetPauseImage(Uri uri) { 446 synchronized (mLock) { 447 logFromInCall("setPauseImage: " + uri); 448 try { 449 mConectionServiceVideoProvider.setPauseImage(uri); 450 } catch (RemoteException e) { 451 } 452 } 453 } 454 455 /** 456 * Add a listener to this {@link VideoProviderProxy}. 457 * 458 * @param listener The listener. 459 */ addListener(Listener listener)460 public void addListener(Listener listener) { 461 mListeners.add(listener); 462 } 463 464 /** 465 * Remove a listener from this {@link VideoProviderProxy}. 466 * 467 * @param listener The listener. 468 */ removeListener(Listener listener)469 public void removeListener(Listener listener) { 470 if (listener != null) { 471 mListeners.remove(listener); 472 } 473 } 474 475 /** 476 * Logs a message originating from the {@link InCallService}. 477 * 478 * @param toLog The message to log. 479 */ logFromInCall(String toLog)480 private void logFromInCall(String toLog) { 481 Log.i(this, "IC->VP (callId=" + (mCall == null ? "?" : mCall.getId()) + "): " + toLog); 482 } 483 484 /** 485 * Logs a message originating from the {@link android.telecom.ConnectionService}'s 486 * {@link Connection.VideoProvider}. 487 * 488 * @param toLog The message to log. 489 */ logFromVideoProvider(String toLog)490 private void logFromVideoProvider(String toLog) { 491 Log.i(this, "VP->IC (callId=" + (mCall == null ? "?" : mCall.getId()) + "): " + toLog); 492 } 493 } 494