impl transactional video API methods
Bug: 311265260
Test: CTS coverage
Change-Id: Ib2196ff4bfda7a5a779d939350a00661b121ce9c
diff --git a/flags/telecom_api_flags.aconfig b/flags/telecom_api_flags.aconfig
index 40b75a2..f3a2b8a 100644
--- a/flags/telecom_api_flags.aconfig
+++ b/flags/telecom_api_flags.aconfig
@@ -30,6 +30,13 @@
}
flag{
+ name: "transactional_video_state"
+ namespace: "telecom"
+ description: "when set, clients using transactional implementations will be able to set & get the video state"
+ bug: "311265260"
+}
+
+flag{
name: "business_call_composer"
namespace: "telecom"
description: "Enables enriched calling features (e.g. Business name will show for a call)"
diff --git a/src/com/android/server/telecom/Call.java b/src/com/android/server/telecom/Call.java
index 624399b..66f9690 100644
--- a/src/com/android/server/telecom/Call.java
+++ b/src/com/android/server/telecom/Call.java
@@ -19,6 +19,8 @@
import static android.provider.CallLog.Calls.MISSED_REASON_NOT_MISSED;
import static android.telephony.TelephonyManager.EVENT_DISPLAY_EMERGENCY_MESSAGE;
+import static com.android.server.telecom.voip.VideoStateTranslation.VideoProfileStateToTransactionalVideoState;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
@@ -652,6 +654,36 @@
private boolean mIsVideoCallingSupportedByPhoneAccount = false;
/**
+ * Indicates whether this individual calls video state can be changed as opposed to be gated
+ * by the {@link PhoneAccount}.
+ *
+ * {@code True} if the call is Transactional && has the CallAttributes.SUPPORTS_VIDEO_CALLING
+ * capability {@code false} otherwise.
+ */
+ private boolean mTransactionalCallSupportsVideoCalling = false;
+
+ public void setTransactionalCallSupportsVideoCalling(CallAttributes callAttributes) {
+ if (!mIsTransactionalCall) {
+ Log.i(this, "setTransactionalCallSupportsVideoCalling: call is not transactional");
+ return;
+ }
+ if (callAttributes == null) {
+ Log.i(this, "setTransactionalCallSupportsVideoCalling: callAttributes is null");
+ return;
+ }
+ if ((callAttributes.getCallCapabilities() & CallAttributes.SUPPORTS_VIDEO_CALLING)
+ == CallAttributes.SUPPORTS_VIDEO_CALLING) {
+ mTransactionalCallSupportsVideoCalling = true;
+ } else {
+ mTransactionalCallSupportsVideoCalling = false;
+ }
+ }
+
+ public boolean isTransactionalCallSupportsVideoCalling() {
+ return mTransactionalCallSupportsVideoCalling;
+ }
+
+ /**
* Indicates whether or not this call can be pulled if it is an external call. If true, respect
* the Connection Capability set by the ConnectionService. If false, override the capability
* set and always remove the ability to pull this external call.
@@ -4023,6 +4055,15 @@
videoState = VideoProfile.STATE_AUDIO_ONLY;
}
+ // Transactional calls have the ability to change video calling capabilities on a per-call
+ // basis as opposed to ConnectionService calls which are only based on the PhoneAccount.
+ if (mFlags.transactionalVideoState()
+ && mIsTransactionalCall && !mTransactionalCallSupportsVideoCalling) {
+ Log.i(this, "setVideoState: The transactional does NOT support video calling."
+ + " defaulted to audio (video not supported)");
+ videoState = VideoProfile.STATE_AUDIO_ONLY;
+ }
+
// Track Video State history during the duration of the call.
// Only update the history when the call is active or disconnected. This ensures we do
// not include the video state history when:
@@ -4045,6 +4086,12 @@
}
}
+ if (mFlags.transactionalVideoState()
+ && mIsTransactionalCall && mTransactionalService != null) {
+ int transactionalVS = VideoProfileStateToTransactionalVideoState(mVideoState);
+ mTransactionalService.onVideoStateChanged(this, transactionalVS);
+ }
+
if (VideoProfile.isVideo(videoState)) {
mAnalytics.setCallIsVideo(true);
}
diff --git a/src/com/android/server/telecom/TelecomServiceImpl.java b/src/com/android/server/telecom/TelecomServiceImpl.java
index 4bda96a..d4d395a 100644
--- a/src/com/android/server/telecom/TelecomServiceImpl.java
+++ b/src/com/android/server/telecom/TelecomServiceImpl.java
@@ -239,6 +239,9 @@
callEventCallback, mCallsManager, call);
call.setTransactionServiceWrapper(serviceWrapper);
+ if (mFeatureFlags.transactionalVideoState()) {
+ call.setTransactionalCallSupportsVideoCalling(callAttributes);
+ }
ICallControl clientCallControl = serviceWrapper.getICallControl();
if (clientCallControl == null) {
diff --git a/src/com/android/server/telecom/TransactionalServiceWrapper.java b/src/com/android/server/telecom/TransactionalServiceWrapper.java
index 938ee58..b9096fa 100644
--- a/src/com/android/server/telecom/TransactionalServiceWrapper.java
+++ b/src/com/android/server/telecom/TransactionalServiceWrapper.java
@@ -43,10 +43,10 @@
import com.android.server.telecom.voip.HoldCallTransaction;
import com.android.server.telecom.voip.EndCallTransaction;
import com.android.server.telecom.voip.MaybeHoldCallForNewCallTransaction;
-import com.android.server.telecom.voip.ParallelTransaction;
import com.android.server.telecom.voip.RequestNewActiveCallTransaction;
import com.android.server.telecom.voip.SerialTransaction;
import com.android.server.telecom.voip.SetMuteStateTransaction;
+import com.android.server.telecom.voip.RequestVideoStateTransaction;
import com.android.server.telecom.voip.TransactionManager;
import com.android.server.telecom.voip.VoipCallTransaction;
import com.android.server.telecom.voip.VoipCallTransactionResult;
@@ -71,6 +71,7 @@
public static final String ANSWER = "Answer";
public static final String DISCONNECT = "Disconnect";
public static final String START_STREAMING = "StartStreaming";
+ public static final String REQUEST_VIDEO_STATE = "RequestVideoState";
// CallEventCallback : Telecom --> Client (ex. voip app)
public static final String ON_SET_ACTIVE = "onSetActive";
@@ -248,6 +249,17 @@
}
}
+ @Override
+ public void requestVideoState(int videoState, String callId, ResultReceiver callback)
+ throws RemoteException {
+ try {
+ Log.startSession("TSW.rVS");
+ createTransactions(callId, callback, REQUEST_VIDEO_STATE, videoState);
+ } finally {
+ Log.endSession();
+ }
+ }
+
private void createTransactions(String callId, ResultReceiver callback, String action,
Object... objects) {
Log.d(TAG, "createTransactions: callId=" + callId);
@@ -274,6 +286,11 @@
addTransactionsToManager(mStreamingController.getStartStreamingTransaction(mCallsManager,
TransactionalServiceWrapper.this, call, mLock), callback);
break;
+ case REQUEST_VIDEO_STATE:
+ addTransactionsToManager(
+ new RequestVideoStateTransaction(mCallsManager, call,
+ (int) objects[0]), callback);
+ break;
}
} else {
Bundle exceptionBundle = new Bundle();
@@ -562,6 +579,15 @@
}
}
+ public void onVideoStateChanged(Call call, int videoState) {
+ if (call != null) {
+ try {
+ mICallEventCallback.onVideoStateChanged(call.getId(), videoState);
+ } catch (RemoteException e) {
+ }
+ }
+ }
+
public void removeCallFromWrappers(Call call) {
if (call != null) {
try {
diff --git a/src/com/android/server/telecom/voip/RequestVideoStateTransaction.java b/src/com/android/server/telecom/voip/RequestVideoStateTransaction.java
new file mode 100644
index 0000000..64596b1
--- /dev/null
+++ b/src/com/android/server/telecom/voip/RequestVideoStateTransaction.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2024 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.server.telecom.voip;
+
+import static com.android.server.telecom.voip.VideoStateTranslation.TransactionalVideoStateToVideoProfileState;
+
+import android.telecom.VideoProfile;
+import android.util.Log;
+
+import com.android.server.telecom.CallsManager;
+import com.android.server.telecom.Call;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
+
+public class RequestVideoStateTransaction extends VoipCallTransaction {
+
+ private static final String TAG = RequestVideoStateTransaction.class.getSimpleName();
+ private final Call mCall;
+ private final int mVideoProfileState;
+
+ public RequestVideoStateTransaction(CallsManager callsManager, Call call,
+ int transactionalVideoState) {
+ super(callsManager.getLock());
+ mCall = call;
+ mVideoProfileState = TransactionalVideoStateToVideoProfileState(transactionalVideoState);
+ }
+
+ @Override
+ public CompletionStage<VoipCallTransactionResult> processTransaction(Void v) {
+ Log.d(TAG, "processTransaction");
+ CompletableFuture<VoipCallTransactionResult> future = new CompletableFuture<>();
+
+ if (isRequestingVideoTransmission(mVideoProfileState) &&
+ !mCall.isVideoCallingSupportedByPhoneAccount()) {
+ future.complete(new VoipCallTransactionResult(
+ VoipCallTransactionResult.RESULT_FAILED,
+ "Video calling is not supported by the target account"));
+ } else if (isRequestingVideoTransmission(mVideoProfileState) &&
+ !mCall.isTransactionalCallSupportsVideoCalling()) {
+ future.complete(new VoipCallTransactionResult(
+ VoipCallTransactionResult.RESULT_FAILED,
+ "Video calling is not supported according to the callAttributes"));
+ } else {
+ mCall.setVideoState(mVideoProfileState);
+ future.complete(new VoipCallTransactionResult(
+ VoipCallTransactionResult.RESULT_SUCCEED,
+ "The Video State was changed successfully"));
+ }
+ return future;
+ }
+
+ private boolean isRequestingVideoTransmission(int targetVideoState) {
+ return targetVideoState != VideoProfile.STATE_AUDIO_ONLY;
+ }
+}
\ No newline at end of file
diff --git a/src/com/android/server/telecom/voip/VideoStateTranslation.java b/src/com/android/server/telecom/voip/VideoStateTranslation.java
new file mode 100644
index 0000000..615e4bc
--- /dev/null
+++ b/src/com/android/server/telecom/voip/VideoStateTranslation.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2024 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.server.telecom.voip;
+
+import android.telecom.CallAttributes;
+import android.telecom.Log;
+import android.telecom.VideoProfile;
+
+/**
+ * This remapping class is needed because {@link VideoProfile} has more fine grain levels of video
+ * states as apposed to Transactional video states (defined in {@link CallAttributes.CallType}.
+ * To be more specific, there are 3 video states (rx, tx, and bi-directional).
+ * {@link CallAttributes.CallType} only has 2 states (audio and video).
+ *
+ * The reason why Transactional calls have fewer states is due to the fact that the framework is
+ * only used by VoIP apps and Telecom only cares to know if the call is audio or video.
+ *
+ * Calls that are backed by a {@link android.telecom.ConnectionService} have the ability to be
+ * managed calls (non-VoIP) and Dialer needs more fine grain video states to update the UI. Thus,
+ * {@link VideoProfile} is used for {@link android.telecom.ConnectionService} backed calls.
+ */
+public class VideoStateTranslation {
+ private static final String TAG = VideoStateTranslation.class.getSimpleName();
+
+ /**
+ * Client --> Telecom
+ * This should be used when the client application is signaling they are changing the video
+ * state.
+ */
+ public static int TransactionalVideoStateToVideoProfileState(int transactionalVideo) {
+ if (transactionalVideo == CallAttributes.AUDIO_CALL) {
+ Log.i(TAG, "%s --> VideoProfile.STATE_AUDIO_ONLY",
+ TransactionalVideoState_toString(transactionalVideo));
+ return VideoProfile.STATE_AUDIO_ONLY;
+ } else {
+ Log.i(TAG, "%s --> VideoProfile.STATE_BIDIRECTIONAL",
+ TransactionalVideoState_toString(transactionalVideo));
+ return VideoProfile.STATE_BIDIRECTIONAL;
+ }
+ }
+
+ /**
+ * Telecom --> Client
+ * This should be used when Telecom is informing the client of a video state change.
+ */
+ public static int VideoProfileStateToTransactionalVideoState(int videoProfileState) {
+ if (videoProfileState == VideoProfile.STATE_AUDIO_ONLY) {
+ Log.i(TAG, "%s --> CallAttributes.AUDIO_CALL",
+ VideoProfileState_toString(videoProfileState));
+ return CallAttributes.AUDIO_CALL;
+ } else {
+ Log.i(TAG, "%s --> CallAttributes.VIDEO_CALL",
+ VideoProfileState_toString(videoProfileState));
+ return CallAttributes.VIDEO_CALL;
+ }
+ }
+
+ private static String TransactionalVideoState_toString(int transactionalVideoState) {
+ if (transactionalVideoState == CallAttributes.AUDIO_CALL) {
+ return "CallAttributes.AUDIO_CALL";
+ } else {
+ return "CallAttributes.VIDEO_CALL";
+ }
+ }
+
+ private static String VideoProfileState_toString(int videoProfileState) {
+ switch (videoProfileState) {
+ case VideoProfile.STATE_BIDIRECTIONAL -> {
+ return "VideoProfile.STATE_BIDIRECTIONAL";
+ }
+ case VideoProfile.STATE_RX_ENABLED -> {
+ return "VideoProfile.STATE_RX_ENABLED";
+ }
+ case VideoProfile.STATE_TX_ENABLED -> {
+ return "VideoProfile.STATE_TX_ENABLED";
+ }
+ }
+ return "VideoProfile.STATE_AUDIO_ONLY";
+ }
+}