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 } 660 abort(String callId)661 private void abort(String callId) { 662 Log.d(this, "abort %s", callId); 663 findConnectionForAction(callId, "abort").onAbort(); 664 } 665 answerVideo(String callId, int videoState)666 private void answerVideo(String callId, int videoState) { 667 Log.d(this, "answerVideo %s", callId); 668 findConnectionForAction(callId, "answer").onAnswer(videoState); 669 } 670 answer(String callId)671 private void answer(String callId) { 672 Log.d(this, "answer %s", callId); 673 findConnectionForAction(callId, "answer").onAnswer(); 674 } 675 reject(String callId)676 private void reject(String callId) { 677 Log.d(this, "reject %s", callId); 678 findConnectionForAction(callId, "reject").onReject(); 679 } 680 disconnect(String callId)681 private void disconnect(String callId) { 682 Log.d(this, "disconnect %s", callId); 683 if (mConnectionById.containsKey(callId)) { 684 findConnectionForAction(callId, "disconnect").onDisconnect(); 685 } else { 686 findConferenceForAction(callId, "disconnect").onDisconnect(); 687 } 688 } 689 hold(String callId)690 private void hold(String callId) { 691 Log.d(this, "hold %s", callId); 692 if (mConnectionById.containsKey(callId)) { 693 findConnectionForAction(callId, "hold").onHold(); 694 } else { 695 findConferenceForAction(callId, "hold").onHold(); 696 } 697 } 698 unhold(String callId)699 private void unhold(String callId) { 700 Log.d(this, "unhold %s", callId); 701 if (mConnectionById.containsKey(callId)) { 702 findConnectionForAction(callId, "unhold").onUnhold(); 703 } else { 704 findConferenceForAction(callId, "unhold").onUnhold(); 705 } 706 } 707 onCallAudioStateChanged(String callId, CallAudioState callAudioState)708 private void onCallAudioStateChanged(String callId, CallAudioState callAudioState) { 709 Log.d(this, "onAudioStateChanged %s %s", callId, callAudioState); 710 if (mConnectionById.containsKey(callId)) { 711 findConnectionForAction(callId, "onCallAudioStateChanged").setCallAudioState( 712 callAudioState); 713 } else { 714 findConferenceForAction(callId, "onCallAudioStateChanged").setCallAudioState( 715 callAudioState); 716 } 717 } 718 playDtmfTone(String callId, char digit)719 private void playDtmfTone(String callId, char digit) { 720 Log.d(this, "playDtmfTone %s %c", callId, digit); 721 if (mConnectionById.containsKey(callId)) { 722 findConnectionForAction(callId, "playDtmfTone").onPlayDtmfTone(digit); 723 } else { 724 findConferenceForAction(callId, "playDtmfTone").onPlayDtmfTone(digit); 725 } 726 } 727 stopDtmfTone(String callId)728 private void stopDtmfTone(String callId) { 729 Log.d(this, "stopDtmfTone %s", callId); 730 if (mConnectionById.containsKey(callId)) { 731 findConnectionForAction(callId, "stopDtmfTone").onStopDtmfTone(); 732 } else { 733 findConferenceForAction(callId, "stopDtmfTone").onStopDtmfTone(); 734 } 735 } 736 conference(String callId1, String callId2)737 private void conference(String callId1, String callId2) { 738 Log.d(this, "conference %s, %s", callId1, callId2); 739 740 // Attempt to get second connection or conference. 741 Connection connection2 = findConnectionForAction(callId2, "conference"); 742 Conference conference2 = getNullConference(); 743 if (connection2 == getNullConnection()) { 744 conference2 = findConferenceForAction(callId2, "conference"); 745 if (conference2 == getNullConference()) { 746 Log.w(this, "Connection2 or Conference2 missing in conference request %s.", 747 callId2); 748 return; 749 } 750 } 751 752 // Attempt to get first connection or conference and perform merge. 753 Connection connection1 = findConnectionForAction(callId1, "conference"); 754 if (connection1 == getNullConnection()) { 755 Conference conference1 = findConferenceForAction(callId1, "addConnection"); 756 if (conference1 == getNullConference()) { 757 Log.w(this, 758 "Connection1 or Conference1 missing in conference request %s.", 759 callId1); 760 } else { 761 // Call 1 is a conference. 762 if (connection2 != getNullConnection()) { 763 // Call 2 is a connection so merge via call 1 (conference). 764 conference1.onMerge(connection2); 765 } else { 766 // Call 2 is ALSO a conference; this should never happen. 767 Log.wtf(this, "There can only be one conference and an attempt was made to " + 768 "merge two conferences."); 769 return; 770 } 771 } 772 } else { 773 // Call 1 is a connection. 774 if (conference2 != getNullConference()) { 775 // Call 2 is a conference, so merge via call 2. 776 conference2.onMerge(connection1); 777 } else { 778 // Call 2 is a connection, so merge together. 779 onConference(connection1, connection2); 780 } 781 } 782 } 783 splitFromConference(String callId)784 private void splitFromConference(String callId) { 785 Log.d(this, "splitFromConference(%s)", callId); 786 787 Connection connection = findConnectionForAction(callId, "splitFromConference"); 788 if (connection == getNullConnection()) { 789 Log.w(this, "Connection missing in conference request %s.", callId); 790 return; 791 } 792 793 Conference conference = connection.getConference(); 794 if (conference != null) { 795 conference.onSeparate(connection); 796 } 797 } 798 mergeConference(String callId)799 private void mergeConference(String callId) { 800 Log.d(this, "mergeConference(%s)", callId); 801 Conference conference = findConferenceForAction(callId, "mergeConference"); 802 if (conference != null) { 803 conference.onMerge(); 804 } 805 } 806 swapConference(String callId)807 private void swapConference(String callId) { 808 Log.d(this, "swapConference(%s)", callId); 809 Conference conference = findConferenceForAction(callId, "swapConference"); 810 if (conference != null) { 811 conference.onSwap(); 812 } 813 } 814 onPostDialContinue(String callId, boolean proceed)815 private void onPostDialContinue(String callId, boolean proceed) { 816 Log.d(this, "onPostDialContinue(%s)", callId); 817 findConnectionForAction(callId, "stopDtmfTone").onPostDialContinue(proceed); 818 } 819 onAdapterAttached()820 private void onAdapterAttached() { 821 if (mAreAccountsInitialized) { 822 // No need to query again if we already did it. 823 return; 824 } 825 826 mAdapter.queryRemoteConnectionServices(new RemoteServiceCallback.Stub() { 827 @Override 828 public void onResult( 829 final List<ComponentName> componentNames, 830 final List<IBinder> services) { 831 mHandler.post(new Runnable() { 832 @Override 833 public void run() { 834 for (int i = 0; i < componentNames.size() && i < services.size(); i++) { 835 mRemoteConnectionManager.addConnectionService( 836 componentNames.get(i), 837 IConnectionService.Stub.asInterface(services.get(i))); 838 } 839 onAccountsInitialized(); 840 Log.d(this, "remote connection services found: " + services); 841 } 842 }); 843 } 844 845 @Override 846 public void onError() { 847 mHandler.post(new Runnable() { 848 @Override 849 public void run() { 850 mAreAccountsInitialized = true; 851 } 852 }); 853 } 854 }); 855 } 856 857 /** 858 * Ask some other {@code ConnectionService} to create a {@code RemoteConnection} given an 859 * incoming request. This is used by {@code ConnectionService}s that are registered with 860 * {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER} and want to be able to manage 861 * SIM-based incoming calls. 862 * 863 * @param connectionManagerPhoneAccount See description at 864 * {@link #onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)}. 865 * @param request Details about the incoming call. 866 * @return The {@code Connection} object to satisfy this call, or {@code null} to 867 * not handle the call. 868 */ createRemoteIncomingConnection( PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request)869 public final RemoteConnection createRemoteIncomingConnection( 870 PhoneAccountHandle connectionManagerPhoneAccount, 871 ConnectionRequest request) { 872 return mRemoteConnectionManager.createRemoteConnection( 873 connectionManagerPhoneAccount, request, true); 874 } 875 876 /** 877 * Ask some other {@code ConnectionService} to create a {@code RemoteConnection} given an 878 * outgoing request. This is used by {@code ConnectionService}s that are registered with 879 * {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER} and want to be able to use the 880 * SIM-based {@code ConnectionService} to place its outgoing calls. 881 * 882 * @param connectionManagerPhoneAccount See description at 883 * {@link #onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)}. 884 * @param request Details about the incoming call. 885 * @return The {@code Connection} object to satisfy this call, or {@code null} to 886 * not handle the call. 887 */ createRemoteOutgoingConnection( PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request)888 public final RemoteConnection createRemoteOutgoingConnection( 889 PhoneAccountHandle connectionManagerPhoneAccount, 890 ConnectionRequest request) { 891 return mRemoteConnectionManager.createRemoteConnection( 892 connectionManagerPhoneAccount, request, false); 893 } 894 895 /** 896 * Indicates to the relevant {@code RemoteConnectionService} that the specified 897 * {@link RemoteConnection}s should be merged into a conference call. 898 * <p> 899 * If the conference request is successful, the method {@link #onRemoteConferenceAdded} will 900 * be invoked. 901 * 902 * @param remoteConnection1 The first of the remote connections to conference. 903 * @param remoteConnection2 The second of the remote connections to conference. 904 */ conferenceRemoteConnections( RemoteConnection remoteConnection1, RemoteConnection remoteConnection2)905 public final void conferenceRemoteConnections( 906 RemoteConnection remoteConnection1, 907 RemoteConnection remoteConnection2) { 908 mRemoteConnectionManager.conferenceRemoteConnections(remoteConnection1, remoteConnection2); 909 } 910 911 /** 912 * Adds a new conference call. When a conference call is created either as a result of an 913 * explicit request via {@link #onConference} or otherwise, the connection service should supply 914 * an instance of {@link Conference} by invoking this method. A conference call provided by this 915 * method will persist until {@link Conference#destroy} is invoked on the conference instance. 916 * 917 * @param conference The new conference object. 918 */ addConference(Conference conference)919 public final void addConference(Conference conference) { 920 Log.d(this, "addConference: conference=%s", conference); 921 922 String id = addConferenceInternal(conference); 923 if (id != null) { 924 List<String> connectionIds = new ArrayList<>(2); 925 for (Connection connection : conference.getConnections()) { 926 if (mIdByConnection.containsKey(connection)) { 927 connectionIds.add(mIdByConnection.get(connection)); 928 } 929 } 930 ParcelableConference parcelableConference = new ParcelableConference( 931 conference.getPhoneAccountHandle(), 932 conference.getState(), 933 conference.getConnectionCapabilities(), 934 connectionIds, 935 conference.getVideoProvider() == null ? 936 null : conference.getVideoProvider().getInterface(), 937 conference.getVideoState(), 938 conference.getConnectTimeMillis(), 939 conference.getStatusHints(), 940 conference.getExtras()); 941 942 mAdapter.addConferenceCall(id, parcelableConference); 943 mAdapter.setVideoProvider(id, conference.getVideoProvider()); 944 mAdapter.setVideoState(id, conference.getVideoState()); 945 946 // Go through any child calls and set the parent. 947 for (Connection connection : conference.getConnections()) { 948 String connectionId = mIdByConnection.get(connection); 949 if (connectionId != null) { 950 mAdapter.setIsConferenced(connectionId, id); 951 } 952 } 953 } 954 } 955 956 /** 957 * Adds a connection created by the {@link ConnectionService} and informs telecom of the new 958 * connection. 959 * 960 * @param phoneAccountHandle The phone account handle for the connection. 961 * @param connection The connection to add. 962 */ addExistingConnection(PhoneAccountHandle phoneAccountHandle, Connection connection)963 public final void addExistingConnection(PhoneAccountHandle phoneAccountHandle, 964 Connection connection) { 965 966 String id = addExistingConnectionInternal(connection); 967 if (id != null) { 968 List<String> emptyList = new ArrayList<>(0); 969 970 ParcelableConnection parcelableConnection = new ParcelableConnection( 971 phoneAccountHandle, 972 connection.getState(), 973 connection.getConnectionCapabilities(), 974 connection.getAddress(), 975 connection.getAddressPresentation(), 976 connection.getCallerDisplayName(), 977 connection.getCallerDisplayNamePresentation(), 978 connection.getVideoProvider() == null ? 979 null : connection.getVideoProvider().getInterface(), 980 connection.getVideoState(), 981 connection.isRingbackRequested(), 982 connection.getAudioModeIsVoip(), 983 connection.getConnectTimeMillis(), 984 connection.getStatusHints(), 985 connection.getDisconnectCause(), 986 emptyList, 987 connection.getExtras()); 988 mAdapter.addExistingConnection(id, parcelableConnection); 989 } 990 } 991 992 /** 993 * Returns all the active {@code Connection}s for which this {@code ConnectionService} 994 * has taken responsibility. 995 * 996 * @return A collection of {@code Connection}s created by this {@code ConnectionService}. 997 */ getAllConnections()998 public final Collection<Connection> getAllConnections() { 999 return mConnectionById.values(); 1000 } 1001 1002 /** 1003 * Create a {@code Connection} given an incoming request. This is used to attach to existing 1004 * incoming calls. 1005 * 1006 * @param connectionManagerPhoneAccount See description at 1007 * {@link #onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)}. 1008 * @param request Details about the incoming call. 1009 * @return The {@code Connection} object to satisfy this call, or {@code null} to 1010 * not handle the call. 1011 */ onCreateIncomingConnection( PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request)1012 public Connection onCreateIncomingConnection( 1013 PhoneAccountHandle connectionManagerPhoneAccount, 1014 ConnectionRequest request) { 1015 return null; 1016 } 1017 1018 /** 1019 * Create a {@code Connection} given an outgoing request. This is used to initiate new 1020 * outgoing calls. 1021 * 1022 * @param connectionManagerPhoneAccount The connection manager account to use for managing 1023 * this call. 1024 * <p> 1025 * If this parameter is not {@code null}, it means that this {@code ConnectionService} 1026 * has registered one or more {@code PhoneAccount}s having 1027 * {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER}. This parameter will contain 1028 * one of these {@code PhoneAccount}s, while the {@code request} will contain another 1029 * (usually but not always distinct) {@code PhoneAccount} to be used for actually 1030 * making the connection. 1031 * <p> 1032 * If this parameter is {@code null}, it means that this {@code ConnectionService} is 1033 * being asked to make a direct connection. The 1034 * {@link ConnectionRequest#getAccountHandle()} of parameter {@code request} will be 1035 * a {@code PhoneAccount} registered by this {@code ConnectionService} to use for 1036 * making the connection. 1037 * @param request Details about the outgoing call. 1038 * @return The {@code Connection} object to satisfy this call, or the result of an invocation 1039 * of {@link Connection#createFailedConnection(DisconnectCause)} to not handle the call. 1040 */ onCreateOutgoingConnection( PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request)1041 public Connection onCreateOutgoingConnection( 1042 PhoneAccountHandle connectionManagerPhoneAccount, 1043 ConnectionRequest request) { 1044 return null; 1045 } 1046 1047 /** 1048 * Create a {@code Connection} for a new unknown call. An unknown call is a call originating 1049 * from the ConnectionService that was neither a user-initiated outgoing call, nor an incoming 1050 * call created using 1051 * {@code TelecomManager#addNewIncomingCall(PhoneAccountHandle, android.os.Bundle)}. 1052 * 1053 * @param connectionManagerPhoneAccount 1054 * @param request 1055 * @return 1056 * 1057 * @hide 1058 */ onCreateUnknownConnection(PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request)1059 public Connection onCreateUnknownConnection(PhoneAccountHandle connectionManagerPhoneAccount, 1060 ConnectionRequest request) { 1061 return null; 1062 } 1063 1064 /** 1065 * Conference two specified connections. Invoked when the user has made a request to merge the 1066 * specified connections into a conference call. In response, the connection service should 1067 * create an instance of {@link Conference} and pass it into {@link #addConference}. 1068 * 1069 * @param connection1 A connection to merge into a conference call. 1070 * @param connection2 A connection to merge into a conference call. 1071 */ onConference(Connection connection1, Connection connection2)1072 public void onConference(Connection connection1, Connection connection2) {} 1073 1074 /** 1075 * Indicates that a remote conference has been created for existing {@link RemoteConnection}s. 1076 * When this method is invoked, this {@link ConnectionService} should create its own 1077 * representation of the conference call and send it to telecom using {@link #addConference}. 1078 * <p> 1079 * This is only relevant to {@link ConnectionService}s which are registered with 1080 * {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER}. 1081 * 1082 * @param conference The remote conference call. 1083 */ onRemoteConferenceAdded(RemoteConference conference)1084 public void onRemoteConferenceAdded(RemoteConference conference) {} 1085 1086 /** 1087 * Called when an existing connection is added remotely. 1088 * @param connection The existing connection which was added. 1089 */ onRemoteExistingConnectionAdded(RemoteConnection connection)1090 public void onRemoteExistingConnectionAdded(RemoteConnection connection) {} 1091 1092 /** 1093 * @hide 1094 */ containsConference(Conference conference)1095 public boolean containsConference(Conference conference) { 1096 return mIdByConference.containsKey(conference); 1097 } 1098 1099 /** {@hide} */ addRemoteConference(RemoteConference remoteConference)1100 void addRemoteConference(RemoteConference remoteConference) { 1101 onRemoteConferenceAdded(remoteConference); 1102 } 1103 1104 /** {@hide} */ addRemoteExistingConnection(RemoteConnection remoteConnection)1105 void addRemoteExistingConnection(RemoteConnection remoteConnection) { 1106 onRemoteExistingConnectionAdded(remoteConnection); 1107 } 1108 onAccountsInitialized()1109 private void onAccountsInitialized() { 1110 mAreAccountsInitialized = true; 1111 for (Runnable r : mPreInitializationConnectionRequests) { 1112 r.run(); 1113 } 1114 mPreInitializationConnectionRequests.clear(); 1115 } 1116 1117 /** 1118 * Adds an existing connection to the list of connections, identified by a new UUID. 1119 * 1120 * @param connection The connection. 1121 * @return The UUID of the connection (e.g. the call-id). 1122 */ addExistingConnectionInternal(Connection connection)1123 private String addExistingConnectionInternal(Connection connection) { 1124 String id = UUID.randomUUID().toString(); 1125 addConnection(id, connection); 1126 return id; 1127 } 1128 addConnection(String callId, Connection connection)1129 private void addConnection(String callId, Connection connection) { 1130 mConnectionById.put(callId, connection); 1131 mIdByConnection.put(connection, callId); 1132 connection.addConnectionListener(mConnectionListener); 1133 connection.setConnectionService(this); 1134 } 1135 1136 /** {@hide} */ removeConnection(Connection connection)1137 protected void removeConnection(Connection connection) { 1138 String id = mIdByConnection.get(connection); 1139 connection.unsetConnectionService(this); 1140 connection.removeConnectionListener(mConnectionListener); 1141 mConnectionById.remove(mIdByConnection.get(connection)); 1142 mIdByConnection.remove(connection); 1143 mAdapter.removeCall(id); 1144 } 1145 addConferenceInternal(Conference conference)1146 private String addConferenceInternal(Conference conference) { 1147 if (mIdByConference.containsKey(conference)) { 1148 Log.w(this, "Re-adding an existing conference: %s.", conference); 1149 } else if (conference != null) { 1150 String id = UUID.randomUUID().toString(); 1151 mConferenceById.put(id, conference); 1152 mIdByConference.put(conference, id); 1153 conference.addListener(mConferenceListener); 1154 return id; 1155 } 1156 1157 return null; 1158 } 1159 removeConference(Conference conference)1160 private void removeConference(Conference conference) { 1161 if (mIdByConference.containsKey(conference)) { 1162 conference.removeListener(mConferenceListener); 1163 1164 String id = mIdByConference.get(conference); 1165 mConferenceById.remove(id); 1166 mIdByConference.remove(conference); 1167 mAdapter.removeCall(id); 1168 } 1169 } 1170 findConnectionForAction(String callId, String action)1171 private Connection findConnectionForAction(String callId, String action) { 1172 if (mConnectionById.containsKey(callId)) { 1173 return mConnectionById.get(callId); 1174 } 1175 Log.w(this, "%s - Cannot find Connection %s", action, callId); 1176 return getNullConnection(); 1177 } 1178 getNullConnection()1179 static synchronized Connection getNullConnection() { 1180 if (sNullConnection == null) { 1181 sNullConnection = new Connection() {}; 1182 } 1183 return sNullConnection; 1184 } 1185 findConferenceForAction(String conferenceId, String action)1186 private Conference findConferenceForAction(String conferenceId, String action) { 1187 if (mConferenceById.containsKey(conferenceId)) { 1188 return mConferenceById.get(conferenceId); 1189 } 1190 Log.w(this, "%s - Cannot find conference %s", action, conferenceId); 1191 return getNullConference(); 1192 } 1193 createConnectionIdList(List<Connection> connections)1194 private List<String> createConnectionIdList(List<Connection> connections) { 1195 List<String> ids = new ArrayList<>(); 1196 for (Connection c : connections) { 1197 if (mIdByConnection.containsKey(c)) { 1198 ids.add(mIdByConnection.get(c)); 1199 } 1200 } 1201 Collections.sort(ids); 1202 return ids; 1203 } 1204 1205 /** 1206 * Builds a list of {@link Connection} and {@link Conference} IDs based on the list of 1207 * {@link Conferenceable}s passed in. 1208 * 1209 * @param conferenceables The {@link Conferenceable} connections and conferences. 1210 * @return List of string conference and call Ids. 1211 */ createIdList(List<Conferenceable> conferenceables)1212 private List<String> createIdList(List<Conferenceable> conferenceables) { 1213 List<String> ids = new ArrayList<>(); 1214 for (Conferenceable c : conferenceables) { 1215 // Only allow Connection and Conference conferenceables. 1216 if (c instanceof Connection) { 1217 Connection connection = (Connection) c; 1218 if (mIdByConnection.containsKey(connection)) { 1219 ids.add(mIdByConnection.get(connection)); 1220 } 1221 } else if (c instanceof Conference) { 1222 Conference conference = (Conference) c; 1223 if (mIdByConference.containsKey(conference)) { 1224 ids.add(mIdByConference.get(conference)); 1225 } 1226 } 1227 } 1228 Collections.sort(ids); 1229 return ids; 1230 } 1231 getNullConference()1232 private Conference getNullConference() { 1233 if (sNullConference == null) { 1234 sNullConference = new Conference(null) {}; 1235 } 1236 return sNullConference; 1237 } 1238 endAllConnections()1239 private void endAllConnections() { 1240 // Unbound from telecomm. We should end all connections and conferences. 1241 for (Connection connection : mIdByConnection.keySet()) { 1242 // only operate on top-level calls. Conference calls will be removed on their own. 1243 if (connection.getConference() == null) { 1244 connection.onDisconnect(); 1245 } 1246 } 1247 for (Conference conference : mIdByConference.keySet()) { 1248 conference.onDisconnect(); 1249 } 1250 } 1251 } 1252