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.SdkConstant;
     20 import android.app.Service;
     21 import android.content.ComponentName;
     22 import android.content.Intent;
     23 import android.net.Uri;
     24 import android.os.Bundle;
     25 import android.os.Handler;
     26 import android.os.IBinder;
     27 import android.os.Looper;
     28 import android.os.Message;
     29 import android.os.ParcelFileDescriptor;
     30 import android.os.RemoteException;
     31 import android.telecom.Logging.Session;
     32 
     33 import com.android.internal.os.SomeArgs;
     34 import com.android.internal.telecom.IConnectionService;
     35 import com.android.internal.telecom.IConnectionServiceAdapter;
     36 import com.android.internal.telecom.RemoteServiceCallback;
     37 
     38 import java.util.ArrayList;
     39 import java.util.Collection;
     40 import java.util.Collections;
     41 import java.util.List;
     42 import java.util.Map;
     43 import java.util.UUID;
     44 import java.util.concurrent.ConcurrentHashMap;
     45 
     46 /**
     47  * An abstract service that should be implemented by any apps which either:
     48  * <ol>
     49  *     <li>Can make phone calls (VoIP or otherwise) and want those calls to be integrated into the
     50  *     built-in phone app.  Referred to as a <b>system managed</b> {@link ConnectionService}.</li>
     51  *     <li>Are a standalone calling app and don't want their calls to be integrated into the
     52  *     built-in phone app.  Referred to as a <b>self managed</b> {@link ConnectionService}.</li>
     53  * </ol>
     54  * Once implemented, the {@link ConnectionService} needs to take the following steps so that Telecom
     55  * will bind to it:
     56  * <p>
     57  * 1. <i>Registration in AndroidManifest.xml</i>
     58  * <br/>
     59  * <pre>
     60  * &lt;service android:name="com.example.package.MyConnectionService"
     61  *    android:label="@string/some_label_for_my_connection_service"
     62  *    android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE"&gt;
     63  *  &lt;intent-filter&gt;
     64  *   &lt;action android:name="android.telecom.ConnectionService" /&gt;
     65  *  &lt;/intent-filter&gt;
     66  * &lt;/service&gt;
     67  * </pre>
     68  * <p>
     69  * 2. <i> Registration of {@link PhoneAccount} with {@link TelecomManager}.</i>
     70  * <br/>
     71  * See {@link PhoneAccount} and {@link TelecomManager#registerPhoneAccount} for more information.
     72  * <p>
     73  * System managed {@link ConnectionService}s must be enabled by the user in the phone app settings
     74  * before Telecom will bind to them.  Self-manged {@link ConnectionService}s must be granted the
     75  * appropriate permission before Telecom will bind to them.
     76  * <p>
     77  * Once registered and enabled by the user in the phone app settings or granted permission, telecom
     78  * will bind to a {@link ConnectionService} implementation when it wants that
     79  * {@link ConnectionService} to place a call or the service has indicated that is has an incoming
     80  * call through {@link TelecomManager#addNewIncomingCall}. The {@link ConnectionService} can then
     81  * expect a call to {@link #onCreateIncomingConnection} or {@link #onCreateOutgoingConnection}
     82  * wherein it should provide a new instance of a {@link Connection} object.  It is through this
     83  * {@link Connection} object that telecom receives state updates and the {@link ConnectionService}
     84  * receives call-commands such as answer, reject, hold and disconnect.
     85  * <p>
     86  * When there are no more live calls, telecom will unbind from the {@link ConnectionService}.
     87  */
     88 public abstract class ConnectionService extends Service {
     89     /**
     90      * The {@link Intent} that must be declared as handled by the service.
     91      */
     92     @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
     93     public static final String SERVICE_INTERFACE = "android.telecom.ConnectionService";
     94 
     95     /**
     96      * Boolean extra used by Telecom to inform a {@link ConnectionService} that the purpose of it
     97      * being asked to create a new outgoing {@link Connection} is to perform a handover of an
     98      * ongoing call on the device from another {@link PhoneAccount}/{@link ConnectionService}.  Will
     99      * be specified in the {@link ConnectionRequest#getExtras()} passed by Telecom when
    100      * {@link #onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)} is called.
    101      * <p>
    102      * When your {@link ConnectionService} receives this extra, it should communicate the fact that
    103      * this is a handover to the other device's matching {@link ConnectionService}.  That
    104      * {@link ConnectionService} will continue the handover using
    105      * {@link TelecomManager#addNewIncomingCall(PhoneAccountHandle, Bundle)}, specifying
    106      * {@link TelecomManager#EXTRA_IS_HANDOVER}.  Telecom will match the phone numbers of the
    107      * handover call on the other device with ongoing calls for {@link ConnectionService}s which
    108      * support {@link PhoneAccount#EXTRA_SUPPORTS_HANDOVER_FROM}.
    109      * @hide
    110      */
    111     public static final String EXTRA_IS_HANDOVER = TelecomManager.EXTRA_IS_HANDOVER;
    112 
    113     // Flag controlling whether PII is emitted into the logs
    114     private static final boolean PII_DEBUG = Log.isLoggable(android.util.Log.DEBUG);
    115 
    116     // Session Definitions
    117     private static final String SESSION_HANDLER = "H.";
    118     private static final String SESSION_ADD_CS_ADAPTER = "CS.aCSA";
    119     private static final String SESSION_REMOVE_CS_ADAPTER = "CS.rCSA";
    120     private static final String SESSION_CREATE_CONN = "CS.crCo";
    121     private static final String SESSION_CREATE_CONN_COMPLETE = "CS.crCoC";
    122     private static final String SESSION_CREATE_CONN_FAILED = "CS.crCoF";
    123     private static final String SESSION_ABORT = "CS.ab";
    124     private static final String SESSION_ANSWER = "CS.an";
    125     private static final String SESSION_ANSWER_VIDEO = "CS.anV";
    126     private static final String SESSION_DEFLECT = "CS.def";
    127     private static final String SESSION_REJECT = "CS.r";
    128     private static final String SESSION_REJECT_MESSAGE = "CS.rWM";
    129     private static final String SESSION_SILENCE = "CS.s";
    130     private static final String SESSION_DISCONNECT = "CS.d";
    131     private static final String SESSION_HOLD = "CS.h";
    132     private static final String SESSION_UNHOLD = "CS.u";
    133     private static final String SESSION_CALL_AUDIO_SC = "CS.cASC";
    134     private static final String SESSION_PLAY_DTMF = "CS.pDT";
    135     private static final String SESSION_STOP_DTMF = "CS.sDT";
    136     private static final String SESSION_CONFERENCE = "CS.c";
    137     private static final String SESSION_SPLIT_CONFERENCE = "CS.sFC";
    138     private static final String SESSION_MERGE_CONFERENCE = "CS.mC";
    139     private static final String SESSION_SWAP_CONFERENCE = "CS.sC";
    140     private static final String SESSION_POST_DIAL_CONT = "CS.oPDC";
    141     private static final String SESSION_PULL_EXTERNAL_CALL = "CS.pEC";
    142     private static final String SESSION_SEND_CALL_EVENT = "CS.sCE";
    143     private static final String SESSION_HANDOVER_COMPLETE = "CS.hC";
    144     private static final String SESSION_EXTRAS_CHANGED = "CS.oEC";
    145     private static final String SESSION_START_RTT = "CS.+RTT";
    146     private static final String SESSION_UPDATE_RTT_PIPES = "CS.uRTT";
    147     private static final String SESSION_STOP_RTT = "CS.-RTT";
    148     private static final String SESSION_RTT_UPGRADE_RESPONSE = "CS.rTRUR";
    149     private static final String SESSION_CONNECTION_SERVICE_FOCUS_LOST = "CS.cSFL";
    150     private static final String SESSION_CONNECTION_SERVICE_FOCUS_GAINED = "CS.cSFG";
    151     private static final String SESSION_HANDOVER_FAILED = "CS.haF";
    152 
    153     private static final int MSG_ADD_CONNECTION_SERVICE_ADAPTER = 1;
    154     private static final int MSG_CREATE_CONNECTION = 2;
    155     private static final int MSG_ABORT = 3;
    156     private static final int MSG_ANSWER = 4;
    157     private static final int MSG_REJECT = 5;
    158     private static final int MSG_DISCONNECT = 6;
    159     private static final int MSG_HOLD = 7;
    160     private static final int MSG_UNHOLD = 8;
    161     private static final int MSG_ON_CALL_AUDIO_STATE_CHANGED = 9;
    162     private static final int MSG_PLAY_DTMF_TONE = 10;
    163     private static final int MSG_STOP_DTMF_TONE = 11;
    164     private static final int MSG_CONFERENCE = 12;
    165     private static final int MSG_SPLIT_FROM_CONFERENCE = 13;
    166     private static final int MSG_ON_POST_DIAL_CONTINUE = 14;
    167     private static final int MSG_REMOVE_CONNECTION_SERVICE_ADAPTER = 16;
    168     private static final int MSG_ANSWER_VIDEO = 17;
    169     private static final int MSG_MERGE_CONFERENCE = 18;
    170     private static final int MSG_SWAP_CONFERENCE = 19;
    171     private static final int MSG_REJECT_WITH_MESSAGE = 20;
    172     private static final int MSG_SILENCE = 21;
    173     private static final int MSG_PULL_EXTERNAL_CALL = 22;
    174     private static final int MSG_SEND_CALL_EVENT = 23;
    175     private static final int MSG_ON_EXTRAS_CHANGED = 24;
    176     private static final int MSG_CREATE_CONNECTION_FAILED = 25;
    177     private static final int MSG_ON_START_RTT = 26;
    178     private static final int MSG_ON_STOP_RTT = 27;
    179     private static final int MSG_RTT_UPGRADE_RESPONSE = 28;
    180     private static final int MSG_CREATE_CONNECTION_COMPLETE = 29;
    181     private static final int MSG_CONNECTION_SERVICE_FOCUS_LOST = 30;
    182     private static final int MSG_CONNECTION_SERVICE_FOCUS_GAINED = 31;
    183     private static final int MSG_HANDOVER_FAILED = 32;
    184     private static final int MSG_HANDOVER_COMPLETE = 33;
    185     private static final int MSG_DEFLECT = 34;
    186 
    187     private static Connection sNullConnection;
    188 
    189     private final Map<String, Connection> mConnectionById = new ConcurrentHashMap<>();
    190     private final Map<Connection, String> mIdByConnection = new ConcurrentHashMap<>();
    191     private final Map<String, Conference> mConferenceById = new ConcurrentHashMap<>();
    192     private final Map<Conference, String> mIdByConference = new ConcurrentHashMap<>();
    193     private final RemoteConnectionManager mRemoteConnectionManager =
    194             new RemoteConnectionManager(this);
    195     private final List<Runnable> mPreInitializationConnectionRequests = new ArrayList<>();
    196     private final ConnectionServiceAdapter mAdapter = new ConnectionServiceAdapter();
    197 
    198     private boolean mAreAccountsInitialized = false;
    199     private Conference sNullConference;
    200     private Object mIdSyncRoot = new Object();
    201     private int mId = 0;
    202 
    203     private final IBinder mBinder = new IConnectionService.Stub() {
    204         @Override
    205         public void addConnectionServiceAdapter(IConnectionServiceAdapter adapter,
    206                 Session.Info sessionInfo) {
    207             Log.startSession(sessionInfo, SESSION_ADD_CS_ADAPTER);
    208             try {
    209                 SomeArgs args = SomeArgs.obtain();
    210                 args.arg1 = adapter;
    211                 args.arg2 = Log.createSubsession();
    212                 mHandler.obtainMessage(MSG_ADD_CONNECTION_SERVICE_ADAPTER, args).sendToTarget();
    213             } finally {
    214                 Log.endSession();
    215             }
    216         }
    217 
    218         public void removeConnectionServiceAdapter(IConnectionServiceAdapter adapter,
    219                 Session.Info sessionInfo) {
    220             Log.startSession(sessionInfo, SESSION_REMOVE_CS_ADAPTER);
    221             try {
    222                 SomeArgs args = SomeArgs.obtain();
    223                 args.arg1 = adapter;
    224                 args.arg2 = Log.createSubsession();
    225                 mHandler.obtainMessage(MSG_REMOVE_CONNECTION_SERVICE_ADAPTER, args).sendToTarget();
    226             } finally {
    227                 Log.endSession();
    228             }
    229         }
    230 
    231         @Override
    232         public void createConnection(
    233                 PhoneAccountHandle connectionManagerPhoneAccount,
    234                 String id,
    235                 ConnectionRequest request,
    236                 boolean isIncoming,
    237                 boolean isUnknown,
    238                 Session.Info sessionInfo) {
    239             Log.startSession(sessionInfo, SESSION_CREATE_CONN);
    240             try {
    241                 SomeArgs args = SomeArgs.obtain();
    242                 args.arg1 = connectionManagerPhoneAccount;
    243                 args.arg2 = id;
    244                 args.arg3 = request;
    245                 args.arg4 = Log.createSubsession();
    246                 args.argi1 = isIncoming ? 1 : 0;
    247                 args.argi2 = isUnknown ? 1 : 0;
    248                 mHandler.obtainMessage(MSG_CREATE_CONNECTION, args).sendToTarget();
    249             } finally {
    250                 Log.endSession();
    251             }
    252         }
    253 
    254         @Override
    255         public void createConnectionComplete(String id, Session.Info sessionInfo) {
    256             Log.startSession(sessionInfo, SESSION_CREATE_CONN_COMPLETE);
    257             try {
    258                 SomeArgs args = SomeArgs.obtain();
    259                 args.arg1 = id;
    260                 args.arg2 = Log.createSubsession();
    261                 mHandler.obtainMessage(MSG_CREATE_CONNECTION_COMPLETE, args).sendToTarget();
    262             } finally {
    263                 Log.endSession();
    264             }
    265         }
    266 
    267         @Override
    268         public void createConnectionFailed(
    269                 PhoneAccountHandle connectionManagerPhoneAccount,
    270                 String callId,
    271                 ConnectionRequest request,
    272                 boolean isIncoming,
    273                 Session.Info sessionInfo) {
    274             Log.startSession(sessionInfo, SESSION_CREATE_CONN_FAILED);
    275             try {
    276                 SomeArgs args = SomeArgs.obtain();
    277                 args.arg1 = callId;
    278                 args.arg2 = request;
    279                 args.arg3 = Log.createSubsession();
    280                 args.arg4 = connectionManagerPhoneAccount;
    281                 args.argi1 = isIncoming ? 1 : 0;
    282                 mHandler.obtainMessage(MSG_CREATE_CONNECTION_FAILED, args).sendToTarget();
    283             } finally {
    284                 Log.endSession();
    285             }
    286         }
    287 
    288         @Override
    289         public void handoverFailed(String callId, ConnectionRequest request, int reason,
    290                                    Session.Info sessionInfo) {
    291             Log.startSession(sessionInfo, SESSION_HANDOVER_FAILED);
    292             try {
    293                 SomeArgs args = SomeArgs.obtain();
    294                 args.arg1 = callId;
    295                 args.arg2 = request;
    296                 args.arg3 = Log.createSubsession();
    297                 args.arg4 = reason;
    298                 mHandler.obtainMessage(MSG_HANDOVER_FAILED, args).sendToTarget();
    299             } finally {
    300                 Log.endSession();
    301             }
    302         }
    303 
    304         @Override
    305         public void handoverComplete(String callId, Session.Info sessionInfo) {
    306             Log.startSession(sessionInfo, SESSION_HANDOVER_COMPLETE);
    307             try {
    308                 SomeArgs args = SomeArgs.obtain();
    309                 args.arg1 = callId;
    310                 args.arg2 = Log.createSubsession();
    311                 mHandler.obtainMessage(MSG_HANDOVER_COMPLETE, args).sendToTarget();
    312             } finally {
    313                 Log.endSession();
    314             }
    315         }
    316 
    317         @Override
    318         public void abort(String callId, Session.Info sessionInfo) {
    319             Log.startSession(sessionInfo, SESSION_ABORT);
    320             try {
    321                 SomeArgs args = SomeArgs.obtain();
    322                 args.arg1 = callId;
    323                 args.arg2 = Log.createSubsession();
    324                 mHandler.obtainMessage(MSG_ABORT, args).sendToTarget();
    325             } finally {
    326                 Log.endSession();
    327             }
    328         }
    329 
    330         @Override
    331         public void answerVideo(String callId, int videoState, Session.Info sessionInfo) {
    332             Log.startSession(sessionInfo, SESSION_ANSWER_VIDEO);
    333             try {
    334                 SomeArgs args = SomeArgs.obtain();
    335                 args.arg1 = callId;
    336                 args.arg2 = Log.createSubsession();
    337                 args.argi1 = videoState;
    338                 mHandler.obtainMessage(MSG_ANSWER_VIDEO, args).sendToTarget();
    339             } finally {
    340                 Log.endSession();
    341             }
    342         }
    343 
    344         @Override
    345         public void answer(String callId, Session.Info sessionInfo) {
    346             Log.startSession(sessionInfo, SESSION_ANSWER);
    347             try {
    348                 SomeArgs args = SomeArgs.obtain();
    349                 args.arg1 = callId;
    350                 args.arg2 = Log.createSubsession();
    351                 mHandler.obtainMessage(MSG_ANSWER, args).sendToTarget();
    352             } finally {
    353                 Log.endSession();
    354             }
    355         }
    356 
    357         @Override
    358         public void deflect(String callId, Uri address, Session.Info sessionInfo) {
    359             Log.startSession(sessionInfo, SESSION_DEFLECT);
    360             try {
    361                 SomeArgs args = SomeArgs.obtain();
    362                 args.arg1 = callId;
    363                 args.arg2 = address;
    364                 args.arg3 = Log.createSubsession();
    365                 mHandler.obtainMessage(MSG_DEFLECT, args).sendToTarget();
    366             } finally {
    367                 Log.endSession();
    368             }
    369         }
    370 
    371         @Override
    372         public void reject(String callId, Session.Info sessionInfo) {
    373             Log.startSession(sessionInfo, SESSION_REJECT);
    374             try {
    375                 SomeArgs args = SomeArgs.obtain();
    376                 args.arg1 = callId;
    377                 args.arg2 = Log.createSubsession();
    378                 mHandler.obtainMessage(MSG_REJECT, args).sendToTarget();
    379             } finally {
    380                 Log.endSession();
    381             }
    382         }
    383 
    384         @Override
    385         public void rejectWithMessage(String callId, String message, Session.Info sessionInfo) {
    386             Log.startSession(sessionInfo, SESSION_REJECT_MESSAGE);
    387             try {
    388                 SomeArgs args = SomeArgs.obtain();
    389                 args.arg1 = callId;
    390                 args.arg2 = message;
    391                 args.arg3 = Log.createSubsession();
    392                 mHandler.obtainMessage(MSG_REJECT_WITH_MESSAGE, args).sendToTarget();
    393             } finally {
    394                 Log.endSession();
    395             }
    396         }
    397 
    398         @Override
    399         public void silence(String callId, Session.Info sessionInfo) {
    400             Log.startSession(sessionInfo, SESSION_SILENCE);
    401             try {
    402                 SomeArgs args = SomeArgs.obtain();
    403                 args.arg1 = callId;
    404                 args.arg2 = Log.createSubsession();
    405                 mHandler.obtainMessage(MSG_SILENCE, args).sendToTarget();
    406             } finally {
    407                 Log.endSession();
    408             }
    409         }
    410 
    411         @Override
    412         public void disconnect(String callId, Session.Info sessionInfo) {
    413             Log.startSession(sessionInfo, SESSION_DISCONNECT);
    414             try {
    415                 SomeArgs args = SomeArgs.obtain();
    416                 args.arg1 = callId;
    417                 args.arg2 = Log.createSubsession();
    418                 mHandler.obtainMessage(MSG_DISCONNECT, args).sendToTarget();
    419             } finally {
    420                 Log.endSession();
    421             }
    422         }
    423 
    424         @Override
    425         public void hold(String callId, Session.Info sessionInfo) {
    426             Log.startSession(sessionInfo, SESSION_HOLD);
    427             try {
    428                 SomeArgs args = SomeArgs.obtain();
    429                 args.arg1 = callId;
    430                 args.arg2 = Log.createSubsession();
    431                 mHandler.obtainMessage(MSG_HOLD, args).sendToTarget();
    432             } finally {
    433                 Log.endSession();
    434             }
    435         }
    436 
    437         @Override
    438         public void unhold(String callId, Session.Info sessionInfo) {
    439             Log.startSession(sessionInfo, SESSION_UNHOLD);
    440             try {
    441                 SomeArgs args = SomeArgs.obtain();
    442                 args.arg1 = callId;
    443                 args.arg2 = Log.createSubsession();
    444                 mHandler.obtainMessage(MSG_UNHOLD, args).sendToTarget();
    445             } finally {
    446                 Log.endSession();
    447             }
    448         }
    449 
    450         @Override
    451         public void onCallAudioStateChanged(String callId, CallAudioState callAudioState,
    452                 Session.Info sessionInfo) {
    453             Log.startSession(sessionInfo, SESSION_CALL_AUDIO_SC);
    454             try {
    455                 SomeArgs args = SomeArgs.obtain();
    456                 args.arg1 = callId;
    457                 args.arg2 = callAudioState;
    458                 args.arg3 = Log.createSubsession();
    459                 mHandler.obtainMessage(MSG_ON_CALL_AUDIO_STATE_CHANGED, args).sendToTarget();
    460             } finally {
    461                 Log.endSession();
    462             }
    463         }
    464 
    465         @Override
    466         public void playDtmfTone(String callId, char digit, Session.Info sessionInfo) {
    467             Log.startSession(sessionInfo, SESSION_PLAY_DTMF);
    468             try {
    469                 SomeArgs args = SomeArgs.obtain();
    470                 args.arg1 = digit;
    471                 args.arg2 = callId;
    472                 args.arg3 = Log.createSubsession();
    473                 mHandler.obtainMessage(MSG_PLAY_DTMF_TONE, args).sendToTarget();
    474             } finally {
    475                 Log.endSession();
    476             }
    477         }
    478 
    479         @Override
    480         public void stopDtmfTone(String callId, Session.Info sessionInfo) {
    481             Log.startSession(sessionInfo, SESSION_STOP_DTMF);
    482             try {
    483                 SomeArgs args = SomeArgs.obtain();
    484                 args.arg1 = callId;
    485                 args.arg2 = Log.createSubsession();
    486                 mHandler.obtainMessage(MSG_STOP_DTMF_TONE, args).sendToTarget();
    487             } finally {
    488                 Log.endSession();
    489             }
    490         }
    491 
    492         @Override
    493         public void conference(String callId1, String callId2, Session.Info sessionInfo) {
    494             Log.startSession(sessionInfo, SESSION_CONFERENCE);
    495             try {
    496                 SomeArgs args = SomeArgs.obtain();
    497                 args.arg1 = callId1;
    498                 args.arg2 = callId2;
    499                 args.arg3 = Log.createSubsession();
    500                 mHandler.obtainMessage(MSG_CONFERENCE, args).sendToTarget();
    501             } finally {
    502                 Log.endSession();
    503             }
    504         }
    505 
    506         @Override
    507         public void splitFromConference(String callId, Session.Info sessionInfo) {
    508             Log.startSession(sessionInfo, SESSION_SPLIT_CONFERENCE);
    509             try {
    510                 SomeArgs args = SomeArgs.obtain();
    511                 args.arg1 = callId;
    512                 args.arg2 = Log.createSubsession();
    513                 mHandler.obtainMessage(MSG_SPLIT_FROM_CONFERENCE, args).sendToTarget();
    514             } finally {
    515                 Log.endSession();
    516             }
    517         }
    518 
    519         @Override
    520         public void mergeConference(String callId, Session.Info sessionInfo) {
    521             Log.startSession(sessionInfo, SESSION_MERGE_CONFERENCE);
    522             try {
    523                 SomeArgs args = SomeArgs.obtain();
    524                 args.arg1 = callId;
    525                 args.arg2 = Log.createSubsession();
    526                 mHandler.obtainMessage(MSG_MERGE_CONFERENCE, args).sendToTarget();
    527             } finally {
    528                 Log.endSession();
    529             }
    530         }
    531 
    532         @Override
    533         public void swapConference(String callId, Session.Info sessionInfo) {
    534             Log.startSession(sessionInfo, SESSION_SWAP_CONFERENCE);
    535             try {
    536                 SomeArgs args = SomeArgs.obtain();
    537                 args.arg1 = callId;
    538                 args.arg2 = Log.createSubsession();
    539                 mHandler.obtainMessage(MSG_SWAP_CONFERENCE, args).sendToTarget();
    540             } finally {
    541                 Log.endSession();
    542             }
    543         }
    544 
    545         @Override
    546         public void onPostDialContinue(String callId, boolean proceed, Session.Info sessionInfo) {
    547             Log.startSession(sessionInfo, SESSION_POST_DIAL_CONT);
    548             try {
    549                 SomeArgs args = SomeArgs.obtain();
    550                 args.arg1 = callId;
    551                 args.arg2 = Log.createSubsession();
    552                 args.argi1 = proceed ? 1 : 0;
    553                 mHandler.obtainMessage(MSG_ON_POST_DIAL_CONTINUE, args).sendToTarget();
    554             } finally {
    555                 Log.endSession();
    556             }
    557         }
    558 
    559         @Override
    560         public void pullExternalCall(String callId, Session.Info sessionInfo) {
    561             Log.startSession(sessionInfo, SESSION_PULL_EXTERNAL_CALL);
    562             try {
    563                 SomeArgs args = SomeArgs.obtain();
    564                 args.arg1 = callId;
    565                 args.arg2 = Log.createSubsession();
    566                 mHandler.obtainMessage(MSG_PULL_EXTERNAL_CALL, args).sendToTarget();
    567             } finally {
    568                 Log.endSession();
    569             }
    570         }
    571 
    572         @Override
    573         public void sendCallEvent(String callId, String event, Bundle extras,
    574                 Session.Info sessionInfo) {
    575             Log.startSession(sessionInfo, SESSION_SEND_CALL_EVENT);
    576             try {
    577                 SomeArgs args = SomeArgs.obtain();
    578                 args.arg1 = callId;
    579                 args.arg2 = event;
    580                 args.arg3 = extras;
    581                 args.arg4 = Log.createSubsession();
    582                 mHandler.obtainMessage(MSG_SEND_CALL_EVENT, args).sendToTarget();
    583             } finally {
    584                 Log.endSession();
    585             }
    586         }
    587 
    588         @Override
    589         public void onExtrasChanged(String callId, Bundle extras, Session.Info sessionInfo) {
    590             Log.startSession(sessionInfo, SESSION_EXTRAS_CHANGED);
    591             try {
    592                 SomeArgs args = SomeArgs.obtain();
    593                 args.arg1 = callId;
    594                 args.arg2 = extras;
    595                 args.arg3 = Log.createSubsession();
    596                 mHandler.obtainMessage(MSG_ON_EXTRAS_CHANGED, args).sendToTarget();
    597             } finally {
    598                 Log.endSession();
    599             }
    600         }
    601 
    602         @Override
    603         public void startRtt(String callId, ParcelFileDescriptor fromInCall,
    604                 ParcelFileDescriptor toInCall, Session.Info sessionInfo) throws RemoteException {
    605             Log.startSession(sessionInfo, SESSION_START_RTT);
    606             try {
    607                 SomeArgs args = SomeArgs.obtain();
    608                 args.arg1 = callId;
    609                 args.arg2 = new Connection.RttTextStream(toInCall, fromInCall);
    610                 args.arg3 = Log.createSubsession();
    611                 mHandler.obtainMessage(MSG_ON_START_RTT, args).sendToTarget();
    612             } finally {
    613                 Log.endSession();
    614             }
    615         }
    616 
    617         @Override
    618         public void stopRtt(String callId, Session.Info sessionInfo) throws RemoteException {
    619             Log.startSession(sessionInfo, SESSION_STOP_RTT);
    620             try {
    621                 SomeArgs args = SomeArgs.obtain();
    622                 args.arg1 = callId;
    623                 args.arg2 = Log.createSubsession();
    624                 mHandler.obtainMessage(MSG_ON_STOP_RTT, args).sendToTarget();
    625             } finally {
    626                 Log.endSession();
    627             }
    628         }
    629 
    630         @Override
    631         public void respondToRttUpgradeRequest(String callId, ParcelFileDescriptor fromInCall,
    632                 ParcelFileDescriptor toInCall, Session.Info sessionInfo) throws RemoteException {
    633             Log.startSession(sessionInfo, SESSION_RTT_UPGRADE_RESPONSE);
    634             try {
    635                 SomeArgs args = SomeArgs.obtain();
    636                 args.arg1 = callId;
    637                 if (toInCall == null || fromInCall == null) {
    638                     args.arg2 = null;
    639                 } else {
    640                     args.arg2 = new Connection.RttTextStream(toInCall, fromInCall);
    641                 }
    642                 args.arg3 = Log.createSubsession();
    643                 mHandler.obtainMessage(MSG_RTT_UPGRADE_RESPONSE, args).sendToTarget();
    644             } finally {
    645                 Log.endSession();
    646             }
    647         }
    648 
    649         @Override
    650         public void connectionServiceFocusLost(Session.Info sessionInfo) throws RemoteException {
    651             Log.startSession(sessionInfo, SESSION_CONNECTION_SERVICE_FOCUS_LOST);
    652             try {
    653                 mHandler.obtainMessage(MSG_CONNECTION_SERVICE_FOCUS_LOST).sendToTarget();
    654             } finally {
    655                 Log.endSession();
    656             }
    657         }
    658 
    659         @Override
    660         public void connectionServiceFocusGained(Session.Info sessionInfo) throws RemoteException {
    661             Log.startSession(sessionInfo, SESSION_CONNECTION_SERVICE_FOCUS_GAINED);
    662             try {
    663                 mHandler.obtainMessage(MSG_CONNECTION_SERVICE_FOCUS_GAINED).sendToTarget();
    664             } finally {
    665                 Log.endSession();
    666             }
    667         }
    668     };
    669 
    670     private final Handler mHandler = new Handler(Looper.getMainLooper()) {
    671         @Override
    672         public void handleMessage(Message msg) {
    673             switch (msg.what) {
    674                 case MSG_ADD_CONNECTION_SERVICE_ADAPTER: {
    675                     SomeArgs args = (SomeArgs) msg.obj;
    676                     try {
    677                         IConnectionServiceAdapter adapter = (IConnectionServiceAdapter) args.arg1;
    678                         Log.continueSession((Session) args.arg2,
    679                                 SESSION_HANDLER + SESSION_ADD_CS_ADAPTER);
    680                         mAdapter.addAdapter(adapter);
    681                         onAdapterAttached();
    682                     } finally {
    683                         args.recycle();
    684                         Log.endSession();
    685                     }
    686                     break;
    687                 }
    688                 case MSG_REMOVE_CONNECTION_SERVICE_ADAPTER: {
    689                     SomeArgs args = (SomeArgs) msg.obj;
    690                     try {
    691                         Log.continueSession((Session) args.arg2,
    692                                 SESSION_HANDLER + SESSION_REMOVE_CS_ADAPTER);
    693                         mAdapter.removeAdapter((IConnectionServiceAdapter) args.arg1);
    694                     } finally {
    695                         args.recycle();
    696                         Log.endSession();
    697                     }
    698                     break;
    699                 }
    700                 case MSG_CREATE_CONNECTION: {
    701                     SomeArgs args = (SomeArgs) msg.obj;
    702                     Log.continueSession((Session) args.arg4, SESSION_HANDLER + SESSION_CREATE_CONN);
    703                     try {
    704                         final PhoneAccountHandle connectionManagerPhoneAccount =
    705                                 (PhoneAccountHandle) args.arg1;
    706                         final String id = (String) args.arg2;
    707                         final ConnectionRequest request = (ConnectionRequest) args.arg3;
    708                         final boolean isIncoming = args.argi1 == 1;
    709                         final boolean isUnknown = args.argi2 == 1;
    710                         if (!mAreAccountsInitialized) {
    711                             Log.d(this, "Enqueueing pre-init request %s", id);
    712                             mPreInitializationConnectionRequests.add(
    713                                     new android.telecom.Logging.Runnable(
    714                                             SESSION_HANDLER + SESSION_CREATE_CONN + ".pICR",
    715                                             null /*lock*/) {
    716                                 @Override
    717                                 public void loggedRun() {
    718                                     createConnection(
    719                                             connectionManagerPhoneAccount,
    720                                             id,
    721                                             request,
    722                                             isIncoming,
    723                                             isUnknown);
    724                                 }
    725                             }.prepare());
    726                         } else {
    727                             createConnection(
    728                                     connectionManagerPhoneAccount,
    729                                     id,
    730                                     request,
    731                                     isIncoming,
    732                                     isUnknown);
    733                         }
    734                     } finally {
    735                         args.recycle();
    736                         Log.endSession();
    737                     }
    738                     break;
    739                 }
    740                 case MSG_CREATE_CONNECTION_COMPLETE: {
    741                     SomeArgs args = (SomeArgs) msg.obj;
    742                     Log.continueSession((Session) args.arg2,
    743                             SESSION_HANDLER + SESSION_CREATE_CONN_COMPLETE);
    744                     try {
    745                         final String id = (String) args.arg1;
    746                         if (!mAreAccountsInitialized) {
    747                             Log.d(this, "Enqueueing pre-init request %s", id);
    748                             mPreInitializationConnectionRequests.add(
    749                                     new android.telecom.Logging.Runnable(
    750                                             SESSION_HANDLER + SESSION_CREATE_CONN_COMPLETE
    751                                                     + ".pICR",
    752                                             null /*lock*/) {
    753                                         @Override
    754                                         public void loggedRun() {
    755                                             notifyCreateConnectionComplete(id);
    756                                         }
    757                                     }.prepare());
    758                         } else {
    759                             notifyCreateConnectionComplete(id);
    760                         }
    761                     } finally {
    762                         args.recycle();
    763                         Log.endSession();
    764                     }
    765                     break;
    766                 }
    767                 case MSG_CREATE_CONNECTION_FAILED: {
    768                     SomeArgs args = (SomeArgs) msg.obj;
    769                     Log.continueSession((Session) args.arg3, SESSION_HANDLER +
    770                             SESSION_CREATE_CONN_FAILED);
    771                     try {
    772                         final String id = (String) args.arg1;
    773                         final ConnectionRequest request = (ConnectionRequest) args.arg2;
    774                         final boolean isIncoming = args.argi1 == 1;
    775                         final PhoneAccountHandle connectionMgrPhoneAccount =
    776                                 (PhoneAccountHandle) args.arg4;
    777                         if (!mAreAccountsInitialized) {
    778                             Log.d(this, "Enqueueing pre-init request %s", id);
    779                             mPreInitializationConnectionRequests.add(
    780                                     new android.telecom.Logging.Runnable(
    781                                             SESSION_HANDLER + SESSION_CREATE_CONN_FAILED + ".pICR",
    782                                             null /*lock*/) {
    783                                         @Override
    784                                         public void loggedRun() {
    785                                             createConnectionFailed(connectionMgrPhoneAccount, id,
    786                                                     request, isIncoming);
    787                                         }
    788                                     }.prepare());
    789                         } else {
    790                             Log.i(this, "createConnectionFailed %s", id);
    791                             createConnectionFailed(connectionMgrPhoneAccount, id, request,
    792                                     isIncoming);
    793                         }
    794                     } finally {
    795                         args.recycle();
    796                         Log.endSession();
    797                     }
    798                     break;
    799                 }
    800                 case MSG_HANDOVER_FAILED: {
    801                     SomeArgs args = (SomeArgs) msg.obj;
    802                     Log.continueSession((Session) args.arg3, SESSION_HANDLER +
    803                             SESSION_HANDOVER_FAILED);
    804                     try {
    805                         final String id = (String) args.arg1;
    806                         final ConnectionRequest request = (ConnectionRequest) args.arg2;
    807                         final int reason = (int) args.arg4;
    808                         if (!mAreAccountsInitialized) {
    809                             Log.d(this, "Enqueueing pre-init request %s", id);
    810                             mPreInitializationConnectionRequests.add(
    811                                     new android.telecom.Logging.Runnable(
    812                                             SESSION_HANDLER
    813                                                     + SESSION_HANDOVER_FAILED + ".pICR",
    814                                             null /*lock*/) {
    815                                         @Override
    816                                         public void loggedRun() {
    817                                             handoverFailed(id, request, reason);
    818                                         }
    819                                     }.prepare());
    820                         } else {
    821                             Log.i(this, "createConnectionFailed %s", id);
    822                             handoverFailed(id, request, reason);
    823                         }
    824                     } finally {
    825                         args.recycle();
    826                         Log.endSession();
    827                     }
    828                     break;
    829                 }
    830                 case MSG_ABORT: {
    831                     SomeArgs args = (SomeArgs) msg.obj;
    832                     Log.continueSession((Session) args.arg2, SESSION_HANDLER + SESSION_ABORT);
    833                     try {
    834                         abort((String) args.arg1);
    835                     } finally {
    836                         args.recycle();
    837                         Log.endSession();
    838                     }
    839                     break;
    840                 }
    841                 case MSG_ANSWER: {
    842                     SomeArgs args = (SomeArgs) msg.obj;
    843                     Log.continueSession((Session) args.arg2, SESSION_HANDLER + SESSION_ANSWER);
    844                     try {
    845                         answer((String) args.arg1);
    846                     } finally {
    847                         args.recycle();
    848                         Log.endSession();
    849                     }
    850                     break;
    851                 }
    852                 case MSG_ANSWER_VIDEO: {
    853                     SomeArgs args = (SomeArgs) msg.obj;
    854                     Log.continueSession((Session) args.arg2,
    855                             SESSION_HANDLER + SESSION_ANSWER_VIDEO);
    856                     try {
    857                         String callId = (String) args.arg1;
    858                         int videoState = args.argi1;
    859                         answerVideo(callId, videoState);
    860                     } finally {
    861                         args.recycle();
    862                         Log.endSession();
    863                     }
    864                     break;
    865                 }
    866                 case MSG_DEFLECT: {
    867                     SomeArgs args = (SomeArgs) msg.obj;
    868                     Log.continueSession((Session) args.arg3, SESSION_HANDLER + SESSION_DEFLECT);
    869                     try {
    870                         deflect((String) args.arg1, (Uri) args.arg2);
    871                     } finally {
    872                         args.recycle();
    873                         Log.endSession();
    874                     }
    875                     break;
    876                 }
    877                 case MSG_REJECT: {
    878                     SomeArgs args = (SomeArgs) msg.obj;
    879                     Log.continueSession((Session) args.arg2, SESSION_HANDLER + SESSION_REJECT);
    880                     try {
    881                         reject((String) args.arg1);
    882                     } finally {
    883                         args.recycle();
    884                         Log.endSession();
    885                     }
    886                     break;
    887                 }
    888                 case MSG_REJECT_WITH_MESSAGE: {
    889                     SomeArgs args = (SomeArgs) msg.obj;
    890                     Log.continueSession((Session) args.arg3,
    891                             SESSION_HANDLER + SESSION_REJECT_MESSAGE);
    892                     try {
    893                         reject((String) args.arg1, (String) args.arg2);
    894                     } finally {
    895                         args.recycle();
    896                         Log.endSession();
    897                     }
    898                     break;
    899                 }
    900                 case MSG_DISCONNECT: {
    901                     SomeArgs args = (SomeArgs) msg.obj;
    902                     Log.continueSession((Session) args.arg2, SESSION_HANDLER + SESSION_DISCONNECT);
    903                     try {
    904                         disconnect((String) args.arg1);
    905                     } finally {
    906                         args.recycle();
    907                         Log.endSession();
    908                     }
    909                     break;
    910                 }
    911                 case MSG_SILENCE: {
    912                     SomeArgs args = (SomeArgs) msg.obj;
    913                     Log.continueSession((Session) args.arg2, SESSION_HANDLER + SESSION_SILENCE);
    914                     try {
    915                         silence((String) args.arg1);
    916                     } finally {
    917                         args.recycle();
    918                         Log.endSession();
    919                     }
    920                     break;
    921                 }
    922                 case MSG_HOLD: {
    923                     SomeArgs args = (SomeArgs) msg.obj;
    924                     Log.continueSession((Session) args.arg2, SESSION_HANDLER + SESSION_REJECT);
    925                     try {
    926                         hold((String) args.arg1);
    927                     } finally {
    928                         args.recycle();
    929                         Log.endSession();
    930                     }
    931                     break;
    932                 }
    933                 case MSG_UNHOLD: {
    934                     SomeArgs args = (SomeArgs) msg.obj;
    935                     Log.continueSession((Session) args.arg2, SESSION_HANDLER + SESSION_UNHOLD);
    936                     try {
    937                         unhold((String) args.arg1);
    938                     } finally {
    939                         args.recycle();
    940                         Log.endSession();
    941                     }
    942                     break;
    943                 }
    944                 case MSG_ON_CALL_AUDIO_STATE_CHANGED: {
    945                     SomeArgs args = (SomeArgs) msg.obj;
    946                     Log.continueSession((Session) args.arg3,
    947                             SESSION_HANDLER + SESSION_CALL_AUDIO_SC);
    948                     try {
    949                         String callId = (String) args.arg1;
    950                         CallAudioState audioState = (CallAudioState) args.arg2;
    951                         onCallAudioStateChanged(callId, new CallAudioState(audioState));
    952                     } finally {
    953                         args.recycle();
    954                         Log.endSession();
    955                     }
    956                     break;
    957                 }
    958                 case MSG_PLAY_DTMF_TONE: {
    959                     SomeArgs args = (SomeArgs) msg.obj;
    960                     try {
    961                         Log.continueSession((Session) args.arg3,
    962                                 SESSION_HANDLER + SESSION_PLAY_DTMF);
    963                         playDtmfTone((String) args.arg2, (char) args.arg1);
    964                     } finally {
    965                         args.recycle();
    966                         Log.endSession();
    967                     }
    968                     break;
    969                 }
    970                 case MSG_STOP_DTMF_TONE: {
    971                     SomeArgs args = (SomeArgs) msg.obj;
    972                     try {
    973                         Log.continueSession((Session) args.arg2,
    974                                 SESSION_HANDLER + SESSION_STOP_DTMF);
    975                         stopDtmfTone((String) args.arg1);
    976                     } finally {
    977                         args.recycle();
    978                         Log.endSession();
    979                     }
    980                     break;
    981                 }
    982                 case MSG_CONFERENCE: {
    983                     SomeArgs args = (SomeArgs) msg.obj;
    984                     try {
    985                         Log.continueSession((Session) args.arg3,
    986                                 SESSION_HANDLER + SESSION_CONFERENCE);
    987                         String callId1 = (String) args.arg1;
    988                         String callId2 = (String) args.arg2;
    989                         conference(callId1, callId2);
    990                     } finally {
    991                         args.recycle();
    992                         Log.endSession();
    993                     }
    994                     break;
    995                 }
    996                 case MSG_SPLIT_FROM_CONFERENCE: {
    997                     SomeArgs args = (SomeArgs) msg.obj;
    998                     try {
    999                         Log.continueSession((Session) args.arg2,
   1000                                 SESSION_HANDLER + SESSION_SPLIT_CONFERENCE);
   1001                         splitFromConference((String) args.arg1);
   1002                     } finally {
   1003                         args.recycle();
   1004                         Log.endSession();
   1005                     }
   1006                     break;
   1007                 }
   1008                 case MSG_MERGE_CONFERENCE: {
   1009                     SomeArgs args = (SomeArgs) msg.obj;
   1010                     try {
   1011                         Log.continueSession((Session) args.arg2,
   1012                                 SESSION_HANDLER + SESSION_MERGE_CONFERENCE);
   1013                         mergeConference((String) args.arg1);
   1014                     } finally {
   1015                         args.recycle();
   1016                         Log.endSession();
   1017                     }
   1018                     break;
   1019                 }
   1020                 case MSG_SWAP_CONFERENCE: {
   1021                     SomeArgs args = (SomeArgs) msg.obj;
   1022                     try {
   1023                         Log.continueSession((Session) args.arg2,
   1024                                 SESSION_HANDLER + SESSION_SWAP_CONFERENCE);
   1025                         swapConference((String) args.arg1);
   1026                     } finally {
   1027                         args.recycle();
   1028                         Log.endSession();
   1029                     }
   1030                     break;
   1031                 }
   1032                 case MSG_ON_POST_DIAL_CONTINUE: {
   1033                     SomeArgs args = (SomeArgs) msg.obj;
   1034                     try {
   1035                         Log.continueSession((Session) args.arg2,
   1036                                 SESSION_HANDLER + SESSION_POST_DIAL_CONT);
   1037                         String callId = (String) args.arg1;
   1038                         boolean proceed = (args.argi1 == 1);
   1039                         onPostDialContinue(callId, proceed);
   1040                     } finally {
   1041                         args.recycle();
   1042                         Log.endSession();
   1043                     }
   1044                     break;
   1045                 }
   1046                 case MSG_PULL_EXTERNAL_CALL: {
   1047                     SomeArgs args = (SomeArgs) msg.obj;
   1048                     try {
   1049                         Log.continueSession((Session) args.arg2,
   1050                                 SESSION_HANDLER + SESSION_PULL_EXTERNAL_CALL);
   1051                         pullExternalCall((String) args.arg1);
   1052                     } finally {
   1053                         args.recycle();
   1054                         Log.endSession();
   1055                     }
   1056                     break;
   1057                 }
   1058                 case MSG_SEND_CALL_EVENT: {
   1059                     SomeArgs args = (SomeArgs) msg.obj;
   1060                     try {
   1061                         Log.continueSession((Session) args.arg4,
   1062                                 SESSION_HANDLER + SESSION_SEND_CALL_EVENT);
   1063                         String callId = (String) args.arg1;
   1064                         String event = (String) args.arg2;
   1065                         Bundle extras = (Bundle) args.arg3;
   1066                         sendCallEvent(callId, event, extras);
   1067                     } finally {
   1068                         args.recycle();
   1069                         Log.endSession();
   1070                     }
   1071                     break;
   1072                 }
   1073                 case MSG_HANDOVER_COMPLETE: {
   1074                     SomeArgs args = (SomeArgs) msg.obj;
   1075                     try {
   1076                         Log.continueSession((Session) args.arg2,
   1077                                 SESSION_HANDLER + SESSION_HANDOVER_COMPLETE);
   1078                         String callId = (String) args.arg1;
   1079                         notifyHandoverComplete(callId);
   1080                     } finally {
   1081                         args.recycle();
   1082                         Log.endSession();
   1083                     }
   1084                     break;
   1085                 }
   1086                 case MSG_ON_EXTRAS_CHANGED: {
   1087                     SomeArgs args = (SomeArgs) msg.obj;
   1088                     try {
   1089                         Log.continueSession((Session) args.arg3,
   1090                                 SESSION_HANDLER + SESSION_EXTRAS_CHANGED);
   1091                         String callId = (String) args.arg1;
   1092                         Bundle extras = (Bundle) args.arg2;
   1093                         handleExtrasChanged(callId, extras);
   1094                     } finally {
   1095                         args.recycle();
   1096                         Log.endSession();
   1097                     }
   1098                     break;
   1099                 }
   1100                 case MSG_ON_START_RTT: {
   1101                     SomeArgs args = (SomeArgs) msg.obj;
   1102                     try {
   1103                         Log.continueSession((Session) args.arg3,
   1104                                 SESSION_HANDLER + SESSION_START_RTT);
   1105                         String callId = (String) args.arg1;
   1106                         Connection.RttTextStream rttTextStream =
   1107                                 (Connection.RttTextStream) args.arg2;
   1108                         startRtt(callId, rttTextStream);
   1109                     } finally {
   1110                         args.recycle();
   1111                         Log.endSession();
   1112                     }
   1113                     break;
   1114                 }
   1115                 case MSG_ON_STOP_RTT: {
   1116                     SomeArgs args = (SomeArgs) msg.obj;
   1117                     try {
   1118                         Log.continueSession((Session) args.arg2,
   1119                                 SESSION_HANDLER + SESSION_STOP_RTT);
   1120                         String callId = (String) args.arg1;
   1121                         stopRtt(callId);
   1122                     } finally {
   1123                         args.recycle();
   1124                         Log.endSession();
   1125                     }
   1126                     break;
   1127                 }
   1128                 case MSG_RTT_UPGRADE_RESPONSE: {
   1129                     SomeArgs args = (SomeArgs) msg.obj;
   1130                     try {
   1131                         Log.continueSession((Session) args.arg3,
   1132                                 SESSION_HANDLER + SESSION_RTT_UPGRADE_RESPONSE);
   1133                         String callId = (String) args.arg1;
   1134                         Connection.RttTextStream rttTextStream =
   1135                                 (Connection.RttTextStream) args.arg2;
   1136                         handleRttUpgradeResponse(callId, rttTextStream);
   1137                     } finally {
   1138                         args.recycle();
   1139                         Log.endSession();
   1140                     }
   1141                     break;
   1142                 }
   1143                 case MSG_CONNECTION_SERVICE_FOCUS_GAINED:
   1144                     onConnectionServiceFocusGained();
   1145                     break;
   1146                 case MSG_CONNECTION_SERVICE_FOCUS_LOST:
   1147                     onConnectionServiceFocusLost();
   1148                     break;
   1149                 default:
   1150                     break;
   1151             }
   1152         }
   1153     };
   1154 
   1155     private final Conference.Listener mConferenceListener = new Conference.Listener() {
   1156         @Override
   1157         public void onStateChanged(Conference conference, int oldState, int newState) {
   1158             String id = mIdByConference.get(conference);
   1159             switch (newState) {
   1160                 case Connection.STATE_ACTIVE:
   1161                     mAdapter.setActive(id);
   1162                     break;
   1163                 case Connection.STATE_HOLDING:
   1164                     mAdapter.setOnHold(id);
   1165                     break;
   1166                 case Connection.STATE_DISCONNECTED:
   1167                     // handled by onDisconnected
   1168                     break;
   1169             }
   1170         }
   1171 
   1172         @Override
   1173         public void onDisconnected(Conference conference, DisconnectCause disconnectCause) {
   1174             String id = mIdByConference.get(conference);
   1175             mAdapter.setDisconnected(id, disconnectCause);
   1176         }
   1177 
   1178         @Override
   1179         public void onConnectionAdded(Conference conference, Connection connection) {
   1180         }
   1181 
   1182         @Override
   1183         public void onConnectionRemoved(Conference conference, Connection connection) {
   1184         }
   1185 
   1186         @Override
   1187         public void onConferenceableConnectionsChanged(
   1188                 Conference conference, List<Connection> conferenceableConnections) {
   1189             mAdapter.setConferenceableConnections(
   1190                     mIdByConference.get(conference),
   1191                     createConnectionIdList(conferenceableConnections));
   1192         }
   1193 
   1194         @Override
   1195         public void onDestroyed(Conference conference) {
   1196             removeConference(conference);
   1197         }
   1198 
   1199         @Override
   1200         public void onConnectionCapabilitiesChanged(
   1201                 Conference conference,
   1202                 int connectionCapabilities) {
   1203             String id = mIdByConference.get(conference);
   1204             Log.d(this, "call capabilities: conference: %s",
   1205                     Connection.capabilitiesToString(connectionCapabilities));
   1206             mAdapter.setConnectionCapabilities(id, connectionCapabilities);
   1207         }
   1208 
   1209         @Override
   1210         public void onConnectionPropertiesChanged(
   1211                 Conference conference,
   1212                 int connectionProperties) {
   1213             String id = mIdByConference.get(conference);
   1214             Log.d(this, "call capabilities: conference: %s",
   1215                     Connection.propertiesToString(connectionProperties));
   1216             mAdapter.setConnectionProperties(id, connectionProperties);
   1217         }
   1218 
   1219         @Override
   1220         public void onVideoStateChanged(Conference c, int videoState) {
   1221             String id = mIdByConference.get(c);
   1222             Log.d(this, "onVideoStateChanged set video state %d", videoState);
   1223             mAdapter.setVideoState(id, videoState);
   1224         }
   1225 
   1226         @Override
   1227         public void onVideoProviderChanged(Conference c, Connection.VideoProvider videoProvider) {
   1228             String id = mIdByConference.get(c);
   1229             Log.d(this, "onVideoProviderChanged: Connection: %s, VideoProvider: %s", c,
   1230                     videoProvider);
   1231             mAdapter.setVideoProvider(id, videoProvider);
   1232         }
   1233 
   1234         @Override
   1235         public void onStatusHintsChanged(Conference conference, StatusHints statusHints) {
   1236             String id = mIdByConference.get(conference);
   1237             if (id != null) {
   1238                 mAdapter.setStatusHints(id, statusHints);
   1239             }
   1240         }
   1241 
   1242         @Override
   1243         public void onExtrasChanged(Conference c, Bundle extras) {
   1244             String id = mIdByConference.get(c);
   1245             if (id != null) {
   1246                 mAdapter.putExtras(id, extras);
   1247             }
   1248         }
   1249 
   1250         @Override
   1251         public void onExtrasRemoved(Conference c, List<String> keys) {
   1252             String id = mIdByConference.get(c);
   1253             if (id != null) {
   1254                 mAdapter.removeExtras(id, keys);
   1255             }
   1256         }
   1257     };
   1258 
   1259     private final Connection.Listener mConnectionListener = new Connection.Listener() {
   1260         @Override
   1261         public void onStateChanged(Connection c, int state) {
   1262             String id = mIdByConnection.get(c);
   1263             Log.d(this, "Adapter set state %s %s", id, Connection.stateToString(state));
   1264             switch (state) {
   1265                 case Connection.STATE_ACTIVE:
   1266                     mAdapter.setActive(id);
   1267                     break;
   1268                 case Connection.STATE_DIALING:
   1269                     mAdapter.setDialing(id);
   1270                     break;
   1271                 case Connection.STATE_PULLING_CALL:
   1272                     mAdapter.setPulling(id);
   1273                     break;
   1274                 case Connection.STATE_DISCONNECTED:
   1275                     // Handled in onDisconnected()
   1276                     break;
   1277                 case Connection.STATE_HOLDING:
   1278                     mAdapter.setOnHold(id);
   1279                     break;
   1280                 case Connection.STATE_NEW:
   1281                     // Nothing to tell Telecom
   1282                     break;
   1283                 case Connection.STATE_RINGING:
   1284                     mAdapter.setRinging(id);
   1285                     break;
   1286             }
   1287         }
   1288 
   1289         @Override
   1290         public void onDisconnected(Connection c, DisconnectCause disconnectCause) {
   1291             String id = mIdByConnection.get(c);
   1292             Log.d(this, "Adapter set disconnected %s", disconnectCause);
   1293             mAdapter.setDisconnected(id, disconnectCause);
   1294         }
   1295 
   1296         @Override
   1297         public void onVideoStateChanged(Connection c, int videoState) {
   1298             String id = mIdByConnection.get(c);
   1299             Log.d(this, "Adapter set video state %d", videoState);
   1300             mAdapter.setVideoState(id, videoState);
   1301         }
   1302 
   1303         @Override
   1304         public void onAddressChanged(Connection c, Uri address, int presentation) {
   1305             String id = mIdByConnection.get(c);
   1306             mAdapter.setAddress(id, address, presentation);
   1307         }
   1308 
   1309         @Override
   1310         public void onCallerDisplayNameChanged(
   1311                 Connection c, String callerDisplayName, int presentation) {
   1312             String id = mIdByConnection.get(c);
   1313             mAdapter.setCallerDisplayName(id, callerDisplayName, presentation);
   1314         }
   1315 
   1316         @Override
   1317         public void onDestroyed(Connection c) {
   1318             removeConnection(c);
   1319         }
   1320 
   1321         @Override
   1322         public void onPostDialWait(Connection c, String remaining) {
   1323             String id = mIdByConnection.get(c);
   1324             Log.d(this, "Adapter onPostDialWait %s, %s", c, remaining);
   1325             mAdapter.onPostDialWait(id, remaining);
   1326         }
   1327 
   1328         @Override
   1329         public void onPostDialChar(Connection c, char nextChar) {
   1330             String id = mIdByConnection.get(c);
   1331             Log.d(this, "Adapter onPostDialChar %s, %s", c, nextChar);
   1332             mAdapter.onPostDialChar(id, nextChar);
   1333         }
   1334 
   1335         @Override
   1336         public void onRingbackRequested(Connection c, boolean ringback) {
   1337             String id = mIdByConnection.get(c);
   1338             Log.d(this, "Adapter onRingback %b", ringback);
   1339             mAdapter.setRingbackRequested(id, ringback);
   1340         }
   1341 
   1342         @Override
   1343         public void onConnectionCapabilitiesChanged(Connection c, int capabilities) {
   1344             String id = mIdByConnection.get(c);
   1345             Log.d(this, "capabilities: parcelableconnection: %s",
   1346                     Connection.capabilitiesToString(capabilities));
   1347             mAdapter.setConnectionCapabilities(id, capabilities);
   1348         }
   1349 
   1350         @Override
   1351         public void onConnectionPropertiesChanged(Connection c, int properties) {
   1352             String id = mIdByConnection.get(c);
   1353             Log.d(this, "properties: parcelableconnection: %s",
   1354                     Connection.propertiesToString(properties));
   1355             mAdapter.setConnectionProperties(id, properties);
   1356         }
   1357 
   1358         @Override
   1359         public void onVideoProviderChanged(Connection c, Connection.VideoProvider videoProvider) {
   1360             String id = mIdByConnection.get(c);
   1361             Log.d(this, "onVideoProviderChanged: Connection: %s, VideoProvider: %s", c,
   1362                     videoProvider);
   1363             mAdapter.setVideoProvider(id, videoProvider);
   1364         }
   1365 
   1366         @Override
   1367         public void onAudioModeIsVoipChanged(Connection c, boolean isVoip) {
   1368             String id = mIdByConnection.get(c);
   1369             mAdapter.setIsVoipAudioMode(id, isVoip);
   1370         }
   1371 
   1372         @Override
   1373         public void onStatusHintsChanged(Connection c, StatusHints statusHints) {
   1374             String id = mIdByConnection.get(c);
   1375             mAdapter.setStatusHints(id, statusHints);
   1376         }
   1377 
   1378         @Override
   1379         public void onConferenceablesChanged(
   1380                 Connection connection, List<Conferenceable> conferenceables) {
   1381             mAdapter.setConferenceableConnections(
   1382                     mIdByConnection.get(connection),
   1383                     createIdList(conferenceables));
   1384         }
   1385 
   1386         @Override
   1387         public void onConferenceChanged(Connection connection, Conference conference) {
   1388             String id = mIdByConnection.get(connection);
   1389             if (id != null) {
   1390                 String conferenceId = null;
   1391                 if (conference != null) {
   1392                     conferenceId = mIdByConference.get(conference);
   1393                 }
   1394                 mAdapter.setIsConferenced(id, conferenceId);
   1395             }
   1396         }
   1397 
   1398         @Override
   1399         public void onConferenceMergeFailed(Connection connection) {
   1400             String id = mIdByConnection.get(connection);
   1401             if (id != null) {
   1402                 mAdapter.onConferenceMergeFailed(id);
   1403             }
   1404         }
   1405 
   1406         @Override
   1407         public void onExtrasChanged(Connection c, Bundle extras) {
   1408             String id = mIdByConnection.get(c);
   1409             if (id != null) {
   1410                 mAdapter.putExtras(id, extras);
   1411             }
   1412         }
   1413 
   1414         @Override
   1415         public void onExtrasRemoved(Connection c, List<String> keys) {
   1416             String id = mIdByConnection.get(c);
   1417             if (id != null) {
   1418                 mAdapter.removeExtras(id, keys);
   1419             }
   1420         }
   1421 
   1422         @Override
   1423         public void onConnectionEvent(Connection connection, String event, Bundle extras) {
   1424             String id = mIdByConnection.get(connection);
   1425             if (id != null) {
   1426                 mAdapter.onConnectionEvent(id, event, extras);
   1427             }
   1428         }
   1429 
   1430         @Override
   1431         public void onAudioRouteChanged(Connection c, int audioRoute, String bluetoothAddress) {
   1432             String id = mIdByConnection.get(c);
   1433             if (id != null) {
   1434                 mAdapter.setAudioRoute(id, audioRoute, bluetoothAddress);
   1435             }
   1436         }
   1437 
   1438         @Override
   1439         public void onRttInitiationSuccess(Connection c) {
   1440             String id = mIdByConnection.get(c);
   1441             if (id != null) {
   1442                 mAdapter.onRttInitiationSuccess(id);
   1443             }
   1444         }
   1445 
   1446         @Override
   1447         public void onRttInitiationFailure(Connection c, int reason) {
   1448             String id = mIdByConnection.get(c);
   1449             if (id != null) {
   1450                 mAdapter.onRttInitiationFailure(id, reason);
   1451             }
   1452         }
   1453 
   1454         @Override
   1455         public void onRttSessionRemotelyTerminated(Connection c) {
   1456             String id = mIdByConnection.get(c);
   1457             if (id != null) {
   1458                 mAdapter.onRttSessionRemotelyTerminated(id);
   1459             }
   1460         }
   1461 
   1462         @Override
   1463         public void onRemoteRttRequest(Connection c) {
   1464             String id = mIdByConnection.get(c);
   1465             if (id != null) {
   1466                 mAdapter.onRemoteRttRequest(id);
   1467             }
   1468         }
   1469 
   1470         @Override
   1471         public void onPhoneAccountChanged(Connection c, PhoneAccountHandle pHandle) {
   1472             String id = mIdByConnection.get(c);
   1473             if (id != null) {
   1474                 mAdapter.onPhoneAccountChanged(id, pHandle);
   1475             }
   1476         }
   1477     };
   1478 
   1479     /** {@inheritDoc} */
   1480     @Override
   1481     public final IBinder onBind(Intent intent) {
   1482         return mBinder;
   1483     }
   1484 
   1485     /** {@inheritDoc} */
   1486     @Override
   1487     public boolean onUnbind(Intent intent) {
   1488         endAllConnections();
   1489         return super.onUnbind(intent);
   1490     }
   1491 
   1492     /**
   1493      * This can be used by telecom to either create a new outgoing call or attach to an existing
   1494      * incoming call. In either case, telecom will cycle through a set of services and call
   1495      * createConnection util a connection service cancels the process or completes it successfully.
   1496      */
   1497     private void createConnection(
   1498             final PhoneAccountHandle callManagerAccount,
   1499             final String callId,
   1500             final ConnectionRequest request,
   1501             boolean isIncoming,
   1502             boolean isUnknown) {
   1503         boolean isLegacyHandover = request.getExtras() != null &&
   1504                 request.getExtras().getBoolean(TelecomManager.EXTRA_IS_HANDOVER, false);
   1505         boolean isHandover = request.getExtras() != null && request.getExtras().getBoolean(
   1506                 TelecomManager.EXTRA_IS_HANDOVER_CONNECTION, false);
   1507         Log.d(this, "createConnection, callManagerAccount: %s, callId: %s, request: %s, " +
   1508                         "isIncoming: %b, isUnknown: %b, isLegacyHandover: %b, isHandover: %b",
   1509                 callManagerAccount, callId, request, isIncoming, isUnknown, isLegacyHandover,
   1510                 isHandover);
   1511 
   1512         Connection connection = null;
   1513         if (isHandover) {
   1514             PhoneAccountHandle fromPhoneAccountHandle = request.getExtras() != null
   1515                     ? (PhoneAccountHandle) request.getExtras().getParcelable(
   1516                     TelecomManager.EXTRA_HANDOVER_FROM_PHONE_ACCOUNT) : null;
   1517             if (!isIncoming) {
   1518                 connection = onCreateOutgoingHandoverConnection(fromPhoneAccountHandle, request);
   1519             } else {
   1520                 connection = onCreateIncomingHandoverConnection(fromPhoneAccountHandle, request);
   1521             }
   1522         } else {
   1523             connection = isUnknown ? onCreateUnknownConnection(callManagerAccount, request)
   1524                     : isIncoming ? onCreateIncomingConnection(callManagerAccount, request)
   1525                     : onCreateOutgoingConnection(callManagerAccount, request);
   1526         }
   1527         Log.d(this, "createConnection, connection: %s", connection);
   1528         if (connection == null) {
   1529             Log.i(this, "createConnection, implementation returned null connection.");
   1530             connection = Connection.createFailedConnection(
   1531                     new DisconnectCause(DisconnectCause.ERROR, "IMPL_RETURNED_NULL_CONNECTION"));
   1532         }
   1533 
   1534         connection.setTelecomCallId(callId);
   1535         if (connection.getState() != Connection.STATE_DISCONNECTED) {
   1536             addConnection(request.getAccountHandle(), callId, connection);
   1537         }
   1538 
   1539         Uri address = connection.getAddress();
   1540         String number = address == null ? "null" : address.getSchemeSpecificPart();
   1541         Log.v(this, "createConnection, number: %s, state: %s, capabilities: %s, properties: %s",
   1542                 Connection.toLogSafePhoneNumber(number),
   1543                 Connection.stateToString(connection.getState()),
   1544                 Connection.capabilitiesToString(connection.getConnectionCapabilities()),
   1545                 Connection.propertiesToString(connection.getConnectionProperties()));
   1546 
   1547         Log.d(this, "createConnection, calling handleCreateConnectionSuccessful %s", callId);
   1548         mAdapter.handleCreateConnectionComplete(
   1549                 callId,
   1550                 request,
   1551                 new ParcelableConnection(
   1552                         request.getAccountHandle(),
   1553                         connection.getState(),
   1554                         connection.getConnectionCapabilities(),
   1555                         connection.getConnectionProperties(),
   1556                         connection.getSupportedAudioRoutes(),
   1557                         connection.getAddress(),
   1558                         connection.getAddressPresentation(),
   1559                         connection.getCallerDisplayName(),
   1560                         connection.getCallerDisplayNamePresentation(),
   1561                         connection.getVideoProvider() == null ?
   1562                                 null : connection.getVideoProvider().getInterface(),
   1563                         connection.getVideoState(),
   1564                         connection.isRingbackRequested(),
   1565                         connection.getAudioModeIsVoip(),
   1566                         connection.getConnectTimeMillis(),
   1567                         connection.getConnectElapsedTimeMillis(),
   1568                         connection.getStatusHints(),
   1569                         connection.getDisconnectCause(),
   1570                         createIdList(connection.getConferenceables()),
   1571                         connection.getExtras()));
   1572 
   1573         if (isIncoming && request.shouldShowIncomingCallUi() &&
   1574                 (connection.getConnectionProperties() & Connection.PROPERTY_SELF_MANAGED) ==
   1575                         Connection.PROPERTY_SELF_MANAGED) {
   1576             // Tell ConnectionService to show its incoming call UX.
   1577             connection.onShowIncomingCallUi();
   1578         }
   1579         if (isUnknown) {
   1580             triggerConferenceRecalculate();
   1581         }
   1582     }
   1583 
   1584     private void createConnectionFailed(final PhoneAccountHandle callManagerAccount,
   1585                                         final String callId, final ConnectionRequest request,
   1586                                         boolean isIncoming) {
   1587 
   1588         Log.i(this, "createConnectionFailed %s", callId);
   1589         if (isIncoming) {
   1590             onCreateIncomingConnectionFailed(callManagerAccount, request);
   1591         } else {
   1592             onCreateOutgoingConnectionFailed(callManagerAccount, request);
   1593         }
   1594     }
   1595 
   1596     private void handoverFailed(final String callId, final ConnectionRequest request,
   1597                                         int reason) {
   1598 
   1599         Log.i(this, "handoverFailed %s", callId);
   1600         onHandoverFailed(request, reason);
   1601     }
   1602 
   1603     /**
   1604      * Called by Telecom when the creation of a new Connection has completed and it is now added
   1605      * to Telecom.
   1606      * @param callId The ID of the connection.
   1607      */
   1608     private void notifyCreateConnectionComplete(final String callId) {
   1609         Log.i(this, "notifyCreateConnectionComplete %s", callId);
   1610         if (callId == null) {
   1611             // This could happen if the connection fails quickly and is removed from the
   1612             // ConnectionService before Telecom sends the create connection complete callback.
   1613             Log.w(this, "notifyCreateConnectionComplete: callId is null.");
   1614             return;
   1615         }
   1616         onCreateConnectionComplete(findConnectionForAction(callId,
   1617                 "notifyCreateConnectionComplete"));
   1618     }
   1619 
   1620     private void abort(String callId) {
   1621         Log.d(this, "abort %s", callId);
   1622         findConnectionForAction(callId, "abort").onAbort();
   1623     }
   1624 
   1625     private void answerVideo(String callId, int videoState) {
   1626         Log.d(this, "answerVideo %s", callId);
   1627         findConnectionForAction(callId, "answer").onAnswer(videoState);
   1628     }
   1629 
   1630     private void answer(String callId) {
   1631         Log.d(this, "answer %s", callId);
   1632         findConnectionForAction(callId, "answer").onAnswer();
   1633     }
   1634 
   1635     private void deflect(String callId, Uri address) {
   1636         Log.d(this, "deflect %s", callId);
   1637         findConnectionForAction(callId, "deflect").onDeflect(address);
   1638     }
   1639 
   1640     private void reject(String callId) {
   1641         Log.d(this, "reject %s", callId);
   1642         findConnectionForAction(callId, "reject").onReject();
   1643     }
   1644 
   1645     private void reject(String callId, String rejectWithMessage) {
   1646         Log.d(this, "reject %s with message", callId);
   1647         findConnectionForAction(callId, "reject").onReject(rejectWithMessage);
   1648     }
   1649 
   1650     private void silence(String callId) {
   1651         Log.d(this, "silence %s", callId);
   1652         findConnectionForAction(callId, "silence").onSilence();
   1653     }
   1654 
   1655     private void disconnect(String callId) {
   1656         Log.d(this, "disconnect %s", callId);
   1657         if (mConnectionById.containsKey(callId)) {
   1658             findConnectionForAction(callId, "disconnect").onDisconnect();
   1659         } else {
   1660             findConferenceForAction(callId, "disconnect").onDisconnect();
   1661         }
   1662     }
   1663 
   1664     private void hold(String callId) {
   1665         Log.d(this, "hold %s", callId);
   1666         if (mConnectionById.containsKey(callId)) {
   1667             findConnectionForAction(callId, "hold").onHold();
   1668         } else {
   1669             findConferenceForAction(callId, "hold").onHold();
   1670         }
   1671     }
   1672 
   1673     private void unhold(String callId) {
   1674         Log.d(this, "unhold %s", callId);
   1675         if (mConnectionById.containsKey(callId)) {
   1676             findConnectionForAction(callId, "unhold").onUnhold();
   1677         } else {
   1678             findConferenceForAction(callId, "unhold").onUnhold();
   1679         }
   1680     }
   1681 
   1682     private void onCallAudioStateChanged(String callId, CallAudioState callAudioState) {
   1683         Log.d(this, "onAudioStateChanged %s %s", callId, callAudioState);
   1684         if (mConnectionById.containsKey(callId)) {
   1685             findConnectionForAction(callId, "onCallAudioStateChanged").setCallAudioState(
   1686                     callAudioState);
   1687         } else {
   1688             findConferenceForAction(callId, "onCallAudioStateChanged").setCallAudioState(
   1689                     callAudioState);
   1690         }
   1691     }
   1692 
   1693     private void playDtmfTone(String callId, char digit) {
   1694         Log.d(this, "playDtmfTone %s %c", callId, digit);
   1695         if (mConnectionById.containsKey(callId)) {
   1696             findConnectionForAction(callId, "playDtmfTone").onPlayDtmfTone(digit);
   1697         } else {
   1698             findConferenceForAction(callId, "playDtmfTone").onPlayDtmfTone(digit);
   1699         }
   1700     }
   1701 
   1702     private void stopDtmfTone(String callId) {
   1703         Log.d(this, "stopDtmfTone %s", callId);
   1704         if (mConnectionById.containsKey(callId)) {
   1705             findConnectionForAction(callId, "stopDtmfTone").onStopDtmfTone();
   1706         } else {
   1707             findConferenceForAction(callId, "stopDtmfTone").onStopDtmfTone();
   1708         }
   1709     }
   1710 
   1711     private void conference(String callId1, String callId2) {
   1712         Log.d(this, "conference %s, %s", callId1, callId2);
   1713 
   1714         // Attempt to get second connection or conference.
   1715         Connection connection2 = findConnectionForAction(callId2, "conference");
   1716         Conference conference2 = getNullConference();
   1717         if (connection2 == getNullConnection()) {
   1718             conference2 = findConferenceForAction(callId2, "conference");
   1719             if (conference2 == getNullConference()) {
   1720                 Log.w(this, "Connection2 or Conference2 missing in conference request %s.",
   1721                         callId2);
   1722                 return;
   1723             }
   1724         }
   1725 
   1726         // Attempt to get first connection or conference and perform merge.
   1727         Connection connection1 = findConnectionForAction(callId1, "conference");
   1728         if (connection1 == getNullConnection()) {
   1729             Conference conference1 = findConferenceForAction(callId1, "addConnection");
   1730             if (conference1 == getNullConference()) {
   1731                 Log.w(this,
   1732                         "Connection1 or Conference1 missing in conference request %s.",
   1733                         callId1);
   1734             } else {
   1735                 // Call 1 is a conference.
   1736                 if (connection2 != getNullConnection()) {
   1737                     // Call 2 is a connection so merge via call 1 (conference).
   1738                     conference1.onMerge(connection2);
   1739                 } else {
   1740                     // Call 2 is ALSO a conference; this should never happen.
   1741                     Log.wtf(this, "There can only be one conference and an attempt was made to " +
   1742                             "merge two conferences.");
   1743                     return;
   1744                 }
   1745             }
   1746         } else {
   1747             // Call 1 is a connection.
   1748             if (conference2 != getNullConference()) {
   1749                 // Call 2 is a conference, so merge via call 2.
   1750                 conference2.onMerge(connection1);
   1751             } else {
   1752                 // Call 2 is a connection, so merge together.
   1753                 onConference(connection1, connection2);
   1754             }
   1755         }
   1756     }
   1757 
   1758     private void splitFromConference(String callId) {
   1759         Log.d(this, "splitFromConference(%s)", callId);
   1760 
   1761         Connection connection = findConnectionForAction(callId, "splitFromConference");
   1762         if (connection == getNullConnection()) {
   1763             Log.w(this, "Connection missing in conference request %s.", callId);
   1764             return;
   1765         }
   1766 
   1767         Conference conference = connection.getConference();
   1768         if (conference != null) {
   1769             conference.onSeparate(connection);
   1770         }
   1771     }
   1772 
   1773     private void mergeConference(String callId) {
   1774         Log.d(this, "mergeConference(%s)", callId);
   1775         Conference conference = findConferenceForAction(callId, "mergeConference");
   1776         if (conference != null) {
   1777             conference.onMerge();
   1778         }
   1779     }
   1780 
   1781     private void swapConference(String callId) {
   1782         Log.d(this, "swapConference(%s)", callId);
   1783         Conference conference = findConferenceForAction(callId, "swapConference");
   1784         if (conference != null) {
   1785             conference.onSwap();
   1786         }
   1787     }
   1788 
   1789     /**
   1790      * Notifies a {@link Connection} of a request to pull an external call.
   1791      *
   1792      * See {@link Call#pullExternalCall()}.
   1793      *
   1794      * @param callId The ID of the call to pull.
   1795      */
   1796     private void pullExternalCall(String callId) {
   1797         Log.d(this, "pullExternalCall(%s)", callId);
   1798         Connection connection = findConnectionForAction(callId, "pullExternalCall");
   1799         if (connection != null) {
   1800             connection.onPullExternalCall();
   1801         }
   1802     }
   1803 
   1804     /**
   1805      * Notifies a {@link Connection} of a call event.
   1806      *
   1807      * See {@link Call#sendCallEvent(String, Bundle)}.
   1808      *
   1809      * @param callId The ID of the call receiving the event.
   1810      * @param event The event.
   1811      * @param extras Extras associated with the event.
   1812      */
   1813     private void sendCallEvent(String callId, String event, Bundle extras) {
   1814         Log.d(this, "sendCallEvent(%s, %s)", callId, event);
   1815         Connection connection = findConnectionForAction(callId, "sendCallEvent");
   1816         if (connection != null) {
   1817             connection.onCallEvent(event, extras);
   1818         }
   1819     }
   1820 
   1821     /**
   1822      * Notifies a {@link Connection} that a handover has completed.
   1823      *
   1824      * @param callId The ID of the call which completed handover.
   1825      */
   1826     private void notifyHandoverComplete(String callId) {
   1827         Log.d(this, "notifyHandoverComplete(%s)", callId);
   1828         Connection connection = findConnectionForAction(callId, "notifyHandoverComplete");
   1829         if (connection != null) {
   1830             connection.onHandoverComplete();
   1831         }
   1832     }
   1833 
   1834     /**
   1835      * Notifies a {@link Connection} or {@link Conference} of a change to the extras from Telecom.
   1836      * <p>
   1837      * These extra changes can originate from Telecom itself, or from an {@link InCallService} via
   1838      * the {@link android.telecom.Call#putExtra(String, boolean)},
   1839      * {@link android.telecom.Call#putExtra(String, int)},
   1840      * {@link android.telecom.Call#putExtra(String, String)},
   1841      * {@link Call#removeExtras(List)}.
   1842      *
   1843      * @param callId The ID of the call receiving the event.
   1844      * @param extras The new extras bundle.
   1845      */
   1846     private void handleExtrasChanged(String callId, Bundle extras) {
   1847         Log.d(this, "handleExtrasChanged(%s, %s)", callId, extras);
   1848         if (mConnectionById.containsKey(callId)) {
   1849             findConnectionForAction(callId, "handleExtrasChanged").handleExtrasChanged(extras);
   1850         } else if (mConferenceById.containsKey(callId)) {
   1851             findConferenceForAction(callId, "handleExtrasChanged").handleExtrasChanged(extras);
   1852         }
   1853     }
   1854 
   1855     private void startRtt(String callId, Connection.RttTextStream rttTextStream) {
   1856         Log.d(this, "startRtt(%s)", callId);
   1857         if (mConnectionById.containsKey(callId)) {
   1858             findConnectionForAction(callId, "startRtt").onStartRtt(rttTextStream);
   1859         } else if (mConferenceById.containsKey(callId)) {
   1860             Log.w(this, "startRtt called on a conference.");
   1861         }
   1862     }
   1863 
   1864     private void stopRtt(String callId) {
   1865         Log.d(this, "stopRtt(%s)", callId);
   1866         if (mConnectionById.containsKey(callId)) {
   1867             findConnectionForAction(callId, "stopRtt").onStopRtt();
   1868         } else if (mConferenceById.containsKey(callId)) {
   1869             Log.w(this, "stopRtt called on a conference.");
   1870         }
   1871     }
   1872 
   1873     private void handleRttUpgradeResponse(String callId, Connection.RttTextStream rttTextStream) {
   1874         Log.d(this, "handleRttUpgradeResponse(%s, %s)", callId, rttTextStream == null);
   1875         if (mConnectionById.containsKey(callId)) {
   1876             findConnectionForAction(callId, "handleRttUpgradeResponse")
   1877                     .handleRttUpgradeResponse(rttTextStream);
   1878         } else if (mConferenceById.containsKey(callId)) {
   1879             Log.w(this, "handleRttUpgradeResponse called on a conference.");
   1880         }
   1881     }
   1882 
   1883     private void onPostDialContinue(String callId, boolean proceed) {
   1884         Log.d(this, "onPostDialContinue(%s)", callId);
   1885         findConnectionForAction(callId, "stopDtmfTone").onPostDialContinue(proceed);
   1886     }
   1887 
   1888     private void onAdapterAttached() {
   1889         if (mAreAccountsInitialized) {
   1890             // No need to query again if we already did it.
   1891             return;
   1892         }
   1893 
   1894         mAdapter.queryRemoteConnectionServices(new RemoteServiceCallback.Stub() {
   1895             @Override
   1896             public void onResult(
   1897                     final List<ComponentName> componentNames,
   1898                     final List<IBinder> services) {
   1899                 mHandler.post(new android.telecom.Logging.Runnable("oAA.qRCS.oR", null /*lock*/) {
   1900                     @Override
   1901                     public void loggedRun() {
   1902                         for (int i = 0; i < componentNames.size() && i < services.size(); i++) {
   1903                             mRemoteConnectionManager.addConnectionService(
   1904                                     componentNames.get(i),
   1905                                     IConnectionService.Stub.asInterface(services.get(i)));
   1906                         }
   1907                         onAccountsInitialized();
   1908                         Log.d(this, "remote connection services found: " + services);
   1909                     }
   1910                 }.prepare());
   1911             }
   1912 
   1913             @Override
   1914             public void onError() {
   1915                 mHandler.post(new android.telecom.Logging.Runnable("oAA.qRCS.oE", null /*lock*/) {
   1916                     @Override
   1917                     public void loggedRun() {
   1918                         mAreAccountsInitialized = true;
   1919                     }
   1920                 }.prepare());
   1921             }
   1922         });
   1923     }
   1924 
   1925     /**
   1926      * Ask some other {@code ConnectionService} to create a {@code RemoteConnection} given an
   1927      * incoming request. This is used by {@code ConnectionService}s that are registered with
   1928      * {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER} and want to be able to manage
   1929      * SIM-based incoming calls.
   1930      *
   1931      * @param connectionManagerPhoneAccount See description at
   1932      *         {@link #onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)}.
   1933      * @param request Details about the incoming call.
   1934      * @return The {@code Connection} object to satisfy this call, or {@code null} to
   1935      *         not handle the call.
   1936      */
   1937     public final RemoteConnection createRemoteIncomingConnection(
   1938             PhoneAccountHandle connectionManagerPhoneAccount,
   1939             ConnectionRequest request) {
   1940         return mRemoteConnectionManager.createRemoteConnection(
   1941                 connectionManagerPhoneAccount, request, true);
   1942     }
   1943 
   1944     /**
   1945      * Ask some other {@code ConnectionService} to create a {@code RemoteConnection} given an
   1946      * outgoing request. This is used by {@code ConnectionService}s that are registered with
   1947      * {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER} and want to be able to use the
   1948      * SIM-based {@code ConnectionService} to place its outgoing calls.
   1949      *
   1950      * @param connectionManagerPhoneAccount See description at
   1951      *         {@link #onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)}.
   1952      * @param request Details about the outgoing call.
   1953      * @return The {@code Connection} object to satisfy this call, or {@code null} to
   1954      *         not handle the call.
   1955      */
   1956     public final RemoteConnection createRemoteOutgoingConnection(
   1957             PhoneAccountHandle connectionManagerPhoneAccount,
   1958             ConnectionRequest request) {
   1959         return mRemoteConnectionManager.createRemoteConnection(
   1960                 connectionManagerPhoneAccount, request, false);
   1961     }
   1962 
   1963     /**
   1964      * Indicates to the relevant {@code RemoteConnectionService} that the specified
   1965      * {@link RemoteConnection}s should be merged into a conference call.
   1966      * <p>
   1967      * If the conference request is successful, the method {@link #onRemoteConferenceAdded} will
   1968      * be invoked.
   1969      *
   1970      * @param remoteConnection1 The first of the remote connections to conference.
   1971      * @param remoteConnection2 The second of the remote connections to conference.
   1972      */
   1973     public final void conferenceRemoteConnections(
   1974             RemoteConnection remoteConnection1,
   1975             RemoteConnection remoteConnection2) {
   1976         mRemoteConnectionManager.conferenceRemoteConnections(remoteConnection1, remoteConnection2);
   1977     }
   1978 
   1979     /**
   1980      * Adds a new conference call. When a conference call is created either as a result of an
   1981      * explicit request via {@link #onConference} or otherwise, the connection service should supply
   1982      * an instance of {@link Conference} by invoking this method. A conference call provided by this
   1983      * method will persist until {@link Conference#destroy} is invoked on the conference instance.
   1984      *
   1985      * @param conference The new conference object.
   1986      */
   1987     public final void addConference(Conference conference) {
   1988         Log.d(this, "addConference: conference=%s", conference);
   1989 
   1990         String id = addConferenceInternal(conference);
   1991         if (id != null) {
   1992             List<String> connectionIds = new ArrayList<>(2);
   1993             for (Connection connection : conference.getConnections()) {
   1994                 if (mIdByConnection.containsKey(connection)) {
   1995                     connectionIds.add(mIdByConnection.get(connection));
   1996                 }
   1997             }
   1998             conference.setTelecomCallId(id);
   1999             ParcelableConference parcelableConference = new ParcelableConference(
   2000                     conference.getPhoneAccountHandle(),
   2001                     conference.getState(),
   2002                     conference.getConnectionCapabilities(),
   2003                     conference.getConnectionProperties(),
   2004                     connectionIds,
   2005                     conference.getVideoProvider() == null ?
   2006                             null : conference.getVideoProvider().getInterface(),
   2007                     conference.getVideoState(),
   2008                     conference.getConnectTimeMillis(),
   2009                     conference.getConnectionStartElapsedRealTime(),
   2010                     conference.getStatusHints(),
   2011                     conference.getExtras());
   2012 
   2013             mAdapter.addConferenceCall(id, parcelableConference);
   2014             mAdapter.setVideoProvider(id, conference.getVideoProvider());
   2015             mAdapter.setVideoState(id, conference.getVideoState());
   2016 
   2017             // Go through any child calls and set the parent.
   2018             for (Connection connection : conference.getConnections()) {
   2019                 String connectionId = mIdByConnection.get(connection);
   2020                 if (connectionId != null) {
   2021                     mAdapter.setIsConferenced(connectionId, id);
   2022                 }
   2023             }
   2024             onConferenceAdded(conference);
   2025         }
   2026     }
   2027 
   2028     /**
   2029      * Adds a connection created by the {@link ConnectionService} and informs telecom of the new
   2030      * connection.
   2031      *
   2032      * @param phoneAccountHandle The phone account handle for the connection.
   2033      * @param connection The connection to add.
   2034      */
   2035     public final void addExistingConnection(PhoneAccountHandle phoneAccountHandle,
   2036             Connection connection) {
   2037         addExistingConnection(phoneAccountHandle, connection, null /* conference */);
   2038     }
   2039 
   2040     /**
   2041      * Call to inform Telecom that your {@link ConnectionService} has released call resources (e.g
   2042      * microphone, camera).
   2043      *
   2044      * @see ConnectionService#onConnectionServiceFocusLost()
   2045      */
   2046     public final void connectionServiceFocusReleased() {
   2047         mAdapter.onConnectionServiceFocusReleased();
   2048     }
   2049 
   2050     /**
   2051      * Adds a connection created by the {@link ConnectionService} and informs telecom of the new
   2052      * connection.
   2053      *
   2054      * @param phoneAccountHandle The phone account handle for the connection.
   2055      * @param connection The connection to add.
   2056      * @param conference The parent conference of the new connection.
   2057      * @hide
   2058      */
   2059     public final void addExistingConnection(PhoneAccountHandle phoneAccountHandle,
   2060             Connection connection, Conference conference) {
   2061 
   2062         String id = addExistingConnectionInternal(phoneAccountHandle, connection);
   2063         if (id != null) {
   2064             List<String> emptyList = new ArrayList<>(0);
   2065             String conferenceId = null;
   2066             if (conference != null) {
   2067                 conferenceId = mIdByConference.get(conference);
   2068             }
   2069 
   2070             ParcelableConnection parcelableConnection = new ParcelableConnection(
   2071                     phoneAccountHandle,
   2072                     connection.getState(),
   2073                     connection.getConnectionCapabilities(),
   2074                     connection.getConnectionProperties(),
   2075                     connection.getSupportedAudioRoutes(),
   2076                     connection.getAddress(),
   2077                     connection.getAddressPresentation(),
   2078                     connection.getCallerDisplayName(),
   2079                     connection.getCallerDisplayNamePresentation(),
   2080                     connection.getVideoProvider() == null ?
   2081                             null : connection.getVideoProvider().getInterface(),
   2082                     connection.getVideoState(),
   2083                     connection.isRingbackRequested(),
   2084                     connection.getAudioModeIsVoip(),
   2085                     connection.getConnectTimeMillis(),
   2086                     connection.getConnectElapsedTimeMillis(),
   2087                     connection.getStatusHints(),
   2088                     connection.getDisconnectCause(),
   2089                     emptyList,
   2090                     connection.getExtras(),
   2091                     conferenceId);
   2092             mAdapter.addExistingConnection(id, parcelableConnection);
   2093         }
   2094     }
   2095 
   2096     /**
   2097      * Returns all the active {@code Connection}s for which this {@code ConnectionService}
   2098      * has taken responsibility.
   2099      *
   2100      * @return A collection of {@code Connection}s created by this {@code ConnectionService}.
   2101      */
   2102     public final Collection<Connection> getAllConnections() {
   2103         return mConnectionById.values();
   2104     }
   2105 
   2106     /**
   2107      * Returns all the active {@code Conference}s for which this {@code ConnectionService}
   2108      * has taken responsibility.
   2109      *
   2110      * @return A collection of {@code Conference}s created by this {@code ConnectionService}.
   2111      */
   2112     public final Collection<Conference> getAllConferences() {
   2113         return mConferenceById.values();
   2114     }
   2115 
   2116     /**
   2117      * Create a {@code Connection} given an incoming request. This is used to attach to existing
   2118      * incoming calls.
   2119      *
   2120      * @param connectionManagerPhoneAccount See description at
   2121      *         {@link #onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)}.
   2122      * @param request Details about the incoming call.
   2123      * @return The {@code Connection} object to satisfy this call, or {@code null} to
   2124      *         not handle the call.
   2125      */
   2126     public Connection onCreateIncomingConnection(
   2127             PhoneAccountHandle connectionManagerPhoneAccount,
   2128             ConnectionRequest request) {
   2129         return null;
   2130     }
   2131 
   2132     /**
   2133      * Called after the {@link Connection} returned by
   2134      * {@link #onCreateIncomingConnection(PhoneAccountHandle, ConnectionRequest)}
   2135      * or {@link #onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)} has been
   2136      * added to the {@link ConnectionService} and sent to Telecom.
   2137      *
   2138      * @param connection the {@link Connection}.
   2139      * @hide
   2140      */
   2141     public void onCreateConnectionComplete(Connection connection) {
   2142     }
   2143 
   2144     /**
   2145      * Called by Telecom to inform the {@link ConnectionService} that its request to create a new
   2146      * incoming {@link Connection} was denied.
   2147      * <p>
   2148      * Used when a self-managed {@link ConnectionService} attempts to create a new incoming
   2149      * {@link Connection}, but Telecom has determined that the call cannot be allowed at this time.
   2150      * The {@link ConnectionService} is responsible for silently rejecting the new incoming
   2151      * {@link Connection}.
   2152      * <p>
   2153      * See {@link TelecomManager#isIncomingCallPermitted(PhoneAccountHandle)} for more information.
   2154      *
   2155      * @param connectionManagerPhoneAccount See description at
   2156      *         {@link #onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)}.
   2157      * @param request The incoming connection request.
   2158      */
   2159     public void onCreateIncomingConnectionFailed(PhoneAccountHandle connectionManagerPhoneAccount,
   2160                                                  ConnectionRequest request) {
   2161     }
   2162 
   2163     /**
   2164      * Called by Telecom to inform the {@link ConnectionService} that its request to create a new
   2165      * outgoing {@link Connection} was denied.
   2166      * <p>
   2167      * Used when a self-managed {@link ConnectionService} attempts to create a new outgoing
   2168      * {@link Connection}, but Telecom has determined that the call cannot be placed at this time.
   2169      * The {@link ConnectionService} is responisible for informing the user that the
   2170      * {@link Connection} cannot be made at this time.
   2171      * <p>
   2172      * See {@link TelecomManager#isOutgoingCallPermitted(PhoneAccountHandle)} for more information.
   2173      *
   2174      * @param connectionManagerPhoneAccount See description at
   2175      *         {@link #onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)}.
   2176      * @param request The outgoing connection request.
   2177      */
   2178     public void onCreateOutgoingConnectionFailed(PhoneAccountHandle connectionManagerPhoneAccount,
   2179                                                  ConnectionRequest request) {
   2180     }
   2181 
   2182     /**
   2183      * Trigger recalculate functinality for conference calls. This is used when a Telephony
   2184      * Connection is part of a conference controller but is not yet added to Connection
   2185      * Service and hence cannot be added to the conference call.
   2186      *
   2187      * @hide
   2188      */
   2189     public void triggerConferenceRecalculate() {
   2190     }
   2191 
   2192     /**
   2193      * Create a {@code Connection} given an outgoing request. This is used to initiate new
   2194      * outgoing calls.
   2195      *
   2196      * @param connectionManagerPhoneAccount The connection manager account to use for managing
   2197      *         this call.
   2198      *         <p>
   2199      *         If this parameter is not {@code null}, it means that this {@code ConnectionService}
   2200      *         has registered one or more {@code PhoneAccount}s having
   2201      *         {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER}. This parameter will contain
   2202      *         one of these {@code PhoneAccount}s, while the {@code request} will contain another
   2203      *         (usually but not always distinct) {@code PhoneAccount} to be used for actually
   2204      *         making the connection.
   2205      *         <p>
   2206      *         If this parameter is {@code null}, it means that this {@code ConnectionService} is
   2207      *         being asked to make a direct connection. The
   2208      *         {@link ConnectionRequest#getAccountHandle()} of parameter {@code request} will be
   2209      *         a {@code PhoneAccount} registered by this {@code ConnectionService} to use for
   2210      *         making the connection.
   2211      * @param request Details about the outgoing call.
   2212      * @return The {@code Connection} object to satisfy this call, or the result of an invocation
   2213      *         of {@link Connection#createFailedConnection(DisconnectCause)} to not handle the call.
   2214      */
   2215     public Connection onCreateOutgoingConnection(
   2216             PhoneAccountHandle connectionManagerPhoneAccount,
   2217             ConnectionRequest request) {
   2218         return null;
   2219     }
   2220 
   2221     /**
   2222      * Called by Telecom to request that a {@link ConnectionService} creates an instance of an
   2223      * outgoing handover {@link Connection}.
   2224      * <p>
   2225      * A call handover is the process where an ongoing call is transferred from one app (i.e.
   2226      * {@link ConnectionService} to another app.  The user could, for example, choose to continue a
   2227      * mobile network call in a video calling app.  The mobile network call via the Telephony stack
   2228      * is referred to as the source of the handover, and the video calling app is referred to as the
   2229      * destination.
   2230      * <p>
   2231      * When considering a handover scenario the <em>initiating</em> device is where a user initiated
   2232      * the handover process (e.g. by calling {@link android.telecom.Call#handoverTo(
   2233      * PhoneAccountHandle, int, Bundle)}, and the other device is considered the <em>receiving</em>
   2234      * device.
   2235      * <p>
   2236      * This method is called on the destination {@link ConnectionService} on <em>initiating</em>
   2237      * device when the user initiates a handover request from one app to another.  The user request
   2238      * originates in the {@link InCallService} via
   2239      * {@link android.telecom.Call#handoverTo(PhoneAccountHandle, int, Bundle)}.
   2240      * <p>
   2241      * For a full discussion of the handover process and the APIs involved, see
   2242      * {@link android.telecom.Call#handoverTo(PhoneAccountHandle, int, Bundle)}.
   2243      * <p>
   2244      * Implementations of this method should return an instance of {@link Connection} which
   2245      * represents the handover.  If your app does not wish to accept a handover to it at this time,
   2246      * you can return {@code null}.  The code below shows an example of how this is done.
   2247      * <pre>
   2248      * {@code
   2249      * public Connection onCreateIncomingHandoverConnection(PhoneAccountHandle
   2250      *     fromPhoneAccountHandle, ConnectionRequest request) {
   2251      *   if (!isHandoverAvailable()) {
   2252      *       return null;
   2253      *   }
   2254      *   MyConnection connection = new MyConnection();
   2255      *   connection.setAddress(request.getAddress(), TelecomManager.PRESENTATION_ALLOWED);
   2256      *   connection.setVideoState(request.getVideoState());
   2257      *   return connection;
   2258      * }
   2259      * }
   2260      * </pre>
   2261      *
   2262      * @param fromPhoneAccountHandle {@link PhoneAccountHandle} associated with the
   2263      *                               ConnectionService which needs to handover the call.
   2264      * @param request Details about the call to handover.
   2265      * @return {@link Connection} instance corresponding to the handover call.
   2266      */
   2267     public Connection onCreateOutgoingHandoverConnection(PhoneAccountHandle fromPhoneAccountHandle,
   2268                                                          ConnectionRequest request) {
   2269         return null;
   2270     }
   2271 
   2272     /**
   2273      * Called by Telecom to request that a {@link ConnectionService} creates an instance of an
   2274      * incoming handover {@link Connection}.
   2275      * <p>
   2276      * A call handover is the process where an ongoing call is transferred from one app (i.e.
   2277      * {@link ConnectionService} to another app.  The user could, for example, choose to continue a
   2278      * mobile network call in a video calling app.  The mobile network call via the Telephony stack
   2279      * is referred to as the source of the handover, and the video calling app is referred to as the
   2280      * destination.
   2281      * <p>
   2282      * When considering a handover scenario the <em>initiating</em> device is where a user initiated
   2283      * the handover process (e.g. by calling {@link android.telecom.Call#handoverTo(
   2284      * PhoneAccountHandle, int, Bundle)}, and the other device is considered the <em>receiving</em>
   2285      * device.
   2286      * <p>
   2287      * This method is called on the destination app on the <em>receiving</em> device when the
   2288      * destination app calls {@link TelecomManager#acceptHandover(Uri, int, PhoneAccountHandle)} to
   2289      * accept an incoming handover from the <em>initiating</em> device.
   2290      * <p>
   2291      * For a full discussion of the handover process and the APIs involved, see
   2292      * {@link android.telecom.Call#handoverTo(PhoneAccountHandle, int, Bundle)}.
   2293      * <p>
   2294      * Implementations of this method should return an instance of {@link Connection} which
   2295      * represents the handover.  The code below shows an example of how this is done.
   2296      * <pre>
   2297      * {@code
   2298      * public Connection onCreateIncomingHandoverConnection(PhoneAccountHandle
   2299      *     fromPhoneAccountHandle, ConnectionRequest request) {
   2300      *   // Given that your app requested to accept the handover, you should not return null here.
   2301      *   MyConnection connection = new MyConnection();
   2302      *   connection.setAddress(request.getAddress(), TelecomManager.PRESENTATION_ALLOWED);
   2303      *   connection.setVideoState(request.getVideoState());
   2304      *   return connection;
   2305      * }
   2306      * }
   2307      * </pre>
   2308      *
   2309      * @param fromPhoneAccountHandle {@link PhoneAccountHandle} associated with the
   2310      *                               ConnectionService which needs to handover the call.
   2311      * @param request Details about the call which needs to be handover.
   2312      * @return {@link Connection} instance corresponding to the handover call.
   2313      */
   2314     public Connection onCreateIncomingHandoverConnection(PhoneAccountHandle fromPhoneAccountHandle,
   2315                                                          ConnectionRequest request) {
   2316         return null;
   2317     }
   2318 
   2319     /**
   2320      * Called by Telecom in response to a {@code TelecomManager#acceptHandover()}
   2321      * invocation which failed.
   2322      * <p>
   2323      * For a full discussion of the handover process and the APIs involved, see
   2324      * {@link android.telecom.Call#handoverTo(PhoneAccountHandle, int, Bundle)}
   2325      *
   2326      * @param request Details about the call which failed to handover.
   2327      * @param error Reason for handover failure.  Will be one of the
   2328      */
   2329     public void onHandoverFailed(ConnectionRequest request,
   2330             @Call.Callback.HandoverFailureErrors int error) {
   2331         return;
   2332     }
   2333 
   2334     /**
   2335      * Create a {@code Connection} for a new unknown call. An unknown call is a call originating
   2336      * from the ConnectionService that was neither a user-initiated outgoing call, nor an incoming
   2337      * call created using
   2338      * {@code TelecomManager#addNewIncomingCall(PhoneAccountHandle, android.os.Bundle)}.
   2339      *
   2340      * @hide
   2341      */
   2342     public Connection onCreateUnknownConnection(PhoneAccountHandle connectionManagerPhoneAccount,
   2343             ConnectionRequest request) {
   2344         return null;
   2345     }
   2346 
   2347     /**
   2348      * Conference two specified connections. Invoked when the user has made a request to merge the
   2349      * specified connections into a conference call. In response, the connection service should
   2350      * create an instance of {@link Conference} and pass it into {@link #addConference}.
   2351      *
   2352      * @param connection1 A connection to merge into a conference call.
   2353      * @param connection2 A connection to merge into a conference call.
   2354      */
   2355     public void onConference(Connection connection1, Connection connection2) {}
   2356 
   2357     /**
   2358      * Called when a connection is added.
   2359      * @hide
   2360      */
   2361     public void onConnectionAdded(Connection connection) {}
   2362 
   2363     /**
   2364      * Called when a connection is removed.
   2365      * @hide
   2366      */
   2367     public void onConnectionRemoved(Connection connection) {}
   2368 
   2369     /**
   2370      * Called when a conference is added.
   2371      * @hide
   2372      */
   2373     public void onConferenceAdded(Conference conference) {}
   2374 
   2375     /**
   2376      * Called when a conference is removed.
   2377      * @hide
   2378      */
   2379     public void onConferenceRemoved(Conference conference) {}
   2380 
   2381     /**
   2382      * Indicates that a remote conference has been created for existing {@link RemoteConnection}s.
   2383      * When this method is invoked, this {@link ConnectionService} should create its own
   2384      * representation of the conference call and send it to telecom using {@link #addConference}.
   2385      * <p>
   2386      * This is only relevant to {@link ConnectionService}s which are registered with
   2387      * {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER}.
   2388      *
   2389      * @param conference The remote conference call.
   2390      */
   2391     public void onRemoteConferenceAdded(RemoteConference conference) {}
   2392 
   2393     /**
   2394      * Called when an existing connection is added remotely.
   2395      * @param connection The existing connection which was added.
   2396      */
   2397     public void onRemoteExistingConnectionAdded(RemoteConnection connection) {}
   2398 
   2399     /**
   2400      * Called when the {@link ConnectionService} has lost the call focus.
   2401      * The {@link ConnectionService} should release the call resources and invokes
   2402      * {@link ConnectionService#connectionServiceFocusReleased()} to inform telecom that it has
   2403      * released the call resources.
   2404      */
   2405     public void onConnectionServiceFocusLost() {}
   2406 
   2407     /**
   2408      * Called when the {@link ConnectionService} has gained the call focus. The
   2409      * {@link ConnectionService} can acquire the call resources at this time.
   2410      */
   2411     public void onConnectionServiceFocusGained() {}
   2412 
   2413     /**
   2414      * @hide
   2415      */
   2416     public boolean containsConference(Conference conference) {
   2417         return mIdByConference.containsKey(conference);
   2418     }
   2419 
   2420     /** {@hide} */
   2421     void addRemoteConference(RemoteConference remoteConference) {
   2422         onRemoteConferenceAdded(remoteConference);
   2423     }
   2424 
   2425     /** {@hide} */
   2426     void addRemoteExistingConnection(RemoteConnection remoteConnection) {
   2427         onRemoteExistingConnectionAdded(remoteConnection);
   2428     }
   2429 
   2430     private void onAccountsInitialized() {
   2431         mAreAccountsInitialized = true;
   2432         for (Runnable r : mPreInitializationConnectionRequests) {
   2433             r.run();
   2434         }
   2435         mPreInitializationConnectionRequests.clear();
   2436     }
   2437 
   2438     /**
   2439      * Adds an existing connection to the list of connections, identified by a new call ID unique
   2440      * to this connection service.
   2441      *
   2442      * @param connection The connection.
   2443      * @return The ID of the connection (e.g. the call-id).
   2444      */
   2445     private String addExistingConnectionInternal(PhoneAccountHandle handle, Connection connection) {
   2446         String id;
   2447 
   2448         if (connection.getExtras() != null && connection.getExtras()
   2449                 .containsKey(Connection.EXTRA_ORIGINAL_CONNECTION_ID)) {
   2450             id = connection.getExtras().getString(Connection.EXTRA_ORIGINAL_CONNECTION_ID);
   2451             Log.d(this, "addExistingConnectionInternal - conn %s reusing original id %s",
   2452                     connection.getTelecomCallId(), id);
   2453         } else if (handle == null) {
   2454             // If no phone account handle was provided, we cannot be sure the call ID is unique,
   2455             // so just use a random UUID.
   2456             id = UUID.randomUUID().toString();
   2457         } else {
   2458             // Phone account handle was provided, so use the ConnectionService class name as a
   2459             // prefix for a unique incremental call ID.
   2460             id = handle.getComponentName().getClassName() + "@" + getNextCallId();
   2461         }
   2462         addConnection(handle, id, connection);
   2463         return id;
   2464     }
   2465 
   2466     private void addConnection(PhoneAccountHandle handle, String callId, Connection connection) {
   2467         connection.setTelecomCallId(callId);
   2468         mConnectionById.put(callId, connection);
   2469         mIdByConnection.put(connection, callId);
   2470         connection.addConnectionListener(mConnectionListener);
   2471         connection.setConnectionService(this);
   2472         connection.setPhoneAccountHandle(handle);
   2473         onConnectionAdded(connection);
   2474     }
   2475 
   2476     /** {@hide} */
   2477     protected void removeConnection(Connection connection) {
   2478         connection.unsetConnectionService(this);
   2479         connection.removeConnectionListener(mConnectionListener);
   2480         String id = mIdByConnection.get(connection);
   2481         if (id != null) {
   2482             mConnectionById.remove(id);
   2483             mIdByConnection.remove(connection);
   2484             mAdapter.removeCall(id);
   2485             onConnectionRemoved(connection);
   2486         }
   2487     }
   2488 
   2489     private String addConferenceInternal(Conference conference) {
   2490         String originalId = null;
   2491         if (conference.getExtras() != null && conference.getExtras()
   2492                 .containsKey(Connection.EXTRA_ORIGINAL_CONNECTION_ID)) {
   2493             originalId = conference.getExtras().getString(Connection.EXTRA_ORIGINAL_CONNECTION_ID);
   2494             Log.d(this, "addConferenceInternal: conf %s reusing original id %s",
   2495                     conference.getTelecomCallId(),
   2496                     originalId);
   2497         }
   2498         if (mIdByConference.containsKey(conference)) {
   2499             Log.w(this, "Re-adding an existing conference: %s.", conference);
   2500         } else if (conference != null) {
   2501             // Conferences do not (yet) have a PhoneAccountHandle associated with them, so we
   2502             // cannot determine a ConnectionService class name to associate with the ID, so use
   2503             // a unique UUID (for now).
   2504             String id = originalId == null ? UUID.randomUUID().toString() : originalId;
   2505             mConferenceById.put(id, conference);
   2506             mIdByConference.put(conference, id);
   2507             conference.addListener(mConferenceListener);
   2508             return id;
   2509         }
   2510 
   2511         return null;
   2512     }
   2513 
   2514     private void removeConference(Conference conference) {
   2515         if (mIdByConference.containsKey(conference)) {
   2516             conference.removeListener(mConferenceListener);
   2517 
   2518             String id = mIdByConference.get(conference);
   2519             mConferenceById.remove(id);
   2520             mIdByConference.remove(conference);
   2521             mAdapter.removeCall(id);
   2522 
   2523             onConferenceRemoved(conference);
   2524         }
   2525     }
   2526 
   2527     private Connection findConnectionForAction(String callId, String action) {
   2528         if (callId != null && mConnectionById.containsKey(callId)) {
   2529             return mConnectionById.get(callId);
   2530         }
   2531         Log.w(this, "%s - Cannot find Connection %s", action, callId);
   2532         return getNullConnection();
   2533     }
   2534 
   2535     static synchronized Connection getNullConnection() {
   2536         if (sNullConnection == null) {
   2537             sNullConnection = new Connection() {};
   2538         }
   2539         return sNullConnection;
   2540     }
   2541 
   2542     private Conference findConferenceForAction(String conferenceId, String action) {
   2543         if (mConferenceById.containsKey(conferenceId)) {
   2544             return mConferenceById.get(conferenceId);
   2545         }
   2546         Log.w(this, "%s - Cannot find conference %s", action, conferenceId);
   2547         return getNullConference();
   2548     }
   2549 
   2550     private List<String> createConnectionIdList(List<Connection> connections) {
   2551         List<String> ids = new ArrayList<>();
   2552         for (Connection c : connections) {
   2553             if (mIdByConnection.containsKey(c)) {
   2554                 ids.add(mIdByConnection.get(c));
   2555             }
   2556         }
   2557         Collections.sort(ids);
   2558         return ids;
   2559     }
   2560 
   2561     /**
   2562      * Builds a list of {@link Connection} and {@link Conference} IDs based on the list of
   2563      * {@link Conferenceable}s passed in.
   2564      *
   2565      * @param conferenceables The {@link Conferenceable} connections and conferences.
   2566      * @return List of string conference and call Ids.
   2567      */
   2568     private List<String> createIdList(List<Conferenceable> conferenceables) {
   2569         List<String> ids = new ArrayList<>();
   2570         for (Conferenceable c : conferenceables) {
   2571             // Only allow Connection and Conference conferenceables.
   2572             if (c instanceof Connection) {
   2573                 Connection connection = (Connection) c;
   2574                 if (mIdByConnection.containsKey(connection)) {
   2575                     ids.add(mIdByConnection.get(connection));
   2576                 }
   2577             } else if (c instanceof Conference) {
   2578                 Conference conference = (Conference) c;
   2579                 if (mIdByConference.containsKey(conference)) {
   2580                     ids.add(mIdByConference.get(conference));
   2581                 }
   2582             }
   2583         }
   2584         Collections.sort(ids);
   2585         return ids;
   2586     }
   2587 
   2588     private Conference getNullConference() {
   2589         if (sNullConference == null) {
   2590             sNullConference = new Conference(null) {};
   2591         }
   2592         return sNullConference;
   2593     }
   2594 
   2595     private void endAllConnections() {
   2596         // Unbound from telecomm.  We should end all connections and conferences.
   2597         for (Connection connection : mIdByConnection.keySet()) {
   2598             // only operate on top-level calls. Conference calls will be removed on their own.
   2599             if (connection.getConference() == null) {
   2600                 connection.onDisconnect();
   2601             }
   2602         }
   2603         for (Conference conference : mIdByConference.keySet()) {
   2604             conference.onDisconnect();
   2605         }
   2606     }
   2607 
   2608     /**
   2609      * Retrieves the next call ID as maintainted by the connection service.
   2610      *
   2611      * @return The call ID.
   2612      */
   2613     private int getNextCallId() {
   2614         synchronized (mIdSyncRoot) {
   2615             return ++mId;
   2616         }
   2617     }
   2618 }
   2619