1 package org.robolectric.shadows; 2 3 import static android.os.Build.VERSION.SDK_INT; 4 import static android.os.Build.VERSION_CODES.LOLLIPOP; 5 import static android.os.Build.VERSION_CODES.LOLLIPOP_MR1; 6 import static android.os.Build.VERSION_CODES.M; 7 import static android.os.Build.VERSION_CODES.N; 8 import static android.os.Build.VERSION_CODES.O; 9 import static android.os.Build.VERSION_CODES.R; 10 import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE; 11 import static com.google.common.base.Verify.verifyNotNull; 12 13 import android.annotation.Nullable; 14 import android.annotation.SystemApi; 15 import android.annotation.TargetApi; 16 import android.bluetooth.BluetoothDevice; 17 import android.content.ComponentName; 18 import android.content.Context; 19 import android.content.Intent; 20 import android.content.pm.ResolveInfo; 21 import android.net.Uri; 22 import android.os.Bundle; 23 import android.telecom.CallAudioState; 24 import android.telecom.Connection; 25 import android.telecom.ConnectionRequest; 26 import android.telecom.ConnectionService; 27 import android.telecom.PhoneAccount; 28 import android.telecom.PhoneAccountHandle; 29 import android.telecom.TelecomManager; 30 import android.telecom.VideoProfile; 31 import android.text.TextUtils; 32 import android.util.ArrayMap; 33 import com.google.common.collect.ImmutableList; 34 import com.google.common.collect.Iterables; 35 import java.util.ArrayList; 36 import java.util.Collection; 37 import java.util.HashSet; 38 import java.util.LinkedHashMap; 39 import java.util.List; 40 import java.util.Map; 41 import java.util.Set; 42 import org.robolectric.android.controller.ServiceController; 43 import org.robolectric.annotation.HiddenApi; 44 import org.robolectric.annotation.Implementation; 45 import org.robolectric.annotation.Implements; 46 import org.robolectric.annotation.RealObject; 47 import org.robolectric.util.ReflectionHelpers; 48 49 @Implements(value = TelecomManager.class, minSdk = LOLLIPOP) 50 public class ShadowTelecomManager { 51 52 /** 53 * Mode describing how the shadow handles incoming ({@link TelecomManager#addNewIncomingCall}) and 54 * outgoing ({@link TelecomManager#placeCall}) call requests. 55 */ 56 public enum CallRequestMode { 57 /** Automatically allows all call requests. */ 58 ALLOW_ALL, 59 60 /** Automatically denies all call requests. */ 61 DENY_ALL, 62 63 /** 64 * Do not automatically allow or deny any call requests. Instead, call requests should be 65 * allowed or denied manually by calling the following methods: 66 * 67 * <ul> 68 * <li>{@link #allowIncomingCall(IncomingCallRecord)} 69 * <li>{@link #denyIncomingCall(IncomingCallRecord)} 70 * <li>{@link #allowOutgoingCall(OutgoingCallRecord)} 71 * <li>{@link #denyOutgoingCall(OutgoingCallRecord)} 72 * </ul> 73 */ 74 MANUAL, 75 } 76 77 @RealObject 78 private TelecomManager realObject; 79 80 private final LinkedHashMap<PhoneAccountHandle, PhoneAccount> accounts = new LinkedHashMap<>(); 81 private final LinkedHashMap<PhoneAccountHandle, String> voicemailNumbers = new LinkedHashMap<>(); 82 private final LinkedHashMap<PhoneAccountHandle, String> line1Numbers = new LinkedHashMap<>(); 83 84 private final List<IncomingCallRecord> incomingCalls = new ArrayList<>(); 85 private final List<OutgoingCallRecord> outgoingCalls = new ArrayList<>(); 86 private final List<UnknownCallRecord> unknownCalls = new ArrayList<>(); 87 private final Map<String, PhoneAccountHandle> defaultOutgoingPhoneAccounts = new ArrayMap<>(); 88 private Intent manageBlockNumbersIntent; 89 private CallRequestMode callRequestMode = CallRequestMode.MANUAL; 90 private PhoneAccountHandle simCallManager; 91 private String defaultDialerPackageName; 92 private String systemDefaultDialerPackageName; 93 private boolean isInCall; 94 private boolean ttySupported; 95 private PhoneAccountHandle userSelectedOutgoingPhoneAccount; 96 private boolean readPhoneStatePermission = true; 97 private boolean callPhonePermission = true; 98 private boolean handleMmiValue = false; 99 private ConnectionService connectionService; 100 private boolean isOutgoingCallPermitted = false; 101 getCallRequestMode()102 public CallRequestMode getCallRequestMode() { 103 return callRequestMode; 104 } 105 setCallRequestMode(CallRequestMode callRequestMode)106 public void setCallRequestMode(CallRequestMode callRequestMode) { 107 this.callRequestMode = callRequestMode; 108 } 109 110 /** 111 * Set default outgoing phone account to be returned from {@link 112 * #getDefaultOutgoingPhoneAccount(String)} for corresponding {@code uriScheme}. 113 */ setDefaultOutgoingPhoneAccount(String uriScheme, PhoneAccountHandle handle)114 public void setDefaultOutgoingPhoneAccount(String uriScheme, PhoneAccountHandle handle) { 115 defaultOutgoingPhoneAccounts.put(uriScheme, handle); 116 } 117 118 /** Remove default outgoing phone account for corresponding {@code uriScheme}. */ removeDefaultOutgoingPhoneAccount(String uriScheme)119 public void removeDefaultOutgoingPhoneAccount(String uriScheme) { 120 defaultOutgoingPhoneAccounts.remove(uriScheme); 121 } 122 123 /** Sets the result of {@link TelecomManager#isOutgoingCallPermitted(PhoneAccountHandle)}. */ setIsOutgoingCallPermitted(boolean isOutgoingCallPermitted)124 public void setIsOutgoingCallPermitted(boolean isOutgoingCallPermitted) { 125 this.isOutgoingCallPermitted = isOutgoingCallPermitted; 126 } 127 128 /** 129 * Returns default outgoing phone account set through {@link 130 * #setDefaultOutgoingPhoneAccount(String, PhoneAccountHandle)} for corresponding {@code 131 * uriScheme}. 132 */ 133 @Implementation getDefaultOutgoingPhoneAccount(String uriScheme)134 protected PhoneAccountHandle getDefaultOutgoingPhoneAccount(String uriScheme) { 135 return defaultOutgoingPhoneAccounts.get(uriScheme); 136 } 137 138 @Implementation 139 @HiddenApi getUserSelectedOutgoingPhoneAccount()140 public PhoneAccountHandle getUserSelectedOutgoingPhoneAccount() { 141 return userSelectedOutgoingPhoneAccount; 142 } 143 144 @Implementation 145 @HiddenApi setUserSelectedOutgoingPhoneAccount(PhoneAccountHandle accountHandle)146 public void setUserSelectedOutgoingPhoneAccount(PhoneAccountHandle accountHandle) { 147 userSelectedOutgoingPhoneAccount = accountHandle; 148 } 149 150 @Implementation getSimCallManager()151 protected PhoneAccountHandle getSimCallManager() { 152 return simCallManager; 153 } 154 155 @Implementation(minSdk = M) 156 @HiddenApi getSimCallManager(int userId)157 public PhoneAccountHandle getSimCallManager(int userId) { 158 return null; 159 } 160 161 @Implementation 162 @HiddenApi getConnectionManager()163 public PhoneAccountHandle getConnectionManager() { 164 return this.getSimCallManager(); 165 } 166 167 @Implementation 168 @HiddenApi getPhoneAccountsSupportingScheme(String uriScheme)169 public List<PhoneAccountHandle> getPhoneAccountsSupportingScheme(String uriScheme) { 170 List<PhoneAccountHandle> result = new ArrayList<>(); 171 172 for (PhoneAccountHandle handle : accounts.keySet()) { 173 PhoneAccount phoneAccount = accounts.get(handle); 174 if (phoneAccount.getSupportedUriSchemes().contains(uriScheme)) { 175 result.add(handle); 176 } 177 } 178 return result; 179 } 180 181 @Implementation(minSdk = M) getCallCapablePhoneAccounts()182 protected List<PhoneAccountHandle> getCallCapablePhoneAccounts() { 183 checkReadPhoneStatePermission(); 184 return this.getCallCapablePhoneAccounts(false); 185 } 186 187 @Implementation(minSdk = M) 188 @HiddenApi getCallCapablePhoneAccounts(boolean includeDisabledAccounts)189 public List<PhoneAccountHandle> getCallCapablePhoneAccounts(boolean includeDisabledAccounts) { 190 List<PhoneAccountHandle> result = new ArrayList<>(); 191 192 for (PhoneAccountHandle handle : accounts.keySet()) { 193 PhoneAccount phoneAccount = accounts.get(handle); 194 if (!phoneAccount.isEnabled() && !includeDisabledAccounts) { 195 continue; 196 } 197 result.add(handle); 198 } 199 return result; 200 } 201 202 @Implementation(minSdk = O) getSelfManagedPhoneAccounts()203 public List<PhoneAccountHandle> getSelfManagedPhoneAccounts() { 204 List<PhoneAccountHandle> result = new ArrayList<>(); 205 206 for (PhoneAccountHandle handle : accounts.keySet()) { 207 PhoneAccount phoneAccount = accounts.get(handle); 208 if ((phoneAccount.getCapabilities() & PhoneAccount.CAPABILITY_SELF_MANAGED) 209 == PhoneAccount.CAPABILITY_SELF_MANAGED) { 210 result.add(handle); 211 } 212 } 213 return result; 214 } 215 216 @Implementation 217 @HiddenApi getPhoneAccountsForPackage()218 public List<PhoneAccountHandle> getPhoneAccountsForPackage() { 219 Context context = ReflectionHelpers.getField(realObject, "mContext"); 220 221 List<PhoneAccountHandle> results = new ArrayList<>(); 222 for (PhoneAccountHandle handle : accounts.keySet()) { 223 if (handle.getComponentName().getPackageName().equals(context.getPackageName())) { 224 results.add(handle); 225 } 226 } 227 return results; 228 } 229 230 @Implementation getPhoneAccount(PhoneAccountHandle account)231 protected PhoneAccount getPhoneAccount(PhoneAccountHandle account) { 232 checkReadPhoneStatePermission(); 233 return accounts.get(account); 234 } 235 236 @Implementation 237 @HiddenApi getAllPhoneAccountsCount()238 public int getAllPhoneAccountsCount() { 239 return accounts.size(); 240 } 241 242 @Implementation 243 @HiddenApi getAllPhoneAccounts()244 public List<PhoneAccount> getAllPhoneAccounts() { 245 return ImmutableList.copyOf(accounts.values()); 246 } 247 248 @Implementation 249 @HiddenApi getAllPhoneAccountHandles()250 public List<PhoneAccountHandle> getAllPhoneAccountHandles() { 251 return ImmutableList.copyOf(accounts.keySet()); 252 } 253 254 @Implementation registerPhoneAccount(PhoneAccount account)255 protected void registerPhoneAccount(PhoneAccount account) { 256 account = adjustCapabilities(account); 257 accounts.put(account.getAccountHandle(), account); 258 } 259 adjustCapabilities(PhoneAccount account)260 private PhoneAccount adjustCapabilities(PhoneAccount account) { 261 // Mirror the capabilities adjustments done in com.android.server.telecom.PhoneAccountRegistrar. 262 if (SDK_INT >= UPSIDE_DOWN_CAKE 263 && account.hasCapabilities(PhoneAccount.CAPABILITY_SUPPORTS_TRANSACTIONAL_OPERATIONS) 264 && !account.hasCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED)) { 265 return account.toBuilder() 266 .setCapabilities(account.getCapabilities() | PhoneAccount.CAPABILITY_SELF_MANAGED) 267 .build(); 268 } 269 return account; 270 } 271 272 @Implementation unregisterPhoneAccount(PhoneAccountHandle accountHandle)273 protected void unregisterPhoneAccount(PhoneAccountHandle accountHandle) { 274 accounts.remove(accountHandle); 275 } 276 277 /** @deprecated */ 278 @Deprecated 279 @Implementation 280 @HiddenApi clearAccounts()281 public void clearAccounts() { 282 accounts.clear(); 283 } 284 285 @Implementation(minSdk = LOLLIPOP_MR1) 286 @HiddenApi clearAccountsForPackage(String packageName)287 public void clearAccountsForPackage(String packageName) { 288 Set<PhoneAccountHandle> phoneAccountHandlesInPackage = new HashSet<>(); 289 290 for (PhoneAccountHandle handle : accounts.keySet()) { 291 if (handle.getComponentName().getPackageName().equals(packageName)) { 292 phoneAccountHandlesInPackage.add(handle); 293 } 294 } 295 296 for (PhoneAccountHandle handle : phoneAccountHandlesInPackage) { 297 accounts.remove(handle); 298 } 299 } 300 301 /** @deprecated */ 302 @Deprecated 303 @Implementation 304 @HiddenApi getDefaultPhoneApp()305 public ComponentName getDefaultPhoneApp() { 306 return null; 307 } 308 309 @Implementation(minSdk = M) getDefaultDialerPackage()310 protected String getDefaultDialerPackage() { 311 return defaultDialerPackageName; 312 } 313 314 /** @deprecated API deprecated since Q, for testing, use setDefaultDialerPackage instead */ 315 @Deprecated 316 @Implementation(minSdk = M) 317 @HiddenApi setDefaultDialer(String packageName)318 public boolean setDefaultDialer(String packageName) { 319 this.defaultDialerPackageName = packageName; 320 return true; 321 } 322 323 /** Set returned value of {@link #getDefaultDialerPackage()}. */ setDefaultDialerPackage(String packageName)324 public void setDefaultDialerPackage(String packageName) { 325 this.defaultDialerPackageName = packageName; 326 } 327 328 @Implementation(minSdk = M) 329 @HiddenApi // API goes public in Q getSystemDialerPackage()330 protected String getSystemDialerPackage() { 331 return systemDefaultDialerPackageName; 332 } 333 334 /** Set returned value of {@link #getSystemDialerPackage()}. */ setSystemDialerPackage(String packageName)335 public void setSystemDialerPackage(String packageName) { 336 this.systemDefaultDialerPackageName = packageName; 337 } 338 setVoicemailNumber(PhoneAccountHandle accountHandle, String number)339 public void setVoicemailNumber(PhoneAccountHandle accountHandle, String number) { 340 voicemailNumbers.put(accountHandle, number); 341 } 342 343 @Implementation(minSdk = M) isVoiceMailNumber(PhoneAccountHandle accountHandle, String number)344 protected boolean isVoiceMailNumber(PhoneAccountHandle accountHandle, String number) { 345 return TextUtils.equals(number, voicemailNumbers.get(accountHandle)); 346 } 347 348 @Implementation(minSdk = M) getVoiceMailNumber(PhoneAccountHandle accountHandle)349 protected String getVoiceMailNumber(PhoneAccountHandle accountHandle) { 350 return voicemailNumbers.get(accountHandle); 351 } 352 353 @Implementation(minSdk = LOLLIPOP_MR1) getLine1Number(PhoneAccountHandle accountHandle)354 protected String getLine1Number(PhoneAccountHandle accountHandle) { 355 checkReadPhoneStatePermission(); 356 return line1Numbers.get(accountHandle); 357 } 358 setLine1Number(PhoneAccountHandle accountHandle, String number)359 public void setLine1Number(PhoneAccountHandle accountHandle, String number) { 360 line1Numbers.put(accountHandle, number); 361 } 362 363 /** Sets the return value for {@link TelecomManager#isInCall}. */ setIsInCall(boolean isInCall)364 public void setIsInCall(boolean isInCall) { 365 this.isInCall = isInCall; 366 } 367 368 /** 369 * Overrides behavior of {@link TelecomManager#isInCall} to return pre-set result. 370 * 371 * @return Value set by calling {@link ShadowTelecomManager#setIsInCall}. If setIsInCall has not 372 * previously been called, will return false. 373 */ 374 @Implementation isInCall()375 protected boolean isInCall() { 376 return isInCall; 377 } 378 379 @Implementation 380 @HiddenApi getCallState()381 public int getCallState() { 382 return 0; 383 } 384 385 @Implementation 386 @HiddenApi isRinging()387 public boolean isRinging() { 388 for (IncomingCallRecord callRecord : incomingCalls) { 389 if (callRecord.isRinging) { 390 return true; 391 } 392 } 393 for (UnknownCallRecord callRecord : unknownCalls) { 394 if (callRecord.isRinging) { 395 return true; 396 } 397 } 398 return false; 399 } 400 401 @Implementation 402 @HiddenApi endCall()403 public boolean endCall() { 404 return false; 405 } 406 407 @Implementation acceptRingingCall()408 protected void acceptRingingCall() {} 409 410 @Implementation silenceRinger()411 protected void silenceRinger() { 412 for (IncomingCallRecord callRecord : incomingCalls) { 413 callRecord.isRinging = false; 414 } 415 for (UnknownCallRecord callRecord : unknownCalls) { 416 callRecord.isRinging = false; 417 } 418 } 419 420 @Implementation isTtySupported()421 protected boolean isTtySupported() { 422 checkReadPhoneStatePermission(); 423 return ttySupported; 424 } 425 426 /** Sets the value to be returned by {@link #isTtySupported()}. */ setTtySupported(boolean isSupported)427 public void setTtySupported(boolean isSupported) { 428 ttySupported = isSupported; 429 } 430 431 @Implementation 432 @HiddenApi getCurrentTtyMode()433 public int getCurrentTtyMode() { 434 return 0; 435 } 436 437 @Implementation addNewIncomingCall(PhoneAccountHandle phoneAccount, Bundle extras)438 protected void addNewIncomingCall(PhoneAccountHandle phoneAccount, Bundle extras) { 439 IncomingCallRecord call = new IncomingCallRecord(phoneAccount, extras); 440 incomingCalls.add(call); 441 442 switch (callRequestMode) { 443 case ALLOW_ALL: 444 allowIncomingCall(call); 445 break; 446 case DENY_ALL: 447 denyIncomingCall(call); 448 break; 449 default: 450 // Do nothing. 451 } 452 } 453 getAllIncomingCalls()454 public List<IncomingCallRecord> getAllIncomingCalls() { 455 return ImmutableList.copyOf(incomingCalls); 456 } 457 getLastIncomingCall()458 public IncomingCallRecord getLastIncomingCall() { 459 return Iterables.getLast(incomingCalls); 460 } 461 getOnlyIncomingCall()462 public IncomingCallRecord getOnlyIncomingCall() { 463 return Iterables.getOnlyElement(incomingCalls); 464 } 465 466 /** 467 * Allows an {@link IncomingCallRecord} created via {@link TelecomManager#addNewIncomingCall}. 468 * 469 * <p>Specifically, this method sets up the relevant {@link ConnectionService} and returns the 470 * result of {@link ConnectionService#onCreateIncomingConnection}. 471 */ 472 @TargetApi(M) 473 @Nullable allowIncomingCall(IncomingCallRecord call)474 public Connection allowIncomingCall(IncomingCallRecord call) { 475 if (call.isHandled) { 476 throw new IllegalStateException("Call has already been allowed or denied."); 477 } 478 call.isHandled = true; 479 480 PhoneAccountHandle phoneAccount = verifyNotNull(call.phoneAccount); 481 ConnectionRequest request = buildConnectionRequestForIncomingCall(call); 482 ConnectionService service = getConnectionService(phoneAccount); 483 return service.onCreateIncomingConnection(phoneAccount, request); 484 } 485 486 /** 487 * Denies an {@link IncomingCallRecord} created via {@link TelecomManager#addNewIncomingCall}. 488 * 489 * <p>Specifically, this method sets up the relevant {@link ConnectionService} and calls {@link 490 * ConnectionService#onCreateIncomingConnectionFailed}. 491 */ 492 @TargetApi(O) denyIncomingCall(IncomingCallRecord call)493 public void denyIncomingCall(IncomingCallRecord call) { 494 if (call.isHandled) { 495 throw new IllegalStateException("Call has already been allowed or denied."); 496 } 497 call.isHandled = true; 498 499 PhoneAccountHandle phoneAccount = verifyNotNull(call.phoneAccount); 500 ConnectionRequest request = buildConnectionRequestForIncomingCall(call); 501 ConnectionService service = getConnectionService(phoneAccount); 502 service.onCreateIncomingConnectionFailed(phoneAccount, request); 503 } 504 buildConnectionRequestForIncomingCall(IncomingCallRecord call)505 private static ConnectionRequest buildConnectionRequestForIncomingCall(IncomingCallRecord call) { 506 PhoneAccountHandle phoneAccount = verifyNotNull(call.phoneAccount); 507 Bundle extras = verifyNotNull(call.extras); 508 Uri address = extras.getParcelable(TelecomManager.EXTRA_INCOMING_CALL_ADDRESS); 509 int videoState = 510 extras.getInt( 511 TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE, VideoProfile.STATE_AUDIO_ONLY); 512 return new ConnectionRequest(phoneAccount, address, new Bundle(extras), videoState); 513 } 514 515 @Implementation(minSdk = M) placeCall(Uri address, Bundle extras)516 protected void placeCall(Uri address, Bundle extras) { 517 checkCallPhonePermission(); 518 OutgoingCallRecord call = new OutgoingCallRecord(address, extras); 519 outgoingCalls.add(call); 520 521 switch (callRequestMode) { 522 case ALLOW_ALL: 523 allowOutgoingCall(call); 524 break; 525 case DENY_ALL: 526 denyOutgoingCall(call); 527 break; 528 default: 529 // Do nothing. 530 } 531 } 532 getAllOutgoingCalls()533 public List<OutgoingCallRecord> getAllOutgoingCalls() { 534 return ImmutableList.copyOf(outgoingCalls); 535 } 536 getLastOutgoingCall()537 public OutgoingCallRecord getLastOutgoingCall() { 538 return Iterables.getLast(outgoingCalls); 539 } 540 getOnlyOutgoingCall()541 public OutgoingCallRecord getOnlyOutgoingCall() { 542 return Iterables.getOnlyElement(outgoingCalls); 543 } 544 545 /** 546 * Allows an {@link OutgoingCallRecord} created via {@link TelecomManager#placeCall}. 547 * 548 * <p>Specifically, this method sets up the relevant {@link ConnectionService} and returns the 549 * result of {@link ConnectionService#onCreateOutgoingConnection}. 550 */ 551 @TargetApi(M) 552 @Nullable allowOutgoingCall(OutgoingCallRecord call)553 public Connection allowOutgoingCall(OutgoingCallRecord call) { 554 if (call.isHandled) { 555 throw new IllegalStateException("Call has already been allowed or denied."); 556 } 557 call.isHandled = true; 558 559 PhoneAccountHandle phoneAccount = verifyNotNull(call.phoneAccount); 560 ConnectionRequest request = buildConnectionRequestForOutgoingCall(call); 561 ConnectionService service = getConnectionService(phoneAccount); 562 return service.onCreateOutgoingConnection(phoneAccount, request); 563 } 564 565 /** 566 * Denies an {@link OutgoingCallRecord} created via {@link TelecomManager#placeCall}. 567 * 568 * <p>Specifically, this method sets up the relevant {@link ConnectionService} and calls {@link 569 * ConnectionService#onCreateOutgoingConnectionFailed}. 570 */ 571 @TargetApi(O) denyOutgoingCall(OutgoingCallRecord call)572 public void denyOutgoingCall(OutgoingCallRecord call) { 573 if (call.isHandled) { 574 throw new IllegalStateException("Call has already been allowed or denied."); 575 } 576 call.isHandled = true; 577 578 PhoneAccountHandle phoneAccount = verifyNotNull(call.phoneAccount); 579 ConnectionRequest request = buildConnectionRequestForOutgoingCall(call); 580 ConnectionService service = getConnectionService(phoneAccount); 581 service.onCreateOutgoingConnectionFailed(phoneAccount, request); 582 } 583 buildConnectionRequestForOutgoingCall(OutgoingCallRecord call)584 private static ConnectionRequest buildConnectionRequestForOutgoingCall(OutgoingCallRecord call) { 585 PhoneAccountHandle phoneAccount = verifyNotNull(call.phoneAccount); 586 Uri address = verifyNotNull(call.address); 587 Bundle extras = verifyNotNull(call.extras); 588 Bundle outgoingCallExtras = extras.getBundle(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS); 589 int videoState = 590 extras.getInt( 591 TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE, VideoProfile.STATE_AUDIO_ONLY); 592 return new ConnectionRequest( 593 phoneAccount, 594 address, 595 outgoingCallExtras == null ? null : new Bundle(outgoingCallExtras), 596 videoState); 597 } 598 599 @Implementation 600 @HiddenApi addNewUnknownCall(PhoneAccountHandle phoneAccount, Bundle extras)601 public void addNewUnknownCall(PhoneAccountHandle phoneAccount, Bundle extras) { 602 unknownCalls.add(new UnknownCallRecord(phoneAccount, extras)); 603 } 604 getAllUnknownCalls()605 public List<UnknownCallRecord> getAllUnknownCalls() { 606 return ImmutableList.copyOf(unknownCalls); 607 } 608 getLastUnknownCall()609 public UnknownCallRecord getLastUnknownCall() { 610 return Iterables.getLast(unknownCalls); 611 } 612 getOnlyUnknownCall()613 public UnknownCallRecord getOnlyUnknownCall() { 614 return Iterables.getOnlyElement(unknownCalls); 615 } 616 617 /** 618 * Set connection service. 619 * 620 * <p>This method can be used in case, when you already created connection service and would like 621 * to use it in telecom manager instead of creating new one. 622 * 623 * @param service existing connection service 624 */ setConnectionService(ConnectionService service)625 public void setConnectionService(ConnectionService service) { 626 connectionService = service; 627 } 628 getConnectionService(PhoneAccountHandle phoneAccount)629 private ConnectionService getConnectionService(PhoneAccountHandle phoneAccount) { 630 if (connectionService == null) { 631 connectionService = setupConnectionService(phoneAccount); 632 } 633 return connectionService; 634 } 635 setupConnectionService(PhoneAccountHandle phoneAccount)636 private static ConnectionService setupConnectionService(PhoneAccountHandle phoneAccount) { 637 ComponentName service = phoneAccount.getComponentName(); 638 Class<? extends ConnectionService> clazz; 639 try { 640 clazz = Class.forName(service.getClassName()).asSubclass(ConnectionService.class); 641 } catch (ClassNotFoundException e) { 642 throw new IllegalArgumentException(e); 643 } 644 return verifyNotNull( 645 ServiceController.of(ReflectionHelpers.callConstructor(clazz), null).create().get()); 646 } 647 setHandleMmiValue(boolean handleMmiValue)648 public void setHandleMmiValue(boolean handleMmiValue) { 649 this.handleMmiValue = handleMmiValue; 650 } 651 652 @Implementation handleMmi(String dialString)653 protected boolean handleMmi(String dialString) { 654 return handleMmiValue; 655 } 656 657 @Implementation(minSdk = M) handleMmi(String dialString, PhoneAccountHandle accountHandle)658 protected boolean handleMmi(String dialString, PhoneAccountHandle accountHandle) { 659 return handleMmiValue; 660 } 661 662 @Implementation(minSdk = LOLLIPOP_MR1) getAdnUriForPhoneAccount(PhoneAccountHandle accountHandle)663 protected Uri getAdnUriForPhoneAccount(PhoneAccountHandle accountHandle) { 664 return Uri.parse("content://icc/adn"); 665 } 666 667 @Implementation cancelMissedCallsNotification()668 protected void cancelMissedCallsNotification() {} 669 670 @Implementation showInCallScreen(boolean showDialpad)671 protected void showInCallScreen(boolean showDialpad) {} 672 673 @Implementation(minSdk = M) 674 @HiddenApi enablePhoneAccount(PhoneAccountHandle handle, boolean isEnabled)675 public void enablePhoneAccount(PhoneAccountHandle handle, boolean isEnabled) { 676 if (getPhoneAccount(handle) == null) { 677 return; 678 } 679 getPhoneAccount(handle).setIsEnabled(isEnabled); 680 } 681 682 /** 683 * Returns the intent set by {@link ShadowTelecomManager#setManageBlockNumbersIntent(Intent)} ()} 684 */ 685 @Implementation(minSdk = N) createManageBlockedNumbersIntent()686 protected Intent createManageBlockedNumbersIntent() { 687 return this.manageBlockNumbersIntent; 688 } 689 690 /** 691 * Sets the BlockNumbersIntent to be returned by {@link 692 * ShadowTelecomManager#createManageBlockedNumbersIntent()} 693 */ setManageBlockNumbersIntent(Intent intent)694 public void setManageBlockNumbersIntent(Intent intent) { 695 this.manageBlockNumbersIntent = intent; 696 } 697 698 @Implementation(maxSdk = LOLLIPOP_MR1) setSimCallManager(PhoneAccountHandle simCallManager)699 public void setSimCallManager(PhoneAccountHandle simCallManager) { 700 this.simCallManager = simCallManager; 701 } 702 703 /** 704 * Creates a new {@link CallAudioState}. The real constructor of {@link CallAudioState} is hidden. 705 */ newCallAudioState( boolean muted, int route, int supportedRouteMask, BluetoothDevice activeBluetoothDevice, Collection<BluetoothDevice> supportedBluetoothDevices)706 public CallAudioState newCallAudioState( 707 boolean muted, 708 int route, 709 int supportedRouteMask, 710 BluetoothDevice activeBluetoothDevice, 711 Collection<BluetoothDevice> supportedBluetoothDevices) { 712 return new CallAudioState( 713 muted, route, supportedRouteMask, activeBluetoothDevice, supportedBluetoothDevices); 714 } 715 716 @Implementation(minSdk = R) 717 @SystemApi createLaunchEmergencyDialerIntent(String number)718 protected Intent createLaunchEmergencyDialerIntent(String number) { 719 // copy of logic from TelecomManager service 720 Context context = ReflectionHelpers.getField(realObject, "mContext"); 721 // use reflection to get resource id since it can vary based on SDK version, and compiler will 722 // inline the value if used explicitly 723 int configEmergencyDialerPackageId = 724 ReflectionHelpers.getStaticField( 725 com.android.internal.R.string.class, "config_emergency_dialer_package"); 726 String packageName = context.getString(configEmergencyDialerPackageId); 727 Intent intent = new Intent(Intent.ACTION_DIAL_EMERGENCY).setPackage(packageName); 728 ResolveInfo resolveInfo = context.getPackageManager().resolveActivity(intent, 0); 729 if (resolveInfo == null) { 730 // No matching activity from config, fallback to default platform implementation 731 intent.setPackage(null); 732 } 733 if (!TextUtils.isEmpty(number) && TextUtils.isDigitsOnly(number)) { 734 intent.setData(Uri.parse("tel:" + number)); 735 } 736 return intent; 737 } 738 739 @Implementation(minSdk = O) isOutgoingCallPermitted(PhoneAccountHandle phoneAccountHandle)740 protected boolean isOutgoingCallPermitted(PhoneAccountHandle phoneAccountHandle) { 741 return this.isOutgoingCallPermitted; 742 } 743 744 /** 745 * Details about a call request made via {@link TelecomManager#addNewIncomingCall} or {@link 746 * TelecomManager#addNewUnknownCall}. 747 * 748 * @deprecated Use {@link IncomingCallRecord} or {@link UnknownCallRecord} instead. 749 */ 750 @Deprecated 751 public static class CallRecord { 752 public final PhoneAccountHandle phoneAccount; 753 public final Bundle extras; 754 protected boolean isRinging = true; 755 756 /** @deprecated Use {@link extras} instead. */ 757 @Deprecated public final Bundle bundle; 758 CallRecord(PhoneAccountHandle phoneAccount, Bundle extras)759 public CallRecord(PhoneAccountHandle phoneAccount, Bundle extras) { 760 this.phoneAccount = phoneAccount; 761 this.extras = extras == null ? null : new Bundle(extras); 762 763 // Keep the deprecated "bundle" name around for a while. 764 this.bundle = this.extras; 765 } 766 } 767 768 /** 769 * When set to false methods requiring {@link android.Manifest.permission.READ_PHONE_STATE} 770 * permission will throw a {@link SecurityException}. By default it's set to true for backwards 771 * compatibility. 772 */ setReadPhoneStatePermission(boolean readPhoneStatePermission)773 public void setReadPhoneStatePermission(boolean readPhoneStatePermission) { 774 this.readPhoneStatePermission = readPhoneStatePermission; 775 } 776 checkReadPhoneStatePermission()777 private void checkReadPhoneStatePermission() { 778 if (!readPhoneStatePermission) { 779 throw new SecurityException(); 780 } 781 } 782 783 /** 784 * When set to false methods requiring {@link android.Manifest.permission.CALL_PHONE} permission 785 * will throw a {@link SecurityException}. By default it's set to true for backwards 786 * compatibility. 787 */ setCallPhonePermission(boolean callPhonePermission)788 public void setCallPhonePermission(boolean callPhonePermission) { 789 this.callPhonePermission = callPhonePermission; 790 } 791 checkCallPhonePermission()792 private void checkCallPhonePermission() { 793 if (!callPhonePermission) { 794 throw new SecurityException(); 795 } 796 } 797 798 /** Details about an incoming call request made via {@link TelecomManager#addNewIncomingCall}. */ 799 public static class IncomingCallRecord extends CallRecord { 800 private boolean isHandled = false; 801 IncomingCallRecord(PhoneAccountHandle phoneAccount, Bundle extras)802 public IncomingCallRecord(PhoneAccountHandle phoneAccount, Bundle extras) { 803 super(phoneAccount, extras); 804 } 805 } 806 807 /** Details about an outgoing call request made via {@link TelecomManager#placeCall}. */ 808 public static class OutgoingCallRecord { 809 public final PhoneAccountHandle phoneAccount; 810 public final Uri address; 811 public final Bundle extras; 812 813 private boolean isHandled = false; 814 OutgoingCallRecord(Uri address, Bundle extras)815 public OutgoingCallRecord(Uri address, Bundle extras) { 816 this.address = address; 817 if (extras != null) { 818 this.extras = new Bundle(extras); 819 this.phoneAccount = extras.getParcelable(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE); 820 } else { 821 this.extras = null; 822 this.phoneAccount = null; 823 } 824 } 825 } 826 827 /** Details about an unknown call request made via {@link TelecomManager#addNewUnknownCall}. */ 828 public static class UnknownCallRecord extends CallRecord { UnknownCallRecord(PhoneAccountHandle phoneAccount, Bundle extras)829 public UnknownCallRecord(PhoneAccountHandle phoneAccount, Bundle extras) { 830 super(phoneAccount, extras); 831 } 832 } 833 } 834