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