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 17 package android.telecom; 18 19 import android.annotation.SdkConstant; 20 import android.app.Service; 21 import android.content.ComponentName; 22 import android.content.Intent; 23 import android.net.Uri; 24 import android.os.Bundle; 25 import android.os.Handler; 26 import android.os.IBinder; 27 import android.os.Looper; 28 import android.os.Message; 29 30 import com.android.internal.os.SomeArgs; 31 import com.android.internal.telecom.IConnectionService; 32 import com.android.internal.telecom.IConnectionServiceAdapter; 33 import com.android.internal.telecom.RemoteServiceCallback; 34 35 import java.util.ArrayList; 36 import java.util.Collection; 37 import java.util.Collections; 38 import java.util.List; 39 import java.util.Map; 40 import java.util.UUID; 41 import java.util.concurrent.ConcurrentHashMap; 42 43 /** 44 * An abstract service that should be implemented by any apps which can make phone calls (VoIP or 45 * otherwise) and want those calls to be integrated into the built-in phone app. 46 * Once implemented, the {@code ConnectionService} needs two additional steps before it will be 47 * integrated into the phone app: 48 * <p> 49 * 1. <i>Registration in AndroidManifest.xml</i> 50 * <br/> 51 * <pre> 52 * <service android:name="com.example.package.MyConnectionService" 53 * android:label="@string/some_label_for_my_connection_service" 54 * android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE"> 55 * <intent-filter> 56 * <action android:name="android.telecom.ConnectionService" /> 57 * </intent-filter> 58 * </service> 59 * </pre> 60 * <p> 61 * 2. <i> Registration of {@link PhoneAccount} with {@link TelecomManager}.</i> 62 * <br/> 63 * See {@link PhoneAccount} and {@link TelecomManager#registerPhoneAccount} for more information. 64 * <p> 65 * Once registered and enabled by the user in the phone app settings, telecom will bind to a 66 * {@code ConnectionService} implementation when it wants that {@code ConnectionService} to place 67 * a call or the service has indicated that is has an incoming call through 68 * {@link TelecomManager#addNewIncomingCall}. The {@code ConnectionService} can then expect a call 69 * to {@link #onCreateIncomingConnection} or {@link #onCreateOutgoingConnection} wherein it 70 * should provide a new instance of a {@link Connection} object. It is through this 71 * {@link Connection} object that telecom receives state updates and the {@code ConnectionService} 72 * receives call-commands such as answer, reject, hold and disconnect. 73 * <p> 74 * When there are no more live calls, telecom will unbind from the {@code ConnectionService}. 75 */ 76 public abstract class ConnectionService extends Service { 77 /** 78 * The {@link Intent} that must be declared as handled by the service. 79 */ 80 @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION) 81 public static final String SERVICE_INTERFACE = "android.telecom.ConnectionService"; 82 83 // Flag controlling whether PII is emitted into the logs 84 private static final boolean PII_DEBUG = Log.isLoggable(android.util.Log.DEBUG); 85 86 private static final int MSG_ADD_CONNECTION_SERVICE_ADAPTER = 1; 87 private static final int MSG_CREATE_CONNECTION = 2; 88 private static final int MSG_ABORT = 3; 89 private static final int MSG_ANSWER = 4; 90 private static final int MSG_REJECT = 5; 91 private static final int MSG_DISCONNECT = 6; 92 private static final int MSG_HOLD = 7; 93 private static final int MSG_UNHOLD = 8; 94 private static final int MSG_ON_CALL_AUDIO_STATE_CHANGED = 9; 95 private static final int MSG_PLAY_DTMF_TONE = 10; 96 private static final int MSG_STOP_DTMF_TONE = 11; 97 private static final int MSG_CONFERENCE = 12; 98 private static final int MSG_SPLIT_FROM_CONFERENCE = 13; 99 private static final int MSG_ON_POST_DIAL_CONTINUE = 14; 100 private static final int MSG_REMOVE_CONNECTION_SERVICE_ADAPTER = 16; 101 private static final int MSG_ANSWER_VIDEO = 17; 102 private static final int MSG_MERGE_CONFERENCE = 18; 103 private static final int MSG_SWAP_CONFERENCE = 19; 104 105 private static Connection sNullConnection; 106 107 private final Map<String, Connection> mConnectionById = new ConcurrentHashMap<>(); 108 private final Map<Connection, String> mIdByConnection = new ConcurrentHashMap<>(); 109 private final Map<String, Conference> mConferenceById = new ConcurrentHashMap<>(); 110 private final Map<Conference, String> mIdByConference = new ConcurrentHashMap<>(); 111 private final RemoteConnectionManager mRemoteConnectionManager = 112 new RemoteConnectionManager(this); 113 private final List<Runnable> mPreInitializationConnectionRequests = new ArrayList<>(); 114 private final ConnectionServiceAdapter mAdapter = new ConnectionServiceAdapter(); 115 116 private boolean mAreAccountsInitialized = false; 117 private Conference sNullConference; 118 119 private final IBinder mBinder = new IConnectionService.Stub() { 120 @Override 121 public void addConnectionServiceAdapter(IConnectionServiceAdapter adapter) { 122 mHandler.obtainMessage(MSG_ADD_CONNECTION_SERVICE_ADAPTER, adapter).sendToTarget(); 123 } 124 125 public void removeConnectionServiceAdapter(IConnectionServiceAdapter adapter) { 126 mHandler.obtainMessage(MSG_REMOVE_CONNECTION_SERVICE_ADAPTER, adapter).sendToTarget(); 127 } 128 129 @Override 130 public void createConnection( 131 PhoneAccountHandle connectionManagerPhoneAccount, 132 String id, 133 ConnectionRequest request, 134 boolean isIncoming, 135 boolean isUnknown) { 136 SomeArgs args = SomeArgs.obtain(); 137 args.arg1 = connectionManagerPhoneAccount; 138 args.arg2 = id; 139 args.arg3 = request; 140 args.argi1 = isIncoming ? 1 : 0; 141 args.argi2 = isUnknown ? 1 : 0; 142 mHandler.obtainMessage(MSG_CREATE_CONNECTION, args).sendToTarget(); 143 } 144 145 @Override 146 public void abort(String callId) { 147 mHandler.obtainMessage(MSG_ABORT, callId).sendToTarget(); 148 } 149 150 @Override 151 public void answerVideo(String callId, int videoState) { 152 SomeArgs args = SomeArgs.obtain(); 153 args.arg1 = callId; 154 args.argi1 = videoState; 155 mHandler.obtainMessage(MSG_ANSWER_VIDEO, args).sendToTarget(); 156 } 157 158 @Override 159 public void answer(String callId) { 160 mHandler.obtainMessage(MSG_ANSWER, callId).sendToTarget(); 161 } 162 163 @Override 164 public void reject(String callId) { 165 mHandler.obtainMessage(MSG_REJECT, callId).sendToTarget(); 166 } 167 168 @Override 169 public void disconnect(String callId) { 170 mHandler.obtainMessage(MSG_DISCONNECT, callId).sendToTarget(); 171 } 172 173 @Override 174 public void hold(String callId) { 175 mHandler.obtainMessage(MSG_HOLD, callId).sendToTarget(); 176 } 177 178 @Override 179 public void unhold(String callId) { 180 mHandler.obtainMessage(MSG_UNHOLD, callId).sendToTarget(); 181 } 182 183 @Override 184 public void onCallAudioStateChanged(String callId, CallAudioState callAudioState) { 185 SomeArgs args = SomeArgs.obtain(); 186 args.arg1 = callId; 187 args.arg2 = callAudioState; 188 mHandler.obtainMessage(MSG_ON_CALL_AUDIO_STATE_CHANGED, args).sendToTarget(); 189 } 190 191 @Override 192 public void playDtmfTone(String callId, char digit) { 193 mHandler.obtainMessage(MSG_PLAY_DTMF_TONE, digit, 0, callId).sendToTarget(); 194 } 195 196 @Override 197 public void stopDtmfTone(String callId) { 198 mHandler.obtainMessage(MSG_STOP_DTMF_TONE, callId).sendToTarget(); 199 } 200 201 @Override 202 public void conference(String callId1, String callId2) { 203 SomeArgs args = SomeArgs.obtain(); 204 args.arg1 = callId1; 205 args.arg2 = callId2; 206 mHandler.obtainMessage(MSG_CONFERENCE, args).sendToTarget(); 207 } 208 209 @Override 210 public void splitFromConference(String callId) { 211 mHandler.obtainMessage(MSG_SPLIT_FROM_CONFERENCE, callId).sendToTarget(); 212 } 213 214 @Override 215 public void mergeConference(String callId) { 216 mHandler.obtainMessage(MSG_MERGE_CONFERENCE, callId).sendToTarget(); 217 } 218 219 @Override 220 public void swapConference(String callId) { 221 mHandler.obtainMessage(MSG_SWAP_CONFERENCE, callId).sendToTarget(); 222 } 223 224 @Override 225 public void onPostDialContinue(String callId, boolean proceed) { 226 SomeArgs args = SomeArgs.obtain(); 227 args.arg1 = callId; 228 args.argi1 = proceed ? 1 : 0; 229 mHandler.obtainMessage(MSG_ON_POST_DIAL_CONTINUE, args).sendToTarget(); 230 } 231 }; 232 233 private final Handler mHandler = new Handler(Looper.getMainLooper()) { 234 @Override 235 public void handleMessage(Message msg) { 236 switch (msg.what) { 237 case MSG_ADD_CONNECTION_SERVICE_ADAPTER: 238 mAdapter.addAdapter((IConnectionServiceAdapter) msg.obj); 239 onAdapterAttached(); 240 break; 241 case MSG_REMOVE_CONNECTION_SERVICE_ADAPTER: 242 mAdapter.removeAdapter((IConnectionServiceAdapter) msg.obj); 243 break; 244 case MSG_CREATE_CONNECTION: { 245 SomeArgs args = (SomeArgs) msg.obj; 246 try { 247 final PhoneAccountHandle connectionManagerPhoneAccount = 248 (PhoneAccountHandle) args.arg1; 249 final String id = (String) args.arg2; 250 final ConnectionRequest request = (ConnectionRequest) args.arg3; 251 final boolean isIncoming = args.argi1 == 1; 252 final boolean isUnknown = args.argi2 == 1; 253 if (!mAreAccountsInitialized) { 254 Log.d(this, "Enqueueing pre-init request %s", id); 255 mPreInitializationConnectionRequests.add(new Runnable() { 256 @Override 257 public void run() { 258 createConnection( 259 connectionManagerPhoneAccount, 260 id, 261 request, 262 isIncoming, 263 isUnknown); 264 } 265 }); 266 } else { 267 createConnection( 268 connectionManagerPhoneAccount, 269 id, 270 request, 271 isIncoming, 272 isUnknown); 273 } 274 } finally { 275 args.recycle(); 276 } 277 break; 278 } 279 case MSG_ABORT: 280 abort((String) msg.obj); 281 break; 282 case MSG_ANSWER: 283 answer((String) msg.obj); 284 break; 285 case MSG_ANSWER_VIDEO: { 286 SomeArgs args = (SomeArgs) msg.obj; 287 try { 288 String callId = (String) args.arg1; 289 int videoState = args.argi1; 290 answerVideo(callId, videoState); 291 } finally { 292 args.recycle(); 293 } 294 break; 295 } 296 case MSG_REJECT: 297 reject((String) msg.obj); 298 break; 299 case MSG_DISCONNECT: 300 disconnect((String) msg.obj); 301 break; 302 case MSG_HOLD: 303 hold((String) msg.obj); 304 break; 305 case MSG_UNHOLD: 306 unhold((String) msg.obj); 307 break; 308 case MSG_ON_CALL_AUDIO_STATE_CHANGED: { 309 SomeArgs args = (SomeArgs) msg.obj; 310 try { 311 String callId = (String) args.arg1; 312 CallAudioState audioState = (CallAudioState) args.arg2; 313 onCallAudioStateChanged(callId, new CallAudioState(audioState)); 314 } finally { 315 args.recycle(); 316 } 317 break; 318 } 319 case MSG_PLAY_DTMF_TONE: 320 playDtmfTone((String) msg.obj, (char) msg.arg1); 321 break; 322 case MSG_STOP_DTMF_TONE: 323 stopDtmfTone((String) msg.obj); 324 break; 325 case MSG_CONFERENCE: { 326 SomeArgs args = (SomeArgs) msg.obj; 327 try { 328 String callId1 = (String) args.arg1; 329 String callId2 = (String) args.arg2; 330 conference(callId1, callId2); 331 } finally { 332 args.recycle(); 333 } 334 break; 335 } 336 case MSG_SPLIT_FROM_CONFERENCE: 337 splitFromConference((String) msg.obj); 338 break; 339 case MSG_MERGE_CONFERENCE: 340 mergeConference((String) msg.obj); 341 break; 342 case MSG_SWAP_CONFERENCE: 343 swapConference((String) msg.obj); 344 break; 345 case MSG_ON_POST_DIAL_CONTINUE: { 346 SomeArgs args = (SomeArgs) msg.obj; 347 try { 348 String callId = (String) args.arg1; 349 boolean proceed = (args.argi1 == 1); 350 onPostDialContinue(callId, proceed); 351 } finally { 352 args.recycle(); 353 } 354 break; 355 } 356 default: 357 break; 358 } 359 } 360 }; 361 362 private final Conference.Listener mConferenceListener = new Conference.Listener() { 363 @Override 364 public void onStateChanged(Conference conference, int oldState, int newState) { 365 String id = mIdByConference.get(conference); 366 switch (newState) { 367 case Connection.STATE_ACTIVE: 368 mAdapter.setActive(id); 369 break; 370 case Connection.STATE_HOLDING: 371 mAdapter.setOnHold(id); 372 break; 373 case Connection.STATE_DISCONNECTED: 374 // handled by onDisconnected 375 break; 376 } 377 } 378 379 @Override 380 public void onDisconnected(Conference conference, DisconnectCause disconnectCause) { 381 String id = mIdByConference.get(conference); 382 mAdapter.setDisconnected(id, disconnectCause); 383 } 384 385 @Override 386 public void onConnectionAdded(Conference conference, Connection connection) { 387 } 388 389 @Override 390 public void onConnectionRemoved(Conference conference, Connection connection) { 391 } 392 393 @Override 394 public void onConferenceableConnectionsChanged( 395 Conference conference, List<Connection> conferenceableConnections) { 396 mAdapter.setConferenceableConnections( 397 mIdByConference.get(conference), 398 createConnectionIdList(conferenceableConnections)); 399 } 400 401 @Override 402 public void onDestroyed(Conference conference) { 403 removeConference(conference); 404 } 405 406 @Override 407 public void onConnectionCapabilitiesChanged( 408 Conference conference, 409 int connectionCapabilities) { 410 String id = mIdByConference.get(conference); 411 Log.d(this, "call capabilities: conference: %s", 412 Connection.capabilitiesToString(connectionCapabilities)); 413 mAdapter.setConnectionCapabilities(id, connectionCapabilities); 414 } 415 416 @Override 417 public void onVideoStateChanged(Conference c, int videoState) { 418 String id = mIdByConference.get(c); 419 Log.d(this, "onVideoStateChanged set video state %d", videoState); 420 mAdapter.setVideoState(id, videoState); 421 } 422 423 @Override 424 public void onVideoProviderChanged(Conference c, Connection.VideoProvider videoProvider) { 425 String id = mIdByConference.get(c); 426 Log.d(this, "onVideoProviderChanged: Connection: %s, VideoProvider: %s", c, 427 videoProvider); 428 mAdapter.setVideoProvider(id, videoProvider); 429 } 430 431 @Override 432 public void onStatusHintsChanged(Conference conference, StatusHints statusHints) { 433 String id = mIdByConference.get(conference); 434 mAdapter.setStatusHints(id, statusHints); 435 } 436 437 @Override 438 public void onExtrasChanged(Conference conference, Bundle extras) { 439 String id = mIdByConference.get(conference); 440 mAdapter.setExtras(id, extras); 441 } 442 }; 443 444 private final Connection.Listener mConnectionListener = new Connection.Listener() { 445 @Override 446 public void onStateChanged(Connection c, int state) { 447 String id = mIdByConnection.get(c); 448 Log.d(this, "Adapter set state %s %s", id, Connection.stateToString(state)); 449 switch (state) { 450 case Connection.STATE_ACTIVE: 451 mAdapter.setActive(id); 452 break; 453 case Connection.STATE_DIALING: 454 mAdapter.setDialing(id); 455 break; 456 case Connection.STATE_DISCONNECTED: 457 // Handled in onDisconnected() 458 break; 459 case Connection.STATE_HOLDING: 460 mAdapter.setOnHold(id); 461 break; 462 case Connection.STATE_NEW: 463 // Nothing to tell Telecom 464 break; 465 case Connection.STATE_RINGING: 466 mAdapter.setRinging(id); 467 break; 468 } 469 } 470 471 @Override 472 public void onDisconnected(Connection c, DisconnectCause disconnectCause) { 473 String id = mIdByConnection.get(c); 474 Log.d(this, "Adapter set disconnected %s", disconnectCause); 475 mAdapter.setDisconnected(id, disconnectCause); 476 } 477 478 @Override 479 public void onVideoStateChanged(Connection c, int videoState) { 480 String id = mIdByConnection.get(c); 481 Log.d(this, "Adapter set video state %d", videoState); 482 mAdapter.setVideoState(id, videoState); 483 } 484 485 @Override 486 public void onAddressChanged(Connection c, Uri address, int presentation) { 487 String id = mIdByConnection.get(c); 488 mAdapter.setAddress(id, address, presentation); 489 } 490 491 @Override 492 public void onCallerDisplayNameChanged( 493 Connection c, String callerDisplayName, int presentation) { 494 String id = mIdByConnection.get(c); 495 mAdapter.setCallerDisplayName(id, callerDisplayName, presentation); 496 } 497 498 @Override 499 public void onDestroyed(Connection c) { 500 removeConnection(c); 501 } 502 503 @Override 504 public void onPostDialWait(Connection c, String remaining) { 505 String id = mIdByConnection.get(c); 506 Log.d(this, "Adapter onPostDialWait %s, %s", c, remaining); 507 mAdapter.onPostDialWait(id, remaining); 508 } 509 510 @Override 511 public void onPostDialChar(Connection c, char nextChar) { 512 String id = mIdByConnection.get(c); 513 Log.d(this, "Adapter onPostDialChar %s, %s", c, nextChar); 514 mAdapter.onPostDialChar(id, nextChar); 515 } 516 517 @Override 518 public void onRingbackRequested(Connection c, boolean ringback) { 519 String id = mIdByConnection.get(c); 520 Log.d(this, "Adapter onRingback %b", ringback); 521 mAdapter.setRingbackRequested(id, ringback); 522 } 523 524 @Override 525 public void onConnectionCapabilitiesChanged(Connection c, int capabilities) { 526 String id = mIdByConnection.get(c); 527 Log.d(this, "capabilities: parcelableconnection: %s", 528 Connection.capabilitiesToString(capabilities)); 529 mAdapter.setConnectionCapabilities(id, capabilities); 530 } 531 532 @Override 533 public void onVideoProviderChanged(Connection c, Connection.VideoProvider videoProvider) { 534 String id = mIdByConnection.get(c); 535 Log.d(this, "onVideoProviderChanged: Connection: %s, VideoProvider: %s", c, 536 videoProvider); 537 mAdapter.setVideoProvider(id, videoProvider); 538 } 539 540 @Override 541 public void onAudioModeIsVoipChanged(Connection c, boolean isVoip) { 542 String id = mIdByConnection.get(c); 543 mAdapter.setIsVoipAudioMode(id, isVoip); 544 } 545 546 @Override 547 public void onStatusHintsChanged(Connection c, StatusHints statusHints) { 548 String id = mIdByConnection.get(c); 549 mAdapter.setStatusHints(id, statusHints); 550 } 551 552 @Override 553 public void onConferenceablesChanged( 554 Connection connection, List<Conferenceable> conferenceables) { 555 mAdapter.setConferenceableConnections( 556 mIdByConnection.get(connection), 557 createIdList(conferenceables)); 558 } 559 560 @Override 561 public void onConferenceChanged(Connection connection, Conference conference) { 562 String id = mIdByConnection.get(connection); 563 if (id != null) { 564 String conferenceId = null; 565 if (conference != null) { 566 conferenceId = mIdByConference.get(conference); 567 } 568 mAdapter.setIsConferenced(id, conferenceId); 569 } 570 } 571 572 @Override 573 public void onConferenceMergeFailed(Connection connection) { 574 String id = mIdByConnection.get(connection); 575 if (id != null) { 576 mAdapter.onConferenceMergeFailed(id); 577 } 578 } 579 580 @Override 581 public void onExtrasChanged(Connection connection, Bundle extras) { 582 String id = mIdByConnection.get(connection); 583 if (id != null) { 584 mAdapter.setExtras(id, extras); 585 } 586 } 587 }; 588 589 /** {@inheritDoc} */ 590 @Override onBind(Intent intent)591 public final IBinder onBind(Intent intent) { 592 return mBinder; 593 } 594 595 /** {@inheritDoc} */ 596 @Override onUnbind(Intent intent)597 public boolean onUnbind(Intent intent) { 598 endAllConnections(); 599 return super.onUnbind(intent); 600 } 601 602 /** 603 * This can be used by telecom to either create a new outgoing call or attach to an existing 604 * incoming call. In either case, telecom will cycle through a set of services and call 605 * createConnection util a connection service cancels the process or completes it successfully. 606 */ createConnection( final PhoneAccountHandle callManagerAccount, final String callId, final ConnectionRequest request, boolean isIncoming, boolean isUnknown)607 private void createConnection( 608 final PhoneAccountHandle callManagerAccount, 609 final String callId, 610 final ConnectionRequest request, 611 boolean isIncoming, 612 boolean isUnknown) { 613 Log.d(this, "createConnection, callManagerAccount: %s, callId: %s, request: %s, " + 614 "isIncoming: %b, isUnknown: %b", callManagerAccount, callId, request, isIncoming, 615 isUnknown); 616 617 Connection connection = isUnknown ? onCreateUnknownConnection(callManagerAccount, request) 618 : isIncoming ? onCreateIncomingConnection(callManagerAccount, request) 619 : onCreateOutgoingConnection(callManagerAccount, request); 620 Log.d(this, "createConnection, connection: %s", connection); 621 if (connection == null) { 622 connection = Connection.createFailedConnection( 623 new DisconnectCause(DisconnectCause.ERROR)); 624 } 625 626 if (connection.getState() != Connection.STATE_DISCONNECTED) { 627 addConnection(callId, connection); 628 } 629 630 Uri address = connection.getAddress(); 631 String number = address == null ? "null" : address.getSchemeSpecificPart(); 632 Log.v(this, "createConnection, number: %s, state: %s, capabilities: %s", 633 Connection.toLogSafePhoneNumber(number), 634 Connection.stateToString(connection.getState()), 635 Connection.capabilitiesToString(connection.getConnectionCapabilities())); 636 637 Log.d(this, "createConnection, calling handleCreateConnectionSuccessful %s", callId); 638 mAdapter.handleCreateConnectionComplete( 639 callId, 640 request, 641 new ParcelableConnection( 642 request.getAccountHandle(), 643 connection.getState(), 644 connection.getConnectionCapabilities(), 645 connection.getAddress(), 646 connection.getAddressPresentation(), 647 connection.getCallerDisplayName(), 648 connection.getCallerDisplayNamePresentation(), 649 connection.getVideoProvider() == null ? 650 null : connection.getVideoProvider().getInterface(), 651 connection.getVideoState(), 652 connection.isRingbackRequested(), 653 connection.getAudioModeIsVoip(), 654 connection.getConnectTimeMillis(), 655 connection.getStatusHints(), 656 connection.getDisconnectCause(), 657 createIdList(connection.getConferenceables()), 658 connection.getExtras())); 659 if (isUnknown) { 660 triggerConferenceRecalculate(); 661 } 662 } 663 abort(String callId)664 private void abort(String callId) { 665 Log.d(this, "abort %s", callId); 666 findConnectionForAction(callId, "abort").onAbort(); 667 } 668 answerVideo(String callId, int videoState)669 private void answerVideo(String callId, int videoState) { 670 Log.d(this, "answerVideo %s", callId); 671 findConnectionForAction(callId, "answer").onAnswer(videoState); 672 } 673 answer(String callId)674 private void answer(String callId) { 675 Log.d(this, "answer %s", callId); 676 findConnectionForAction(callId, "answer").onAnswer(); 677 } 678 reject(String callId)679 private void reject(String callId) { 680 Log.d(this, "reject %s", callId); 681 findConnectionForAction(callId, "reject").onReject(); 682 } 683 disconnect(String callId)684 private void disconnect(String callId) { 685 Log.d(this, "disconnect %s", callId); 686 if (mConnectionById.containsKey(callId)) { 687 findConnectionForAction(callId, "disconnect").onDisconnect(); 688 } else { 689 findConferenceForAction(callId, "disconnect").onDisconnect(); 690 } 691 } 692 hold(String callId)693 private void hold(String callId) { 694 Log.d(this, "hold %s", callId); 695 if (mConnectionById.containsKey(callId)) { 696 findConnectionForAction(callId, "hold").onHold(); 697 } else { 698 findConferenceForAction(callId, "hold").onHold(); 699 } 700 } 701 unhold(String callId)702 private void unhold(String callId) { 703 Log.d(this, "unhold %s", callId); 704 if (mConnectionById.containsKey(callId)) { 705 findConnectionForAction(callId, "unhold").onUnhold(); 706 } else { 707 findConferenceForAction(callId, "unhold").onUnhold(); 708 } 709 } 710 onCallAudioStateChanged(String callId, CallAudioState callAudioState)711 private void onCallAudioStateChanged(String callId, CallAudioState callAudioState) { 712 Log.d(this, "onAudioStateChanged %s %s", callId, callAudioState); 713 if (mConnectionById.containsKey(callId)) { 714 findConnectionForAction(callId, "onCallAudioStateChanged").setCallAudioState( 715 callAudioState); 716 } else { 717 findConferenceForAction(callId, "onCallAudioStateChanged").setCallAudioState( 718 callAudioState); 719 } 720 } 721 playDtmfTone(String callId, char digit)722 private void playDtmfTone(String callId, char digit) { 723 Log.d(this, "playDtmfTone %s %c", callId, digit); 724 if (mConnectionById.containsKey(callId)) { 725 findConnectionForAction(callId, "playDtmfTone").onPlayDtmfTone(digit); 726 } else { 727 findConferenceForAction(callId, "playDtmfTone").onPlayDtmfTone(digit); 728 } 729 } 730 stopDtmfTone(String callId)731 private void stopDtmfTone(String callId) { 732 Log.d(this, "stopDtmfTone %s", callId); 733 if (mConnectionById.containsKey(callId)) { 734 findConnectionForAction(callId, "stopDtmfTone").onStopDtmfTone(); 735 } else { 736 findConferenceForAction(callId, "stopDtmfTone").onStopDtmfTone(); 737 } 738 } 739 conference(String callId1, String callId2)740 private void conference(String callId1, String callId2) { 741 Log.d(this, "conference %s, %s", callId1, callId2); 742 743 // Attempt to get second connection or conference. 744 Connection connection2 = findConnectionForAction(callId2, "conference"); 745 Conference conference2 = getNullConference(); 746 if (connection2 == getNullConnection()) { 747 conference2 = findConferenceForAction(callId2, "conference"); 748 if (conference2 == getNullConference()) { 749 Log.w(this, "Connection2 or Conference2 missing in conference request %s.", 750 callId2); 751 return; 752 } 753 } 754 755 // Attempt to get first connection or conference and perform merge. 756 Connection connection1 = findConnectionForAction(callId1, "conference"); 757 if (connection1 == getNullConnection()) { 758 Conference conference1 = findConferenceForAction(callId1, "addConnection"); 759 if (conference1 == getNullConference()) { 760 Log.w(this, 761 "Connection1 or Conference1 missing in conference request %s.", 762 callId1); 763 } else { 764 // Call 1 is a conference. 765 if (connection2 != getNullConnection()) { 766 // Call 2 is a connection so merge via call 1 (conference). 767 conference1.onMerge(connection2); 768 } else { 769 // Call 2 is ALSO a conference; this should never happen. 770 Log.wtf(this, "There can only be one conference and an attempt was made to " + 771 "merge two conferences."); 772 return; 773 } 774 } 775 } else { 776 // Call 1 is a connection. 777 if (conference2 != getNullConference()) { 778 // Call 2 is a conference, so merge via call 2. 779 conference2.onMerge(connection1); 780 } else { 781 // Call 2 is a connection, so merge together. 782 onConference(connection1, connection2); 783 } 784 } 785 } 786 splitFromConference(String callId)787 private void splitFromConference(String callId) { 788 Log.d(this, "splitFromConference(%s)", callId); 789 790 Connection connection = findConnectionForAction(callId, "splitFromConference"); 791 if (connection == getNullConnection()) { 792 Log.w(this, "Connection missing in conference request %s.", callId); 793 return; 794 } 795 796 Conference conference = connection.getConference(); 797 if (conference != null) { 798 conference.onSeparate(connection); 799 } 800 } 801 mergeConference(String callId)802 private void mergeConference(String callId) { 803 Log.d(this, "mergeConference(%s)", callId); 804 Conference conference = findConferenceForAction(callId, "mergeConference"); 805 if (conference != null) { 806 conference.onMerge(); 807 } 808 } 809 swapConference(String callId)810 private void swapConference(String callId) { 811 Log.d(this, "swapConference(%s)", callId); 812 Conference conference = findConferenceForAction(callId, "swapConference"); 813 if (conference != null) { 814 conference.onSwap(); 815 } 816 } 817 onPostDialContinue(String callId, boolean proceed)818 private void onPostDialContinue(String callId, boolean proceed) { 819 Log.d(this, "onPostDialContinue(%s)", callId); 820 findConnectionForAction(callId, "stopDtmfTone").onPostDialContinue(proceed); 821 } 822 onAdapterAttached()823 private void onAdapterAttached() { 824 if (mAreAccountsInitialized) { 825 // No need to query again if we already did it. 826 return; 827 } 828 829 mAdapter.queryRemoteConnectionServices(new RemoteServiceCallback.Stub() { 830 @Override 831 public void onResult( 832 final List<ComponentName> componentNames, 833 final List<IBinder> services) { 834 mHandler.post(new Runnable() { 835 @Override 836 public void run() { 837 for (int i = 0; i < componentNames.size() && i < services.size(); i++) { 838 mRemoteConnectionManager.addConnectionService( 839 componentNames.get(i), 840 IConnectionService.Stub.asInterface(services.get(i))); 841 } 842 onAccountsInitialized(); 843 Log.d(this, "remote connection services found: " + services); 844 } 845 }); 846 } 847 848 @Override 849 public void onError() { 850 mHandler.post(new Runnable() { 851 @Override 852 public void run() { 853 mAreAccountsInitialized = true; 854 } 855 }); 856 } 857 }); 858 } 859 860 /** 861 * Ask some other {@code ConnectionService} to create a {@code RemoteConnection} given an 862 * incoming request. This is used by {@code ConnectionService}s that are registered with 863 * {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER} and want to be able to manage 864 * SIM-based incoming calls. 865 * 866 * @param connectionManagerPhoneAccount See description at 867 * {@link #onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)}. 868 * @param request Details about the incoming call. 869 * @return The {@code Connection} object to satisfy this call, or {@code null} to 870 * not handle the call. 871 */ createRemoteIncomingConnection( PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request)872 public final RemoteConnection createRemoteIncomingConnection( 873 PhoneAccountHandle connectionManagerPhoneAccount, 874 ConnectionRequest request) { 875 return mRemoteConnectionManager.createRemoteConnection( 876 connectionManagerPhoneAccount, request, true); 877 } 878 879 /** 880 * Ask some other {@code ConnectionService} to create a {@code RemoteConnection} given an 881 * outgoing request. This is used by {@code ConnectionService}s that are registered with 882 * {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER} and want to be able to use the 883 * SIM-based {@code ConnectionService} to place its outgoing calls. 884 * 885 * @param connectionManagerPhoneAccount See description at 886 * {@link #onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)}. 887 * @param request Details about the incoming call. 888 * @return The {@code Connection} object to satisfy this call, or {@code null} to 889 * not handle the call. 890 */ createRemoteOutgoingConnection( PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request)891 public final RemoteConnection createRemoteOutgoingConnection( 892 PhoneAccountHandle connectionManagerPhoneAccount, 893 ConnectionRequest request) { 894 return mRemoteConnectionManager.createRemoteConnection( 895 connectionManagerPhoneAccount, request, false); 896 } 897 898 /** 899 * Indicates to the relevant {@code RemoteConnectionService} that the specified 900 * {@link RemoteConnection}s should be merged into a conference call. 901 * <p> 902 * If the conference request is successful, the method {@link #onRemoteConferenceAdded} will 903 * be invoked. 904 * 905 * @param remoteConnection1 The first of the remote connections to conference. 906 * @param remoteConnection2 The second of the remote connections to conference. 907 */ conferenceRemoteConnections( RemoteConnection remoteConnection1, RemoteConnection remoteConnection2)908 public final void conferenceRemoteConnections( 909 RemoteConnection remoteConnection1, 910 RemoteConnection remoteConnection2) { 911 mRemoteConnectionManager.conferenceRemoteConnections(remoteConnection1, remoteConnection2); 912 } 913 914 /** 915 * Adds a new conference call. When a conference call is created either as a result of an 916 * explicit request via {@link #onConference} or otherwise, the connection service should supply 917 * an instance of {@link Conference} by invoking this method. A conference call provided by this 918 * method will persist until {@link Conference#destroy} is invoked on the conference instance. 919 * 920 * @param conference The new conference object. 921 */ addConference(Conference conference)922 public final void addConference(Conference conference) { 923 Log.d(this, "addConference: conference=%s", conference); 924 925 String id = addConferenceInternal(conference); 926 if (id != null) { 927 List<String> connectionIds = new ArrayList<>(2); 928 for (Connection connection : conference.getConnections()) { 929 if (mIdByConnection.containsKey(connection)) { 930 connectionIds.add(mIdByConnection.get(connection)); 931 } 932 } 933 ParcelableConference parcelableConference = new ParcelableConference( 934 conference.getPhoneAccountHandle(), 935 conference.getState(), 936 conference.getConnectionCapabilities(), 937 connectionIds, 938 conference.getVideoProvider() == null ? 939 null : conference.getVideoProvider().getInterface(), 940 conference.getVideoState(), 941 conference.getConnectTimeMillis(), 942 conference.getStatusHints(), 943 conference.getExtras()); 944 945 mAdapter.addConferenceCall(id, parcelableConference); 946 mAdapter.setVideoProvider(id, conference.getVideoProvider()); 947 mAdapter.setVideoState(id, conference.getVideoState()); 948 949 // Go through any child calls and set the parent. 950 for (Connection connection : conference.getConnections()) { 951 String connectionId = mIdByConnection.get(connection); 952 if (connectionId != null) { 953 mAdapter.setIsConferenced(connectionId, id); 954 } 955 } 956 } 957 } 958 959 /** 960 * Adds a connection created by the {@link ConnectionService} and informs telecom of the new 961 * connection. 962 * 963 * @param phoneAccountHandle The phone account handle for the connection. 964 * @param connection The connection to add. 965 */ addExistingConnection(PhoneAccountHandle phoneAccountHandle, Connection connection)966 public final void addExistingConnection(PhoneAccountHandle phoneAccountHandle, 967 Connection connection) { 968 969 String id = addExistingConnectionInternal(connection); 970 if (id != null) { 971 List<String> emptyList = new ArrayList<>(0); 972 973 ParcelableConnection parcelableConnection = new ParcelableConnection( 974 phoneAccountHandle, 975 connection.getState(), 976 connection.getConnectionCapabilities(), 977 connection.getAddress(), 978 connection.getAddressPresentation(), 979 connection.getCallerDisplayName(), 980 connection.getCallerDisplayNamePresentation(), 981 connection.getVideoProvider() == null ? 982 null : connection.getVideoProvider().getInterface(), 983 connection.getVideoState(), 984 connection.isRingbackRequested(), 985 connection.getAudioModeIsVoip(), 986 connection.getConnectTimeMillis(), 987 connection.getStatusHints(), 988 connection.getDisconnectCause(), 989 emptyList, 990 connection.getExtras()); 991 mAdapter.addExistingConnection(id, parcelableConnection); 992 } 993 } 994 995 /** 996 * Returns all the active {@code Connection}s for which this {@code ConnectionService} 997 * has taken responsibility. 998 * 999 * @return A collection of {@code Connection}s created by this {@code ConnectionService}. 1000 */ getAllConnections()1001 public final Collection<Connection> getAllConnections() { 1002 return mConnectionById.values(); 1003 } 1004 1005 /** 1006 * Create a {@code Connection} given an incoming request. This is used to attach to existing 1007 * incoming calls. 1008 * 1009 * @param connectionManagerPhoneAccount See description at 1010 * {@link #onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)}. 1011 * @param request Details about the incoming call. 1012 * @return The {@code Connection} object to satisfy this call, or {@code null} to 1013 * not handle the call. 1014 */ onCreateIncomingConnection( PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request)1015 public Connection onCreateIncomingConnection( 1016 PhoneAccountHandle connectionManagerPhoneAccount, 1017 ConnectionRequest request) { 1018 return null; 1019 } 1020 1021 /** 1022 * Trigger recalculate functinality for conference calls. This is used when a Telephony 1023 * Connection is part of a conference controller but is not yet added to Connection 1024 * Service and hence cannot be added to the conference call. 1025 * 1026 * @hide 1027 */ triggerConferenceRecalculate()1028 public void triggerConferenceRecalculate() { 1029 } 1030 1031 /** 1032 * Create a {@code Connection} given an outgoing request. This is used to initiate new 1033 * outgoing calls. 1034 * 1035 * @param connectionManagerPhoneAccount The connection manager account to use for managing 1036 * this call. 1037 * <p> 1038 * If this parameter is not {@code null}, it means that this {@code ConnectionService} 1039 * has registered one or more {@code PhoneAccount}s having 1040 * {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER}. This parameter will contain 1041 * one of these {@code PhoneAccount}s, while the {@code request} will contain another 1042 * (usually but not always distinct) {@code PhoneAccount} to be used for actually 1043 * making the connection. 1044 * <p> 1045 * If this parameter is {@code null}, it means that this {@code ConnectionService} is 1046 * being asked to make a direct connection. The 1047 * {@link ConnectionRequest#getAccountHandle()} of parameter {@code request} will be 1048 * a {@code PhoneAccount} registered by this {@code ConnectionService} to use for 1049 * making the connection. 1050 * @param request Details about the outgoing call. 1051 * @return The {@code Connection} object to satisfy this call, or the result of an invocation 1052 * of {@link Connection#createFailedConnection(DisconnectCause)} to not handle the call. 1053 */ onCreateOutgoingConnection( PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request)1054 public Connection onCreateOutgoingConnection( 1055 PhoneAccountHandle connectionManagerPhoneAccount, 1056 ConnectionRequest request) { 1057 return null; 1058 } 1059 1060 /** 1061 * Create a {@code Connection} for a new unknown call. An unknown call is a call originating 1062 * from the ConnectionService that was neither a user-initiated outgoing call, nor an incoming 1063 * call created using 1064 * {@code TelecomManager#addNewIncomingCall(PhoneAccountHandle, android.os.Bundle)}. 1065 * 1066 * @param connectionManagerPhoneAccount 1067 * @param request 1068 * @return 1069 * 1070 * @hide 1071 */ onCreateUnknownConnection(PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request)1072 public Connection onCreateUnknownConnection(PhoneAccountHandle connectionManagerPhoneAccount, 1073 ConnectionRequest request) { 1074 return null; 1075 } 1076 1077 /** 1078 * Conference two specified connections. Invoked when the user has made a request to merge the 1079 * specified connections into a conference call. In response, the connection service should 1080 * create an instance of {@link Conference} and pass it into {@link #addConference}. 1081 * 1082 * @param connection1 A connection to merge into a conference call. 1083 * @param connection2 A connection to merge into a conference call. 1084 */ onConference(Connection connection1, Connection connection2)1085 public void onConference(Connection connection1, Connection connection2) {} 1086 1087 /** 1088 * Indicates that a remote conference has been created for existing {@link RemoteConnection}s. 1089 * When this method is invoked, this {@link ConnectionService} should create its own 1090 * representation of the conference call and send it to telecom using {@link #addConference}. 1091 * <p> 1092 * This is only relevant to {@link ConnectionService}s which are registered with 1093 * {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER}. 1094 * 1095 * @param conference The remote conference call. 1096 */ onRemoteConferenceAdded(RemoteConference conference)1097 public void onRemoteConferenceAdded(RemoteConference conference) {} 1098 1099 /** 1100 * Called when an existing connection is added remotely. 1101 * @param connection The existing connection which was added. 1102 */ onRemoteExistingConnectionAdded(RemoteConnection connection)1103 public void onRemoteExistingConnectionAdded(RemoteConnection connection) {} 1104 1105 /** 1106 * @hide 1107 */ containsConference(Conference conference)1108 public boolean containsConference(Conference conference) { 1109 return mIdByConference.containsKey(conference); 1110 } 1111 1112 /** {@hide} */ addRemoteConference(RemoteConference remoteConference)1113 void addRemoteConference(RemoteConference remoteConference) { 1114 onRemoteConferenceAdded(remoteConference); 1115 } 1116 1117 /** {@hide} */ addRemoteExistingConnection(RemoteConnection remoteConnection)1118 void addRemoteExistingConnection(RemoteConnection remoteConnection) { 1119 onRemoteExistingConnectionAdded(remoteConnection); 1120 } 1121 onAccountsInitialized()1122 private void onAccountsInitialized() { 1123 mAreAccountsInitialized = true; 1124 for (Runnable r : mPreInitializationConnectionRequests) { 1125 r.run(); 1126 } 1127 mPreInitializationConnectionRequests.clear(); 1128 } 1129 1130 /** 1131 * Adds an existing connection to the list of connections, identified by a new UUID. 1132 * 1133 * @param connection The connection. 1134 * @return The UUID of the connection (e.g. the call-id). 1135 */ addExistingConnectionInternal(Connection connection)1136 private String addExistingConnectionInternal(Connection connection) { 1137 String id = UUID.randomUUID().toString(); 1138 addConnection(id, connection); 1139 return id; 1140 } 1141 addConnection(String callId, Connection connection)1142 private void addConnection(String callId, Connection connection) { 1143 mConnectionById.put(callId, connection); 1144 mIdByConnection.put(connection, callId); 1145 connection.addConnectionListener(mConnectionListener); 1146 connection.setConnectionService(this); 1147 } 1148 1149 /** {@hide} */ removeConnection(Connection connection)1150 protected void removeConnection(Connection connection) { 1151 String id = mIdByConnection.get(connection); 1152 connection.unsetConnectionService(this); 1153 connection.removeConnectionListener(mConnectionListener); 1154 mConnectionById.remove(mIdByConnection.get(connection)); 1155 mIdByConnection.remove(connection); 1156 mAdapter.removeCall(id); 1157 } 1158 addConferenceInternal(Conference conference)1159 private String addConferenceInternal(Conference conference) { 1160 if (mIdByConference.containsKey(conference)) { 1161 Log.w(this, "Re-adding an existing conference: %s.", conference); 1162 } else if (conference != null) { 1163 String id = UUID.randomUUID().toString(); 1164 mConferenceById.put(id, conference); 1165 mIdByConference.put(conference, id); 1166 conference.addListener(mConferenceListener); 1167 return id; 1168 } 1169 1170 return null; 1171 } 1172 removeConference(Conference conference)1173 private void removeConference(Conference conference) { 1174 if (mIdByConference.containsKey(conference)) { 1175 conference.removeListener(mConferenceListener); 1176 1177 String id = mIdByConference.get(conference); 1178 mConferenceById.remove(id); 1179 mIdByConference.remove(conference); 1180 mAdapter.removeCall(id); 1181 } 1182 } 1183 findConnectionForAction(String callId, String action)1184 private Connection findConnectionForAction(String callId, String action) { 1185 if (mConnectionById.containsKey(callId)) { 1186 return mConnectionById.get(callId); 1187 } 1188 Log.w(this, "%s - Cannot find Connection %s", action, callId); 1189 return getNullConnection(); 1190 } 1191 getNullConnection()1192 static synchronized Connection getNullConnection() { 1193 if (sNullConnection == null) { 1194 sNullConnection = new Connection() {}; 1195 } 1196 return sNullConnection; 1197 } 1198 findConferenceForAction(String conferenceId, String action)1199 private Conference findConferenceForAction(String conferenceId, String action) { 1200 if (mConferenceById.containsKey(conferenceId)) { 1201 return mConferenceById.get(conferenceId); 1202 } 1203 Log.w(this, "%s - Cannot find conference %s", action, conferenceId); 1204 return getNullConference(); 1205 } 1206 createConnectionIdList(List<Connection> connections)1207 private List<String> createConnectionIdList(List<Connection> connections) { 1208 List<String> ids = new ArrayList<>(); 1209 for (Connection c : connections) { 1210 if (mIdByConnection.containsKey(c)) { 1211 ids.add(mIdByConnection.get(c)); 1212 } 1213 } 1214 Collections.sort(ids); 1215 return ids; 1216 } 1217 1218 /** 1219 * Builds a list of {@link Connection} and {@link Conference} IDs based on the list of 1220 * {@link Conferenceable}s passed in. 1221 * 1222 * @param conferenceables The {@link Conferenceable} connections and conferences. 1223 * @return List of string conference and call Ids. 1224 */ createIdList(List<Conferenceable> conferenceables)1225 private List<String> createIdList(List<Conferenceable> conferenceables) { 1226 List<String> ids = new ArrayList<>(); 1227 for (Conferenceable c : conferenceables) { 1228 // Only allow Connection and Conference conferenceables. 1229 if (c instanceof Connection) { 1230 Connection connection = (Connection) c; 1231 if (mIdByConnection.containsKey(connection)) { 1232 ids.add(mIdByConnection.get(connection)); 1233 } 1234 } else if (c instanceof Conference) { 1235 Conference conference = (Conference) c; 1236 if (mIdByConference.containsKey(conference)) { 1237 ids.add(mIdByConference.get(conference)); 1238 } 1239 } 1240 } 1241 Collections.sort(ids); 1242 return ids; 1243 } 1244 getNullConference()1245 private Conference getNullConference() { 1246 if (sNullConference == null) { 1247 sNullConference = new Conference(null) {}; 1248 } 1249 return sNullConference; 1250 } 1251 endAllConnections()1252 private void endAllConnections() { 1253 // Unbound from telecomm. We should end all connections and conferences. 1254 for (Connection connection : mIdByConnection.keySet()) { 1255 // only operate on top-level calls. Conference calls will be removed on their own. 1256 if (connection.getConference() == null) { 1257 connection.onDisconnect(); 1258 } 1259 } 1260 for (Conference conference : mIdByConference.keySet()) { 1261 conference.onDisconnect(); 1262 } 1263 } 1264 } 1265