packages/apps/Dialer/java/com/android/incallui/videotech/ims/ImsVideoCallCallback.java

236 lines
10 KiB
Java
Raw Permalink Normal View History

2025-08-25 08:38:42 +08:00
/*
* 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;
}
}