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