From d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9 Mon Sep 17 00:00:00 2001 From: Eric Erfanian Date: Wed, 15 Mar 2017 14:41:07 -0700 Subject: Update Dialer source from latest green build. * Refactor voicemail component * Add new enriched calling components Test: treehugger, manual aosp testing Change-Id: I521a0f86327d4b42e14d93927c7d613044ed5942 --- java/com/android/incallui/videotech/VideoTech.java | 96 ++++++++++ .../incallui/videotech/empty/EmptyVideoTech.java | 76 ++++++++ .../videotech/ims/ImsVideoCallCallback.java | 201 +++++++++++++++++++ .../incallui/videotech/ims/ImsVideoTech.java | 212 +++++++++++++++++++++ .../incallui/videotech/rcs/RcsVideoShare.java | 195 +++++++++++++++++++ 5 files changed, 780 insertions(+) create mode 100644 java/com/android/incallui/videotech/VideoTech.java create mode 100644 java/com/android/incallui/videotech/empty/EmptyVideoTech.java create mode 100644 java/com/android/incallui/videotech/ims/ImsVideoCallCallback.java create mode 100644 java/com/android/incallui/videotech/ims/ImsVideoTech.java create mode 100644 java/com/android/incallui/videotech/rcs/RcsVideoShare.java (limited to 'java/com/android/incallui/videotech') diff --git a/java/com/android/incallui/videotech/VideoTech.java b/java/com/android/incallui/videotech/VideoTech.java new file mode 100644 index 000000000..fb2641793 --- /dev/null +++ b/java/com/android/incallui/videotech/VideoTech.java @@ -0,0 +1,96 @@ +/* + * 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; + +import android.support.annotation.IntDef; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** Video calling interface. */ +public interface VideoTech { + + boolean isAvailable(); + + boolean isTransmittingOrReceiving(); + + void onCallStateChanged(int newState); + + @SessionModificationState + int getSessionModificationState(); + + void upgradeToVideo(); + + void acceptVideoRequest(); + + void acceptVideoRequestAsAudio(); + + void declineVideoRequest(); + + boolean isTransmitting(); + + void stopTransmission(); + + void resumeTransmission(); + + void pause(); + + void unpause(); + + void setCamera(String cameraId); + + void setDeviceOrientation(int rotation); + + /** Listener for video call events. */ + interface VideoTechListener { + + void onVideoTechStateChanged(); + + void onSessionModificationStateChanged(); + + void onCameraDimensionsChanged(int width, int height); + + void onPeerDimensionsChanged(int width, int height); + + void onVideoUpgradeRequestReceived(); + } + + /** + * Defines different states of session modify requests, which are used to upgrade to video, or + * downgrade to audio. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({ + SESSION_MODIFICATION_STATE_NO_REQUEST, + SESSION_MODIFICATION_STATE_WAITING_FOR_UPGRADE_TO_VIDEO_RESPONSE, + SESSION_MODIFICATION_STATE_REQUEST_FAILED, + SESSION_MODIFICATION_STATE_RECEIVED_UPGRADE_TO_VIDEO_REQUEST, + SESSION_MODIFICATION_STATE_UPGRADE_TO_VIDEO_REQUEST_TIMED_OUT, + SESSION_MODIFICATION_STATE_UPGRADE_TO_VIDEO_REQUEST_FAILED, + SESSION_MODIFICATION_STATE_REQUEST_REJECTED, + SESSION_MODIFICATION_STATE_WAITING_FOR_RESPONSE + }) + @interface SessionModificationState {} + + int SESSION_MODIFICATION_STATE_NO_REQUEST = 0; + int SESSION_MODIFICATION_STATE_WAITING_FOR_UPGRADE_TO_VIDEO_RESPONSE = 1; + int SESSION_MODIFICATION_STATE_REQUEST_FAILED = 2; + int SESSION_MODIFICATION_STATE_RECEIVED_UPGRADE_TO_VIDEO_REQUEST = 3; + int SESSION_MODIFICATION_STATE_UPGRADE_TO_VIDEO_REQUEST_TIMED_OUT = 4; + int SESSION_MODIFICATION_STATE_UPGRADE_TO_VIDEO_REQUEST_FAILED = 5; + int SESSION_MODIFICATION_STATE_REQUEST_REJECTED = 6; + int SESSION_MODIFICATION_STATE_WAITING_FOR_RESPONSE = 7; +} diff --git a/java/com/android/incallui/videotech/empty/EmptyVideoTech.java b/java/com/android/incallui/videotech/empty/EmptyVideoTech.java new file mode 100644 index 000000000..bc8db4c07 --- /dev/null +++ b/java/com/android/incallui/videotech/empty/EmptyVideoTech.java @@ -0,0 +1,76 @@ +/* + * 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.empty; + +import com.android.incallui.videotech.VideoTech; + +/** Default video tech that is always available but doesn't do anything. */ +public class EmptyVideoTech implements VideoTech { + + @Override + public boolean isAvailable() { + return false; + } + + @Override + public boolean isTransmittingOrReceiving() { + return false; + } + + @Override + public void onCallStateChanged(int newState) {} + + @Override + public int getSessionModificationState() { + return VideoTech.SESSION_MODIFICATION_STATE_NO_REQUEST; + } + + @Override + public void upgradeToVideo() {} + + @Override + public void acceptVideoRequest() {} + + @Override + public void acceptVideoRequestAsAudio() {} + + @Override + public void declineVideoRequest() {} + + @Override + public boolean isTransmitting() { + return false; + } + + @Override + public void stopTransmission() {} + + @Override + public void resumeTransmission() {} + + @Override + public void pause() {} + + @Override + public void unpause() {} + + @Override + public void setCamera(String cameraId) {} + + @Override + public void setDeviceOrientation(int rotation) {} +} diff --git a/java/com/android/incallui/videotech/ims/ImsVideoCallCallback.java b/java/com/android/incallui/videotech/ims/ImsVideoCallCallback.java new file mode 100644 index 000000000..0a15f7e65 --- /dev/null +++ b/java/com/android/incallui/videotech/ims/ImsVideoCallCallback.java @@ -0,0 +1,201 @@ +/* + * 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.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.incallui.videotech.VideoTech; +import com.android.incallui.videotech.VideoTech.SessionModificationState; +import com.android.incallui.videotech.VideoTech.VideoTechListener; + +/** 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 Call call; + private final ImsVideoTech videoTech; + private final VideoTechListener listener; + private int requestedVideoState = VideoProfile.STATE_AUDIO_ONLY; + + ImsVideoCallCallback(final Call call, ImsVideoTech videoTech, VideoTechListener listener) { + this.call = call; + this.videoTech = videoTech; + this.listener = listener; + } + + @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; + videoTech.setSessionModificationState( + VideoTech.SESSION_MODIFICATION_STATE_RECEIVED_UPGRADE_TO_VIDEO_REQUEST); + listener.onVideoUpgradeRequestReceived(); + } + } + + /** + * @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() + == VideoTech.SESSION_MODIFICATION_STATE_WAITING_FOR_UPGRADE_TO_VIDEO_RESPONSE) { + handler.removeCallbacksAndMessages(null); // Clear everything + + final int newSessionModificationState = getSessionModificationStateFromTelecomStatus(status); + if (status != VideoProvider.SESSION_MODIFY_REQUEST_SUCCESS) { + // This will update the video UI to display the error message. + videoTech.setSessionModificationState(newSessionModificationState); + } + + // 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. + // + // 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. + handler.postDelayed( + () -> { + if (videoTech.getSessionModificationState() == newSessionModificationState) { + LogUtil.i("ImsVideoCallCallback.onSessionModifyResponseReceived", "clearing state"); + videoTech.setSessionModificationState( + VideoTech.SESSION_MODIFICATION_STATE_NO_REQUEST); + } else { + LogUtil.i( + "ImsVideoCallCallback.onSessionModifyResponseReceived", + "session modification state has changed, not clearing state"); + } + }, + CLEAR_FAILED_REQUEST_TIMEOUT_MILLIS); + } else if (videoTech.getSessionModificationState() + == VideoTech.SESSION_MODIFICATION_STATE_RECEIVED_UPGRADE_TO_VIDEO_REQUEST) { + videoTech.setSessionModificationState(VideoTech.SESSION_MODIFICATION_STATE_NO_REQUEST); + } else if (videoTech.getSessionModificationState() + == VideoTech.SESSION_MODIFICATION_STATE_WAITING_FOR_RESPONSE) { + videoTech.setSessionModificationState(getSessionModificationStateFromTelecomStatus(status)); + } else { + LogUtil.i( + "ImsVideoCallCallback.onSessionModifyResponseReceived", + "call is not waiting for response, doing nothing"); + } + } + + @SessionModificationState + private int getSessionModificationStateFromTelecomStatus(int telecomStatus) { + switch (telecomStatus) { + case VideoProvider.SESSION_MODIFY_REQUEST_SUCCESS: + return VideoTech.SESSION_MODIFICATION_STATE_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 VideoTech.SESSION_MODIFICATION_STATE_REQUEST_FAILED; + } else { + return VideoTech.SESSION_MODIFICATION_STATE_UPGRADE_TO_VIDEO_REQUEST_FAILED; + } + case VideoProvider.SESSION_MODIFY_REQUEST_TIMED_OUT: + return VideoTech.SESSION_MODIFICATION_STATE_UPGRADE_TO_VIDEO_REQUEST_TIMED_OUT; + case VideoProvider.SESSION_MODIFY_REQUEST_REJECTED_BY_REMOTE: + return VideoTech.SESSION_MODIFICATION_STATE_REQUEST_REJECTED; + default: + LogUtil.e( + "ImsVideoCallCallback.getSessionModificationStateFromTelecomStatus", + "unknown status: %d", + telecomStatus); + return VideoTech.SESSION_MODIFICATION_STATE_REQUEST_FAILED; + } + } + + @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; + } +} diff --git a/java/com/android/incallui/videotech/ims/ImsVideoTech.java b/java/com/android/incallui/videotech/ims/ImsVideoTech.java new file mode 100644 index 000000000..890e5c80c --- /dev/null +++ b/java/com/android/incallui/videotech/ims/ImsVideoTech.java @@ -0,0 +1,212 @@ +/* + * 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.os.Build; +import android.telecom.Call; +import android.telecom.Call.Details; +import android.telecom.VideoProfile; +import com.android.dialer.common.Assert; +import com.android.dialer.common.LogUtil; +import com.android.incallui.videotech.VideoTech; + +/** ViLTE implementation */ +public class ImsVideoTech implements VideoTech { + private final Call call; + private final VideoTechListener listener; + private ImsVideoCallCallback callback; + private @SessionModificationState int sessionModificationState = + VideoTech.SESSION_MODIFICATION_STATE_NO_REQUEST; + private int previousVideoState = VideoProfile.STATE_AUDIO_ONLY; + + public ImsVideoTech(VideoTechListener listener, Call call) { + this.listener = listener; + this.call = call; + } + + @Override + public boolean isAvailable() { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { + return false; + } + + boolean hasCapabilities = + call.getDetails().can(Call.Details.CAPABILITY_SUPPORTS_VT_LOCAL_TX) + && call.getDetails().can(Call.Details.CAPABILITY_SUPPORTS_VT_REMOTE_RX); + + return call.getVideoCall() != null + && (hasCapabilities || VideoProfile.isVideo(call.getDetails().getVideoState())); + } + + @Override + public boolean isTransmittingOrReceiving() { + return VideoProfile.isVideo(call.getDetails().getVideoState()); + } + + @Override + public void onCallStateChanged(int newState) { + if (!isAvailable()) { + return; + } + + if (callback == null) { + callback = new ImsVideoCallCallback(call, this, listener); + call.getVideoCall().registerCallback(callback); + } + + if (getSessionModificationState() + == VideoTech.SESSION_MODIFICATION_STATE_WAITING_FOR_UPGRADE_TO_VIDEO_RESPONSE + && isTransmittingOrReceiving()) { + // We don't clear the session modification state right away when we find out the video upgrade + // request was accepted to avoid having the UI switch from video to voice to video. + // Once the underlying telecom call updates to video mode it's safe to clear the state. + LogUtil.i( + "ImsVideoTech.onCallStateChanged", + "upgraded to video, clearing session modification state"); + setSessionModificationState(VideoTech.SESSION_MODIFICATION_STATE_NO_REQUEST); + } + + // Determines if a received upgrade to video request should be cancelled. This can happen if + // another InCall UI responds to the upgrade to video request. + int newVideoState = call.getDetails().getVideoState(); + if (newVideoState != previousVideoState + && sessionModificationState + == VideoTech.SESSION_MODIFICATION_STATE_RECEIVED_UPGRADE_TO_VIDEO_REQUEST) { + LogUtil.i("ImsVideoTech.onCallStateChanged", "cancelling upgrade notification"); + setSessionModificationState(VideoTech.SESSION_MODIFICATION_STATE_NO_REQUEST); + } + previousVideoState = newVideoState; + } + + @Override + public int getSessionModificationState() { + return sessionModificationState; + } + + void setSessionModificationState(@SessionModificationState int state) { + if (state != sessionModificationState) { + LogUtil.i( + "ImsVideoTech.setSessionModificationState", "%d -> %d", sessionModificationState, state); + sessionModificationState = state; + listener.onSessionModificationStateChanged(); + } + } + + @Override + public void upgradeToVideo() { + LogUtil.enterBlock("ImsVideoTech.upgradeToVideo"); + + int unpausedVideoState = getUnpausedVideoState(call.getDetails().getVideoState()); + call.getVideoCall() + .sendSessionModifyRequest( + new VideoProfile(unpausedVideoState | VideoProfile.STATE_BIDIRECTIONAL)); + setSessionModificationState( + VideoTech.SESSION_MODIFICATION_STATE_WAITING_FOR_UPGRADE_TO_VIDEO_RESPONSE); + } + + @Override + public void acceptVideoRequest() { + int requestedVideoState = callback.getRequestedVideoState(); + Assert.checkArgument(requestedVideoState != VideoProfile.STATE_AUDIO_ONLY); + LogUtil.i("ImsVideoTech.acceptUpgradeRequest", "videoState: " + requestedVideoState); + call.getVideoCall().sendSessionModifyResponse(new VideoProfile(requestedVideoState)); + setSessionModificationState(VideoTech.SESSION_MODIFICATION_STATE_NO_REQUEST); + } + + @Override + public void acceptVideoRequestAsAudio() { + LogUtil.enterBlock("ImsVideoTech.acceptVideoRequestAsAudio"); + call.getVideoCall().sendSessionModifyResponse(new VideoProfile(VideoProfile.STATE_AUDIO_ONLY)); + setSessionModificationState(VideoTech.SESSION_MODIFICATION_STATE_NO_REQUEST); + } + + @Override + public void declineVideoRequest() { + LogUtil.enterBlock("ImsVideoTech.declineUpgradeRequest"); + call.getVideoCall() + .sendSessionModifyResponse(new VideoProfile(call.getDetails().getVideoState())); + setSessionModificationState(VideoTech.SESSION_MODIFICATION_STATE_NO_REQUEST); + } + + @Override + public boolean isTransmitting() { + return VideoProfile.isTransmissionEnabled(call.getDetails().getVideoState()); + } + + @Override + public void stopTransmission() { + LogUtil.enterBlock("ImsVideoTech.stopTransmission"); + + int unpausedVideoState = getUnpausedVideoState(call.getDetails().getVideoState()); + call.getVideoCall() + .sendSessionModifyRequest( + new VideoProfile(unpausedVideoState & ~VideoProfile.STATE_TX_ENABLED)); + } + + @Override + public void resumeTransmission() { + LogUtil.enterBlock("ImsVideoTech.resumeTransmission"); + + int unpausedVideoState = getUnpausedVideoState(call.getDetails().getVideoState()); + call.getVideoCall() + .sendSessionModifyRequest( + new VideoProfile(unpausedVideoState | VideoProfile.STATE_TX_ENABLED)); + setSessionModificationState(VideoTech.SESSION_MODIFICATION_STATE_WAITING_FOR_RESPONSE); + } + + @Override + public void pause() { + if (canPause()) { + LogUtil.i("ImsVideoTech.pause", "sending pause request"); + int pausedVideoState = call.getDetails().getVideoState() | VideoProfile.STATE_PAUSED; + call.getVideoCall().sendSessionModifyRequest(new VideoProfile(pausedVideoState)); + } else { + LogUtil.i("ImsVideoTech.pause", "not sending request: canPause: %b", canPause()); + } + } + + @Override + public void unpause() { + if (canPause()) { + LogUtil.i("ImsVideoTech.unpause", "sending unpause request"); + int unpausedVideoState = getUnpausedVideoState(call.getDetails().getVideoState()); + call.getVideoCall().sendSessionModifyRequest(new VideoProfile(unpausedVideoState)); + } else { + LogUtil.i("ImsVideoTech.unpause", "not sending request: canPause: %b", canPause()); + } + } + + @Override + public void setCamera(String cameraId) { + call.getVideoCall().setCamera(cameraId); + call.getVideoCall().requestCameraCapabilities(); + } + + @Override + public void setDeviceOrientation(int rotation) { + call.getVideoCall().setDeviceOrientation(rotation); + } + + private boolean canPause() { + return call.getDetails().can(Details.CAPABILITY_CAN_PAUSE_VIDEO) + && call.getState() == Call.STATE_ACTIVE; + } + + static int getUnpausedVideoState(int videoState) { + return videoState & (~VideoProfile.STATE_PAUSED); + } +} diff --git a/java/com/android/incallui/videotech/rcs/RcsVideoShare.java b/java/com/android/incallui/videotech/rcs/RcsVideoShare.java new file mode 100644 index 000000000..2cb43036f --- /dev/null +++ b/java/com/android/incallui/videotech/rcs/RcsVideoShare.java @@ -0,0 +1,195 @@ +/* + * 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.rcs; + +import android.support.annotation.NonNull; +import android.telecom.Call; +import com.android.dialer.common.Assert; +import com.android.dialer.common.LogUtil; +import com.android.dialer.enrichedcall.EnrichedCallCapabilities; +import com.android.dialer.enrichedcall.EnrichedCallManager; +import com.android.dialer.enrichedcall.EnrichedCallManager.CapabilitiesListener; +import com.android.dialer.enrichedcall.Session; +import com.android.dialer.enrichedcall.videoshare.VideoShareListener; +import com.android.incallui.videotech.VideoTech; + +/** Allows the in-call UI to make video calls over RCS. */ +public class RcsVideoShare implements VideoTech, CapabilitiesListener, VideoShareListener { + private final EnrichedCallManager enrichedCallManager; + private final VideoTechListener listener; + private final String callingNumber; + private int previousCallState = Call.STATE_NEW; + private long inviteSessionId = Session.NO_SESSION_ID; + private long transmittingSessionId = Session.NO_SESSION_ID; + private long receivingSessionId = Session.NO_SESSION_ID; + + private @SessionModificationState int sessionModificationState = + VideoTech.SESSION_MODIFICATION_STATE_NO_REQUEST; + + public RcsVideoShare( + @NonNull EnrichedCallManager enrichedCallManager, + @NonNull VideoTechListener listener, + @NonNull String callingNumber) { + this.enrichedCallManager = Assert.isNotNull(enrichedCallManager); + this.listener = Assert.isNotNull(listener); + this.callingNumber = Assert.isNotNull(callingNumber); + + enrichedCallManager.registerCapabilitiesListener(this); + enrichedCallManager.registerVideoShareListener(this); + } + + @Override + public boolean isAvailable() { + EnrichedCallCapabilities capabilities = enrichedCallManager.getCapabilities(callingNumber); + return capabilities != null && capabilities.supportsVideoShare(); + } + + @Override + public boolean isTransmittingOrReceiving() { + return transmittingSessionId != Session.NO_SESSION_ID + || receivingSessionId != Session.NO_SESSION_ID; + } + + @Override + public void onCallStateChanged(int newState) { + if (newState == Call.STATE_DISCONNECTING) { + enrichedCallManager.unregisterVideoShareListener(this); + enrichedCallManager.unregisterCapabilitiesListener(this); + } + + if (newState != previousCallState && newState == Call.STATE_ACTIVE) { + // Per spec, request capabilities when the call becomes active + enrichedCallManager.requestCapabilities(callingNumber); + } + + previousCallState = newState; + } + + @Override + public int getSessionModificationState() { + return sessionModificationState; + } + + private void setSessionModificationState(@SessionModificationState int state) { + if (state != sessionModificationState) { + LogUtil.i( + "RcsVideoShare.setSessionModificationState", "%d -> %d", sessionModificationState, state); + sessionModificationState = state; + listener.onSessionModificationStateChanged(); + } + } + + @Override + public void upgradeToVideo() { + LogUtil.enterBlock("RcsVideoShare.upgradeToVideo"); + transmittingSessionId = enrichedCallManager.startVideoShareSession(callingNumber); + if (transmittingSessionId != Session.NO_SESSION_ID) { + setSessionModificationState( + VideoTech.SESSION_MODIFICATION_STATE_WAITING_FOR_UPGRADE_TO_VIDEO_RESPONSE); + } + } + + @Override + public void acceptVideoRequest() { + LogUtil.enterBlock("RcsVideoShare.acceptVideoRequest"); + if (enrichedCallManager.acceptVideoShareSession(inviteSessionId)) { + receivingSessionId = inviteSessionId; + } + inviteSessionId = Session.NO_SESSION_ID; + setSessionModificationState(VideoTech.SESSION_MODIFICATION_STATE_NO_REQUEST); + } + + @Override + public void acceptVideoRequestAsAudio() { + throw Assert.createUnsupportedOperationFailException(); + } + + @Override + public void declineVideoRequest() { + LogUtil.enterBlock("RcsVideoTech.declineUpgradeRequest"); + enrichedCallManager.endVideoShareSession( + enrichedCallManager.getVideoShareInviteSessionId(callingNumber)); + inviteSessionId = Session.NO_SESSION_ID; + setSessionModificationState(VideoTech.SESSION_MODIFICATION_STATE_NO_REQUEST); + } + + @Override + public boolean isTransmitting() { + return transmittingSessionId != Session.NO_SESSION_ID; + } + + @Override + public void stopTransmission() { + LogUtil.enterBlock("RcsVideoTech.stopTransmission"); + } + + @Override + public void resumeTransmission() { + LogUtil.enterBlock("RcsVideoTech.resumeTransmission"); + } + + @Override + public void pause() {} + + @Override + public void unpause() {} + + @Override + public void setCamera(String cameraId) {} + + @Override + public void setDeviceOrientation(int rotation) {} + + @Override + public void onCapabilitiesUpdated() { + listener.onVideoTechStateChanged(); + } + + @Override + public void onVideoShareChanged() { + long existingInviteSessionId = inviteSessionId; + + inviteSessionId = enrichedCallManager.getVideoShareInviteSessionId(callingNumber); + if (inviteSessionId != Session.NO_SESSION_ID) { + if (existingInviteSessionId == Session.NO_SESSION_ID) { + // This is a new invite + setSessionModificationState( + VideoTech.SESSION_MODIFICATION_STATE_RECEIVED_UPGRADE_TO_VIDEO_REQUEST); + listener.onVideoUpgradeRequestReceived(); + } + } else { + setSessionModificationState(VideoTech.SESSION_MODIFICATION_STATE_NO_REQUEST); + } + + if (sessionIsClosed(transmittingSessionId)) { + LogUtil.i("RcsVideoShare.onSessionClosed", "transmitting session closed"); + transmittingSessionId = Session.NO_SESSION_ID; + } + + if (sessionIsClosed(receivingSessionId)) { + LogUtil.i("RcsVideoShare.onSessionClosed", "receiving session closed"); + receivingSessionId = Session.NO_SESSION_ID; + } + + listener.onVideoTechStateChanged(); + } + + private boolean sessionIsClosed(long sessionId) { + return sessionId != Session.NO_SESSION_ID + && enrichedCallManager.getVideoShareSession(sessionId) == null; + } +} -- cgit v1.2.3