1 /*
2  * Copyright (C) 2017 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.incallui.videotech.ims;
18 
19 import android.content.Context;
20 import android.os.Build;
21 import android.telecom.Call;
22 import android.telecom.Call.Details;
23 import android.telecom.VideoProfile;
24 import com.android.dialer.common.Assert;
25 import com.android.dialer.common.LogUtil;
26 import com.android.dialer.logging.DialerImpression;
27 import com.android.dialer.logging.LoggingBindings;
28 import com.android.incallui.video.protocol.VideoCallScreen;
29 import com.android.incallui.video.protocol.VideoCallScreenDelegate;
30 import com.android.incallui.videotech.VideoTech;
31 import com.android.incallui.videotech.utils.SessionModificationState;
32 
33 /** ViLTE implementation */
34 public class ImsVideoTech implements VideoTech {
35   private final LoggingBindings logger;
36   private final Call call;
37   private final VideoTechListener listener;
38   private ImsVideoCallCallback callback;
39   private @SessionModificationState int sessionModificationState =
40       SessionModificationState.NO_REQUEST;
41   private int previousVideoState = VideoProfile.STATE_AUDIO_ONLY;
42   private boolean paused = false;
43 
ImsVideoTech(LoggingBindings logger, VideoTechListener listener, Call call)44   public ImsVideoTech(LoggingBindings logger, VideoTechListener listener, Call call) {
45     this.logger = logger;
46     this.listener = listener;
47     this.call = call;
48   }
49 
50   @Override
isAvailable(Context context)51   public boolean isAvailable(Context context) {
52     if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
53       return false;
54     }
55 
56     boolean hasCapabilities =
57         call.getDetails().can(Call.Details.CAPABILITY_SUPPORTS_VT_LOCAL_TX)
58             && call.getDetails().can(Call.Details.CAPABILITY_SUPPORTS_VT_REMOTE_RX);
59 
60     return call.getVideoCall() != null
61         && (hasCapabilities || VideoProfile.isVideo(call.getDetails().getVideoState()));
62   }
63 
64   @Override
isTransmittingOrReceiving()65   public boolean isTransmittingOrReceiving() {
66     return VideoProfile.isVideo(call.getDetails().getVideoState());
67   }
68 
69   @Override
isSelfManagedCamera()70   public boolean isSelfManagedCamera() {
71     // Return false to indicate that the answer UI shouldn't open the camera itself.
72     // For IMS Video the modem is responsible for opening the camera.
73     return false;
74   }
75 
76   @Override
shouldUseSurfaceView()77   public boolean shouldUseSurfaceView() {
78     return false;
79   }
80 
81   @Override
createVideoCallScreenDelegate( Context context, VideoCallScreen videoCallScreen)82   public VideoCallScreenDelegate createVideoCallScreenDelegate(
83       Context context, VideoCallScreen videoCallScreen) {
84     // TODO move creating VideoCallPresenter here
85     throw Assert.createUnsupportedOperationFailException();
86   }
87 
88   @Override
onCallStateChanged(Context context, int newState)89   public void onCallStateChanged(Context context, int newState) {
90     if (!isAvailable(context)) {
91       return;
92     }
93 
94     if (callback == null) {
95       callback = new ImsVideoCallCallback(logger, call, this, listener);
96       call.getVideoCall().registerCallback(callback);
97     }
98 
99     if (getSessionModificationState()
100             == SessionModificationState.WAITING_FOR_UPGRADE_TO_VIDEO_RESPONSE
101         && isTransmittingOrReceiving()) {
102       // We don't clear the session modification state right away when we find out the video upgrade
103       // request was accepted to avoid having the UI switch from video to voice to video.
104       // Once the underlying telecom call updates to video mode it's safe to clear the state.
105       LogUtil.i(
106           "ImsVideoTech.onCallStateChanged",
107           "upgraded to video, clearing session modification state");
108       setSessionModificationState(SessionModificationState.NO_REQUEST);
109     }
110 
111     // Determines if a received upgrade to video request should be cancelled. This can happen if
112     // another InCall UI responds to the upgrade to video request.
113     int newVideoState = call.getDetails().getVideoState();
114     if (newVideoState != previousVideoState
115         && sessionModificationState == SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST) {
116       LogUtil.i("ImsVideoTech.onCallStateChanged", "cancelling upgrade notification");
117       setSessionModificationState(SessionModificationState.NO_REQUEST);
118     }
119     previousVideoState = newVideoState;
120   }
121 
122   @Override
getSessionModificationState()123   public int getSessionModificationState() {
124     return sessionModificationState;
125   }
126 
setSessionModificationState(@essionModificationState int state)127   void setSessionModificationState(@SessionModificationState int state) {
128     if (state != sessionModificationState) {
129       LogUtil.i(
130           "ImsVideoTech.setSessionModificationState", "%d -> %d", sessionModificationState, state);
131       sessionModificationState = state;
132       listener.onSessionModificationStateChanged();
133     }
134   }
135 
136   @Override
upgradeToVideo()137   public void upgradeToVideo() {
138     LogUtil.enterBlock("ImsVideoTech.upgradeToVideo");
139 
140     int unpausedVideoState = getUnpausedVideoState(call.getDetails().getVideoState());
141     call.getVideoCall()
142         .sendSessionModifyRequest(
143             new VideoProfile(unpausedVideoState | VideoProfile.STATE_BIDIRECTIONAL));
144     setSessionModificationState(SessionModificationState.WAITING_FOR_UPGRADE_TO_VIDEO_RESPONSE);
145     logger.logImpression(DialerImpression.Type.IMS_VIDEO_UPGRADE_REQUESTED);
146   }
147 
148   @Override
acceptVideoRequest()149   public void acceptVideoRequest() {
150     int requestedVideoState = callback.getRequestedVideoState();
151     Assert.checkArgument(requestedVideoState != VideoProfile.STATE_AUDIO_ONLY);
152     LogUtil.i("ImsVideoTech.acceptUpgradeRequest", "videoState: " + requestedVideoState);
153     call.getVideoCall().sendSessionModifyResponse(new VideoProfile(requestedVideoState));
154     setSessionModificationState(SessionModificationState.NO_REQUEST);
155     // Telecom manages audio route for us
156     listener.onUpgradedToVideo(false /* switchToSpeaker */);
157     logger.logImpression(DialerImpression.Type.IMS_VIDEO_REQUEST_ACCEPTED);
158   }
159 
160   @Override
acceptVideoRequestAsAudio()161   public void acceptVideoRequestAsAudio() {
162     LogUtil.enterBlock("ImsVideoTech.acceptVideoRequestAsAudio");
163     call.getVideoCall().sendSessionModifyResponse(new VideoProfile(VideoProfile.STATE_AUDIO_ONLY));
164     setSessionModificationState(SessionModificationState.NO_REQUEST);
165     logger.logImpression(DialerImpression.Type.IMS_VIDEO_REQUEST_ACCEPTED_AS_AUDIO);
166   }
167 
168   @Override
declineVideoRequest()169   public void declineVideoRequest() {
170     LogUtil.enterBlock("ImsVideoTech.declineUpgradeRequest");
171     call.getVideoCall()
172         .sendSessionModifyResponse(new VideoProfile(call.getDetails().getVideoState()));
173     setSessionModificationState(SessionModificationState.NO_REQUEST);
174     logger.logImpression(DialerImpression.Type.IMS_VIDEO_REQUEST_DECLINED);
175   }
176 
177   @Override
isTransmitting()178   public boolean isTransmitting() {
179     return VideoProfile.isTransmissionEnabled(call.getDetails().getVideoState());
180   }
181 
182   @Override
stopTransmission()183   public void stopTransmission() {
184     LogUtil.enterBlock("ImsVideoTech.stopTransmission");
185 
186     setCamera(null);
187 
188     int unpausedVideoState = getUnpausedVideoState(call.getDetails().getVideoState());
189     call.getVideoCall()
190         .sendSessionModifyRequest(
191             new VideoProfile(unpausedVideoState & ~VideoProfile.STATE_TX_ENABLED));
192   }
193 
194   @Override
resumeTransmission()195   public void resumeTransmission() {
196     LogUtil.enterBlock("ImsVideoTech.resumeTransmission");
197 
198     int unpausedVideoState = getUnpausedVideoState(call.getDetails().getVideoState());
199     call.getVideoCall()
200         .sendSessionModifyRequest(
201             new VideoProfile(unpausedVideoState | VideoProfile.STATE_TX_ENABLED));
202     setSessionModificationState(SessionModificationState.WAITING_FOR_RESPONSE);
203   }
204 
205   @Override
pause()206   public void pause() {
207     if (canPause() && !paused) {
208       LogUtil.i("ImsVideoTech.pause", "sending pause request");
209       paused = true;
210       int pausedVideoState = call.getDetails().getVideoState() | VideoProfile.STATE_PAUSED;
211       call.getVideoCall().sendSessionModifyRequest(new VideoProfile(pausedVideoState));
212     } else {
213       LogUtil.i(
214           "ImsVideoTech.pause",
215           "not sending request: canPause: %b, paused: %b",
216           canPause(),
217           paused);
218     }
219   }
220 
221   @Override
unpause()222   public void unpause() {
223     if (canPause() && paused) {
224       LogUtil.i("ImsVideoTech.unpause", "sending unpause request");
225       paused = false;
226       int unpausedVideoState = getUnpausedVideoState(call.getDetails().getVideoState());
227       call.getVideoCall().sendSessionModifyRequest(new VideoProfile(unpausedVideoState));
228     } else {
229       LogUtil.i(
230           "ImsVideoTech.unpause",
231           "not sending request: canPause: %b, paused: %b",
232           canPause(),
233           paused);
234     }
235   }
236 
237   @Override
setCamera(String cameraId)238   public void setCamera(String cameraId) {
239     call.getVideoCall().setCamera(cameraId);
240     call.getVideoCall().requestCameraCapabilities();
241   }
242 
243   @Override
setDeviceOrientation(int rotation)244   public void setDeviceOrientation(int rotation) {
245     call.getVideoCall().setDeviceOrientation(rotation);
246   }
247 
canPause()248   private boolean canPause() {
249     return call.getDetails().can(Details.CAPABILITY_CAN_PAUSE_VIDEO)
250         && call.getState() == Call.STATE_ACTIVE
251         && isTransmitting();
252   }
253 
getUnpausedVideoState(int videoState)254   static int getUnpausedVideoState(int videoState) {
255     return videoState & (~VideoProfile.STATE_PAUSED);
256   }
257 }
258