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