1 /* 2 * Copyright (C) 2022 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.internal.telecom; 18 19 import static android.telecom.TelecomManager.TELECOM_TRANSACTION_SUCCESS; 20 21 import android.os.Binder; 22 import android.os.Bundle; 23 import android.os.OutcomeReceiver; 24 import android.os.ResultReceiver; 25 import android.telecom.CallAttributes; 26 import android.telecom.CallControl; 27 import android.telecom.CallControlCallback; 28 import android.telecom.CallEndpoint; 29 import android.telecom.CallEventCallback; 30 import android.telecom.CallException; 31 import android.telecom.DisconnectCause; 32 import android.telecom.PhoneAccountHandle; 33 import android.text.TextUtils; 34 import android.util.Log; 35 36 import com.android.server.telecom.flags.Flags; 37 38 import java.util.List; 39 import java.util.UUID; 40 import java.util.concurrent.ConcurrentHashMap; 41 import java.util.concurrent.Executor; 42 import java.util.function.Consumer; 43 44 /** 45 * wraps {@link CallControlCallback}, {@link CallEventCallback}, and {@link CallControl} on a 46 * per-{@link android.telecom.PhoneAccountHandle} basis to track ongoing calls. 47 * 48 * @hide 49 */ 50 public class ClientTransactionalServiceWrapper { 51 52 private static final String TAG = ClientTransactionalServiceWrapper.class.getSimpleName(); 53 private final PhoneAccountHandle mPhoneAccountHandle; 54 private final ClientTransactionalServiceRepository mRepository; 55 private final ConcurrentHashMap<String, TransactionalCall> mCallIdToTransactionalCall = 56 new ConcurrentHashMap<>(); 57 private static final String EXECUTOR_FAIL_MSG = 58 "Telecom hit an exception while handling a CallEventCallback on an executor: "; 59 ClientTransactionalServiceWrapper(PhoneAccountHandle handle, ClientTransactionalServiceRepository repo)60 public ClientTransactionalServiceWrapper(PhoneAccountHandle handle, 61 ClientTransactionalServiceRepository repo) { 62 mPhoneAccountHandle = handle; 63 mRepository = repo; 64 } 65 66 /** 67 * remove the given call from the class HashMap 68 * 69 * @param callId that is tied to TransactionalCall object 70 */ untrackCall(String callId)71 public void untrackCall(String callId) { 72 Log.i(TAG, TextUtils.formatSimple("removeCall: with id=[%s]", callId)); 73 if (mCallIdToTransactionalCall.containsKey(callId)) { 74 // remove the call from the hashmap 75 TransactionalCall call = mCallIdToTransactionalCall.remove(callId); 76 // null out interface to avoid memory leaks 77 CallControl control = call.getCallControl(); 78 if (control != null) { 79 call.setCallControl(null); 80 } 81 } 82 // possibly cleanup service wrapper if there are no more calls 83 if (mCallIdToTransactionalCall.size() == 0) { 84 mRepository.removeServiceWrapper(mPhoneAccountHandle); 85 } 86 } 87 88 /** 89 * start tracking a newly created call for a particular package 90 * 91 * @param callAttributes of the new call 92 * @param executor to run callbacks on 93 * @param pendingControl that allows telecom to call into the client 94 * @param handshakes that overrides the CallControlCallback 95 * @param events that overrides the CallStateCallback 96 * @return the callId of the newly created call 97 */ trackCall(CallAttributes callAttributes, Executor executor, OutcomeReceiver<CallControl, CallException> pendingControl, CallControlCallback handshakes, CallEventCallback events)98 public String trackCall(CallAttributes callAttributes, Executor executor, 99 OutcomeReceiver<CallControl, CallException> pendingControl, 100 CallControlCallback handshakes, 101 CallEventCallback events) { 102 // generate a new id for this new call 103 String newCallId = UUID.randomUUID().toString(); 104 105 // couple the objects passed from the client side 106 mCallIdToTransactionalCall.put(newCallId, new TransactionalCall(newCallId, callAttributes, 107 executor, pendingControl, handshakes, events)); 108 109 return newCallId; 110 } 111 getCallEventCallback()112 public ICallEventCallback getCallEventCallback() { 113 return mCallEventCallback; 114 } 115 116 /** 117 * Consumers that is to be completed by the client and the result relayed back to telecom server 118 * side via a {@link ResultReceiver}. see com.android.server.telecom.TransactionalServiceWrapper 119 * for how the response is handled. 120 */ 121 private class ReceiverWrapper implements Consumer<Boolean> { 122 private final ResultReceiver mRepeaterReceiver; 123 ReceiverWrapper(ResultReceiver resultReceiver)124 ReceiverWrapper(ResultReceiver resultReceiver) { 125 mRepeaterReceiver = resultReceiver; 126 } 127 128 @Override accept(Boolean clientCompletedCallbackSuccessfully)129 public void accept(Boolean clientCompletedCallbackSuccessfully) { 130 if (clientCompletedCallbackSuccessfully) { 131 mRepeaterReceiver.send(TELECOM_TRANSACTION_SUCCESS, null); 132 } else { 133 mRepeaterReceiver.send(CallException.CODE_ERROR_UNKNOWN, null); 134 } 135 } 136 137 @Override andThen(Consumer<? super Boolean> after)138 public Consumer<Boolean> andThen(Consumer<? super Boolean> after) { 139 return Consumer.super.andThen(after); 140 } 141 } 142 143 private final ICallEventCallback mCallEventCallback = new ICallEventCallback.Stub() { 144 145 private static final String ON_SET_ACTIVE = "onSetActive"; 146 private static final String ON_SET_INACTIVE = "onSetInactive"; 147 private static final String ON_ANSWER = "onAnswer"; 148 private static final String ON_DISCONNECT = "onDisconnect"; 149 private static final String ON_STREAMING_STARTED = "onStreamingStarted"; 150 private static final String ON_REQ_ENDPOINT_CHANGE = "onRequestEndpointChange"; 151 private static final String ON_AVAILABLE_CALL_ENDPOINTS = "onAvailableCallEndpointsChanged"; 152 private static final String ON_MUTE_STATE_CHANGED = "onMuteStateChanged"; 153 private static final String ON_VIDEO_STATE_CHANGED = "onVideoStateChanged"; 154 private static final String ON_CALL_STREAMING_FAILED = "onCallStreamingFailed"; 155 private static final String ON_EVENT = "onEvent"; 156 157 private void handleCallEventCallback(String action, String callId, 158 ResultReceiver ackResultReceiver, Object... args) { 159 Log.i(TAG, TextUtils.formatSimple("hCEC: id=[%s], action=[%s]", callId, action)); 160 // lookup the callEventCallback associated with the particular call 161 TransactionalCall call = mCallIdToTransactionalCall.get(callId); 162 163 if (call != null) { 164 // Get the CallEventCallback interface 165 CallControlCallback callback = call.getCallControlCallback(); 166 // Get Receiver to wait on client ack 167 ReceiverWrapper outcomeReceiverWrapper = new ReceiverWrapper(ackResultReceiver); 168 169 // wait for the client to complete the CallEventCallback 170 final long identity = Binder.clearCallingIdentity(); 171 try { 172 call.getExecutor().execute(() -> { 173 switch (action) { 174 case ON_SET_ACTIVE: 175 callback.onSetActive(outcomeReceiverWrapper); 176 break; 177 case ON_SET_INACTIVE: 178 callback.onSetInactive(outcomeReceiverWrapper); 179 break; 180 case ON_DISCONNECT: 181 callback.onDisconnect((DisconnectCause) args[0], 182 outcomeReceiverWrapper); 183 untrackCall(callId); 184 break; 185 case ON_ANSWER: 186 callback.onAnswer((int) args[0], outcomeReceiverWrapper); 187 break; 188 case ON_STREAMING_STARTED: 189 callback.onCallStreamingStarted(outcomeReceiverWrapper); 190 break; 191 } 192 }); 193 } catch (Exception e) { 194 Log.e(TAG, EXECUTOR_FAIL_MSG + e); 195 } finally { 196 Binder.restoreCallingIdentity(identity); 197 } 198 } 199 } 200 201 @Override 202 public void onAddCallControl(String callId, int resultCode, ICallControl callControl, 203 CallException transactionalException) { 204 Log.i(TAG, TextUtils.formatSimple("oACC: id=[%s], code=[%d]", callId, resultCode)); 205 TransactionalCall call = mCallIdToTransactionalCall.get(callId); 206 207 if (call != null) { 208 OutcomeReceiver<CallControl, CallException> pendingControl = 209 call.getPendingControl(); 210 211 if (resultCode == TELECOM_TRANSACTION_SUCCESS) { 212 213 // create the interface object that the client will interact with 214 CallControl control = new CallControl(callId, callControl); 215 // give the client the object via the OR that was passed into addCall 216 pendingControl.onResult(control); 217 218 // store for later reference 219 call.setCallControl(control); 220 } else { 221 pendingControl.onError(transactionalException); 222 mCallIdToTransactionalCall.remove(callId); 223 } 224 225 } else { 226 untrackCall(callId); 227 Log.e(TAG, "oACC: TransactionalCall object not found for call w/ id=" + callId); 228 } 229 } 230 231 @Override 232 public void onSetActive(String callId, ResultReceiver resultReceiver) { 233 handleCallEventCallback(ON_SET_ACTIVE, callId, resultReceiver); 234 } 235 236 @Override 237 public void onSetInactive(String callId, ResultReceiver resultReceiver) { 238 handleCallEventCallback(ON_SET_INACTIVE, callId, resultReceiver); 239 } 240 241 @Override 242 public void onAnswer(String callId, int videoState, ResultReceiver resultReceiver) { 243 handleCallEventCallback(ON_ANSWER, callId, resultReceiver, videoState); 244 } 245 246 @Override 247 public void onDisconnect(String callId, DisconnectCause cause, 248 ResultReceiver resultReceiver) { 249 handleCallEventCallback(ON_DISCONNECT, callId, resultReceiver, cause); 250 } 251 252 @Override 253 public void onCallEndpointChanged(String callId, CallEndpoint endpoint) { 254 handleEventCallback(callId, ON_REQ_ENDPOINT_CHANGE, endpoint); 255 } 256 257 @Override 258 public void onAvailableCallEndpointsChanged(String callId, List<CallEndpoint> endpoints) { 259 handleEventCallback(callId, ON_AVAILABLE_CALL_ENDPOINTS, endpoints); 260 } 261 262 @Override 263 public void onMuteStateChanged(String callId, boolean isMuted) { 264 handleEventCallback(callId, ON_MUTE_STATE_CHANGED, isMuted); 265 } 266 267 @Override 268 public void onVideoStateChanged(String callId, int videoState) { 269 handleEventCallback(callId, ON_VIDEO_STATE_CHANGED, videoState); 270 } 271 272 public void handleEventCallback(String callId, String action, Object arg) { 273 Log.d(TAG, TextUtils.formatSimple("hEC: [%s], callId=[%s]", action, callId)); 274 // lookup the callEventCallback associated with the particular call 275 TransactionalCall call = mCallIdToTransactionalCall.get(callId); 276 if (call != null) { 277 CallEventCallback callback = call.getCallStateCallback(); 278 Executor executor = call.getExecutor(); 279 final long identity = Binder.clearCallingIdentity(); 280 try { 281 executor.execute(() -> { 282 switch (action) { 283 case ON_REQ_ENDPOINT_CHANGE: 284 callback.onCallEndpointChanged((CallEndpoint) arg); 285 break; 286 case ON_AVAILABLE_CALL_ENDPOINTS: 287 callback.onAvailableCallEndpointsChanged((List<CallEndpoint>) arg); 288 break; 289 case ON_MUTE_STATE_CHANGED: 290 callback.onMuteStateChanged((boolean) arg); 291 break; 292 case ON_VIDEO_STATE_CHANGED: 293 if (Flags.transactionalVideoState()) { 294 callback.onVideoStateChanged((int) arg); 295 } 296 break; 297 case ON_CALL_STREAMING_FAILED: 298 callback.onCallStreamingFailed((int) arg /* reason */); 299 break; 300 } 301 }); 302 } finally { 303 Binder.restoreCallingIdentity(identity); 304 } 305 } 306 } 307 308 @Override 309 public void removeCallFromTransactionalServiceWrapper(String callId) { 310 untrackCall(callId); 311 } 312 313 @Override 314 public void onCallStreamingStarted(String callId, ResultReceiver resultReceiver) { 315 handleCallEventCallback(ON_STREAMING_STARTED, callId, resultReceiver); 316 } 317 318 @Override 319 public void onCallStreamingFailed(String callId, int reason) { 320 Log.i(TAG, TextUtils.formatSimple("oCSF: id=[%s], reason=[%s]", callId, reason)); 321 handleEventCallback(callId, ON_CALL_STREAMING_FAILED, reason); 322 } 323 324 @Override 325 public void onEvent(String callId, String event, Bundle extras) { 326 // lookup the callEventCallback associated with the particular call 327 TransactionalCall call = mCallIdToTransactionalCall.get(callId); 328 if (call != null) { 329 CallEventCallback callback = call.getCallStateCallback(); 330 Executor executor = call.getExecutor(); 331 final long identity = Binder.clearCallingIdentity(); 332 try { 333 executor.execute(() -> { 334 callback.onEvent(event, extras); 335 }); 336 } finally { 337 Binder.restoreCallingIdentity(identity); 338 } 339 } 340 } 341 }; 342 } 343