TelephonyConnection.java revision 0e40c46c113095d3c29bdb2882cfb4d1ea1d12f4
1/* 2 * Copyright (C) 2014 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 17package com.android.services.telephony; 18 19import android.net.Uri; 20import android.os.AsyncResult; 21import android.os.Handler; 22import android.os.Message; 23import android.telecom.AudioState; 24import android.telecom.Connection; 25import android.telecom.PhoneAccount; 26import android.telecom.PhoneCapabilities; 27 28import com.android.internal.telephony.Call; 29import com.android.internal.telephony.CallStateException; 30import com.android.internal.telephony.Connection.PostDialListener; 31import com.android.internal.telephony.Phone; 32import com.android.internal.telephony.imsphone.ImsPhoneConnection; 33 34import java.lang.Override; 35import java.util.Objects; 36 37/** 38 * Base class for CDMA and GSM connections. 39 */ 40abstract class TelephonyConnection extends Connection { 41 private static final int MSG_PRECISE_CALL_STATE_CHANGED = 1; 42 private static final int MSG_RINGBACK_TONE = 2; 43 private static final int MSG_HANDOVER_STATE_CHANGED = 3; 44 private static final int MSG_DISCONNECT = 4; 45 46 private final Handler mHandler = new Handler() { 47 @Override 48 public void handleMessage(Message msg) { 49 switch (msg.what) { 50 case MSG_PRECISE_CALL_STATE_CHANGED: 51 Log.v(TelephonyConnection.this, "MSG_PRECISE_CALL_STATE_CHANGED"); 52 updateState(); 53 break; 54 case MSG_HANDOVER_STATE_CHANGED: 55 Log.v(TelephonyConnection.this, "MSG_HANDOVER_STATE_CHANGED"); 56 AsyncResult ar = (AsyncResult) msg.obj; 57 com.android.internal.telephony.Connection connection = 58 (com.android.internal.telephony.Connection) ar.result; 59 setOriginalConnection(connection); 60 break; 61 case MSG_RINGBACK_TONE: 62 Log.v(TelephonyConnection.this, "MSG_RINGBACK_TONE"); 63 // TODO: This code assumes that there is only one connection in the foreground 64 // call, in other words, it punts on network-mediated conference calling. 65 if (getOriginalConnection() != getForegroundConnection()) { 66 Log.v(TelephonyConnection.this, "handleMessage, original connection is " + 67 "not foreground connection, skipping"); 68 return; 69 } 70 setRingbackRequested((Boolean) ((AsyncResult) msg.obj).result); 71 break; 72 case MSG_DISCONNECT: 73 updateState(); 74 break; 75 } 76 } 77 }; 78 79 private final PostDialListener mPostDialListener = new PostDialListener() { 80 @Override 81 public void onPostDialWait() { 82 Log.v(TelephonyConnection.this, "onPostDialWait"); 83 if (mOriginalConnection != null) { 84 setPostDialWait(mOriginalConnection.getRemainingPostDialString()); 85 } 86 } 87 }; 88 89 /** 90 * Listener for listening to events in the {@link com.android.internal.telephony.Connection}. 91 */ 92 private final com.android.internal.telephony.Connection.Listener mOriginalConnectionListener = 93 new com.android.internal.telephony.Connection.ListenerBase() { 94 @Override 95 public void onVideoStateChanged(int videoState) { 96 setVideoState(videoState); 97 } 98 99 /** 100 * The {@link com.android.internal.telephony.Connection} has reported a change in local 101 * video capability. 102 * 103 * @param capable True if capable. 104 */ 105 @Override 106 public void onLocalVideoCapabilityChanged(boolean capable) { 107 setLocalVideoCapable(capable); 108 } 109 110 /** 111 * The {@link com.android.internal.telephony.Connection} has reported a change in remote 112 * video capability. 113 * 114 * @param capable True if capable. 115 */ 116 @Override 117 public void onRemoteVideoCapabilityChanged(boolean capable) { 118 setRemoteVideoCapable(capable); 119 } 120 121 /** 122 * The {@link com.android.internal.telephony.Connection} has reported a change in the 123 * video call provider. 124 * 125 * @param videoProvider The video call provider. 126 */ 127 @Override 128 public void onVideoProviderChanged(VideoProvider videoProvider) { 129 setVideoProvider(videoProvider); 130 } 131 132 /** 133 * Used by the {@link com.android.internal.telephony.Connection} to report a change in the 134 * audio quality for the current call. 135 * 136 * @param audioQuality The audio quality. 137 */ 138 @Override 139 public void onAudioQualityChanged(int audioQuality) { 140 setAudioQuality(audioQuality); 141 } 142 }; 143 144 private com.android.internal.telephony.Connection mOriginalConnection; 145 private Call.State mOriginalConnectionState = Call.State.IDLE; 146 147 /** 148 * Determines if the {@link TelephonyConnection} has local video capabilities. 149 * This is used when {@link TelephonyConnection#updateCallCapabilities()}} is called, 150 * ensuring the appropriate {@link PhoneCapabilities} are set. Since {@link PhoneCapabilities} 151 * can be rebuilt at any time it is necessary to track the video capabilities between rebuild. 152 * The {@link PhoneCapabilities} (including video capabilities) are communicated to the telecom 153 * layer. 154 */ 155 private boolean mLocalVideoCapable; 156 157 /** 158 * Determines if the {@link TelephonyConnection} has remote video capabilities. 159 * This is used when {@link TelephonyConnection#updateCallCapabilities()}} is called, 160 * ensuring the appropriate {@link PhoneCapabilities} are set. Since {@link PhoneCapabilities} 161 * can be rebuilt at any time it is necessary to track the video capabilities between rebuild. 162 * The {@link PhoneCapabilities} (including video capabilities) are communicated to the telecom 163 * layer. 164 */ 165 private boolean mRemoteVideoCapable; 166 167 /** 168 * Determines the current audio quality for the {@link TelephonyConnection}. 169 * This is used when {@link TelephonyConnection#updateCallCapabilities}} is called to indicate 170 * whether a call has the {@link android.telecom.CallCapabilities#VoLTE} capability. 171 */ 172 private int mAudioQuality; 173 174 protected TelephonyConnection(com.android.internal.telephony.Connection originalConnection) { 175 if (originalConnection != null) { 176 setOriginalConnection(originalConnection); 177 } 178 } 179 180 @Override 181 public void onAudioStateChanged(AudioState audioState) { 182 // TODO: update TTY mode. 183 if (getPhone() != null) { 184 getPhone().setEchoSuppressionEnabled(); 185 } 186 } 187 188 @Override 189 public void onStateChanged(int state) { 190 Log.v(this, "onStateChanged, state: " + Connection.stateToString(state)); 191 } 192 193 @Override 194 public void onDisconnect() { 195 Log.v(this, "onDisconnect"); 196 hangup(android.telephony.DisconnectCause.LOCAL); 197 } 198 199 @Override 200 public void onSeparate() { 201 Log.v(this, "onSeparate"); 202 if (mOriginalConnection != null) { 203 try { 204 mOriginalConnection.separate(); 205 } catch (CallStateException e) { 206 Log.e(this, e, "Call to Connection.separate failed with exception"); 207 } 208 } 209 } 210 211 @Override 212 public void onAbort() { 213 Log.v(this, "onAbort"); 214 hangup(android.telephony.DisconnectCause.LOCAL); 215 } 216 217 @Override 218 public void onHold() { 219 performHold(); 220 } 221 222 @Override 223 public void onUnhold() { 224 performUnhold(); 225 } 226 227 @Override 228 public void onAnswer(int videoState) { 229 Log.v(this, "onAnswer"); 230 if (isValidRingingCall() && getPhone() != null) { 231 try { 232 getPhone().acceptCall(videoState); 233 } catch (CallStateException e) { 234 Log.e(this, e, "Failed to accept call."); 235 } 236 } 237 } 238 239 @Override 240 public void onReject() { 241 Log.v(this, "onReject"); 242 if (isValidRingingCall()) { 243 hangup(android.telephony.DisconnectCause.INCOMING_REJECTED); 244 } 245 super.onReject(); 246 } 247 248 @Override 249 public void onPostDialContinue(boolean proceed) { 250 Log.v(this, "onPostDialContinue, proceed: " + proceed); 251 if (mOriginalConnection != null) { 252 if (proceed) { 253 mOriginalConnection.proceedAfterWaitChar(); 254 } else { 255 mOriginalConnection.cancelPostDial(); 256 } 257 } 258 } 259 260 public void performHold() { 261 Log.v(this, "performHold"); 262 // TODO: Can dialing calls be put on hold as well since they take up the 263 // foreground call slot? 264 if (Call.State.ACTIVE == mOriginalConnectionState) { 265 Log.v(this, "Holding active call"); 266 try { 267 Phone phone = mOriginalConnection.getCall().getPhone(); 268 Call ringingCall = phone.getRingingCall(); 269 270 // Although the method says switchHoldingAndActive, it eventually calls a RIL method 271 // called switchWaitingOrHoldingAndActive. What this means is that if we try to put 272 // a call on hold while a call-waiting call exists, it'll end up accepting the 273 // call-waiting call, which is bad if that was not the user's intention. We are 274 // cheating here and simply skipping it because we know any attempt to hold a call 275 // while a call-waiting call is happening is likely a request from Telecom prior to 276 // accepting the call-waiting call. 277 // TODO: Investigate a better solution. It would be great here if we 278 // could "fake" hold by silencing the audio and microphone streams for this call 279 // instead of actually putting it on hold. 280 if (ringingCall.getState() != Call.State.WAITING) { 281 phone.switchHoldingAndActive(); 282 } 283 284 // TODO: Cdma calls are slightly different. 285 } catch (CallStateException e) { 286 Log.e(this, e, "Exception occurred while trying to put call on hold."); 287 } 288 } else { 289 Log.w(this, "Cannot put a call that is not currently active on hold."); 290 } 291 } 292 293 public void performUnhold() { 294 Log.v(this, "performUnhold"); 295 if (Call.State.HOLDING == mOriginalConnectionState) { 296 try { 297 // Here's the deal--Telephony hold/unhold is weird because whenever there exists 298 // more than one call, one of them must always be active. In other words, if you 299 // have an active call and holding call, and you put the active call on hold, it 300 // will automatically activate the holding call. This is weird with how Telecom 301 // sends its commands. When a user opts to "unhold" a background call, telecom 302 // issues hold commands to all active calls, and then the unhold command to the 303 // background call. This means that we get two commands...each of which reduces to 304 // switchHoldingAndActive(). The result is that they simply cancel each other out. 305 // To fix this so that it works well with telecom we add a minor hack. If we 306 // have one telephony call, everything works as normally expected. But if we have 307 // two or more calls, we will ignore all requests to "unhold" knowing that the hold 308 // requests already do what we want. If you've read up to this point, I'm very sorry 309 // that we are doing this. I didn't think of a better solution that wouldn't also 310 // make the Telecom APIs very ugly. 311 312 if (!hasMultipleTopLevelCalls()) { 313 mOriginalConnection.getCall().getPhone().switchHoldingAndActive(); 314 } else { 315 Log.i(this, "Skipping unhold command for %s", this); 316 } 317 } catch (CallStateException e) { 318 Log.e(this, e, "Exception occurred while trying to release call from hold."); 319 } 320 } else { 321 Log.w(this, "Cannot release a call that is not already on hold from hold."); 322 } 323 } 324 325 public void performConference(TelephonyConnection otherConnection) {} 326 327 protected abstract int buildCallCapabilities(); 328 329 protected final void updateCallCapabilities() { 330 int newCallCapabilities = buildCallCapabilities(); 331 newCallCapabilities = applyVideoCapabilities(newCallCapabilities); 332 newCallCapabilities = applyAudioQualityCapabilities(newCallCapabilities); 333 newCallCapabilities = applyConferenceTerminationCapabilities(newCallCapabilities); 334 335 if (getCallCapabilities() != newCallCapabilities) { 336 setCallCapabilities(newCallCapabilities); 337 } 338 } 339 340 protected final void updateAddress() { 341 updateCallCapabilities(); 342 if (mOriginalConnection != null) { 343 Uri address = getAddressFromNumber(mOriginalConnection.getAddress()); 344 int presentation = mOriginalConnection.getNumberPresentation(); 345 if (!Objects.equals(address, getAddress()) || 346 presentation != getAddressPresentation()) { 347 Log.v(this, "updateAddress, address changed"); 348 setAddress(address, presentation); 349 } 350 351 String name = mOriginalConnection.getCnapName(); 352 int namePresentation = mOriginalConnection.getCnapNamePresentation(); 353 if (!Objects.equals(name, getCallerDisplayName()) || 354 namePresentation != getCallerDisplayNamePresentation()) { 355 Log.v(this, "updateAddress, caller display name changed"); 356 setCallerDisplayName(name, namePresentation); 357 } 358 } 359 } 360 361 void onRemovedFromCallService() { 362 // Subclass can override this to do cleanup. 363 } 364 365 void setOriginalConnection(com.android.internal.telephony.Connection originalConnection) { 366 Log.v(this, "new TelephonyConnection, originalConnection: " + originalConnection); 367 if (mOriginalConnection != null) { 368 getPhone().unregisterForPreciseCallStateChanged(mHandler); 369 getPhone().unregisterForRingbackTone(mHandler); 370 getPhone().unregisterForHandoverStateChanged(mHandler); 371 getPhone().unregisterForDisconnect(mHandler); 372 } 373 mOriginalConnection = originalConnection; 374 getPhone().registerForPreciseCallStateChanged( 375 mHandler, MSG_PRECISE_CALL_STATE_CHANGED, null); 376 getPhone().registerForHandoverStateChanged( 377 mHandler, MSG_HANDOVER_STATE_CHANGED, null); 378 getPhone().registerForRingbackTone(mHandler, MSG_RINGBACK_TONE, null); 379 getPhone().registerForDisconnect(mHandler, MSG_DISCONNECT, null); 380 mOriginalConnection.addPostDialListener(mPostDialListener); 381 mOriginalConnection.addListener(mOriginalConnectionListener); 382 383 // Set video state and capabilities 384 setVideoState(mOriginalConnection.getVideoState()); 385 setLocalVideoCapable(mOriginalConnection.isLocalVideoCapable()); 386 setRemoteVideoCapable(mOriginalConnection.isRemoteVideoCapable()); 387 setVideoProvider(mOriginalConnection.getVideoProvider()); 388 setAudioQuality(mOriginalConnection.getAudioQuality()); 389 390 updateAddress(); 391 } 392 393 protected void hangup(int telephonyDisconnectCode) { 394 if (mOriginalConnection != null) { 395 try { 396 // Hanging up a ringing call requires that we invoke call.hangup() as opposed to 397 // connection.hangup(). Without this change, the party originating the call will not 398 // get sent to voicemail if the user opts to reject the call. 399 if (isValidRingingCall()) { 400 Call call = getCall(); 401 if (call != null) { 402 call.hangup(); 403 } else { 404 Log.w(this, "Attempting to hangup a connection without backing call."); 405 } 406 } else { 407 // We still prefer to call connection.hangup() for non-ringing calls in order 408 // to support hanging-up specific calls within a conference call. If we invoked 409 // call.hangup() while in a conference, we would end up hanging up the entire 410 // conference call instead of the specific connection. 411 mOriginalConnection.hangup(); 412 } 413 } catch (CallStateException e) { 414 Log.e(this, e, "Call to Connection.hangup failed with exception"); 415 } 416 } 417 } 418 419 com.android.internal.telephony.Connection getOriginalConnection() { 420 return mOriginalConnection; 421 } 422 423 protected Call getCall() { 424 if (mOriginalConnection != null) { 425 return mOriginalConnection.getCall(); 426 } 427 return null; 428 } 429 430 Phone getPhone() { 431 Call call = getCall(); 432 if (call != null) { 433 return call.getPhone(); 434 } 435 return null; 436 } 437 438 private boolean hasMultipleTopLevelCalls() { 439 int numCalls = 0; 440 Phone phone = getPhone(); 441 if (phone != null) { 442 if (!phone.getRingingCall().isIdle()) { 443 numCalls++; 444 } 445 if (!phone.getForegroundCall().isIdle()) { 446 numCalls++; 447 } 448 if (!phone.getBackgroundCall().isIdle()) { 449 numCalls++; 450 } 451 } 452 return numCalls > 1; 453 } 454 455 private com.android.internal.telephony.Connection getForegroundConnection() { 456 if (getPhone() != null) { 457 return getPhone().getForegroundCall().getEarliestConnection(); 458 } 459 return null; 460 } 461 462 /** 463 * Checks to see the original connection corresponds to an active incoming call. Returns false 464 * if there is no such actual call, or if the associated call is not incoming (See 465 * {@link Call.State#isRinging}). 466 */ 467 private boolean isValidRingingCall() { 468 if (getPhone() == null) { 469 Log.v(this, "isValidRingingCall, phone is null"); 470 return false; 471 } 472 473 Call ringingCall = getPhone().getRingingCall(); 474 if (!ringingCall.getState().isRinging()) { 475 Log.v(this, "isValidRingingCall, ringing call is not in ringing state"); 476 return false; 477 } 478 479 if (ringingCall.getEarliestConnection() != mOriginalConnection) { 480 Log.v(this, "isValidRingingCall, ringing call connection does not match"); 481 return false; 482 } 483 484 Log.v(this, "isValidRingingCall, returning true"); 485 return true; 486 } 487 488 void updateState() { 489 if (mOriginalConnection == null) { 490 return; 491 } 492 493 Call.State newState = mOriginalConnection.getState(); 494 Log.v(this, "Update state from %s to %s for %s", mOriginalConnectionState, newState, this); 495 if (mOriginalConnectionState != newState) { 496 mOriginalConnectionState = newState; 497 switch (newState) { 498 case IDLE: 499 break; 500 case ACTIVE: 501 setActiveInternal(); 502 break; 503 case HOLDING: 504 setOnHold(); 505 break; 506 case DIALING: 507 case ALERTING: 508 setDialing(); 509 break; 510 case INCOMING: 511 case WAITING: 512 setRinging(); 513 break; 514 case DISCONNECTED: 515 setDisconnected(DisconnectCauseUtil.toTelecomDisconnectCause( 516 mOriginalConnection.getDisconnectCause())); 517 close(); 518 break; 519 case DISCONNECTING: 520 break; 521 } 522 } 523 updateCallCapabilities(); 524 updateAddress(); 525 } 526 527 private void setActiveInternal() { 528 if (getState() == STATE_ACTIVE) { 529 Log.w(this, "Should not be called if this is already ACTIVE"); 530 return; 531 } 532 533 // When we set a call to active, we need to make sure that there are no other active 534 // calls. However, the ordering of state updates to connections can be non-deterministic 535 // since all connections register for state changes on the phone independently. 536 // To "optimize", we check here to see if there already exists any active calls. If so, 537 // we issue an update for those calls first to make sure we only have one top-level 538 // active call. 539 if (getConnectionService() != null) { 540 for (Connection current : getConnectionService().getAllConnections()) { 541 if (current != this && current instanceof TelephonyConnection) { 542 TelephonyConnection other = (TelephonyConnection) current; 543 if (other.getState() == STATE_ACTIVE) { 544 other.updateState(); 545 } 546 } 547 } 548 } 549 setActive(); 550 } 551 552 private void close() { 553 Log.v(this, "close"); 554 if (getPhone() != null) { 555 getPhone().unregisterForPreciseCallStateChanged(mHandler); 556 getPhone().unregisterForRingbackTone(mHandler); 557 getPhone().unregisterForHandoverStateChanged(mHandler); 558 } 559 mOriginalConnection = null; 560 destroy(); 561 } 562 563 /** 564 * Applies the video capability states to the CallCapabilities bit-mask. 565 * 566 * @param capabilities The CallCapabilities bit-mask. 567 * @return The capabilities with video capabilities applied. 568 */ 569 private int applyVideoCapabilities(int capabilities) { 570 int currentCapabilities = capabilities; 571 if (mRemoteVideoCapable) { 572 currentCapabilities = applyCapability(currentCapabilities, 573 PhoneCapabilities.SUPPORTS_VT_REMOTE); 574 } else { 575 currentCapabilities = removeCapability(currentCapabilities, 576 PhoneCapabilities.SUPPORTS_VT_REMOTE); 577 } 578 579 if (mLocalVideoCapable) { 580 currentCapabilities = applyCapability(currentCapabilities, 581 PhoneCapabilities.SUPPORTS_VT_LOCAL); 582 } else { 583 currentCapabilities = removeCapability(currentCapabilities, 584 PhoneCapabilities.SUPPORTS_VT_LOCAL); 585 } 586 return currentCapabilities; 587 } 588 589 /** 590 * Applies the audio capabilities to the {@code CallCapabilities} bit-mask. A call with high 591 * definition audio is considered to have the {@code VoLTE} call capability as VoLTE uses high 592 * definition audio. 593 * 594 * @param callCapabilities The {@code CallCapabilities} bit-mask. 595 * @return The capabilities with the audio capabilities applied. 596 */ 597 private int applyAudioQualityCapabilities(int callCapabilities) { 598 int currentCapabilities = callCapabilities; 599 600 if (mAudioQuality == 601 com.android.internal.telephony.Connection.AUDIO_QUALITY_HIGH_DEFINITION) { 602 currentCapabilities = applyCapability(currentCapabilities, PhoneCapabilities.VoLTE); 603 } else { 604 currentCapabilities = removeCapability(currentCapabilities, PhoneCapabilities.VoLTE); 605 } 606 607 return currentCapabilities; 608 } 609 610 /** 611 * Applies capabilities specific to conferences termination to the 612 * {@code CallCapabilities} bit-mask. 613 * 614 * @param callCapabilities The {@code CallCapabilities} bit-mask. 615 * @return The capabilities with the IMS conference capabilities applied. 616 */ 617 private int applyConferenceTerminationCapabilities(int callCapabilities) { 618 int currentCapabilities = callCapabilities; 619 620 // An IMS call cannot be individually disconnected or separated from its parent conference 621 boolean isImsCall = getOriginalConnection() instanceof ImsPhoneConnection; 622 if (!isImsCall) { 623 currentCapabilities |= 624 PhoneCapabilities.DISCONNECT_FROM_CONFERENCE 625 | PhoneCapabilities.SEPARATE_FROM_CONFERENCE; 626 } 627 628 return currentCapabilities; 629 } 630 631 /** 632 * Returns the local video capability state for the connection. 633 * 634 * @return {@code True} if the connection has local video capabilities. 635 */ 636 public boolean isLocalVideoCapable() { 637 return mLocalVideoCapable; 638 } 639 640 /** 641 * Returns the remote video capability state for the connection. 642 * 643 * @return {@code True} if the connection has remote video capabilities. 644 */ 645 public boolean isRemoteVideoCapable() { 646 return mRemoteVideoCapable; 647 } 648 649 /** 650 * Sets whether video capability is present locally. Used during rebuild of the 651 * {@link PhoneCapabilities} to set the video call capabilities. 652 * 653 * @param capable {@code True} if video capable. 654 */ 655 public void setLocalVideoCapable(boolean capable) { 656 mLocalVideoCapable = capable; 657 updateCallCapabilities(); 658 } 659 660 /** 661 * Sets whether video capability is present remotely. Used during rebuild of the 662 * {@link PhoneCapabilities} to set the video call capabilities. 663 * 664 * @param capable {@code True} if video capable. 665 */ 666 public void setRemoteVideoCapable(boolean capable) { 667 mRemoteVideoCapable = capable; 668 updateCallCapabilities(); 669 } 670 671 /** 672 * Sets the current call audio quality. Used during rebuild of the 673 * {@link PhoneCapabilities} to set or unset the {@link PhoneCapabilities#VoLTE} capability. 674 * 675 * @param audioQuality The audio quality. 676 */ 677 public void setAudioQuality(int audioQuality) { 678 mAudioQuality = audioQuality; 679 updateCallCapabilities(); 680 } 681 682 /** 683 * Obtains the current call audio quality. 684 */ 685 public int getAudioQuality() { 686 return mAudioQuality; 687 } 688 689 void resetStateForConference() { 690 if (getState() == Connection.STATE_HOLDING) { 691 if (mOriginalConnection.getState() == Call.State.ACTIVE) { 692 setActive(); 693 } 694 } 695 } 696 697 boolean setHoldingForConference() { 698 if (getState() == Connection.STATE_ACTIVE) { 699 setOnHold(); 700 return true; 701 } 702 return false; 703 } 704 705 private static Uri getAddressFromNumber(String number) { 706 // Address can be null for blocked calls. 707 if (number == null) { 708 number = ""; 709 } 710 return Uri.fromParts(PhoneAccount.SCHEME_TEL, number, null); 711 } 712 713 /** 714 * Applies a capability to a capabilities bit-mask. 715 * 716 * @param capabilities The capabilities bit-mask. 717 * @param capability The capability to apply. 718 * @return The capabilities bit-mask with the capability applied. 719 */ 720 private int applyCapability(int capabilities, int capability) { 721 int newCapabilities = capabilities | capability; 722 return newCapabilities; 723 } 724 725 /** 726 * Removes a capability from a capabilities bit-mask. 727 * 728 * @param capabilities The capabilities bit-mask. 729 * @param capability The capability to remove. 730 * @return The capabilities bit-mask with the capability removed. 731 */ 732 private int removeCapability(int capabilities, int capability) { 733 int newCapabilities = capabilities & ~capability; 734 return newCapabilities; 735 } 736} 737