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