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.os.Handler;
20 import android.telecom.Call;
21 import android.telecom.Connection;
22 import android.telecom.Connection.VideoProvider;
23 import android.telecom.InCallService.VideoCall;
24 import android.telecom.VideoProfile;
25 import android.telecom.VideoProfile.CameraCapabilities;
26 import com.android.dialer.common.LogUtil;
27 import com.android.dialer.logging.DialerImpression;
28 import com.android.dialer.logging.LoggingBindings;
29 import com.android.incallui.videotech.VideoTech.VideoTechListener;
30 import com.android.incallui.videotech.utils.SessionModificationState;
31 
32 /** Receives IMS video call state updates. */
33 public class ImsVideoCallCallback extends VideoCall.Callback {
34   private static final int CLEAR_FAILED_REQUEST_TIMEOUT_MILLIS = 4000;
35   private final Handler handler = new Handler();
36   private final LoggingBindings logger;
37   private final Call call;
38   private final ImsVideoTech videoTech;
39   private final VideoTechListener listener;
40   private int requestedVideoState = VideoProfile.STATE_AUDIO_ONLY;
41 
ImsVideoCallCallback( final LoggingBindings logger, final Call call, ImsVideoTech videoTech, VideoTechListener listener)42   ImsVideoCallCallback(
43       final LoggingBindings logger,
44       final Call call,
45       ImsVideoTech videoTech,
46       VideoTechListener listener) {
47     this.logger = logger;
48     this.call = call;
49     this.videoTech = videoTech;
50     this.listener = listener;
51   }
52 
53   @Override
onSessionModifyRequestReceived(VideoProfile videoProfile)54   public void onSessionModifyRequestReceived(VideoProfile videoProfile) {
55     LogUtil.i(
56         "ImsVideoCallCallback.onSessionModifyRequestReceived", "videoProfile: " + videoProfile);
57 
58     int previousVideoState = ImsVideoTech.getUnpausedVideoState(call.getDetails().getVideoState());
59     int newVideoState = ImsVideoTech.getUnpausedVideoState(videoProfile.getVideoState());
60 
61     boolean wasVideoCall = VideoProfile.isVideo(previousVideoState);
62     boolean isVideoCall = VideoProfile.isVideo(newVideoState);
63 
64     if (wasVideoCall && !isVideoCall) {
65       LogUtil.i(
66           "ImsVideoTech.onSessionModifyRequestReceived", "call downgraded to %d", newVideoState);
67     } else if (previousVideoState != newVideoState) {
68       requestedVideoState = newVideoState;
69       videoTech.setSessionModificationState(
70           SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST);
71       listener.onVideoUpgradeRequestReceived();
72       logger.logImpression(DialerImpression.Type.IMS_VIDEO_REQUEST_RECEIVED);
73     }
74   }
75 
76   /**
77    * @param status Status of the session modify request. Valid values are {@link
78    *     Connection.VideoProvider#SESSION_MODIFY_REQUEST_SUCCESS}, {@link
79    *     Connection.VideoProvider#SESSION_MODIFY_REQUEST_FAIL}, {@link
80    *     Connection.VideoProvider#SESSION_MODIFY_REQUEST_INVALID}
81    * @param responseProfile The actual profile changes made by the peer device.
82    */
83   @Override
onSessionModifyResponseReceived( int status, VideoProfile requestedProfile, VideoProfile responseProfile)84   public void onSessionModifyResponseReceived(
85       int status, VideoProfile requestedProfile, VideoProfile responseProfile) {
86     LogUtil.i(
87         "ImsVideoCallCallback.onSessionModifyResponseReceived",
88         "status: %d, requestedProfile: %s, responseProfile: %s, session modification state: %d",
89         status,
90         requestedProfile,
91         responseProfile,
92         videoTech.getSessionModificationState());
93 
94     if (videoTech.getSessionModificationState()
95         == SessionModificationState.WAITING_FOR_UPGRADE_TO_VIDEO_RESPONSE) {
96       handler.removeCallbacksAndMessages(null); // Clear everything
97 
98       final int newSessionModificationState = getSessionModificationStateFromTelecomStatus(status);
99       if (status == VideoProvider.SESSION_MODIFY_REQUEST_SUCCESS) {
100         // Telecom manages audio route for us
101         listener.onUpgradedToVideo(false /* switchToSpeaker */);
102       } else {
103         // This will update the video UI to display the error message.
104         videoTech.setSessionModificationState(newSessionModificationState);
105       }
106 
107       // Wait for 4 seconds and then clean the session modification state. This allows the video UI
108       // to stay up so that the user can read the error message.
109       //
110       // If the other person accepted the upgrade request then this will keep the video UI up until
111       // the call's video state change. Without this we would switch to the voice call and then
112       // switch back to video UI.
113       handler.postDelayed(
114           () -> {
115             if (videoTech.getSessionModificationState() == newSessionModificationState) {
116               LogUtil.i("ImsVideoCallCallback.onSessionModifyResponseReceived", "clearing state");
117               videoTech.setSessionModificationState(SessionModificationState.NO_REQUEST);
118             } else {
119               LogUtil.i(
120                   "ImsVideoCallCallback.onSessionModifyResponseReceived",
121                   "session modification state has changed, not clearing state");
122             }
123           },
124           CLEAR_FAILED_REQUEST_TIMEOUT_MILLIS);
125     } else if (videoTech.getSessionModificationState()
126         == SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST) {
127       videoTech.setSessionModificationState(SessionModificationState.NO_REQUEST);
128     } else if (videoTech.getSessionModificationState()
129         == SessionModificationState.WAITING_FOR_RESPONSE) {
130       videoTech.setSessionModificationState(getSessionModificationStateFromTelecomStatus(status));
131     } else {
132       LogUtil.i(
133           "ImsVideoCallCallback.onSessionModifyResponseReceived",
134           "call is not waiting for response, doing nothing");
135     }
136   }
137 
138   @SessionModificationState
getSessionModificationStateFromTelecomStatus(int telecomStatus)139   private int getSessionModificationStateFromTelecomStatus(int telecomStatus) {
140     switch (telecomStatus) {
141       case VideoProvider.SESSION_MODIFY_REQUEST_SUCCESS:
142         return SessionModificationState.NO_REQUEST;
143       case VideoProvider.SESSION_MODIFY_REQUEST_FAIL:
144       case VideoProvider.SESSION_MODIFY_REQUEST_INVALID:
145         // Check if it's already video call, which means the request is not video upgrade request.
146         if (VideoProfile.isVideo(call.getDetails().getVideoState())) {
147           return SessionModificationState.REQUEST_FAILED;
148         } else {
149           return SessionModificationState.UPGRADE_TO_VIDEO_REQUEST_FAILED;
150         }
151       case VideoProvider.SESSION_MODIFY_REQUEST_TIMED_OUT:
152         return SessionModificationState.UPGRADE_TO_VIDEO_REQUEST_TIMED_OUT;
153       case VideoProvider.SESSION_MODIFY_REQUEST_REJECTED_BY_REMOTE:
154         return SessionModificationState.REQUEST_REJECTED;
155       default:
156         LogUtil.e(
157             "ImsVideoCallCallback.getSessionModificationStateFromTelecomStatus",
158             "unknown status: %d",
159             telecomStatus);
160         return SessionModificationState.REQUEST_FAILED;
161     }
162   }
163 
164   @Override
onCallSessionEvent(int event)165   public void onCallSessionEvent(int event) {
166     switch (event) {
167       case Connection.VideoProvider.SESSION_EVENT_RX_PAUSE:
168         LogUtil.i("ImsVideoCallCallback.onCallSessionEvent", "rx_pause");
169         break;
170       case Connection.VideoProvider.SESSION_EVENT_RX_RESUME:
171         LogUtil.i("ImsVideoCallCallback.onCallSessionEvent", "rx_resume");
172         break;
173       case Connection.VideoProvider.SESSION_EVENT_CAMERA_FAILURE:
174         LogUtil.i("ImsVideoCallCallback.onCallSessionEvent", "camera_failure");
175         break;
176       case Connection.VideoProvider.SESSION_EVENT_CAMERA_READY:
177         LogUtil.i("ImsVideoCallCallback.onCallSessionEvent", "camera_ready");
178         break;
179       default:
180         LogUtil.i("ImsVideoCallCallback.onCallSessionEvent", "unknown event = : " + event);
181         break;
182     }
183   }
184 
185   @Override
onPeerDimensionsChanged(int width, int height)186   public void onPeerDimensionsChanged(int width, int height) {
187     listener.onPeerDimensionsChanged(width, height);
188   }
189 
190   @Override
onVideoQualityChanged(int videoQuality)191   public void onVideoQualityChanged(int videoQuality) {
192     LogUtil.i("ImsVideoCallCallback.onVideoQualityChanged", "videoQuality: %d", videoQuality);
193   }
194 
195   @Override
onCallDataUsageChanged(long dataUsage)196   public void onCallDataUsageChanged(long dataUsage) {
197     LogUtil.i("ImsVideoCallCallback.onCallDataUsageChanged", "dataUsage: %d", dataUsage);
198   }
199 
200   @Override
onCameraCapabilitiesChanged(CameraCapabilities cameraCapabilities)201   public void onCameraCapabilitiesChanged(CameraCapabilities cameraCapabilities) {
202     if (cameraCapabilities != null) {
203       listener.onCameraDimensionsChanged(
204           cameraCapabilities.getWidth(), cameraCapabilities.getHeight());
205     }
206   }
207 
getRequestedVideoState()208   int getRequestedVideoState() {
209     return requestedVideoState;
210   }
211 }
212