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