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.net.Uri;
     20 import android.os.Bundle;
     21 import android.os.IBinder;
     22 import android.os.IBinder.DeathRecipient;
     23 import android.os.RemoteException;
     24 import android.telecom.Logging.Session;
     25 
     26 import com.android.internal.telecom.IConnectionService;
     27 import com.android.internal.telecom.IConnectionServiceAdapter;
     28 import com.android.internal.telecom.IVideoProvider;
     29 import com.android.internal.telecom.RemoteServiceCallback;
     30 
     31 import java.util.ArrayList;
     32 import java.util.HashMap;
     33 import java.util.HashSet;
     34 import java.util.Map;
     35 import java.util.Set;
     36 import java.util.List;
     37 import java.util.UUID;
     38 
     39 /**
     40  * Remote connection service which other connection services can use to place calls on their behalf.
     41  *
     42  * @hide
     43  */
     44 final class RemoteConnectionService {
     45 
     46     // Note: Casting null to avoid ambiguous constructor reference.
     47     private static final RemoteConnection NULL_CONNECTION =
     48             new RemoteConnection("NULL", null, (ConnectionRequest) null);
     49 
     50     private static final RemoteConference NULL_CONFERENCE =
     51             new RemoteConference("NULL", null);
     52 
     53     private final IConnectionServiceAdapter mServantDelegate = new IConnectionServiceAdapter() {
     54         @Override
     55         public void handleCreateConnectionComplete(
     56                 String id,
     57                 ConnectionRequest request,
     58                 ParcelableConnection parcel,
     59                 Session.Info info) {
     60             RemoteConnection connection =
     61                     findConnectionForAction(id, "handleCreateConnectionSuccessful");
     62             if (connection != NULL_CONNECTION && mPendingConnections.contains(connection)) {
     63                 mPendingConnections.remove(connection);
     64                 // Unconditionally initialize the connection ...
     65                 connection.setConnectionCapabilities(parcel.getConnectionCapabilities());
     66                 connection.setConnectionProperties(parcel.getConnectionProperties());
     67                 if (parcel.getHandle() != null
     68                     || parcel.getState() != Connection.STATE_DISCONNECTED) {
     69                     connection.setAddress(parcel.getHandle(), parcel.getHandlePresentation());
     70                 }
     71                 if (parcel.getCallerDisplayName() != null
     72                     || parcel.getState() != Connection.STATE_DISCONNECTED) {
     73                     connection.setCallerDisplayName(
     74                             parcel.getCallerDisplayName(),
     75                             parcel.getCallerDisplayNamePresentation());
     76                 }
     77                 // Set state after handle so that the client can identify the connection.
     78                 if (parcel.getState() == Connection.STATE_DISCONNECTED) {
     79                     connection.setDisconnected(parcel.getDisconnectCause());
     80                 } else {
     81                     connection.setState(parcel.getState());
     82                 }
     83                 List<RemoteConnection> conferenceable = new ArrayList<>();
     84                 for (String confId : parcel.getConferenceableConnectionIds()) {
     85                     if (mConnectionById.containsKey(confId)) {
     86                         conferenceable.add(mConnectionById.get(confId));
     87                     }
     88                 }
     89                 connection.setConferenceableConnections(conferenceable);
     90                 connection.setVideoState(parcel.getVideoState());
     91                 if (connection.getState() == Connection.STATE_DISCONNECTED) {
     92                     // ... then, if it was created in a disconnected state, that indicates
     93                     // failure on the providing end, so immediately mark it destroyed
     94                     connection.setDestroyed();
     95                 }
     96                 connection.setStatusHints(parcel.getStatusHints());
     97                 connection.setIsVoipAudioMode(parcel.getIsVoipAudioMode());
     98                 connection.setRingbackRequested(parcel.isRingbackRequested());
     99                 connection.putExtras(parcel.getExtras());
    100             }
    101         }
    102 
    103         @Override
    104         public void setActive(String callId, Session.Info sessionInfo) {
    105             if (mConnectionById.containsKey(callId)) {
    106                 findConnectionForAction(callId, "setActive")
    107                         .setState(Connection.STATE_ACTIVE);
    108             } else {
    109                 findConferenceForAction(callId, "setActive")
    110                         .setState(Connection.STATE_ACTIVE);
    111             }
    112         }
    113 
    114         @Override
    115         public void setRinging(String callId, Session.Info sessionInfo) {
    116             findConnectionForAction(callId, "setRinging")
    117                     .setState(Connection.STATE_RINGING);
    118         }
    119 
    120         @Override
    121         public void setDialing(String callId, Session.Info sessionInfo) {
    122             findConnectionForAction(callId, "setDialing")
    123                     .setState(Connection.STATE_DIALING);
    124         }
    125 
    126         @Override
    127         public void setPulling(String callId, Session.Info sessionInfo) {
    128             findConnectionForAction(callId, "setPulling")
    129                     .setState(Connection.STATE_PULLING_CALL);
    130         }
    131 
    132         @Override
    133         public void setDisconnected(String callId, DisconnectCause disconnectCause,
    134                 Session.Info sessionInfo) {
    135             if (mConnectionById.containsKey(callId)) {
    136                 findConnectionForAction(callId, "setDisconnected")
    137                         .setDisconnected(disconnectCause);
    138             } else {
    139                 findConferenceForAction(callId, "setDisconnected")
    140                         .setDisconnected(disconnectCause);
    141             }
    142         }
    143 
    144         @Override
    145         public void setOnHold(String callId, Session.Info sessionInfo) {
    146             if (mConnectionById.containsKey(callId)) {
    147                 findConnectionForAction(callId, "setOnHold")
    148                         .setState(Connection.STATE_HOLDING);
    149             } else {
    150                 findConferenceForAction(callId, "setOnHold")
    151                         .setState(Connection.STATE_HOLDING);
    152             }
    153         }
    154 
    155         @Override
    156         public void setRingbackRequested(String callId, boolean ringing, Session.Info sessionInfo) {
    157             findConnectionForAction(callId, "setRingbackRequested")
    158                     .setRingbackRequested(ringing);
    159         }
    160 
    161         @Override
    162         public void setConnectionCapabilities(String callId, int connectionCapabilities,
    163                 Session.Info sessionInfo) {
    164             if (mConnectionById.containsKey(callId)) {
    165                 findConnectionForAction(callId, "setConnectionCapabilities")
    166                         .setConnectionCapabilities(connectionCapabilities);
    167             } else {
    168                 findConferenceForAction(callId, "setConnectionCapabilities")
    169                         .setConnectionCapabilities(connectionCapabilities);
    170             }
    171         }
    172 
    173         @Override
    174         public void setConnectionProperties(String callId, int connectionProperties,
    175                 Session.Info sessionInfo) {
    176             if (mConnectionById.containsKey(callId)) {
    177                 findConnectionForAction(callId, "setConnectionProperties")
    178                         .setConnectionProperties(connectionProperties);
    179             } else {
    180                 findConferenceForAction(callId, "setConnectionProperties")
    181                         .setConnectionProperties(connectionProperties);
    182             }
    183         }
    184 
    185         @Override
    186         public void setIsConferenced(String callId, String conferenceCallId,
    187                 Session.Info sessionInfo) {
    188             // Note: callId should not be null; conferenceCallId may be null
    189             RemoteConnection connection =
    190                     findConnectionForAction(callId, "setIsConferenced");
    191             if (connection != NULL_CONNECTION) {
    192                 if (conferenceCallId == null) {
    193                     // 'connection' is being split from its conference
    194                     if (connection.getConference() != null) {
    195                         connection.getConference().removeConnection(connection);
    196                     }
    197                 } else {
    198                     RemoteConference conference =
    199                             findConferenceForAction(conferenceCallId, "setIsConferenced");
    200                     if (conference != NULL_CONFERENCE) {
    201                         conference.addConnection(connection);
    202                     }
    203                 }
    204             }
    205         }
    206 
    207         @Override
    208         public void setConferenceMergeFailed(String callId, Session.Info sessionInfo) {
    209             // Nothing to do here.
    210             // The event has already been handled and there is no state to update
    211             // in the underlying connection or conference objects
    212         }
    213 
    214         @Override
    215         public void onPhoneAccountChanged(String callId, PhoneAccountHandle pHandle,
    216                 Session.Info sessionInfo) {
    217         }
    218 
    219         @Override
    220         public void onConnectionServiceFocusReleased(Session.Info sessionInfo) {}
    221 
    222         @Override
    223         public void addConferenceCall(
    224                 final String callId, ParcelableConference parcel, Session.Info sessionInfo) {
    225             RemoteConference conference = new RemoteConference(callId,
    226                     mOutgoingConnectionServiceRpc);
    227 
    228             for (String id : parcel.getConnectionIds()) {
    229                 RemoteConnection c = mConnectionById.get(id);
    230                 if (c != null) {
    231                     conference.addConnection(c);
    232                 }
    233             }
    234             if (conference.getConnections().size() == 0) {
    235                 // A conference was created, but none of its connections are ones that have been
    236                 // created by, and therefore being tracked by, this remote connection service. It
    237                 // is of no interest to us.
    238                 Log.d(this, "addConferenceCall - skipping");
    239                 return;
    240             }
    241 
    242             conference.setState(parcel.getState());
    243             conference.setConnectionCapabilities(parcel.getConnectionCapabilities());
    244             conference.setConnectionProperties(parcel.getConnectionProperties());
    245             conference.putExtras(parcel.getExtras());
    246             mConferenceById.put(callId, conference);
    247 
    248             // Stash the original connection ID as it exists in the source ConnectionService.
    249             // Telecom will use this to avoid adding duplicates later.
    250             // See comments on Connection.EXTRA_ORIGINAL_CONNECTION_ID for more information.
    251             Bundle newExtras = new Bundle();
    252             newExtras.putString(Connection.EXTRA_ORIGINAL_CONNECTION_ID, callId);
    253             conference.putExtras(newExtras);
    254 
    255             conference.registerCallback(new RemoteConference.Callback() {
    256                 @Override
    257                 public void onDestroyed(RemoteConference c) {
    258                     mConferenceById.remove(callId);
    259                     maybeDisconnectAdapter();
    260                 }
    261             });
    262 
    263             mOurConnectionServiceImpl.addRemoteConference(conference);
    264         }
    265 
    266         @Override
    267         public void removeCall(String callId, Session.Info sessionInfo) {
    268             if (mConnectionById.containsKey(callId)) {
    269                 findConnectionForAction(callId, "removeCall")
    270                         .setDestroyed();
    271             } else {
    272                 findConferenceForAction(callId, "removeCall")
    273                         .setDestroyed();
    274             }
    275         }
    276 
    277         @Override
    278         public void onPostDialWait(String callId, String remaining, Session.Info sessionInfo) {
    279             findConnectionForAction(callId, "onPostDialWait")
    280                     .setPostDialWait(remaining);
    281         }
    282 
    283         @Override
    284         public void onPostDialChar(String callId, char nextChar, Session.Info sessionInfo) {
    285             findConnectionForAction(callId, "onPostDialChar")
    286                     .onPostDialChar(nextChar);
    287         }
    288 
    289         @Override
    290         public void queryRemoteConnectionServices(RemoteServiceCallback callback,
    291                 Session.Info sessionInfo) {
    292             // Not supported from remote connection service.
    293         }
    294 
    295         @Override
    296         public void setVideoProvider(String callId, IVideoProvider videoProvider,
    297                 Session.Info sessionInfo) {
    298 
    299             String callingPackage = mOurConnectionServiceImpl.getApplicationContext()
    300                     .getOpPackageName();
    301             int targetSdkVersion = mOurConnectionServiceImpl.getApplicationInfo().targetSdkVersion;
    302             RemoteConnection.VideoProvider remoteVideoProvider = null;
    303             if (videoProvider != null) {
    304                 remoteVideoProvider = new RemoteConnection.VideoProvider(videoProvider,
    305                         callingPackage, targetSdkVersion);
    306             }
    307             findConnectionForAction(callId, "setVideoProvider")
    308                     .setVideoProvider(remoteVideoProvider);
    309         }
    310 
    311         @Override
    312         public void setVideoState(String callId, int videoState, Session.Info sessionInfo) {
    313             findConnectionForAction(callId, "setVideoState")
    314                     .setVideoState(videoState);
    315         }
    316 
    317         @Override
    318         public void setIsVoipAudioMode(String callId, boolean isVoip, Session.Info sessionInfo) {
    319             findConnectionForAction(callId, "setIsVoipAudioMode")
    320                     .setIsVoipAudioMode(isVoip);
    321         }
    322 
    323         @Override
    324         public void setStatusHints(String callId, StatusHints statusHints,
    325                 Session.Info sessionInfo) {
    326             findConnectionForAction(callId, "setStatusHints")
    327                     .setStatusHints(statusHints);
    328         }
    329 
    330         @Override
    331         public void setAddress(String callId, Uri address, int presentation,
    332                 Session.Info sessionInfo) {
    333             findConnectionForAction(callId, "setAddress")
    334                     .setAddress(address, presentation);
    335         }
    336 
    337         @Override
    338         public void setCallerDisplayName(String callId, String callerDisplayName,
    339                 int presentation, Session.Info sessionInfo) {
    340             findConnectionForAction(callId, "setCallerDisplayName")
    341                     .setCallerDisplayName(callerDisplayName, presentation);
    342         }
    343 
    344         @Override
    345         public IBinder asBinder() {
    346             throw new UnsupportedOperationException();
    347         }
    348 
    349         @Override
    350         public final void setConferenceableConnections(String callId,
    351                 List<String> conferenceableConnectionIds, Session.Info sessionInfo) {
    352             List<RemoteConnection> conferenceable = new ArrayList<>();
    353             for (String id : conferenceableConnectionIds) {
    354                 if (mConnectionById.containsKey(id)) {
    355                     conferenceable.add(mConnectionById.get(id));
    356                 }
    357             }
    358 
    359             if (hasConnection(callId)) {
    360                 findConnectionForAction(callId, "setConferenceableConnections")
    361                         .setConferenceableConnections(conferenceable);
    362             } else {
    363                 findConferenceForAction(callId, "setConferenceableConnections")
    364                         .setConferenceableConnections(conferenceable);
    365             }
    366         }
    367 
    368         @Override
    369         public void addExistingConnection(String callId, ParcelableConnection connection,
    370                 Session.Info sessionInfo) {
    371             String callingPackage = mOurConnectionServiceImpl.getApplicationContext().
    372                     getOpPackageName();
    373             int callingTargetSdkVersion = mOurConnectionServiceImpl.getApplicationInfo()
    374                     .targetSdkVersion;
    375             RemoteConnection remoteConnection = new RemoteConnection(callId,
    376                     mOutgoingConnectionServiceRpc, connection, callingPackage,
    377                     callingTargetSdkVersion);
    378             mConnectionById.put(callId, remoteConnection);
    379             remoteConnection.registerCallback(new RemoteConnection.Callback() {
    380                 @Override
    381                 public void onDestroyed(RemoteConnection connection) {
    382                     mConnectionById.remove(callId);
    383                     maybeDisconnectAdapter();
    384                 }
    385             });
    386             mOurConnectionServiceImpl.addRemoteExistingConnection(remoteConnection);
    387         }
    388 
    389         @Override
    390         public void putExtras(String callId, Bundle extras, Session.Info sessionInfo) {
    391             if (hasConnection(callId)) {
    392                 findConnectionForAction(callId, "putExtras").putExtras(extras);
    393             } else {
    394                 findConferenceForAction(callId, "putExtras").putExtras(extras);
    395             }
    396         }
    397 
    398         @Override
    399         public void removeExtras(String callId, List<String> keys, Session.Info sessionInfo) {
    400             if (hasConnection(callId)) {
    401                 findConnectionForAction(callId, "removeExtra").removeExtras(keys);
    402             } else {
    403                 findConferenceForAction(callId, "removeExtra").removeExtras(keys);
    404             }
    405         }
    406 
    407         @Override
    408         public void setAudioRoute(String callId, int audioRoute, String bluetoothAddress,
    409                 Session.Info sessionInfo) {
    410             if (hasConnection(callId)) {
    411                 // TODO(3pcalls): handle this for remote connections.
    412                 // Likely we don't want to do anything since it doesn't make sense for self-managed
    413                 // connections to go through a connection mgr.
    414             }
    415         }
    416 
    417         @Override
    418         public void onConnectionEvent(String callId, String event, Bundle extras,
    419                 Session.Info sessionInfo) {
    420             if (mConnectionById.containsKey(callId)) {
    421                 findConnectionForAction(callId, "onConnectionEvent").onConnectionEvent(event,
    422                         extras);
    423             }
    424         }
    425 
    426         @Override
    427         public void onRttInitiationSuccess(String callId, Session.Info sessionInfo)
    428                 throws RemoteException {
    429             if (hasConnection(callId)) {
    430                 findConnectionForAction(callId, "onRttInitiationSuccess")
    431                         .onRttInitiationSuccess();
    432             } else {
    433                 Log.w(this, "onRttInitiationSuccess called on a remote conference");
    434             }
    435         }
    436 
    437         @Override
    438         public void onRttInitiationFailure(String callId, int reason, Session.Info sessionInfo)
    439                 throws RemoteException {
    440             if (hasConnection(callId)) {
    441                 findConnectionForAction(callId, "onRttInitiationFailure")
    442                         .onRttInitiationFailure(reason);
    443             } else {
    444                 Log.w(this, "onRttInitiationFailure called on a remote conference");
    445             }
    446         }
    447 
    448         @Override
    449         public void onRttSessionRemotelyTerminated(String callId, Session.Info sessionInfo)
    450                 throws RemoteException {
    451             if (hasConnection(callId)) {
    452                 findConnectionForAction(callId, "onRttSessionRemotelyTerminated")
    453                         .onRttSessionRemotelyTerminated();
    454             } else {
    455                 Log.w(this, "onRttSessionRemotelyTerminated called on a remote conference");
    456             }
    457         }
    458 
    459         @Override
    460         public void onRemoteRttRequest(String callId, Session.Info sessionInfo)
    461                 throws RemoteException {
    462             if (hasConnection(callId)) {
    463                 findConnectionForAction(callId, "onRemoteRttRequest")
    464                         .onRemoteRttRequest();
    465             } else {
    466                 Log.w(this, "onRemoteRttRequest called on a remote conference");
    467             }
    468         }
    469     };
    470 
    471     private final ConnectionServiceAdapterServant mServant =
    472             new ConnectionServiceAdapterServant(mServantDelegate);
    473 
    474     private final DeathRecipient mDeathRecipient = new DeathRecipient() {
    475         @Override
    476         public void binderDied() {
    477             for (RemoteConnection c : mConnectionById.values()) {
    478                 c.setDestroyed();
    479             }
    480             for (RemoteConference c : mConferenceById.values()) {
    481                 c.setDestroyed();
    482             }
    483             mConnectionById.clear();
    484             mConferenceById.clear();
    485             mPendingConnections.clear();
    486             mOutgoingConnectionServiceRpc.asBinder().unlinkToDeath(mDeathRecipient, 0);
    487         }
    488     };
    489 
    490     private final IConnectionService mOutgoingConnectionServiceRpc;
    491     private final ConnectionService mOurConnectionServiceImpl;
    492     private final Map<String, RemoteConnection> mConnectionById = new HashMap<>();
    493     private final Map<String, RemoteConference> mConferenceById = new HashMap<>();
    494     private final Set<RemoteConnection> mPendingConnections = new HashSet<>();
    495 
    496     RemoteConnectionService(
    497             IConnectionService outgoingConnectionServiceRpc,
    498             ConnectionService ourConnectionServiceImpl) throws RemoteException {
    499         mOutgoingConnectionServiceRpc = outgoingConnectionServiceRpc;
    500         mOutgoingConnectionServiceRpc.asBinder().linkToDeath(mDeathRecipient, 0);
    501         mOurConnectionServiceImpl = ourConnectionServiceImpl;
    502     }
    503 
    504     @Override
    505     public String toString() {
    506         return "[RemoteCS - " + mOutgoingConnectionServiceRpc.asBinder().toString() + "]";
    507     }
    508 
    509     final RemoteConnection createRemoteConnection(
    510             PhoneAccountHandle connectionManagerPhoneAccount,
    511             ConnectionRequest request,
    512             boolean isIncoming) {
    513         final String id = UUID.randomUUID().toString();
    514         final ConnectionRequest newRequest = new ConnectionRequest.Builder()
    515                 .setAccountHandle(request.getAccountHandle())
    516                 .setAddress(request.getAddress())
    517                 .setExtras(request.getExtras())
    518                 .setVideoState(request.getVideoState())
    519                 .setRttPipeFromInCall(request.getRttPipeFromInCall())
    520                 .setRttPipeToInCall(request.getRttPipeToInCall())
    521                 .build();
    522         try {
    523             if (mConnectionById.isEmpty()) {
    524                 mOutgoingConnectionServiceRpc.addConnectionServiceAdapter(mServant.getStub(),
    525                         null /*Session.Info*/);
    526             }
    527             RemoteConnection connection =
    528                     new RemoteConnection(id, mOutgoingConnectionServiceRpc, newRequest);
    529             mPendingConnections.add(connection);
    530             mConnectionById.put(id, connection);
    531             mOutgoingConnectionServiceRpc.createConnection(
    532                     connectionManagerPhoneAccount,
    533                     id,
    534                     newRequest,
    535                     isIncoming,
    536                     false /* isUnknownCall */,
    537                     null /*Session.info*/);
    538             connection.registerCallback(new RemoteConnection.Callback() {
    539                 @Override
    540                 public void onDestroyed(RemoteConnection connection) {
    541                     mConnectionById.remove(id);
    542                     maybeDisconnectAdapter();
    543                 }
    544             });
    545             return connection;
    546         } catch (RemoteException e) {
    547             return RemoteConnection.failure(
    548                     new DisconnectCause(DisconnectCause.ERROR, e.toString()));
    549         }
    550     }
    551 
    552     private boolean hasConnection(String callId) {
    553         return mConnectionById.containsKey(callId);
    554     }
    555 
    556     private RemoteConnection findConnectionForAction(
    557             String callId, String action) {
    558         if (mConnectionById.containsKey(callId)) {
    559             return mConnectionById.get(callId);
    560         }
    561         Log.w(this, "%s - Cannot find Connection %s", action, callId);
    562         return NULL_CONNECTION;
    563     }
    564 
    565     private RemoteConference findConferenceForAction(
    566             String callId, String action) {
    567         if (mConferenceById.containsKey(callId)) {
    568             return mConferenceById.get(callId);
    569         }
    570         Log.w(this, "%s - Cannot find Conference %s", action, callId);
    571         return NULL_CONFERENCE;
    572     }
    573 
    574     private void maybeDisconnectAdapter() {
    575         if (mConnectionById.isEmpty() && mConferenceById.isEmpty()) {
    576             try {
    577                 mOutgoingConnectionServiceRpc.removeConnectionServiceAdapter(mServant.getStub(),
    578                         null /*Session.info*/);
    579             } catch (RemoteException e) {
    580             }
    581         }
    582     }
    583 }
    584