summaryrefslogtreecommitdiffstats
path: root/java/com/android/incallui/videotech/ims/ImsVideoCallCallback.java
blob: 3e6f4417b3a9d6f62f6319cd7c4da1fcc11a2c41 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
/*
 * Copyright (C) 2017 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.incallui.videotech.ims;

import android.content.Context;
import android.os.Handler;
import android.telecom.Call;
import android.telecom.Connection;
import android.telecom.Connection.VideoProvider;
import android.telecom.InCallService.VideoCall;
import android.telecom.VideoProfile;
import android.telecom.VideoProfile.CameraCapabilities;
import com.android.dialer.common.LogUtil;
import com.android.dialer.logging.DialerImpression;
import com.android.dialer.logging.LoggingBindings;
import com.android.incallui.videotech.VideoTech.VideoTechListener;
import com.android.incallui.videotech.utils.SessionModificationState;

/** Receives IMS video call state updates. */
public class ImsVideoCallCallback extends VideoCall.Callback {
  private static final int CLEAR_FAILED_REQUEST_TIMEOUT_MILLIS = 4000;
  private final Handler handler = new Handler();
  private final LoggingBindings logger;
  private final Call call;
  private final ImsVideoTech videoTech;
  private final VideoTechListener listener;
  private final Context context;
  private int requestedVideoState = VideoProfile.STATE_AUDIO_ONLY;

  ImsVideoCallCallback(
      final LoggingBindings logger,
      final Call call,
      ImsVideoTech videoTech,
      VideoTechListener listener,
      Context context) {
    this.logger = logger;
    this.call = call;
    this.videoTech = videoTech;
    this.listener = listener;
    this.context = context;
  }

  @Override
  public void onSessionModifyRequestReceived(VideoProfile videoProfile) {
    LogUtil.i(
        "ImsVideoCallCallback.onSessionModifyRequestReceived", "videoProfile: " + videoProfile);

    int previousVideoState = ImsVideoTech.getUnpausedVideoState(call.getDetails().getVideoState());
    int newVideoState = ImsVideoTech.getUnpausedVideoState(videoProfile.getVideoState());

    boolean wasVideoCall = VideoProfile.isVideo(previousVideoState);
    boolean isVideoCall = VideoProfile.isVideo(newVideoState);

    if (wasVideoCall && !isVideoCall) {
      LogUtil.i(
          "ImsVideoTech.onSessionModifyRequestReceived", "call downgraded to %d", newVideoState);
    } else if (previousVideoState != newVideoState) {
      requestedVideoState = newVideoState;
      if (!wasVideoCall) {
        videoTech.setSessionModificationState(
            SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST);
        listener.onVideoUpgradeRequestReceived();
        logger.logImpression(DialerImpression.Type.IMS_VIDEO_REQUEST_RECEIVED);
      } else {
        LogUtil.i(
            "ImsVideoTech.onSessionModifyRequestReceived", "call updated to %d", newVideoState);
        videoTech.acceptVideoRequest(context);
      }
    }
  }

  /**
   * @param status Status of the session modify request. Valid values are {@link
   *     Connection.VideoProvider#SESSION_MODIFY_REQUEST_SUCCESS}, {@link
   *     Connection.VideoProvider#SESSION_MODIFY_REQUEST_FAIL}, {@link
   *     Connection.VideoProvider#SESSION_MODIFY_REQUEST_INVALID}
   * @param responseProfile The actual profile changes made by the peer device.
   */
  @Override
  public void onSessionModifyResponseReceived(
      int status, VideoProfile requestedProfile, VideoProfile responseProfile) {
    LogUtil.i(
        "ImsVideoCallCallback.onSessionModifyResponseReceived",
        "status: %d, requestedProfile: %s, responseProfile: %s, session modification state: %d",
        status,
        requestedProfile,
        responseProfile,
        videoTech.getSessionModificationState());

    if (videoTech.getSessionModificationState()
        == SessionModificationState.WAITING_FOR_UPGRADE_TO_VIDEO_RESPONSE) {
      final int newSessionModificationState = getSessionModificationStateFromTelecomStatus(status);
      if (status == VideoProvider.SESSION_MODIFY_REQUEST_SUCCESS) {
        // Telecom manages audio route for us
        listener.onUpgradedToVideo(false /* switchToSpeaker */);
      } else {
        // This will update the video UI to display the error message.
        videoTech.setSessionModificationState(newSessionModificationState);
      }

      // If the other person accepted the upgrade request then this will keep the video UI up until
      // the call's video state change. Without this we would switch to the voice call and then
      // switch back to video UI.
      clearFailedResponseState(newSessionModificationState);
    } else if (videoTech.getSessionModificationState()
        == SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST) {
      requestedVideoState = VideoProfile.STATE_AUDIO_ONLY;
      videoTech.setSessionModificationState(SessionModificationState.NO_REQUEST);
    } else if (videoTech.getSessionModificationState()
        == SessionModificationState.WAITING_FOR_RESPONSE) {
      final int newSessionModificationState = getSessionModificationStateFromTelecomStatus(status);
      videoTech.setSessionModificationState(newSessionModificationState);
      if (status != VideoProvider.SESSION_MODIFY_REQUEST_SUCCESS) {
        clearFailedResponseState(newSessionModificationState);
      }
    } else {
      LogUtil.i(
          "ImsVideoCallCallback.onSessionModifyResponseReceived",
          "call is not waiting for response, doing nothing");
    }
  }

