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_PULLING_CALL: 560 mAdapter.setPulling(id); 561 break; 562 case Connection.STATE_DISCONNECTED: 563 // Handled in onDisconnected() 564 break; 565 case Connection.STATE_HOLDING: 566 mAdapter.setOnHold(id); 567 break; 568 case Connection.STATE_NEW: 569 // Nothing to tell Telecom 570 break; 571 case Connection.STATE_RINGING: 572 mAdapter.setRinging(id); 573 break; 574 } 575 } 576 577 @Override 578 public void onDisconnected(Connection c, DisconnectCause disconnectCause) { 579 String id = mIdByConnection.get(c); 580 Log.d(this, "Adapter set disconnected %s", disconnectCause); 581 mAdapter.setDisconnected(id, disconnectCause); 582 } 583 584 @Override 585 public void onVideoStateChanged(Connection c, int videoState) { 586 String id = mIdByConnection.get(c); 587 Log.d(this, "Adapter set video state %d", videoState); 588 mAdapter.setVideoState(id, videoState); 589 } 590 591 @Override 592 public void onAddressChanged(Connection c, Uri address, int presentation) { 593 String id = mIdByConnection.get(c); 594 mAdapter.setAddress(id, address, presentation); 595 } 596 597 @Override 598 public void onCallerDisplayNameChanged( 599 Connection c, String callerDisplayName, int presentation) { 600 String id = mIdByConnection.get(c); 601 mAdapter.setCallerDisplayName(id, callerDisplayName, presentation); 602 } 603 604 @Override 605 public void onDestroyed(Connection c) { 606 removeConnection(c); 607 } 608 609 @Override 610 public void onPostDialWait(Connection c, String remaining) { 611 String id = mIdByConnection.get(c); 612 Log.d(this, "Adapter onPostDialWait %s, %s", c, remaining); 613 mAdapter.onPostDialWait(id, remaining); 614 } 615 616 @Override 617 public void onPostDialChar(Connection c, char nextChar) { 618 String id = mIdByConnection.get(c); 619 Log.d(this, "Adapter onPostDialChar %s, %s", c, nextChar); 620 mAdapter.onPostDialChar(id, nextChar); 621 } 622 623 @Override 624 public void onRingbackRequested(Connection c, boolean ringback) { 625 String id = mIdByConnection.get(c); 626 Log.d(this, "Adapter onRingback %b", ringback); 627 mAdapter.setRingbackRequested(id, ringback); 628 } 629 630 @Override 631 public void onConnectionCapabilitiesChanged(Connection c, int capabilities) { 632 String id = mIdByConnection.get(c); 633 Log.d(this, "capabilities: parcelableconnection: %s", 634 Connection.capabilitiesToString(capabilities)); 635 mAdapter.setConnectionCapabilities(id, capabilities); 636 } 637 638 @Override 639 public void onConnectionPropertiesChanged(Connection c, int properties) { 640 String id = mIdByConnection.get(c); 641 Log.d(this, "properties: parcelableconnection: %s", 642 Connection.propertiesToString(properties)); 643 mAdapter.setConnectionProperties(id, properties); 644 } 645 646 @Override 647 public void onVideoProviderChanged(Connection c, Connection.VideoProvider videoProvider) { 648 String id = mIdByConnection.get(c); 649 Log.d(this, "onVideoProviderChanged: Connection: %s, VideoProvider: %s", c, 650 videoProvider); 651 mAdapter.setVideoProvider(id, videoProvider); 652 } 653 654 @Override 655 public void onAudioModeIsVoipChanged(Connection c, boolean isVoip) { 656 String id = mIdByConnection.get(c); 657 mAdapter.setIsVoipAudioMode(id, isVoip); 658 } 659 660 @Override 661 public void onStatusHintsChanged(Connection c, StatusHints statusHints) { 662 String id = mIdByConnection.get(c); 663 mAdapter.setStatusHints(id, statusHints); 664 } 665 666 @Override 667 public void onConferenceablesChanged( 668 Connection connection, List<Conferenceable> conferenceables) { 669 mAdapter.setConferenceableConnections( 670 mIdByConnection.get(connection), 671 createIdList(conferenceables)); 672 } 673 674 @Override 675 public void onConferenceChanged(Connection connection, Conference conference) { 676 String id = mIdByConnection.get(connection); 677 if (id != null) { 678 String conferenceId = null; 679 if (conference != null) { 680 conferenceId = mIdByConference.get(conference); 681 } 682 mAdapter.setIsConferenced(id, conferenceId); 683 } 684 } 685 686 @Override 687 public void onConferenceMergeFailed(Connection connection) { 688 String id = mIdByConnection.get(connection); 689 if (id != null) { 690 mAdapter.onConferenceMergeFailed(id); 691 } 692 } 693 694 @Override 695 public void onExtrasChanged(Connection c, Bundle extras) { 696 String id = mIdByConnection.get(c); 697 if (id != null) { 698 mAdapter.putExtras(id, extras); 699 } 700 } 701 702 public void onExtrasRemoved(Connection c, List<String> keys) { 703 String id = mIdByConnection.get(c); 704 if (id != null) { 705 mAdapter.removeExtras(id, keys); 706 } 707 } 708 709 710 @Override 711 public void onConnectionEvent(Connection connection, String event, Bundle extras) { 712 String id = mIdByConnection.get(connection); 713 if (id != null) { 714 mAdapter.onConnectionEvent(id, event, extras); 715 } 716 } 717 }; 718 719 /** {@inheritDoc} */ 720 @Override onBind(Intent intent)721 public final IBinder onBind(Intent intent) { 722 return mBinder; 723 } 724 725 /** {@inheritDoc} */ 726 @Override onUnbind(Intent intent)727 public boolean onUnbind(Intent intent) { 728 endAllConnections(); 729 return super.onUnbind(intent); 730 } 731 732 /** 733 * This can be used by telecom to either create a new outgoing call or attach to an existing 734 * incoming call. In either case, telecom will cycle through a set of services and call 735 * createConnection util a connection service cancels the process or completes it successfully. 736 */ createConnection( final PhoneAccountHandle callManagerAccount, final String callId, final ConnectionRequest request, boolean isIncoming, boolean isUnknown)737 private void createConnection( 738 final PhoneAccountHandle callManagerAccount, 739 final String callId, 740 final ConnectionRequest request, 741 boolean isIncoming, 742 boolean isUnknown) { 743 Log.d(this, "createConnection, callManagerAccount: %s, callId: %s, request: %s, " + 744 "isIncoming: %b, isUnknown: %b", callManagerAccount, callId, request, 745 isIncoming, 746 isUnknown); 747 748 Connection connection = isUnknown ? onCreateUnknownConnection(callManagerAccount, request) 749 : isIncoming ? onCreateIncomingConnection(callManagerAccount, request) 750 : onCreateOutgoingConnection(callManagerAccount, request); 751 Log.d(this, "createConnection, connection: %s", connection); 752 if (connection == null) { 753 connection = Connection.createFailedConnection( 754 new DisconnectCause(DisconnectCause.ERROR)); 755 } 756 757 connection.setTelecomCallId(callId); 758 if (connection.getState() != Connection.STATE_DISCONNECTED) { 759 addConnection(callId, connection); 760 } 761 762 Uri address = connection.getAddress(); 763 String number = address == null ? "null" : address.getSchemeSpecificPart(); 764 Log.v(this, "createConnection, number: %s, state: %s, capabilities: %s, properties: %s", 765 Connection.toLogSafePhoneNumber(number), 766 Connection.stateToString(connection.getState()), 767 Connection.capabilitiesToString(connection.getConnectionCapabilities()), 768 Connection.propertiesToString(connection.getConnectionProperties())); 769 770 Log.d(this, "createConnection, calling handleCreateConnectionSuccessful %s", callId); 771 mAdapter.handleCreateConnectionComplete( 772 callId, 773 request, 774 new ParcelableConnection( 775 request.getAccountHandle(), 776 connection.getState(), 777 connection.getConnectionCapabilities(), 778 connection.getConnectionProperties(), 779 connection.getAddress(), 780 connection.getAddressPresentation(), 781 connection.getCallerDisplayName(), 782 connection.getCallerDisplayNamePresentation(), 783 connection.getVideoProvider() == null ? 784 null : connection.getVideoProvider().getInterface(), 785 connection.getVideoState(), 786 connection.isRingbackRequested(), 787 connection.getAudioModeIsVoip(), 788 connection.getConnectTimeMillis(), 789 connection.getStatusHints(), 790 connection.getDisconnectCause(), 791 createIdList(connection.getConferenceables()), 792 connection.getExtras())); 793 if (isUnknown) { 794 triggerConferenceRecalculate(); 795 } 796 } 797 abort(String callId)798 private void abort(String callId) { 799 Log.d(this, "abort %s", callId); 800 findConnectionForAction(callId, "abort").onAbort(); 801 } 802 answerVideo(String callId, int videoState)803 private void answerVideo(String callId, int videoState) { 804 Log.d(this, "answerVideo %s", callId); 805 findConnectionForAction(callId, "answer").onAnswer(videoState); 806 } 807 answer(String callId)808 private void answer(String callId) { 809 Log.d(this, "answer %s", callId); 810 findConnectionForAction(callId, "answer").onAnswer(); 811 } 812 reject(String callId)813 private void reject(String callId) { 814 Log.d(this, "reject %s", callId); 815 findConnectionForAction(callId, "reject").onReject(); 816 } 817 reject(String callId, String rejectWithMessage)818 private void reject(String callId, String rejectWithMessage) { 819 Log.d(this, "reject %s with message", callId); 820 findConnectionForAction(callId, "reject").onReject(rejectWithMessage); 821 } 822 silence(String callId)823 private void silence(String callId) { 824 Log.d(this, "silence %s", callId); 825 findConnectionForAction(callId, "silence").onSilence(); 826 } 827 disconnect(String callId)828 private void disconnect(String callId) { 829 Log.d(this, "disconnect %s", callId); 830 if (mConnectionById.containsKey(callId)) { 831 findConnectionForAction(callId, "disconnect").onDisconnect(); 832 } else { 833 findConferenceForAction(callId, "disconnect").onDisconnect(); 834 } 835 } 836 hold(String callId)837 private void hold(String callId) { 838 Log.d(this, "hold %s", callId); 839 if (mConnectionById.containsKey(callId)) { 840 findConnectionForAction(callId, "hold").onHold(); 841 } else { 842 findConferenceForAction(callId, "hold").onHold(); 843 } 844 } 845 unhold(String callId)846 private void unhold(String callId) { 847 Log.d(this, "unhold %s", callId); 848 if (mConnectionById.containsKey(callId)) { 849 findConnectionForAction(callId, "unhold").onUnhold(); 850 } else { 851 findConferenceForAction(callId, "unhold").onUnhold(); 852 } 853 } 854 onCallAudioStateChanged(String callId, CallAudioState callAudioState)855 private void onCallAudioStateChanged(String callId, CallAudioState callAudioState) { 856 Log.d(this, "onAudioStateChanged %s %s", callId, callAudioState); 857 if (mConnectionById.containsKey(callId)) { 858 findConnectionForAction(callId, "onCallAudioStateChanged").setCallAudioState( 859 callAudioState); 860 } else { 861 findConferenceForAction(callId, "onCallAudioStateChanged").setCallAudioState( 862 callAudioState); 863 } 864 } 865 playDtmfTone(String callId, char digit)866 private void playDtmfTone(String callId, char digit) { 867 Log.d(this, "playDtmfTone %s %c", callId, digit); 868 if (mConnectionById.containsKey(callId)) { 869 findConnectionForAction(callId, "playDtmfTone").onPlayDtmfTone(digit); 870 } else { 871 findConferenceForAction(callId, "playDtmfTone").onPlayDtmfTone(digit); 872 } 873 } 874 stopDtmfTone(String callId)875 private void stopDtmfTone(String callId) { 876 Log.d(this, "stopDtmfTone %s", callId); 877 if (mConnectionById.containsKey(callId)) { 878 findConnectionForAction(callId, "stopDtmfTone").onStopDtmfTone(); 879 } else { 880 findConferenceForAction(callId, "stopDtmfTone").onStopDtmfTone(); 881 } 882 } 883 conference(String callId1, String callId2)884 private void conference(String callId1, String callId2) { 885 Log.d(this, "conference %s, %s", callId1, callId2); 886 887 // Attempt to get second connection or conference. 888 Connection connection2 = findConnectionForAction(callId2, "conference"); 889 Conference conference2 = getNullConference(); 890 if (connection2 == getNullConnection()) { 891 conference2 = findConferenceForAction(callId2, "conference"); 892 if (conference2 == getNullConference()) { 893 Log.w(this, "Connection2 or Conference2 missing in conference request %s.", 894 callId2); 895 return; 896 } 897 } 898 899 // Attempt to get first connection or conference and perform merge. 900 Connection connection1 = findConnectionForAction(callId1, "conference"); 901 if (connection1 == getNullConnection()) { 902 Conference conference1 = findConferenceForAction(callId1, "addConnection"); 903 if (conference1 == getNullConference()) { 904 Log.w(this, 905 "Connection1 or Conference1 missing in conference request %s.", 906 callId1); 907 } else { 908 // Call 1 is a conference. 909 if (connection2 != getNullConnection()) { 910 // Call 2 is a connection so merge via call 1 (conference). 911 conference1.onMerge(connection2); 912 } else { 913 // Call 2 is ALSO a conference; this should never happen. 914 Log.wtf(this, "There can only be one conference and an attempt was made to " + 915 "merge two conferences."); 916 return; 917 } 918 } 919 } else { 920 // Call 1 is a connection. 921 if (conference2 != getNullConference()) { 922 // Call 2 is a conference, so merge via call 2. 923 conference2.onMerge(connection1); 924 } else { 925 // Call 2 is a connection, so merge together. 926 onConference(connection1, connection2); 927 } 928 } 929 } 930 splitFromConference(String callId)931 private void splitFromConference(String callId) { 932 Log.d(this, "splitFromConference(%s)", callId); 933 934 Connection connection = findConnectionForAction(callId, "splitFromConference"); 935 if (connection == getNullConnection()) { 936 Log.w(this, "Connection missing in conference request %s.", callId); 937 return; 938 } 939 940 Conference conference = connection.getConference(); 941 if (conference != null) { 942 conference.onSeparate(connection); 943 } 944 } 945 mergeConference(String callId)946 private void mergeConference(String callId) { 947 Log.d(this, "mergeConference(%s)", callId); 948 Conference conference = findConferenceForAction(callId, "mergeConference"); 949 if (conference != null) { 950 conference.onMerge(); 951 } 952 } 953 swapConference(String callId)954 private void swapConference(String callId) { 955 Log.d(this, "swapConference(%s)", callId); 956 Conference conference = findConferenceForAction(callId, "swapConference"); 957 if (conference != null) { 958 conference.onSwap(); 959 } 960 } 961 962 /** 963 * Notifies a {@link Connection} of a request to pull an external call. 964 * 965 * See {@link Call#pullExternalCall()}. 966 * 967 * @param callId The ID of the call to pull. 968 */ pullExternalCall(String callId)969 private void pullExternalCall(String callId) { 970 Log.d(this, "pullExternalCall(%s)", callId); 971 Connection connection = findConnectionForAction(callId, "pullExternalCall"); 972 if (connection != null) { 973 connection.onPullExternalCall(); 974 } 975 } 976 977 /** 978 * Notifies a {@link Connection} of a call event. 979 * 980 * See {@link Call#sendCallEvent(String, Bundle)}. 981 * 982 * @param callId The ID of the call receiving the event. 983 * @param event The event. 984 * @param extras Extras associated with the event. 985 */ sendCallEvent(String callId, String event, Bundle extras)986 private void sendCallEvent(String callId, String event, Bundle extras) { 987 Log.d(this, "sendCallEvent(%s, %s)", callId, event); 988 Connection connection = findConnectionForAction(callId, "sendCallEvent"); 989 if (connection != null) { 990 connection.onCallEvent(event, extras); 991 } 992 993 } 994 995 /** 996 * Notifies a {@link Connection} or {@link Conference} of a change to the extras from Telecom. 997 * <p> 998 * These extra changes can originate from Telecom itself, or from an {@link InCallService} via 999 * the {@link android.telecom.Call#putExtra(String, boolean)}, 1000 * {@link android.telecom.Call#putExtra(String, int)}, 1001 * {@link android.telecom.Call#putExtra(String, String)}, 1002 * {@link Call#removeExtras(List)}. 1003 * 1004 * @param callId The ID of the call receiving the event. 1005 * @param extras The new extras bundle. 1006 */ handleExtrasChanged(String callId, Bundle extras)1007 private void handleExtrasChanged(String callId, Bundle extras) { 1008 Log.d(this, "handleExtrasChanged(%s, %s)", callId, extras); 1009 if (mConnectionById.containsKey(callId)) { 1010 findConnectionForAction(callId, "handleExtrasChanged").handleExtrasChanged(extras); 1011 } else if (mConferenceById.containsKey(callId)) { 1012 findConferenceForAction(callId, "handleExtrasChanged").handleExtrasChanged(extras); 1013 } 1014 } 1015 onPostDialContinue(String callId, boolean proceed)1016 private void onPostDialContinue(String callId, boolean proceed) { 1017 Log.d(this, "onPostDialContinue(%s)", callId); 1018 findConnectionForAction(callId, "stopDtmfTone").onPostDialContinue(proceed); 1019 } 1020 onAdapterAttached()1021 private void onAdapterAttached() { 1022 if (mAreAccountsInitialized) { 1023 // No need to query again if we already did it. 1024 return; 1025 } 1026 1027 mAdapter.queryRemoteConnectionServices(new RemoteServiceCallback.Stub() { 1028 @Override 1029 public void onResult( 1030 final List<ComponentName> componentNames, 1031 final List<IBinder> services) { 1032 mHandler.post(new Runnable() { 1033 @Override 1034 public void run() { 1035 for (int i = 0; i < componentNames.size() && i < services.size(); i++) { 1036 mRemoteConnectionManager.addConnectionService( 1037 componentNames.get(i), 1038 IConnectionService.Stub.asInterface(services.get(i))); 1039 } 1040 onAccountsInitialized(); 1041 Log.d(this, "remote connection services found: " + services); 1042 } 1043 }); 1044 } 1045 1046 @Override 1047 public void onError() { 1048 mHandler.post(new Runnable() { 1049 @Override 1050 public void run() { 1051 mAreAccountsInitialized = true; 1052 } 1053 }); 1054 } 1055 }); 1056 } 1057 1058 /** 1059 * Ask some other {@code ConnectionService} to create a {@code RemoteConnection} given an 1060 * incoming request. This is used by {@code ConnectionService}s that are registered with 1061 * {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER} and want to be able to manage 1062 * SIM-based incoming calls. 1063 * 1064 * @param connectionManagerPhoneAccount See description at 1065 * {@link #onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)}. 1066 * @param request Details about the incoming call. 1067 * @return The {@code Connection} object to satisfy this call, or {@code null} to 1068 * not handle the call. 1069 */ createRemoteIncomingConnection( PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request)1070 public final RemoteConnection createRemoteIncomingConnection( 1071 PhoneAccountHandle connectionManagerPhoneAccount, 1072 ConnectionRequest request) { 1073 return mRemoteConnectionManager.createRemoteConnection( 1074 connectionManagerPhoneAccount, request, true); 1075 } 1076 1077 /** 1078 * Ask some other {@code ConnectionService} to create a {@code RemoteConnection} given an 1079 * outgoing request. This is used by {@code ConnectionService}s that are registered with 1080 * {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER} and want to be able to use the 1081 * SIM-based {@code ConnectionService} to place its outgoing calls. 1082 * 1083 * @param connectionManagerPhoneAccount See description at 1084 * {@link #onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)}. 1085 * @param request Details about the incoming call. 1086 * @return The {@code Connection} object to satisfy this call, or {@code null} to 1087 * not handle the call. 1088 */ createRemoteOutgoingConnection( PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request)1089 public final RemoteConnection createRemoteOutgoingConnection( 1090 PhoneAccountHandle connectionManagerPhoneAccount, 1091 ConnectionRequest request) { 1092 return mRemoteConnectionManager.createRemoteConnection( 1093 connectionManagerPhoneAccount, request, false); 1094 } 1095 1096 /** 1097 * Indicates to the relevant {@code RemoteConnectionService} that the specified 1098 * {@link RemoteConnection}s should be merged into a conference call. 1099 * <p> 1100 * If the conference request is successful, the method {@link #onRemoteConferenceAdded} will 1101 * be invoked. 1102 * 1103 * @param remoteConnection1 The first of the remote connections to conference. 1104 * @param remoteConnection2 The second of the remote connections to conference. 1105 */ conferenceRemoteConnections( RemoteConnection remoteConnection1, RemoteConnection remoteConnection2)1106 public final void conferenceRemoteConnections( 1107 RemoteConnection remoteConnection1, 1108 RemoteConnection remoteConnection2) { 1109 mRemoteConnectionManager.conferenceRemoteConnections(remoteConnection1, remoteConnection2); 1110 } 1111 1112 /** 1113 * Adds a new conference call. When a conference call is created either as a result of an 1114 * explicit request via {@link #onConference} or otherwise, the connection service should supply 1115 * an instance of {@link Conference} by invoking this method. A conference call provided by this 1116 * method will persist until {@link Conference#destroy} is invoked on the conference instance. 1117 * 1118 * @param conference The new conference object. 1119 */ addConference(Conference conference)1120 public final void addConference(Conference conference) { 1121 Log.d(this, "addConference: conference=%s", conference); 1122 1123 String id = addConferenceInternal(conference); 1124 if (id != null) { 1125 List<String> connectionIds = new ArrayList<>(2); 1126 for (Connection connection : conference.getConnections()) { 1127 if (mIdByConnection.containsKey(connection)) { 1128 connectionIds.add(mIdByConnection.get(connection)); 1129 } 1130 } 1131 conference.setTelecomCallId(id); 1132 ParcelableConference parcelableConference = new ParcelableConference( 1133 conference.getPhoneAccountHandle(), 1134 conference.getState(), 1135 conference.getConnectionCapabilities(), 1136 conference.getConnectionProperties(), 1137 connectionIds, 1138 conference.getVideoProvider() == null ? 1139 null : conference.getVideoProvider().getInterface(), 1140 conference.getVideoState(), 1141 conference.getConnectTimeMillis(), 1142 conference.getStatusHints(), 1143 conference.getExtras()); 1144 1145 mAdapter.addConferenceCall(id, parcelableConference); 1146 mAdapter.setVideoProvider(id, conference.getVideoProvider()); 1147 mAdapter.setVideoState(id, conference.getVideoState()); 1148 1149 // Go through any child calls and set the parent. 1150 for (Connection connection : conference.getConnections()) { 1151 String connectionId = mIdByConnection.get(connection); 1152 if (connectionId != null) { 1153 mAdapter.setIsConferenced(connectionId, id); 1154 } 1155 } 1156 } 1157 } 1158 1159 /** 1160 * Adds a connection created by the {@link ConnectionService} and informs telecom of the new 1161 * connection. 1162 * 1163 * @param phoneAccountHandle The phone account handle for the connection. 1164 * @param connection The connection to add. 1165 */ addExistingConnection(PhoneAccountHandle phoneAccountHandle, Connection connection)1166 public final void addExistingConnection(PhoneAccountHandle phoneAccountHandle, 1167 Connection connection) { 1168 1169 String id = addExistingConnectionInternal(phoneAccountHandle, connection); 1170 if (id != null) { 1171 List<String> emptyList = new ArrayList<>(0); 1172 1173 ParcelableConnection parcelableConnection = new ParcelableConnection( 1174 phoneAccountHandle, 1175 connection.getState(), 1176 connection.getConnectionCapabilities(), 1177 connection.getConnectionProperties(), 1178 connection.getAddress(), 1179 connection.getAddressPresentation(), 1180 connection.getCallerDisplayName(), 1181 connection.getCallerDisplayNamePresentation(), 1182 connection.getVideoProvider() == null ? 1183 null : connection.getVideoProvider().getInterface(), 1184 connection.getVideoState(), 1185 connection.isRingbackRequested(), 1186 connection.getAudioModeIsVoip(), 1187 connection.getConnectTimeMillis(), 1188 connection.getStatusHints(), 1189 connection.getDisconnectCause(), 1190 emptyList, 1191 connection.getExtras()); 1192 mAdapter.addExistingConnection(id, parcelableConnection); 1193 } 1194 } 1195 1196 /** 1197 * Returns all the active {@code Connection}s for which this {@code ConnectionService} 1198 * has taken responsibility. 1199 * 1200 * @return A collection of {@code Connection}s created by this {@code ConnectionService}. 1201 */ getAllConnections()1202 public final Collection<Connection> getAllConnections() { 1203 return mConnectionById.values(); 1204 } 1205 1206 /** 1207 * Returns all the active {@code Conference}s for which this {@code ConnectionService} 1208 * has taken responsibility. 1209 * 1210 * @return A collection of {@code Conference}s created by this {@code ConnectionService}. 1211 */ getAllConferences()1212 public final Collection<Conference> getAllConferences() { 1213 return mConferenceById.values(); 1214 } 1215 1216 /** 1217 * Create a {@code Connection} given an incoming request. This is used to attach to existing 1218 * incoming calls. 1219 * 1220 * @param connectionManagerPhoneAccount See description at 1221 * {@link #onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)}. 1222 * @param request Details about the incoming call. 1223 * @return The {@code Connection} object to satisfy this call, or {@code null} to 1224 * not handle the call. 1225 */ onCreateIncomingConnection( PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request)1226 public Connection onCreateIncomingConnection( 1227 PhoneAccountHandle connectionManagerPhoneAccount, 1228 ConnectionRequest request) { 1229 return null; 1230 } 1231 1232 /** 1233 * Trigger recalculate functinality for conference calls. This is used when a Telephony 1234 * Connection is part of a conference controller but is not yet added to Connection 1235 * Service and hence cannot be added to the conference call. 1236 * 1237 * @hide 1238 */ triggerConferenceRecalculate()1239 public void triggerConferenceRecalculate() { 1240 } 1241 1242 /** 1243 * Create a {@code Connection} given an outgoing request. This is used to initiate new 1244 * outgoing calls. 1245 * 1246 * @param connectionManagerPhoneAccount The connection manager account to use for managing 1247 * this call. 1248 * <p> 1249 * If this parameter is not {@code null}, it means that this {@code ConnectionService} 1250 * has registered one or more {@code PhoneAccount}s having 1251 * {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER}. This parameter will contain 1252 * one of these {@code PhoneAccount}s, while the {@code request} will contain another 1253 * (usually but not always distinct) {@code PhoneAccount} to be used for actually 1254 * making the connection. 1255 * <p> 1256 * If this parameter is {@code null}, it means that this {@code ConnectionService} is 1257 * being asked to make a direct connection. The 1258 * {@link ConnectionRequest#getAccountHandle()} of parameter {@code request} will be 1259 * a {@code PhoneAccount} registered by this {@code ConnectionService} to use for 1260 * making the connection. 1261 * @param request Details about the outgoing call. 1262 * @return The {@code Connection} object to satisfy this call, or the result of an invocation 1263 * of {@link Connection#createFailedConnection(DisconnectCause)} to not handle the call. 1264 */ onCreateOutgoingConnection( PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request)1265 public Connection onCreateOutgoingConnection( 1266 PhoneAccountHandle connectionManagerPhoneAccount, 1267 ConnectionRequest request) { 1268 return null; 1269 } 1270 1271 /** 1272 * Create a {@code Connection} for a new unknown call. An unknown call is a call originating 1273 * from the ConnectionService that was neither a user-initiated outgoing call, nor an incoming 1274 * call created using 1275 * {@code TelecomManager#addNewIncomingCall(PhoneAccountHandle, android.os.Bundle)}. 1276 * 1277 * @param connectionManagerPhoneAccount 1278 * @param request 1279 * @return 1280 * 1281 * @hide 1282 */ onCreateUnknownConnection(PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request)1283 public Connection onCreateUnknownConnection(PhoneAccountHandle connectionManagerPhoneAccount, 1284 ConnectionRequest request) { 1285 return null; 1286 } 1287 1288 /** 1289 * Conference two specified connections. Invoked when the user has made a request to merge the 1290 * specified connections into a conference call. In response, the connection service should 1291 * create an instance of {@link Conference} and pass it into {@link #addConference}. 1292 * 1293 * @param connection1 A connection to merge into a conference call. 1294 * @param connection2 A connection to merge into a conference call. 1295 */ onConference(Connection connection1, Connection connection2)1296 public void onConference(Connection connection1, Connection connection2) {} 1297 1298 /** 1299 * Indicates that a remote conference has been created for existing {@link RemoteConnection}s. 1300 * When this method is invoked, this {@link ConnectionService} should create its own 1301 * representation of the conference call and send it to telecom using {@link #addConference}. 1302 * <p> 1303 * This is only relevant to {@link ConnectionService}s which are registered with 1304 * {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER}. 1305 * 1306 * @param conference The remote conference call. 1307 */ onRemoteConferenceAdded(RemoteConference conference)1308 public void onRemoteConferenceAdded(RemoteConference conference) {} 1309 1310 /** 1311 * Called when an existing connection is added remotely. 1312 * @param connection The existing connection which was added. 1313 */ onRemoteExistingConnectionAdded(RemoteConnection connection)1314 public void onRemoteExistingConnectionAdded(RemoteConnection connection) {} 1315 1316 /** 1317 * @hide 1318 */ containsConference(Conference conference)1319 public boolean containsConference(Conference conference) { 1320 return mIdByConference.containsKey(conference); 1321 } 1322 1323 /** {@hide} */ addRemoteConference(RemoteConference remoteConference)1324 void addRemoteConference(RemoteConference remoteConference) { 1325 onRemoteConferenceAdded(remoteConference); 1326 } 1327 1328 /** {@hide} */ addRemoteExistingConnection(RemoteConnection remoteConnection)1329 void addRemoteExistingConnection(RemoteConnection remoteConnection) { 1330 onRemoteExistingConnectionAdded(remoteConnection); 1331 } 1332 onAccountsInitialized()1333 private void onAccountsInitialized() { 1334 mAreAccountsInitialized = true; 1335 for (Runnable r : mPreInitializationConnectionRequests) { 1336 r.run(); 1337 } 1338 mPreInitializationConnectionRequests.clear(); 1339 } 1340 1341 /** 1342 * Adds an existing connection to the list of connections, identified by a new call ID unique 1343 * to this connection service. 1344 * 1345 * @param connection The connection. 1346 * @return The ID of the connection (e.g. the call-id). 1347 */ addExistingConnectionInternal(PhoneAccountHandle handle, Connection connection)1348 private String addExistingConnectionInternal(PhoneAccountHandle handle, Connection connection) { 1349 String id; 1350 if (handle == null) { 1351 // If no phone account handle was provided, we cannot be sure the call ID is unique, 1352 // so just use a random UUID. 1353 id = UUID.randomUUID().toString(); 1354 } else { 1355 // Phone account handle was provided, so use the ConnectionService class name as a 1356 // prefix for a unique incremental call ID. 1357 id = handle.getComponentName().getClassName() + "@" + getNextCallId(); 1358 } 1359 addConnection(id, connection); 1360 return id; 1361 } 1362 addConnection(String callId, Connection connection)1363 private void addConnection(String callId, Connection connection) { 1364 connection.setTelecomCallId(callId); 1365 mConnectionById.put(callId, connection); 1366 mIdByConnection.put(connection, callId); 1367 connection.addConnectionListener(mConnectionListener); 1368 connection.setConnectionService(this); 1369 } 1370 1371 /** {@hide} */ removeConnection(Connection connection)1372 protected void removeConnection(Connection connection) { 1373 connection.unsetConnectionService(this); 1374 connection.removeConnectionListener(mConnectionListener); 1375 String id = mIdByConnection.get(connection); 1376 if (id != null) { 1377 mConnectionById.remove(id); 1378 mIdByConnection.remove(connection); 1379 mAdapter.removeCall(id); 1380 } 1381 } 1382 addConferenceInternal(Conference conference)1383 private String addConferenceInternal(Conference conference) { 1384 if (mIdByConference.containsKey(conference)) { 1385 Log.w(this, "Re-adding an existing conference: %s.", conference); 1386 } else if (conference != null) { 1387 // Conferences do not (yet) have a PhoneAccountHandle associated with them, so we 1388 // cannot determine a ConnectionService class name to associate with the ID, so use 1389 // a unique UUID (for now). 1390 String id = UUID.randomUUID().toString(); 1391 mConferenceById.put(id, conference); 1392 mIdByConference.put(conference, id); 1393 conference.addListener(mConferenceListener); 1394 return id; 1395 } 1396 1397 return null; 1398 } 1399 removeConference(Conference conference)1400 private void removeConference(Conference conference) { 1401 if (mIdByConference.containsKey(conference)) { 1402 conference.removeListener(mConferenceListener); 1403 1404 String id = mIdByConference.get(conference); 1405 mConferenceById.remove(id); 1406 mIdByConference.remove(conference); 1407 mAdapter.removeCall(id); 1408 } 1409 } 1410 findConnectionForAction(String callId, String action)1411 private Connection findConnectionForAction(String callId, String action) { 1412 if (mConnectionById.containsKey(callId)) { 1413 return mConnectionById.get(callId); 1414 } 1415 Log.w(this, "%s - Cannot find Connection %s", action, callId); 1416 return getNullConnection(); 1417 } 1418 getNullConnection()1419 static synchronized Connection getNullConnection() { 1420 if (sNullConnection == null) { 1421 sNullConnection = new Connection() {}; 1422 } 1423 return sNullConnection; 1424 } 1425 findConferenceForAction(String conferenceId, String action)1426 private Conference findConferenceForAction(String conferenceId, String action) { 1427 if (mConferenceById.containsKey(conferenceId)) { 1428 return mConferenceById.get(conferenceId); 1429 } 1430 Log.w(this, "%s - Cannot find conference %s", action, conferenceId); 1431 return getNullConference(); 1432 } 1433 createConnectionIdList(List<Connection> connections)1434 private List<String> createConnectionIdList(List<Connection> connections) { 1435 List<String> ids = new ArrayList<>(); 1436 for (Connection c : connections) { 1437 if (mIdByConnection.containsKey(c)) { 1438 ids.add(mIdByConnection.get(c)); 1439 } 1440 } 1441 Collections.sort(ids); 1442 return ids; 1443 } 1444 1445 /** 1446 * Builds a list of {@link Connection} and {@link Conference} IDs based on the list of 1447 * {@link Conferenceable}s passed in. 1448 * 1449 * @param conferenceables The {@link Conferenceable} connections and conferences. 1450 * @return List of string conference and call Ids. 1451 */ createIdList(List<Conferenceable> conferenceables)1452 private List<String> createIdList(List<Conferenceable> conferenceables) { 1453 List<String> ids = new ArrayList<>(); 1454 for (Conferenceable c : conferenceables) { 1455 // Only allow Connection and Conference conferenceables. 1456 if (c instanceof Connection) { 1457 Connection connection = (Connection) c; 1458 if (mIdByConnection.containsKey(connection)) { 1459 ids.add(mIdByConnection.get(connection)); 1460 } 1461 } else if (c instanceof Conference) { 1462 Conference conference = (Conference) c; 1463 if (mIdByConference.containsKey(conference)) { 1464 ids.add(mIdByConference.get(conference)); 1465 } 1466 } 1467 } 1468 Collections.sort(ids); 1469 return ids; 1470 } 1471 getNullConference()1472 private Conference getNullConference() { 1473 if (sNullConference == null) { 1474 sNullConference = new Conference(null) {}; 1475 } 1476 return sNullConference; 1477 } 1478 endAllConnections()1479 private void endAllConnections() { 1480 // Unbound from telecomm. We should end all connections and conferences. 1481 for (Connection connection : mIdByConnection.keySet()) { 1482 // only operate on top-level calls. Conference calls will be removed on their own. 1483 if (connection.getConference() == null) { 1484 connection.onDisconnect(); 1485 } 1486 } 1487 for (Conference conference : mIdByConference.keySet()) { 1488 conference.onDisconnect(); 1489 } 1490 } 1491 1492 /** 1493 * Retrieves the next call ID as maintainted by the connection service. 1494 * 1495 * @return The call ID. 1496 */ getNextCallId()1497 private int getNextCallId() { 1498 synchronized(mIdSyncRoot) { 1499 return ++mId; 1500 } 1501 } 1502 } 1503