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.os.SystemClock.sleep; 20 import static android.telecom.Call.STATE_ACTIVE; 21 import static android.telecom.Call.STATE_DISCONNECTED; 22 import static android.telecom.Call.STATE_RINGING; 23 import static android.telecom.cts.apps.AttributesUtil.getDefaultAttributesForApp; 24 import static android.telecom.cts.apps.AttributesUtil.getDefaultAttributesForManaged; 25 import static android.telecom.cts.apps.AttributesUtil.getDefaultMmiAttributesForApp; 26 import static android.telecom.cts.apps.AttributesUtil.getRandomAttributesForApp; 27 import static android.telecom.cts.apps.AttributesUtil.getRandomAttributesForManaged; 28 import static android.telecom.cts.apps.ShellCommandExecutor.COMMAND_CLEANUP_STUCK_CALLS; 29 import static android.telecom.cts.apps.ShellCommandExecutor.COMMAND_RESET_CAR; 30 import static android.telecom.cts.apps.ShellCommandExecutor.dumpTelecom; 31 import static android.telecom.cts.apps.ShellCommandExecutor.executeShellCommand; 32 import static android.telecom.cts.apps.TelecomTestApp.ManagedConnectionServiceApp; 33 import static android.telecom.cts.apps.TelecomTestApp.ManagedConnectionServiceAppClone; 34 import static android.telecom.cts.apps.WaitForInCallService.verifyCallExtras; 35 import static android.telecom.cts.apps.WaitForInCallService.verifyCallState; 36 import static android.telecom.cts.apps.WaitForInCallService.waitForInCallServiceBinding; 37 import static android.telecom.cts.apps.WaitForInCallService.waitUntilExpectCallCount; 38 import static android.telecom.cts.apps.WaitForInCallService.waitUntilNewCallId; 39 import static android.telecom.cts.apps.WaitUntil.DEFAULT_TIMEOUT_MS; 40 import static android.telecom.cts.apps.WaitUntil.waitUntilConditionIsTrueOrTimeout; 41 42 import static junit.framework.Assert.assertEquals; 43 44 import static org.junit.Assert.assertFalse; 45 import static org.junit.Assert.assertNotNull; 46 import static org.junit.Assert.assertTrue; 47 import static org.junit.Assert.fail; 48 49 import android.app.AppOpsManager; 50 import android.app.Instrumentation; 51 import android.content.Context; 52 import android.content.pm.PackageManager; 53 import android.media.AudioAttributes; 54 import android.media.AudioFocusRequest; 55 import android.media.AudioManager; 56 import android.os.Build; 57 import android.os.Bundle; 58 import android.os.Process; 59 import android.os.RemoteException; 60 import android.telecom.Call; 61 import android.telecom.CallAttributes; 62 import android.telecom.CallEndpoint; 63 import android.telecom.CallException; 64 import android.telecom.Connection; 65 import android.telecom.PhoneAccount; 66 import android.telecom.PhoneAccountHandle; 67 import android.telecom.TelecomManager; 68 import android.util.Log; 69 70 import androidx.test.platform.app.InstrumentationRegistry; 71 72 import com.android.compatibility.common.util.ShellIdentityUtils; 73 74 import java.util.ArrayList; 75 import java.util.Arrays; 76 import java.util.List; 77 import java.util.concurrent.LinkedBlockingQueue; 78 import java.util.concurrent.TimeUnit; 79 import java.util.function.Consumer; 80 81 /** 82 * This class implements all the methods test classes call into to perform some action on an 83 * application that is bound to in the cts/tests/tests/telecom-apps dir. 84 */ 85 public class BaseAppVerifierImpl { 86 static final String TAG = BaseAppVerifierImpl.class.getSimpleName(); 87 static final boolean HAS_TELECOM = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP; 88 private static final String REGISTER_SIM_SUBSCRIPTION_PERMISSION = 89 "android.permission.REGISTER_SIM_SUBSCRIPTION"; 90 private static final String MODIFY_PHONE_STATE_PERMISSION = 91 "android.permission.MODIFY_PHONE_STATE"; 92 private static final int FOCUS_TIMEOUT_MILLIS = 6000; 93 private static final int TIME_BETWEEN_FOCUS_ATTEMPTS_MILLIS = 500; 94 private static final int MAX_FOCUS_ATTEMPTS = 10; 95 96 /** 97 * Audio attributes for a typical music app. 98 * Adapted from the <a href="https://developer.android.com/media/optimize/audio-focus">Android 99 * Developers site</a>. 100 */ 101 private static final AudioAttributes MUSIC_AUDIO_ATTRIBUTES = new AudioAttributes.Builder() 102 .setUsage(AudioAttributes.USAGE_MEDIA) 103 .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC) 104 .build(); 105 106 public Context mContext; 107 public TelecomManager mTelecomManager; 108 private final BindUtils mBindUtils = new BindUtils(); 109 private final List<PhoneAccount> mManagedAccounts; 110 private final List<PhoneAccount> mManagedCloneAccounts; 111 private final Instrumentation mInstrumentation; 112 private final InCallServiceMethods mVerifierMethods; 113 private final String mCallingPackageName; 114 private final AudioManager mAudioManager; 115 private boolean mIsEmergencyCallingSetup = false; 116 117 public String mPreviousDefaultDialer = ""; 118 public PhoneAccountHandle mPreviousDefaultPhoneAccount = null; 119 public boolean mEnableCallOperationCapture = false; 120 121 // Stores the current audio focus 122 private final LinkedBlockingQueue<Integer> mMusicAudioFocusQueue = 123 new LinkedBlockingQueue<>(); 124 125 /** 126 * Handle audio focus changes for simulated music playback; put these onto a focus queue so we 127 * can wait for it later. 128 */ 129 private final AudioManager.OnAudioFocusChangeListener mMusicAudioFocusChangeListener = 130 focusChange -> { 131 android.util.Log.i(TAG, "onAudioFocusChange: changed to " + focusChange); 132 mMusicAudioFocusQueue.offer(focusChange); 133 }; 134 135 /** 136 * Setup an audio focus request for simulated pre-call music playback. We want to get notified 137 * of focus changes pertaining to the music playback. 138 * Adapted from the <a href="https://developer.android.com/media/optimize/audio-focus">Android 139 * Developers Site</a>. 140 */ 141 public final AudioFocusRequest mMusicFocusRequest = new AudioFocusRequest 142 .Builder(AudioManager.AUDIOFOCUS_GAIN) 143 .setAudioAttributes(MUSIC_AUDIO_ATTRIBUTES) 144 .setAcceptsDelayedFocusGain(true) 145 .setWillPauseWhenDucked(true) 146 .setOnAudioFocusChangeListener(mMusicAudioFocusChangeListener) 147 .build(); 148 BaseAppVerifierImpl(Instrumentation i, List<PhoneAccount> pAs, List<PhoneAccount> pAClones, InCallServiceMethods vm)149 public BaseAppVerifierImpl(Instrumentation i, List<PhoneAccount> pAs, 150 List<PhoneAccount> pAClones, InCallServiceMethods vm) { 151 mInstrumentation = i; 152 mContext = i.getContext(); 153 mTelecomManager = mContext.getSystemService(TelecomManager.class); 154 mManagedAccounts = pAs; 155 mManagedCloneAccounts = pAClones; 156 mVerifierMethods = vm; 157 mCallingPackageName = mContext.getPackageName(); 158 mAudioManager = mContext.getSystemService(AudioManager.class); 159 } 160 setUp()161 public void setUp() throws Exception { 162 executeShellCommand(mInstrumentation, COMMAND_RESET_CAR); 163 AppOpsManager aom = mContext.getSystemService(AppOpsManager.class); 164 ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(aom, 165 (appOpsMan) -> appOpsMan.setUidMode(AppOpsManager.OPSTR_PROCESS_OUTGOING_CALLS, 166 Process.myUid(), AppOpsManager.MODE_ALLOWED)); 167 mPreviousDefaultDialer = ShellCommandExecutor.getDefaultDialer(mInstrumentation); 168 ShellCommandExecutor.setDefaultDialer(mInstrumentation, mCallingPackageName); 169 // In order to isolate cascading test failures, cleanup the telecom or cts test process 170 maybeCleanupTelecom(); 171 HoldableTracker.clearTrackedConnections(); 172 } 173 tearDown()174 public void tearDown() throws Exception { 175 executeShellCommand(mInstrumentation, COMMAND_CLEANUP_STUCK_CALLS); 176 if (!mPreviousDefaultDialer.equals("")) { 177 ShellCommandExecutor.setDefaultDialer(mInstrumentation, mPreviousDefaultDialer); 178 } 179 clearUserDefaultPhoneAccountOverride(); 180 ShellIdentityUtils.dropShellPermissionIdentity(); 181 HoldableTracker.clearTrackedConnections(); 182 } 183 maybeCleanupTelecom()184 public void maybeCleanupTelecom() { 185 if (!shouldTestTelecom(mContext)) { 186 return; 187 } 188 try { 189 if (mTelecomManager.isInCall()) { 190 Log.w(TAG, "maybeCleanupTelecom: Telecom is in a call"); 191 dumpTelecom(mInstrumentation); 192 executeShellCommand(mInstrumentation, COMMAND_CLEANUP_STUCK_CALLS); 193 } 194 if (BindUtils.hasBoundTestApp()) { 195 Log.w(TAG, "maybeCleanupTelecom: A test app is bound when it should not be"); 196 BindUtils.printBoundTestApps(); 197 } 198 } catch (Exception e) { 199 // ignore exception 200 } 201 } 202 shouldTestTelecom(Context context)203 public static boolean shouldTestTelecom(Context context) { 204 if (!HAS_TELECOM) { 205 return false; 206 } 207 final PackageManager pm = context.getPackageManager(); 208 return pm.hasSystemFeature(PackageManager.FEATURE_TELECOM); 209 } 210 bindToApp(TelecomTestApp applicationName)211 public AppControlWrapper bindToApp(TelecomTestApp applicationName) throws Exception { 212 AppControlWrapper control = mBindUtils.bindToApp(mContext, applicationName); 213 Log.i("BaseAppVerifierImpl", "bindToApp: applicationName = " + applicationName); 214 if (isManagedConnectionService(applicationName)) { 215 for (PhoneAccount pA : mManagedAccounts) { 216 registerManagedPhoneAccount(pA); 217 } 218 } else if (isManagedConnectionServiceClone(applicationName)) { 219 Log.i( 220 "BaseAppVerifierImpl", 221 "bindToApp: clone app managed - phone account: " 222 + mManagedCloneAccounts.get(0)); 223 for (PhoneAccount pA : mManagedCloneAccounts) { 224 registerManagedPhoneAccount(pA); 225 } 226 } 227 return control; 228 } 229 isManagedConnectionService(TelecomTestApp applicationName)230 private boolean isManagedConnectionService(TelecomTestApp applicationName) { 231 return applicationName.equals(ManagedConnectionServiceApp); 232 } 233 isManagedConnectionServiceClone(TelecomTestApp applicationName)234 private boolean isManagedConnectionServiceClone(TelecomTestApp applicationName) { 235 return applicationName.equals(ManagedConnectionServiceAppClone); 236 } 237 bindToApps(List<TelecomTestApp> applicationNames)238 public List<AppControlWrapper> bindToApps(List<TelecomTestApp> applicationNames) 239 throws Exception { 240 ArrayList<AppControlWrapper> controls = new ArrayList<>(); 241 for (TelecomTestApp name : applicationNames) { 242 AppControlWrapper control = bindToApp(name); 243 controls.add(control); 244 } 245 return controls; 246 } 247 tearDownApp(AppControlWrapper appControl)248 public void tearDownApp(AppControlWrapper appControl) { 249 if (appControl != null) { 250 mBindUtils.unbindFromApp(mContext, appControl); 251 } 252 } 253 tearDownApps(List<AppControlWrapper> appControls)254 public void tearDownApps(List<AppControlWrapper> appControls) { 255 for (AppControlWrapper control : appControls) { 256 tearDownApp(control); 257 } 258 } 259 260 /*********************************************************** 261 / core methods 262 /***********************************************************/ 263 getDefaultAttributes(TelecomTestApp name, boolean isOutgoing)264 public CallAttributes getDefaultAttributes(TelecomTestApp name, 265 boolean isOutgoing) 266 throws Exception { 267 if (name.equals(ManagedConnectionServiceApp)) { 268 // Treat the first element in mManagedAccounts as the "default" 269 return getDefaultAttributesForManaged( 270 mManagedAccounts.get(0).getAccountHandle(), 271 isOutgoing, 272 false /* isEmergency */); 273 } else if (name.equals(ManagedConnectionServiceAppClone)) { 274 return getDefaultAttributesForManaged( 275 mManagedCloneAccounts.get(0).getAccountHandle(), 276 isOutgoing, 277 false /* isEmergency */); 278 } 279 return getDefaultAttributesForApp(name, isOutgoing); 280 } 281 getDefaultAttributes(TelecomTestApp name, PhoneAccountHandle pAH, boolean isOutgoing)282 public CallAttributes getDefaultAttributes(TelecomTestApp name, PhoneAccountHandle pAH, 283 boolean isOutgoing) 284 throws Exception { 285 if (name.equals(ManagedConnectionServiceApp) 286 || name.equals(ManagedConnectionServiceAppClone)) { 287 return getDefaultAttributesForManaged(pAH, isOutgoing, false /* isEmergency */); 288 } 289 return getDefaultAttributesForApp(name, isOutgoing); 290 } 291 getDefaultAttributesForEmergency(TelecomTestApp name)292 public CallAttributes getDefaultAttributesForEmergency(TelecomTestApp name) { 293 if (name.equals(ManagedConnectionServiceApp)) { 294 // Treat the first element in mManagedAccounts as the "default" 295 return getDefaultAttributesForManaged( 296 mManagedAccounts.get(0).getAccountHandle(), 297 true /* isOutgoing */, 298 true /* isEmergency */); 299 } else if (name.equals(ManagedConnectionServiceAppClone)) { 300 return getDefaultAttributesForManaged( 301 mManagedCloneAccounts.get(0).getAccountHandle(), 302 true /* isOutgoing */, 303 true /* isEmergency */); 304 } 305 return null; 306 } 307 getDefaultMmiAttributes(TelecomTestApp name, boolean inCallMmi)308 public CallAttributes getDefaultMmiAttributes(TelecomTestApp name, boolean inCallMmi) 309 throws Exception { 310 if (name.equals(ManagedConnectionServiceApp)) { 311 // Treat the first element in mManagedAccounts as the "default" 312 return getDefaultMmiAttributesForApp( 313 name, mManagedAccounts.get(0).getAccountHandle(), inCallMmi); 314 } else if (name.equals(ManagedConnectionServiceAppClone)) { 315 return getDefaultMmiAttributesForApp( 316 name, mManagedCloneAccounts.get(0).getAccountHandle(), inCallMmi); 317 } 318 return getDefaultMmiAttributesForApp(name, null /* handle */, inCallMmi); 319 } 320 getRandomAttributes(TelecomTestApp name, boolean isOutgoing, boolean isHoldable)321 public CallAttributes getRandomAttributes(TelecomTestApp name, 322 boolean isOutgoing, 323 boolean isHoldable) 324 throws Exception { 325 if (name.equals(ManagedConnectionServiceApp)) { 326 // Treat the first element in mManagedAccounts as the "default" 327 return getRandomAttributesForManaged(mManagedAccounts.get(0).getAccountHandle(), 328 isOutgoing, isHoldable); 329 } else if (name.equals(ManagedConnectionServiceAppClone)) { 330 return getRandomAttributesForManaged(mManagedCloneAccounts.get(0).getAccountHandle(), 331 isOutgoing, isHoldable); 332 } 333 return getRandomAttributesForApp(name, isOutgoing, isHoldable); 334 } 335 addCall(AppControlWrapper appControl, CallAttributes attributes)336 public int addCall(AppControlWrapper appControl, CallAttributes attributes) 337 throws Exception { 338 int currentCallCount = mVerifierMethods.getCurrentCallCount(); 339 appControl.addCall(attributes); 340 waitForInCallServiceBinding(mVerifierMethods); 341 return currentCallCount; 342 } 343 addCall(AppControlWrapper appControl, CallAttributes attributes, Consumer<CallStateTransitionOperation> consumer)344 public int addCall(AppControlWrapper appControl, CallAttributes attributes, 345 Consumer<CallStateTransitionOperation> consumer) throws Exception { 346 int currentCallCount = mVerifierMethods.getCurrentCallCount(); 347 appControl.addCall(attributes, consumer); 348 waitForInCallServiceBinding(mVerifierMethods); 349 return currentCallCount; 350 } 351 addCallAndVerify(AppControlWrapper appControl, CallAttributes attributes, Consumer<CallStateTransitionOperation> consumer)352 public String addCallAndVerify(AppControlWrapper appControl, CallAttributes attributes, 353 Consumer<CallStateTransitionOperation> consumer) throws Exception { 354 int currentCallCount = addCall(appControl, attributes, consumer); 355 waitUntilExpectedCallCount(currentCallCount + 1); 356 return mVerifierMethods.getLastAddedCall().getDetails().getId(); 357 } 358 addCallAndVerify(AppControlWrapper appControl, CallAttributes attributes)359 public String addCallAndVerify(AppControlWrapper appControl, CallAttributes attributes) 360 throws Exception { 361 int currentCallCount = addCall(appControl, attributes); 362 waitUntilExpectedCallCount(currentCallCount + 1); 363 return mVerifierMethods.getLastAddedCall().getDetails().getId(); 364 } 365 verifyAddEmergencyCall( AppControlWrapper appControl, CallAttributes attributes, Consumer<CallStateTransitionOperation> consumer, int numDisconnectDueToEcc)366 public String verifyAddEmergencyCall( 367 AppControlWrapper appControl, 368 CallAttributes attributes, 369 Consumer<CallStateTransitionOperation> consumer, 370 int numDisconnectDueToEcc) 371 throws Exception { 372 int currentCallCount = mVerifierMethods.getCurrentCallCount(); 373 appControl.verifyAddCall(attributes, consumer); 374 waitForInCallServiceBinding(mVerifierMethods); 375 waitUntilExpectedCallCount(currentCallCount + 1 - numDisconnectDueToEcc); 376 return mVerifierMethods.getLastAddedCall().getDetails().getId(); 377 } 378 addAndGetNewCall(AppControlWrapper appControl, CallAttributes attr, String idToExclude, Consumer<CallStateTransitionOperation> consumer )379 public String addAndGetNewCall(AppControlWrapper appControl, CallAttributes attr, 380 String idToExclude, Consumer<CallStateTransitionOperation> consumer 381 ) throws Exception { 382 addCall(appControl, attr, consumer); 383 waitUntilNewCallId(mVerifierMethods, idToExclude); 384 return mVerifierMethods.getLastAddedCall().getDetails().getId(); 385 } 386 addCallAndVerifyFailure(AppControlWrapper appControl, CallAttributes attributes)387 public void addCallAndVerifyFailure(AppControlWrapper appControl, CallAttributes attributes) 388 throws Exception { 389 appControl.addFailedCall(attributes); 390 } 391 addFailedCallWithCreateConnectionVerify( AppControlWrapper appControl, CallAttributes attributes)392 public void addFailedCallWithCreateConnectionVerify( 393 AppControlWrapper appControl, CallAttributes attributes) throws Exception { 394 appControl.addFailedCallWithCreateConnectionVerify(attributes); 395 } 396 waitUntilExpectedCallCount(int expectedCallCount)397 public void waitUntilExpectedCallCount(int expectedCallCount) { 398 waitUntilExpectCallCount(mVerifierMethods, expectedCallCount); 399 } 400 401 // -- call state setCallState(AppControlWrapper appControl, String id, int callState)402 public void setCallState(AppControlWrapper appControl, String id, int callState) 403 throws Exception { 404 appControl.setCallState(id, callState, true, new Bundle()); 405 } 406 setCallStateAndVerify(AppControlWrapper appControl, String id, int callState)407 public void setCallStateAndVerify(AppControlWrapper appControl, String id, int callState) 408 throws Exception { 409 appControl.setCallState(id, callState, true, new Bundle()); 410 verifyCallState(mVerifierMethods, id, callState); 411 } 412 setCallStateAndVerify(AppControlWrapper appControl, String id, int targetCallState, int arg)413 public void setCallStateAndVerify(AppControlWrapper appControl, String id, int targetCallState, 414 int arg) throws Exception { 415 Bundle extras = new Bundle(); 416 if (targetCallState == STATE_ACTIVE) { 417 verifyCallIsInState(id, STATE_RINGING); 418 extras = CallControlExtras.addVideoStateExtra(extras, arg); 419 } else if (targetCallState == STATE_DISCONNECTED) { 420 extras = CallControlExtras.addDisconnectCauseExtra(extras, arg); 421 } 422 appControl.setCallState(id, targetCallState, true, extras); 423 // verify the call was added in the ICS 424 verifyCallState(mVerifierMethods, id, targetCallState); 425 } 426 verifyCallExtraPresent(String id, String extraToVerify, boolean expected)427 public void verifyCallExtraPresent(String id, String extraToVerify, boolean expected) 428 throws Exception { 429 verifyCallExtras(mVerifierMethods, id, extraToVerify, expected); 430 } 431 setCallStateButExpectOnError(AppControlWrapper appControl, String id, int targetCallState)432 public CallException setCallStateButExpectOnError(AppControlWrapper appControl, 433 String id, 434 int targetCallState) 435 throws Exception { 436 verifyAppIsTransactional(appControl); 437 return appControl.setCallState(id, targetCallState, false, new Bundle()); 438 439 } 440 verifyAppIsTransactional(AppControlWrapper appControlWrapper)441 private void verifyAppIsTransactional(AppControlWrapper appControlWrapper) throws Exception { 442 if (!appControlWrapper.isTransactionalControl()) { 443 throw new Exception("This method is only for Transactional Apps"); 444 } 445 } 446 setCallStateButExpectOnError(AppControlWrapper appControl, String id, int targetCallState, int arg)447 public CallException setCallStateButExpectOnError(AppControlWrapper appControl, 448 String id, 449 int targetCallState, 450 int arg) throws Exception { 451 verifyAppIsTransactional(appControl); 452 Bundle extras = new Bundle(); 453 if (targetCallState == STATE_ACTIVE) { 454 verifyCallIsInState(id, STATE_RINGING); 455 extras = CallControlExtras.addVideoStateExtra(extras, arg); 456 } else if (targetCallState == STATE_DISCONNECTED) { 457 extras = CallControlExtras.addDisconnectCauseExtra(extras, arg); 458 } 459 return appControl.setCallState(id, targetCallState, false, extras); 460 } 461 verifyCallIsInState(String id, int state)462 public void verifyCallIsInState(String id, int state) throws Exception { 463 waitForInCallServiceBinding(mVerifierMethods); 464 verifyCallState(mVerifierMethods, id, state); 465 } 466 answerViaInCallService(String id, int videoState)467 public void answerViaInCallService(String id, int videoState) throws Exception { 468 Call targetCall = findTargetCall(id); 469 targetCall.answer(videoState); 470 } 471 answerViaInCallServiceAndVerify(String id, int videoState)472 public void answerViaInCallServiceAndVerify(String id, int videoState) throws Exception { 473 answerViaInCallService(id, videoState); 474 verifyCallIsInState(id, STATE_ACTIVE); 475 } 476 holdCallViaInCallService(String id)477 public void holdCallViaInCallService(String id) { 478 Call call = findTargetCall(id); 479 call.hold(); 480 } 481 unholdCallViaInCallService(String id)482 public void unholdCallViaInCallService(String id) { 483 Call call = findTargetCall(id); 484 call.unhold(); 485 } 486 disconnectCallViaInCallService(String id)487 public void disconnectCallViaInCallService(String id) { 488 Call call = findTargetCall(id); 489 call.disconnect(); 490 } 491 isCallHoldable(String id)492 public boolean isCallHoldable(String id) { 493 Call call = findTargetCall(id); 494 return (call.getDetails().getCallCapabilities() & Connection.CAPABILITY_HOLD) != 0; 495 } 496 497 // -- audio state getAnotherCallEndpoint(AppControlWrapper appControl, String id)498 public CallEndpoint getAnotherCallEndpoint(AppControlWrapper appControl, String id) 499 throws Exception { 500 CallEndpoint currentCallEndpoint = getCurrentCallEndpoint(appControl, id); 501 List<CallEndpoint> endpoints = getAvailableCallEndpoints(appControl, id); 502 503 if (currentCallEndpoint == null) { 504 fail("currentCallEndpoint is NULL"); 505 } 506 if (endpoints == null) { 507 fail("available endpoints list is NULL"); 508 } 509 if (endpoints.size() == 1) { 510 return null; 511 } 512 for (CallEndpoint endpoint : endpoints) { 513 if (endpoint.getEndpointType() != currentCallEndpoint.getEndpointType()) { 514 return endpoint; 515 } 516 } 517 return null; 518 } 519 getCurrentCallEndpoint(AppControlWrapper appControl, String id)520 public CallEndpoint getCurrentCallEndpoint(AppControlWrapper appControl, String id) 521 throws Exception { 522 return appControl.getCurrentCallEndpoint(id); 523 } 524 getAvailableCallEndpoints(AppControlWrapper appControl, String id)525 public List<CallEndpoint> getAvailableCallEndpoints(AppControlWrapper appControl, String id) 526 throws Exception { 527 return appControl.getAvailableCallEndpoints(id); 528 } 529 530 setAudioRouteStateAndVerify(AppControlWrapper appControl, String id, CallEndpoint newCallEndpoint)531 public void setAudioRouteStateAndVerify(AppControlWrapper appControl, String id, 532 CallEndpoint newCallEndpoint) throws Exception { 533 appControl.setAudioRouteStateAndVerify(id, newCallEndpoint); 534 } 535 isMuted(AppControlWrapper appControl, String id)536 public boolean isMuted(AppControlWrapper appControl, String id) throws RemoteException { 537 return appControl.isMuted(id); 538 } 539 setMuteState(AppControlWrapper appControl, String id, boolean isMuted)540 public void setMuteState(AppControlWrapper appControl, String id, boolean isMuted) 541 throws RemoteException { 542 appControl.setMuteState(id, isMuted); 543 } 544 sendConnectionEvent(AppControlWrapper appControl, String id, String event)545 public void sendConnectionEvent(AppControlWrapper appControl, String id, String event) 546 throws RemoteException { 547 appControl.sendConnectionEvent(id, event); 548 } 549 550 // -- phone accounts registerDefaultPhoneAccount(AppControlWrapper appControl)551 public void registerDefaultPhoneAccount(AppControlWrapper appControl) throws RemoteException { 552 appControl.registerDefaultPhoneAccount(); 553 if (appControl.isManagedAppControl()) { 554 for (PhoneAccount pa : mManagedAccounts) { 555 assertTrue("Managed PhoneAccount [ +" + pa.getAccountHandle() + " ] is not" 556 + "registered.", isPhoneAccountRegistered(pa.getAccountHandle())); 557 } 558 } if (appControl.isManagedAppControlClone()) { 559 for (PhoneAccount pa : mManagedCloneAccounts) { 560 assertTrue("Managed Clone PhoneAccount [ +" + pa.getAccountHandle() + " ] is not" 561 + "registered.", isPhoneAccountRegistered(pa.getAccountHandle())); 562 } 563 } else { 564 PhoneAccount account = appControl.getDefaultPhoneAccount(); 565 assertTrue(isPhoneAccountRegistered(account.getAccountHandle())); 566 } 567 } 568 registerCustomPhoneAccount(AppControlWrapper appControl, PhoneAccount account)569 public void registerCustomPhoneAccount(AppControlWrapper appControl, PhoneAccount account) 570 throws RemoteException { 571 appControl.registerCustomPhoneAccount(account); 572 assertTrue(isPhoneAccountRegistered(account.getAccountHandle())); 573 } 574 registerManagedPhoneAccount(PhoneAccount pa)575 public void registerManagedPhoneAccount(PhoneAccount pa) throws Exception { 576 ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mTelecomManager, 577 tm -> tm.registerPhoneAccount(pa), 578 MODIFY_PHONE_STATE_PERMISSION, 579 REGISTER_SIM_SUBSCRIPTION_PERMISSION); 580 ShellCommandExecutor.enablePhoneAccount(mInstrumentation, pa.getAccountHandle()); 581 } 582 setUserDefaultPhoneAccountOverride(PhoneAccountHandle handle)583 public void setUserDefaultPhoneAccountOverride(PhoneAccountHandle handle) throws Exception { 584 mPreviousDefaultPhoneAccount = mTelecomManager.getUserSelectedOutgoingPhoneAccount(); 585 ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mTelecomManager, 586 (tm) -> tm.setUserSelectedOutgoingPhoneAccount(handle)); 587 assertEquals("Could not set " + handle + "as the user default" , handle, 588 mTelecomManager.getUserSelectedOutgoingPhoneAccount()); 589 } 590 clearUserDefaultPhoneAccountOverride()591 private void clearUserDefaultPhoneAccountOverride() throws Exception { 592 ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mTelecomManager, 593 (tm) -> tm.setUserSelectedOutgoingPhoneAccount(mPreviousDefaultPhoneAccount)); 594 } 595 unregisterPhoneAccountWithHandle(AppControlWrapper appControl, PhoneAccountHandle handle)596 public void unregisterPhoneAccountWithHandle(AppControlWrapper appControl, 597 PhoneAccountHandle handle) throws RemoteException { 598 appControl.unregisterPhoneAccountWithHandle(handle); 599 assertFalse(isPhoneAccountRegistered(handle)); 600 } 601 getAccountHandlesForApp(AppControlWrapper appControl)602 public List<PhoneAccountHandle> getAccountHandlesForApp(AppControlWrapper appControl) 603 throws RemoteException { 604 return appControl.getAccountHandlesForApp(); 605 } 606 verifyCallPhoneAccount(String id, PhoneAccountHandle handle)607 public void verifyCallPhoneAccount(String id, PhoneAccountHandle handle) { 608 Call targetCall = findTargetCall(id); 609 if (targetCall.getDetails() == null) { 610 fail("verifyCallPhoneAccount: failed to find target call details, id=" + id); 611 } 612 assertEquals("Call PhoneAccount did not match expected", handle, 613 targetCall.getDetails().getAccountHandle()); 614 } 615 isPhoneAccountRegistered(PhoneAccountHandle handle)616 public boolean isPhoneAccountRegistered(PhoneAccountHandle handle) { 617 return mTelecomManager.getPhoneAccount(handle) != null; 618 } 619 assertAudioMode(final int expectedMode)620 public void assertAudioMode(final int expectedMode) { 621 waitUntilConditionIsTrueOrTimeout( 622 new Condition() { 623 @Override 624 public Object expected() { 625 return true; 626 } 627 628 @Override 629 public Object actual() { 630 return mAudioManager.getMode() == expectedMode; 631 } 632 }, 633 DEFAULT_TIMEOUT_MS, 634 "Audio mode was expected to be " + expectedMode 635 ); 636 } verifyNotificationPostedForCall(AppControlWrapper appControlWrapper, String callId)637 public void verifyNotificationPostedForCall(AppControlWrapper appControlWrapper, String callId){ 638 waitUntilConditionIsTrueOrTimeout( 639 new Condition() { 640 @Override 641 public Object expected() { 642 return true; 643 } 644 645 @Override 646 public Object actual() { 647 try { 648 return appControlWrapper.isNotificationPostedForCall(callId); 649 } catch (RemoteException e) { 650 throw new RuntimeException(e); 651 } 652 } 653 }, 654 DEFAULT_TIMEOUT_MS, 655 String.format("Expected to find notification for call with id=[%s], " 656 + "for application=[%s], but no notification was posted by the" 657 + " notification manager", callId, 658 appControlWrapper.getTelecomApps())); 659 } 660 661 /** 662 * Acquire media focus for music playback; pretend we are listening to music so that we can 663 * verify that focus is lost during a call and restored later. 664 */ acquireAudioFocusForMusic()665 public void acquireAudioFocusForMusic() { 666 final int[] result = {AudioManager.AUDIOFOCUS_REQUEST_FAILED}; 667 ShellIdentityUtils.invokeWithShellPermissions(() -> { 668 int attempts = 0; 669 while (result[0] == AudioManager.AUDIOFOCUS_REQUEST_FAILED 670 && attempts <= MAX_FOCUS_ATTEMPTS) { 671 attempts++; 672 result[0] = mAudioManager.requestAudioFocus(mMusicFocusRequest); 673 if (result[0] == AudioManager.AUDIOFOCUS_REQUEST_FAILED) { 674 sleep(TIME_BETWEEN_FOCUS_ATTEMPTS_MILLIS); 675 } 676 } 677 }); 678 if (result[0] == AudioManager.AUDIOFOCUS_REQUEST_DELAYED) { 679 waitForAndVerifyMusicFocus(true, new int[] {AudioManager.AUDIOFOCUS_REQUEST_GRANTED}); 680 } else { 681 assertEquals("Failed to acquire focus for media playback in order to verify that " 682 + "media focus is lost in calls.", 683 AudioManager.AUDIOFOCUS_REQUEST_GRANTED, 684 result[0]); 685 } 686 } 687 688 /** Waits to ensure that the music audio focus was one of the expected values. */ waitForAndVerifyMusicFocus(boolean verifyPresence, int[] expectedValues)689 public boolean waitForAndVerifyMusicFocus(boolean verifyPresence, int[] expectedValues) { 690 Integer newFocus = null; 691 try { 692 newFocus = mMusicAudioFocusQueue.poll(FOCUS_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); 693 } catch (InterruptedException ie) { 694 fail("Expected to get new music focus but timed out."); 695 } 696 if (verifyPresence) { 697 assertNotNull( 698 "Expected focus to be reported but none was within the timeout.", newFocus); 699 } 700 boolean wasExpectedFocusValue = false; 701 if (newFocus != null) { 702 int newFocusValue = newFocus.intValue(); 703 704 // We expect to have lost focus; it will likely be reported as transient focus lost. 705 // Both of these focus lost types indicate that something else has gained exclusive 706 // access to the audio focus. 707 wasExpectedFocusValue = Arrays.stream(expectedValues).anyMatch(v -> v == newFocusValue); 708 assertTrue( 709 "Expected focus to be one of " 710 + Arrays.toString(expectedValues) 711 + " but was " 712 + newFocusValue, 713 wasExpectedFocusValue); 714 } 715 return wasExpectedFocusValue; 716 } 717 718 /** 719 * Release media focus for media playback; pretend we are not listening to music any longer. 720 */ releaseAudioFocusForMusic()721 public void releaseAudioFocusForMusic() { 722 int result = mAudioManager.abandonAudioFocusRequest(mMusicFocusRequest); 723 assertEquals(AudioManager.AUDIOFOCUS_REQUEST_GRANTED, result); 724 } 725 setupForEmergencyCalling()726 public void setupForEmergencyCalling() throws Exception { 727 mIsEmergencyCallingSetup = true; 728 ShellCommandExecutor.setSystemDialerOverride( 729 InstrumentationRegistry.getInstrumentation(), 730 AttributesUtil.SYSTEM_DIALER_PKG_NAME); 731 ShellCommandExecutor.addTestEmergencyNumber( 732 InstrumentationRegistry.getInstrumentation(), AttributesUtil.TEST_EMERGENCY_NUMBER); 733 ShellCommandExecutor.setTestEmergencyPhoneAccountPackageFilter( 734 InstrumentationRegistry.getInstrumentation(), 735 AttributesUtil.TEST_EMERGENCY_MANAGED_PHONE_ACCOUNT_PKG_NAME 736 + "," 737 + AttributesUtil.TEST_EMERGENCY_MANAGED_CLONE_PHONE_ACCOUNT_PKG_NAME); 738 } 739 tearDownEmergencyCalling()740 public void tearDownEmergencyCalling() throws Exception { 741 if (!mIsEmergencyCallingSetup) return; 742 ShellCommandExecutor.clearSystemDialerOverride( 743 InstrumentationRegistry.getInstrumentation()); 744 ShellCommandExecutor.clearTestEmergencyNumbers( 745 InstrumentationRegistry.getInstrumentation()); 746 ShellCommandExecutor.clearTestEmergencyPhoneAccountPackageFilter( 747 InstrumentationRegistry.getInstrumentation()); 748 } 749 findTargetCall(String id)750 private Call findTargetCall(String id) { 751 waitForInCallServiceBinding(mVerifierMethods); 752 List<Call> calls = mVerifierMethods.getOngoingCalls(); 753 Call targetCall = null; 754 for (Call call : calls) { 755 if (call.getDetails().getId().equals(id)) { 756 targetCall = call; 757 break; 758 } 759 } 760 if (targetCall == null) { 761 fail("answerViaInCallServiceAndVerify: failed to find target call id=" + id); 762 } 763 return targetCall; 764 } 765 } 766