  private void clearFailedResponseState(final int newSessionModificationState) {
    handler.removeCallbacksAndMessages(null); // Clear everything
    // Wait for 4 seconds and then clean the session modification state. This allows the video UI
    // to stay up so that the user can read the error message.
    handler.postDelayed(
        () -> {
          if (videoTech.getSessionModificationState() == newSessionModificationState) {
            LogUtil.i("ImsVideoCallCallback.onSessionModifyResponseReceived", "clearing state");
            videoTech.setSessionModificationState(SessionModificationState.NO_REQUEST);
          } else {
            LogUtil.i(
                "ImsVideoCallCallback.onSessionModifyResponseReceived",
                "session modification state has changed, not clearing state");
          }
        },
        CLEAR_FAILED_REQUEST_TIMEOUT_MILLIS);
  }

  @SessionModificationState
  private int getSessionModificationStateFromTelecomStatus(int telecomStatus) {
    switch (telecomStatus) {
      case VideoProvider.SESSION_MODIFY_REQUEST_SUCCESS:
        return SessionModificationState.NO_REQUEST;
      case VideoProvider.SESSION_MODIFY_REQUEST_FAIL:
      case VideoProvider.SESSION_MODIFY_REQUEST_INVALID:
        // Check if it's already video call, which means the request is not video upgrade request.
        if (VideoProfile.isVideo(call.getDetails().getVideoState())) {
          return SessionModificationState.REQUEST_FAILED;
        } else {
          return SessionModificationState.UPGRADE_TO_VIDEO_REQUEST_FAILED;
        }
      case VideoProvider.SESSION_MODIFY_REQUEST_TIMED_OUT:
        return SessionModificationState.UPGRADE_TO_VIDEO_REQUEST_TIMED_OUT;
      case VideoProvider.SESSION_MODIFY_REQUEST_REJECTED_BY_REMOTE:
        return SessionModificationState.REQUEST_REJECTED;
      default:
        LogUtil.e(
            "ImsVideoCallCallback.getSessionModificationStateFromTelecomStatus",
            "unknown status: %d",
            telecomStatus);
        return SessionModificationState.REQUEST_FAILED;
    }
  }

  // In the vendor code rx_pause and rx_resume get triggered when the video player starts or stops
  // playing the incoming video stream.  For the case where you're resuming a held call, its
  // definitely a good signal to use to know that the video is resuming (though the video state
  // should change to indicate its not paused in this case as well).  However, keep in mind you'll
  // get these signals as well on carriers that don't support the video pause signalling (like TMO)
  // so you want to ensure you don't send sessionModifyRequests with pause/resume based on these
  // signals. Also, its technically possible to have a pause/resume if the video signal degrades.
  @Override
  public void onCallSessionEvent(int event) {
    switch (event) {
      case Connection.VideoProvider.SESSION_EVENT_RX_PAUSE:
        LogUtil.i("ImsVideoCallCallback.onCallSessionEvent", "rx_pause");
        break;
      case Connection.VideoProvider.SESSION_EVENT_RX_RESUME:
        LogUtil.i("ImsVideoCallCallback.onCallSessionEvent", "rx_resume");
        break;
      case Connection.VideoProvider.SESSION_EVENT_CAMERA_FAILURE:
        LogUtil.i("ImsVideoCallCallback.onCallSessionEvent", "camera_failure");
        break;
      case Connection.VideoProvider.SESSION_EVENT_CAMERA_READY:
        LogUtil.i("ImsVideoCallCallback.onCallSessionEvent", "camera_ready");
        break;
      default:
        LogUtil.i("ImsVideoCallCallback.onCallSessionEvent", "unknown event = : " + event);
        break;
    }
  }

  @Override
  public void onPeerDimensionsChanged(int width, int height) {
    listener.onPeerDimensionsChanged(width, height);
  }

  @Override
  public void onVideoQualityChanged(int videoQuality) {
    LogUtil.i("ImsVideoCallCallback.onVideoQualityChanged", "videoQuality: %d", videoQuality);
  }

  @Override
  public void onCallDataUsageChanged(long dataUsage) {
    LogUtil.i("ImsVideoCallCallback.onCallDataUsageChanged", "dataUsage: %d", dataUsage);
  }

  @Override
  public void onCameraCapabilitiesChanged(CameraCapabilities cameraCapabilities) {
    if (cameraCapabilities != null) {
      listener.onCameraDimensionsChanged(
          cameraCapabilities.getWidth(), cameraCapabilities.getHeight());
    }
  }

  int getRequestedVideoState() {
    return requestedVideoState;
  }
}