1/* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package android.telecom; 18 19import android.annotation.SdkConstant; 20import android.app.Service; 21import android.content.ComponentName; 22import android.content.Intent; 23import android.net.Uri; 24import android.os.Bundle; 25import android.os.Handler; 26import android.os.IBinder; 27import android.os.Looper; 28import android.os.Message; 29 30import com.android.internal.os.SomeArgs; 31import com.android.internal.telecom.IConnectionService; 32import com.android.internal.telecom.IConnectionServiceAdapter; 33import com.android.internal.telecom.RemoteServiceCallback; 34 35import java.util.ArrayList; 36import java.util.Collection; 37import java.util.Collections; 38import java.util.List; 39import java.util.Map; 40import java.util.UUID; 41import java.util.concurrent.ConcurrentHashMap; 42 43/** 44 * An abstract service that should be implemented by any apps which can make phone calls (VoIP or 45 * otherwise) and want those calls to be integrated into the built-in phone app. 46 * Once implemented, the {@code ConnectionService} needs two additional steps before it will be 47 * integrated into the phone app: 48 * <p> 49 * 1. <i>Registration in AndroidManifest.xml</i> 50 * <br/> 51 * <pre> 52 * <service android:name="com.example.package.MyConnectionService" 53 * android:label="@string/some_label_for_my_connection_service" 54 * android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE"> 55 * <intent-filter> 56 * <action android:name="android.telecom.ConnectionService" /> 57 * </intent-filter> 58 * </service> 59 * </pre> 60 * <p> 61 * 2. <i> Registration of {@link PhoneAccount} with {@link TelecomManager}.</i> 62 * <br/> 63 * See {@link PhoneAccount} and {@link TelecomManager#registerPhoneAccount} for more information. 64 * <p> 65 * Once registered and enabled by the user in the phone app settings, telecom will bind to a 66 * {@code ConnectionService} implementation when it wants that {@code ConnectionService} to place 67 * a call or the service has indicated that is has an incoming call through 68 * {@link TelecomManager#addNewIncomingCall}. The {@code ConnectionService} can then expect a call 69 * to {@link #onCreateIncomingConnection} or {@link #onCreateOutgoingConnection} wherein it 70 * should provide a new instance of a {@link Connection} object. It is through this 71 * {@link Connection} object that telecom receives state updates and the {@code ConnectionService} 72 * receives call-commands such as answer, reject, hold and disconnect. 73 * <p> 74 * When there are no more live calls, telecom will unbind from the {@code ConnectionService}. 75 */ 76public abstract class ConnectionService extends Service { 77 /** 78 * The {@link Intent} that must be declared as handled by the service. 79 */ 80 @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION) 81 public static final String SERVICE_INTERFACE = "android.telecom.ConnectionService"; 82 83 // Flag controlling whether PII is emitted into the logs 84 private static final boolean PII_DEBUG = Log.isLoggable(android.util.Log.DEBUG); 85 86 private static final int MSG_ADD_CONNECTION_SERVICE_ADAPTER = 1; 87 private static final int MSG_CREATE_CONNECTION = 2; 88 private static final int MSG_ABORT = 3; 89 private static final int MSG_ANSWER = 4; 90 private static final int MSG_REJECT = 5; 91 private static final int MSG_DISCONNECT = 6; 92 private static final int MSG_HOLD = 7; 93 private static final int MSG_UNHOLD = 8; 94 private static final int MSG_ON_CALL_AUDIO_STATE_CHANGED = 9; 95 private static final int MSG_PLAY_DTMF_TONE = 10; 96 private static final int MSG_STOP_DTMF_TONE = 11; 97 private static final int MSG_CONFERENCE = 12; 98 private static final int MSG_SPLIT_FROM_CONFERENCE = 13; 99 private static final int MSG_ON_POST_DIAL_CONTINUE = 14; 100 private static final int MSG_REMOVE_CONNECTION_SERVICE_ADAPTER = 16; 101 private static final int MSG_ANSWER_VIDEO = 17; 102 private static final int MSG_MERGE_CONFERENCE = 18; 103 private static final int MSG_SWAP_CONFERENCE = 19; 104 private static final int MSG_REJECT_WITH_MESSAGE = 20; 105 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 718 public final IBinder onBind(Intent intent) { 719 return mBinder; 720 } 721 722 /** {@inheritDoc} */ 723 @Override 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 */ 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 795 private void abort(String callId) { 796 Log.d(this, "abort %s", callId); 797 findConnectionForAction(callId, "abort").onAbort(); 798 } 799 800 private void answerVideo(String callId, int videoState) { 801 Log.d(this, "answerVideo %s", callId); 802 findConnectionForAction(callId, "answer").onAnswer(videoState); 803 } 804 805 private void answer(String callId) { 806 Log.d(this, "answer %s", callId); 807 findConnectionForAction(callId, "answer").onAnswer(); 808 } 809 810 private void reject(String callId) { 811 Log.d(this, "reject %s", callId); 812 findConnectionForAction(callId, "reject").onReject(); 813 } 814 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 820 private void silence(String callId) { 821 Log.d(this, "silence %s", callId); 822 findConnectionForAction(callId, "silence").onSilence(); 823 } 824 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 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 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 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 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 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 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 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 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 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 */ 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 */ 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 */ 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 1013 private void onPostDialContinue(String callId, boolean proceed) { 1014 Log.d(this, "onPostDialContinue(%s)", callId); 1015 findConnectionForAction(callId, "stopDtmfTone").onPostDialContinue(proceed); 1016 } 1017 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 1311 public void onRemoteExistingConnectionAdded(RemoteConnection connection) {} 1312 1313 /** 1314 * @hide 1315 */ 1316 public boolean containsConference(Conference conference) { 1317 return mIdByConference.containsKey(conference); 1318 } 1319 1320 /** {@hide} */ 1321 void addRemoteConference(RemoteConference remoteConference) { 1322 onRemoteConferenceAdded(remoteConference); 1323 } 1324 1325 /** {@hide} */ 1326 void addRemoteExistingConnection(RemoteConnection remoteConnection) { 1327 onRemoteExistingConnectionAdded(remoteConnection); 1328 } 1329 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 */ 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 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} */ 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 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 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 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 1416 static synchronized Connection getNullConnection() { 1417 if (sNullConnection == null) { 1418 sNullConnection = new Connection() {}; 1419 } 1420 return sNullConnection; 1421 } 1422 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 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 */ 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 1469 private Conference getNullConference() { 1470 if (sNullConference == null) { 1471 sNullConference = new Conference(null) {}; 1472 } 1473 return sNullConference; 1474 } 1475 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 */ 1494 private int getNextCallId() { 1495 synchronized(mIdSyncRoot) { 1496 return ++mId; 1497 } 1498 } 1499} 1500