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