1 /* 2 * Copyright (C) 2023 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 android.telecom.cts.apps; 18 19 import static android.telecom.cts.apps.TelecomTestApp.ConnectionServiceVoipAppClone; 20 import static android.telecom.cts.apps.TelecomTestApp.ConnectionServiceVoipAppMain; 21 import static android.telecom.cts.apps.TelecomTestApp.TransactionalVoipAppClone; 22 import static android.telecom.cts.apps.TelecomTestApp.TransactionalVoipAppMain; 23 24 import static org.junit.Assert.fail; 25 26 import android.os.Bundle; 27 import android.os.DeadObjectException; 28 import android.os.RemoteException; 29 import android.os.UserHandle; 30 import android.telecom.CallAttributes; 31 import android.telecom.CallEndpoint; 32 import android.telecom.CallException; 33 import android.telecom.PhoneAccount; 34 import android.telecom.PhoneAccountHandle; 35 import android.util.Log; 36 37 import androidx.test.platform.app.InstrumentationRegistry; 38 39 import java.util.List; 40 import java.util.function.Consumer; 41 42 public class AppControlWrapper { 43 private static final String TAG = AppControlWrapper.class.getSimpleName(); 44 private static final String CLASS = AppControlWrapper.class.getCanonicalName(); 45 private final IAppControl mBinder; 46 private final TelecomTestApp mTelecomApps; 47 private final boolean mIsManagedAppControl; 48 private final boolean mIsManagedAppControlClone; 49 getTelecomApps()50 public TelecomTestApp getTelecomApps() { 51 return mTelecomApps; 52 } 53 isManagedAppControl()54 public boolean isManagedAppControl() { 55 return mIsManagedAppControl; 56 } 57 isManagedAppControlClone()58 public boolean isManagedAppControlClone() { 59 return mIsManagedAppControlClone; 60 } 61 isManagedControl()62 public boolean isManagedControl() { 63 return isManagedAppControl() || isManagedAppControlClone(); 64 } 65 isTransactionalControl()66 public boolean isTransactionalControl() { 67 return mTelecomApps.equals(TransactionalVoipAppClone) 68 || mTelecomApps.equals(TransactionalVoipAppMain); 69 } 70 isVoipControl()71 public boolean isVoipControl() { 72 return mTelecomApps.equals(ConnectionServiceVoipAppMain) 73 || mTelecomApps.equals(ConnectionServiceVoipAppClone) 74 || isTransactionalControl(); 75 } 76 AppControlWrapper(IAppControl binder, TelecomTestApp name)77 public AppControlWrapper(IAppControl binder, TelecomTestApp name) { 78 mBinder = binder; 79 mTelecomApps = name; 80 if (mTelecomApps.equals(TelecomTestApp.ManagedConnectionServiceApp)) { 81 mIsManagedAppControl = true; 82 mIsManagedAppControlClone = false; 83 } else if (mTelecomApps.equals(TelecomTestApp.ManagedConnectionServiceAppClone)) { 84 mIsManagedAppControlClone = true; 85 mIsManagedAppControl = false; 86 } else { 87 mIsManagedAppControl = false; 88 mIsManagedAppControlClone = false; 89 } 90 } 91 92 /** 93 * This method helps determine if the application attached to this wrapper is currently bound 94 * to. Typically this can help to determine if the test process needs to wait longer before 95 * calling binder methods. 96 * 97 * @return true if the telecom app is bound to at this current time. otherwise returns false. 98 */ isBound()99 public boolean isBound() { 100 Log.i(TAG, "isBound"); 101 try { 102 return mBinder.isBound(); 103 } catch (RemoteException e) { 104 Log.e(TAG, "failed to get isBound", e); 105 } 106 return false; 107 } 108 getProcessUserHandle()109 public UserHandle getProcessUserHandle() { 110 Log.i(TAG, "getProcessUserHandle"); 111 try { 112 return mBinder.getProcessUserHandle(); 113 } catch (RemoteException e) { 114 Log.e(TAG, "failed to get getProcessUserHandle", e); 115 } 116 return null; 117 } 118 119 getProcessUid()120 public int getProcessUid() { 121 Log.i(TAG, "getProcessUserHandle"); 122 try { 123 return mBinder.getProcessUid(); 124 } catch (RemoteException e) { 125 Log.e(TAG, "failed to getProcessUid", e); 126 } 127 return -1; 128 } 129 130 /** 131 * This method requests the app that is bound to add a new call with the given callAttributes. 132 * Note: This method does not verify the call is added for ConnectionService implementations 133 * and that job should be left for the InCallService to verify. 134 */ addCall(CallAttributes callAttributes)135 public void addCall(CallAttributes callAttributes) throws Exception { 136 Log.i(TAG, "addCall"); 137 try { 138 NoDataTransaction transactionResult = mBinder.addCall(callAttributes); 139 maybeFailTest(transactionResult); 140 } catch (RemoteException re) { 141 handleRemoteException(re, "addCall"); 142 } 143 } 144 145 /** 146 * This method requests the app that is bound to add a new call with the given callAttributes 147 * that is expected to fail. Instead, it verifies whether the onCreateOutgoingConnection 148 * callback is invoked. Note: This method is purely being used to verify MMI code deflection 149 * behavior for DSDA cases and verifies that the callback wasn't invoked. 150 */ addFailedCallWithCreateConnectionVerify(CallAttributes callAttributes)151 public void addFailedCallWithCreateConnectionVerify(CallAttributes callAttributes) 152 throws Exception { 153 Log.i(TAG, "addFailedCallWithCreateConnectionVerify"); 154 try { 155 NoDataTransaction transactionResult = 156 mBinder.addFailedCallWithCreateConnectionVerify(callAttributes); 157 maybeFailTest(transactionResult); 158 } catch (RemoteException re) { 159 handleRemoteException(re, "addCall"); 160 } 161 } 162 163 /** 164 * This method requests the app that is bound to add a new call with the given callAttributes 165 * that is expected to fail. 166 */ addFailedCall(CallAttributes callAttributes)167 public void addFailedCall(CallAttributes callAttributes) throws Exception { 168 Log.i(TAG, "addFailedCall"); 169 try { 170 NoDataTransaction transactionResult = mBinder.addFailedCall(callAttributes); 171 maybeFailTest(transactionResult); 172 } catch (RemoteException re) { 173 handleRemoteException(re, "addCall"); 174 } 175 } 176 177 /** 178 * This method requests the app that is bound to add a new call with the given callAttributes. 179 * Note: This method does not verify the call is added for ConnectionService implementations 180 * and that job should be left for the InCallService to verify. 181 */ addCall(CallAttributes callAttributes, Consumer<CallStateTransitionOperation> consumer)182 public void addCall(CallAttributes callAttributes, 183 Consumer<CallStateTransitionOperation> consumer) throws Exception { 184 Log.i(TAG, "addCall"); 185 try { 186 NoDataTransaction transactionResult = 187 mBinder.addCallWithConsumer( 188 callAttributes, 189 new IRemoteOperationConsumer.Stub() { 190 @Override 191 public void complete(CallStateTransitionOperation op) { 192 consumer.accept(op); 193 } 194 }); 195 maybeFailTest(transactionResult); 196 } catch (RemoteException re) { 197 handleRemoteException(re, "addCall"); 198 } 199 } 200 201 /** 202 * This method requests the app that is bound to add a new call with the given callAttributes. 203 * Note: This method does not verify the call is added for ConnectionService implementations and 204 * that job should be left for the InCallService to verify. 205 */ verifyAddCall( CallAttributes callAttributes, Consumer<CallStateTransitionOperation> consumer)206 public void verifyAddCall( 207 CallAttributes callAttributes, Consumer<CallStateTransitionOperation> consumer) 208 throws Exception { 209 Log.i(TAG, "verifyAddCall"); 210 try { 211 NoDataTransaction transactionResult = 212 mBinder.verifyCallWithConsumer( 213 callAttributes, 214 new IRemoteOperationConsumer.Stub() { 215 @Override 216 public void complete(CallStateTransitionOperation op) { 217 consumer.accept(op); 218 } 219 }); 220 maybeFailTest(transactionResult); 221 } catch (RemoteException re) { 222 handleRemoteException(re, "addCall"); 223 } 224 } 225 226 /** 227 * This method requests the app that is bound to transition the call state to newCallState 228 * 229 * @param expectSuccess is used for transactional applications only so that both the success and 230 * fail cases can be tested. 231 * @param extras contains videoState and disconnectCause info. 232 */ setCallState(String id, int newCallState, boolean expectSuccess, Bundle extras)233 public CallException setCallState(String id, 234 int newCallState, 235 boolean expectSuccess, 236 Bundle extras) throws Exception { 237 Log.i(TAG, "setCallState"); 238 try { 239 CallExceptionTransaction transactionResult = 240 mBinder.transitionCallStateTo(id, newCallState, expectSuccess, extras); 241 maybeFailTest(transactionResult); 242 return transactionResult.getCallException(); 243 } catch (RemoteException e) { 244 handleRemoteException(e, "setCallState"); 245 } 246 return null; 247 } 248 249 /** 250 * Waits for the current [CallEndpoint] to be non-null before returning. Otherwise, the 251 * application will throw an error. 252 */ getCurrentCallEndpoint(String id)253 public CallEndpoint getCurrentCallEndpoint(String id) throws Exception { 254 Log.i(TAG, "getCurrentCallEndpoint"); 255 CallEndpointTransaction transactionResult = null; 256 try { 257 transactionResult = mBinder.getCurrentCallEndpoint(id); 258 maybeFailTest(transactionResult); 259 } catch (RemoteException e) { 260 handleRemoteException(e, "getCurrentCallEndpoint"); 261 } 262 return transactionResult.getCallEndpoint(); 263 } 264 265 /** 266 * Waits for the available [CallEndpoint]s to be non-null before returning. Otherwise, the 267 * application will throw an error. 268 */ getAvailableCallEndpoints(String id)269 public List<CallEndpoint> getAvailableCallEndpoints(String id) throws Exception { 270 Log.i(TAG, "getAvailableCallEndpoints"); 271 AvailableEndpointsTransaction transactionResult = null; 272 try { 273 transactionResult = mBinder.getAvailableCallEndpoints(id); 274 maybeFailTest(transactionResult); 275 } catch (RemoteException e) { 276 handleRemoteException(e, "getAvailableCallEndpoints"); 277 } 278 return transactionResult.getCallEndpoint(); 279 } 280 281 /** 282 * switches the current [CallEndpoint] 283 */ setAudioRouteStateAndVerify(String id, CallEndpoint newCallEndpoint)284 public void setAudioRouteStateAndVerify(String id, CallEndpoint newCallEndpoint) 285 throws RemoteException { 286 Log.i(TAG, "setAudioRouteStateAndVerify"); 287 try { 288 NoDataTransaction transactionResult = 289 mBinder.requestCallEndpointChange(id, newCallEndpoint); 290 maybeFailTest(transactionResult); 291 } catch (RemoteException e) { 292 handleRemoteException(e, "setAudioRouteStateAndVerify"); 293 } 294 } 295 296 /** 297 * Gets the current mute value of the application. This is tracked locally and updated every 298 * time the audio state changes. 299 */ isMuted(String id)300 public boolean isMuted(String id) throws RemoteException { 301 Log.i(TAG, "isMuted"); 302 try { 303 BooleanTransaction transactionResult = mBinder.isMuted(id); 304 maybeFailTest(transactionResult); 305 return transactionResult.getBoolResult(); 306 } catch (RemoteException e) { 307 handleRemoteException(e, "isMuted"); 308 } 309 return false; 310 } 311 312 /** 313 * Sets the mute state 314 */ setMuteState(String id, boolean isMuted)315 public void setMuteState(String id, boolean isMuted) throws RemoteException { 316 Log.i(TAG, "setMuteState"); 317 try { 318 NoDataTransaction transactionResult = mBinder.setMuteState(id, isMuted); 319 maybeFailTest(transactionResult); 320 } catch (RemoteException e) { 321 handleRemoteException(e, "setMuteState"); 322 } 323 } 324 325 /** Sends the specified connection event on the associated identifier for the call. */ sendConnectionEvent(String id, String event)326 public void sendConnectionEvent(String id, String event) throws RemoteException { 327 Log.i(TAG, "sendConnectionEvent"); 328 try { 329 NoDataTransaction transactionResult = mBinder.sendConnectionEvent(id, event); 330 maybeFailTest(transactionResult); 331 } catch (RemoteException e) { 332 handleRemoteException(e, "sendConnectionEvent"); 333 } 334 } 335 336 /** 337 * Registers the default account that is defined in the application.info class that corresponds 338 * to the implementation class 339 */ registerDefaultPhoneAccount()340 public void registerDefaultPhoneAccount() throws RemoteException { 341 Log.i(TAG, "registerDefaultPhoneAccount"); 342 try { 343 NoDataTransaction transactionResult = mBinder.registerDefaultPhoneAccount(); 344 maybeFailTest(transactionResult); 345 } catch (RemoteException e) { 346 handleRemoteException(e, "registerDefaultPhoneAccount"); 347 } 348 } 349 350 /** 351 * Gets the default account that is defined in the application.info class that corresponds 352 * to the implementation class. 353 */ getDefaultPhoneAccount()354 public PhoneAccount getDefaultPhoneAccount() throws RemoteException { 355 Log.i(TAG, "getDefaultPhoneAccount"); 356 try { 357 PhoneAccountTransaction transactionResult = mBinder.getDefaultPhoneAccount(); 358 maybeFailTest(transactionResult); 359 return transactionResult.getPhoneAccount(); 360 } catch (RemoteException e) { 361 handleRemoteException(e, "getDefaultPhoneAccount"); 362 } 363 return null; 364 } 365 366 /** 367 * Registers a custom account that is usually defined at the test class level. 368 */ registerCustomPhoneAccount(PhoneAccount account)369 public void registerCustomPhoneAccount(PhoneAccount account) throws RemoteException { 370 Log.i(TAG, "registerCustomPhoneAccount"); 371 try { 372 mBinder.registerCustomPhoneAccount(account); 373 } catch (RemoteException e) { 374 handleRemoteException(e, "registerCustomPhoneAccount"); 375 } 376 } 377 378 /** 379 * Unregisters a given account from the client side 380 */ unregisterPhoneAccountWithHandle(PhoneAccountHandle handle)381 public void unregisterPhoneAccountWithHandle(PhoneAccountHandle handle) throws RemoteException { 382 Log.i(TAG, "unregisterPhoneAccountWithHandle"); 383 try { 384 mBinder.unregisterPhoneAccountWithHandle(handle); 385 } catch (RemoteException e) { 386 handleRemoteException(e, "unregisterPhoneAccountWithHandle"); 387 } 388 } 389 390 /** 391 * Gets all the SELF_MANAGED accounts that are retrievable to the client! Helpful to see what 392 * accounts the client is unable to fetch. 393 */ getAccountHandlesForApp()394 public List<PhoneAccountHandle> getAccountHandlesForApp() throws RemoteException { 395 Log.i(TAG, "getAccountHandlesForApp"); 396 try { 397 return mBinder.getOwnAccountHandlesForApp(); 398 } catch (RemoteException e) { 399 handleRemoteException(e, "getAccountHandlesForApp"); 400 } 401 return null; 402 } 403 404 /** 405 * Fetch all the PhoneAccounts associated with the application. 406 */ getRegisteredPhoneAccounts()407 public List<PhoneAccount> getRegisteredPhoneAccounts() throws RemoteException { 408 Log.i(TAG, "getRegisteredPhoneAccounts"); 409 try { 410 return mBinder.getRegisteredPhoneAccounts(); 411 } catch (RemoteException e) { 412 handleRemoteException(e, "getRegisteredPhoneAccounts"); 413 } 414 return null; 415 } 416 handleRemoteException(RemoteException e, String callingMethod)417 private void handleRemoteException(RemoteException e, String callingMethod) 418 throws RemoteException { 419 // before failing the test or throwing the exception, attempt to clean-up the test app 420 // and dump the telecom state to help with debugging since test reports do not include the 421 // Telecom tab 422 dumpTelecomStateAndCleanupApp(); 423 if (e.getClass().equals(DeadObjectException.class)) { 424 fail(CLASS + "." + callingMethod + " threw a DeadObjectException meaning that " 425 + "Process=[" + mTelecomApps + "] died while processing the " + callingMethod 426 + " binder transaction. Look at earlier logs to determine what caused the test" 427 + "app to crash."); 428 } else { 429 throw e; 430 } 431 } 432 dumpTelecomStateAndCleanupApp()433 private void dumpTelecomStateAndCleanupApp() { 434 try { 435 ShellCommandExecutor.dumpTelecom(InstrumentationRegistry.getInstrumentation()); 436 mBinder.cleanup(); 437 } catch (Exception cleanupException) { 438 // ignore exception while trying to clean up 439 } 440 } 441 maybeFailTest(BaseTransaction transactionResult)442 private void maybeFailTest(BaseTransaction transactionResult) { 443 if (transactionResult != null 444 && transactionResult.getResult().equals(TestAppTransaction.Failure)) { 445 // before failing the test or throwing the exception, attempt to clean-up the test app 446 // and dump the telecom state to help with debugging since test reports do not include 447 // the Telecom tab 448 dumpTelecomStateAndCleanupApp(); 449 fail(transactionResult.getTestAppException().getMessage()); 450 } 451 } 452 isNotificationPostedForCall(String callId)453 public boolean isNotificationPostedForCall(String callId) throws RemoteException { 454 Log.i(TAG, "isNotificationPostedForCall"); 455 try { 456 BooleanTransaction transactionResult = mBinder.isNotificationPostedForCall(callId); 457 maybeFailTest(transactionResult); 458 return transactionResult.getBoolResult(); 459 } catch (RemoteException e) { 460 handleRemoteException(e, "isNotificationPostedForCall"); 461 } 462 return false; 463 } 464 removeNotificationForCall(String callId)465 public void removeNotificationForCall(String callId) throws RemoteException { 466 Log.i(TAG, "removeNotificationForCall"); 467 try { 468 NoDataTransaction transactionResult = mBinder.removeNotificationForCall(callId); 469 maybeFailTest(transactionResult); 470 } catch (RemoteException e) { 471 handleRemoteException(e, "removeNotificationForCall"); 472 } 473 } 474 475 /** fetches the foreground service delegation state for a particular (app, handle) combo */ isForegroundServiceDelegationActive(PhoneAccountHandle handle)476 public boolean isForegroundServiceDelegationActive(PhoneAccountHandle handle) 477 throws RemoteException { 478 try { 479 BooleanTransaction transactionResult = 480 mBinder.isForegroundServiceDelegationActive(handle); 481 maybeFailTest(transactionResult); 482 return transactionResult.getBoolResult(); 483 } catch (RemoteException e) { 484 handleRemoteException(e, "isForegroundServiceDelegationActive"); 485 } 486 return false; 487 } 488 } 489