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.IBinder;
     21 import android.os.IBinder.DeathRecipient;
     22 import android.os.RemoteException;
     23 
     24 import com.android.internal.telecom.IConnectionService;
     25 import com.android.internal.telecom.IConnectionServiceAdapter;
     26 import com.android.internal.telecom.IVideoProvider;
     27 import com.android.internal.telecom.RemoteServiceCallback;
     28 
     29 import java.util.ArrayList;
     30 import java.util.HashMap;
     31 import java.util.HashSet;
     32 import java.util.Map;
     33 import java.util.Set;
     34 import java.util.List;
     35 import java.util.UUID;
     36 
     37 /**
     38  * Remote connection service which other connection services can use to place calls on their behalf.
     39  *
     40  * @hide
     41  */
     42 final class RemoteConnectionService {
     43 
     44     // Note: Casting null to avoid ambiguous constructor reference.
     45     private static final RemoteConnection NULL_CONNECTION =
     46             new RemoteConnection("NULL", null, (ConnectionRequest) null);
     47 
     48     private static final RemoteConference NULL_CONFERENCE =
     49             new RemoteConference("NULL", null);
     50 
     51     private final IConnectionServiceAdapter mServantDelegate = new IConnectionServiceAdapter() {
     52         @Override
     53         public void handleCreateConnectionComplete(
     54                 String id,
     55                 ConnectionRequest request,
     56                 ParcelableConnection parcel) {
     57             RemoteConnection connection =
     58                     findConnectionForAction(id, "handleCreateConnectionSuccessful");
     59             if (connection != NULL_CONNECTION && mPendingConnections.contains(connection)) {
     60                 mPendingConnections.remove(connection);
     61                 // Unconditionally initialize the connection ...
     62                 connection.setConnectionCapabilities(parcel.getConnectionCapabilities());
     63                 connection.setAddress(
     64                         parcel.getHandle(), parcel.getHandlePresentation());
     65                 connection.setCallerDisplayName(
     66                         parcel.getCallerDisplayName(),
     67                         parcel.getCallerDisplayNamePresentation());
     68                 // Set state after handle so that the client can identify the connection.
     69                 if (parcel.getState() == Connection.STATE_DISCONNECTED) {
     70                     connection.setDisconnected(parcel.getDisconnectCause());
     71                 } else {
     72                     connection.setState(parcel.getState());
     73                 }
     74                 List<RemoteConnection> conferenceable = new ArrayList<>();
     75                 for (String confId : parcel.getConferenceableConnectionIds()) {
     76                     if (mConnectionById.containsKey(confId)) {
     77                         conferenceable.add(mConnectionById.get(confId));
     78                     }
     79                 }
     80                 connection.setConferenceableConnections(conferenceable);
     81                 connection.setVideoState(parcel.getVideoState());
     82                 if (connection.getState() == Connection.STATE_DISCONNECTED) {
     83                     // ... then, if it was created in a disconnected state, that indicates
     84                     // failure on the providing end, so immediately mark it destroyed
     85                     connection.setDestroyed();
     86                 }
     87             }
     88         }
     89 
     90         @Override
     91         public void setActive(String callId) {
     92             if (mConnectionById.containsKey(callId)) {
     93                 findConnectionForAction(callId, "setActive")
     94                         .setState(Connection.STATE_ACTIVE);
     95             } else {
     96                 findConferenceForAction(callId, "setActive")
     97                         .setState(Connection.STATE_ACTIVE);
     98             }
     99         }
    100 
    101         @Override
    102         public void setRinging(String callId) {
    103             findConnectionForAction(callId, "setRinging")
    104                     .setState(Connection.STATE_RINGING);
    105         }
    106 
    107         @Override
    108         public void setDialing(String callId) {
    109             findConnectionForAction(callId, "setDialing")
    110                     .setState(Connection.STATE_DIALING);
    111         }
    112 
    113         @Override
    114         public void setDisconnected(String callId, DisconnectCause disconnectCause) {
    115             if (mConnectionById.containsKey(callId)) {
    116                 findConnectionForAction(callId, "setDisconnected")
    117                         .setDisconnected(disconnectCause);
    118             } else {
    119                 findConferenceForAction(callId, "setDisconnected")
    120                         .setDisconnected(disconnectCause);
    121             }
    122         }
    123 
    124         @Override
    125         public void setOnHold(String callId) {
    126             if (mConnectionById.containsKey(callId)) {
    127                 findConnectionForAction(callId, "setOnHold")
    128                         .setState(Connection.STATE_HOLDING);
    129             } else {
    130                 findConferenceForAction(callId, "setOnHold")
    131                         .setState(Connection.STATE_HOLDING);
    132             }
    133         }
    134 
    135         @Override
    136         public void setRingbackRequested(String callId, boolean ringing) {
    137             findConnectionForAction(callId, "setRingbackRequested")
    138                     .setRingbackRequested(ringing);
    139         }
    140 
    141         @Override
    142         public void setConnectionCapabilities(String callId, int connectionCapabilities) {
    143             if (mConnectionById.containsKey(callId)) {
    144                 findConnectionForAction(callId, "setConnectionCapabilities")
    145                         .setConnectionCapabilities(connectionCapabilities);
    146             } else {
    147                 findConferenceForAction(callId, "setConnectionCapabilities")
    148                         .setConnectionCapabilities(connectionCapabilities);
    149             }
    150         }
    151 
    152         @Override
    153         public void setIsConferenced(String callId, String conferenceCallId) {
    154             // Note: callId should not be null; conferenceCallId may be null
    155             RemoteConnection connection =
    156                     findConnectionForAction(callId, "setIsConferenced");
    157             if (connection != NULL_CONNECTION) {
    158                 if (conferenceCallId == null) {
    159                     // 'connection' is being split from its conference
    160                     if (connection.getConference() != null) {
    161                         connection.getConference().removeConnection(connection);
    162                     }
    163                 } else {
    164                     RemoteConference conference =
    165                             findConferenceForAction(conferenceCallId, "setIsConferenced");
    166                     if (conference != NULL_CONFERENCE) {
    167                         conference.addConnection(connection);
    168                     }
    169                 }
    170             }
    171         }
    172 
    173         @Override
    174         public void addConferenceCall(
    175                 final String callId,
    176                 ParcelableConference parcel) {
    177             RemoteConference conference = new RemoteConference(callId,
    178                     mOutgoingConnectionServiceRpc);
    179 
    180             for (String id : parcel.getConnectionIds()) {
    181                 RemoteConnection c = mConnectionById.get(id);
    182                 if (c != null) {
    183                     conference.addConnection(c);
    184                 }
    185             }
    186 
    187             if (conference.getConnections().size() == 0) {
    188                 // A conference was created, but none of its connections are ones that have been
    189                 // created by, and therefore being tracked by, this remote connection service. It
    190                 // is of no interest to us.
    191                 return;
    192             }
    193 
    194             conference.setState(parcel.getState());
    195             conference.setConnectionCapabilities(parcel.getConnectionCapabilities());
    196             mConferenceById.put(callId, conference);
    197             conference.registerCallback(new RemoteConference.Callback() {
    198                 @Override
    199                 public void onDestroyed(RemoteConference c) {
    200                     mConferenceById.remove(callId);
    201                     maybeDisconnectAdapter();
    202                 }
    203             });
    204 
    205             mOurConnectionServiceImpl.addRemoteConference(conference);
    206         }
    207 
    208         @Override
    209         public void removeCall(String callId) {
    210             if (mConnectionById.containsKey(callId)) {
    211                 findConnectionForAction(callId, "removeCall")
    212                         .setDestroyed();
    213             } else {
    214                 findConferenceForAction(callId, "removeCall")
    215                         .setDestroyed();
    216             }
    217         }
    218 
    219         @Override
    220         public void onPostDialWait(String callId, String remaining) {
    221             findConnectionForAction(callId, "onPostDialWait")
    222                     .setPostDialWait(remaining);
    223         }
    224 
    225         @Override
    226         public void onPostDialChar(String callId, char nextChar) {
    227             findConnectionForAction(callId, "onPostDialChar")
    228                     .onPostDialChar(nextChar);
    229         }
    230 
    231         @Override
    232         public void queryRemoteConnectionServices(RemoteServiceCallback callback) {
    233             // Not supported from remote connection service.
    234         }
    235 
    236         @Override
    237         public void setVideoProvider(String callId, IVideoProvider videoProvider) {
    238             RemoteConnection.VideoProvider remoteVideoProvider = null;
    239             if (videoProvider != null) {
    240                 remoteVideoProvider = new RemoteConnection.VideoProvider(videoProvider);
    241             }
    242             findConnectionForAction(callId, "setVideoProvider")
    243                     .setVideoProvider(remoteVideoProvider);
    244         }
    245 
    246         @Override
    247         public void setVideoState(String callId, int videoState) {
    248             findConnectionForAction(callId, "setVideoState")
    249                     .setVideoState(videoState);
    250         }
    251 
    252         @Override
    253         public void setIsVoipAudioMode(String callId, boolean isVoip) {
    254             findConnectionForAction(callId, "setIsVoipAudioMode")
    255                     .setIsVoipAudioMode(isVoip);
    256         }
    257 
    258         @Override
    259         public void setStatusHints(String callId, StatusHints statusHints) {
    260             findConnectionForAction(callId, "setStatusHints")
    261                     .setStatusHints(statusHints);
    262         }
    263 
    264         @Override
    265         public void setAddress(String callId, Uri address, int presentation) {
    266             findConnectionForAction(callId, "setAddress")
    267                     .setAddress(address, presentation);
    268         }
    269 
    270         @Override
    271         public void setCallerDisplayName(String callId, String callerDisplayName,
    272                 int presentation) {
    273             findConnectionForAction(callId, "setCallerDisplayName")
    274                     .setCallerDisplayName(callerDisplayName, presentation);
    275         }
    276 
    277         @Override
    278         public IBinder asBinder() {
    279             throw new UnsupportedOperationException();
    280         }
    281 
    282         @Override
    283         public final void setConferenceableConnections(
    284                 String callId, List<String> conferenceableConnectionIds) {
    285             List<RemoteConnection> conferenceable = new ArrayList<>();
    286             for (String id : conferenceableConnectionIds) {
    287                 if (mConnectionById.containsKey(id)) {
    288                     conferenceable.add(mConnectionById.get(id));
    289                 }
    290             }
    291 
    292             if (hasConnection(callId)) {
    293                 findConnectionForAction(callId, "setConferenceableConnections")
    294                         .setConferenceableConnections(conferenceable);
    295             } else {
    296                 findConferenceForAction(callId, "setConferenceableConnections")
    297                         .setConferenceableConnections(conferenceable);
    298             }
    299         }
    300 
    301         @Override
    302         public void addExistingConnection(String callId, ParcelableConnection connection) {
    303             // TODO: add contents of this method
    304             RemoteConnection remoteConnction = new RemoteConnection(callId,
    305                     mOutgoingConnectionServiceRpc, connection);
    306 
    307             mOurConnectionServiceImpl.addRemoteExistingConnection(remoteConnction);
    308         }
    309     };
    310 
    311     private final ConnectionServiceAdapterServant mServant =
    312             new ConnectionServiceAdapterServant(mServantDelegate);
    313 
    314     private final DeathRecipient mDeathRecipient = new DeathRecipient() {
    315         @Override
    316         public void binderDied() {
    317             for (RemoteConnection c : mConnectionById.values()) {
    318                 c.setDestroyed();
    319             }
    320             for (RemoteConference c : mConferenceById.values()) {
    321                 c.setDestroyed();
    322             }
    323             mConnectionById.clear();
    324             mConferenceById.clear();
    325             mPendingConnections.clear();
    326             mOutgoingConnectionServiceRpc.asBinder().unlinkToDeath(mDeathRecipient, 0);
    327         }
    328     };
    329 
    330     private final IConnectionService mOutgoingConnectionServiceRpc;
    331     private final ConnectionService mOurConnectionServiceImpl;
    332     private final Map<String, RemoteConnection> mConnectionById = new HashMap<>();
    333     private final Map<String, RemoteConference> mConferenceById = new HashMap<>();
    334     private final Set<RemoteConnection> mPendingConnections = new HashSet<>();
    335 
    336     RemoteConnectionService(
    337             IConnectionService outgoingConnectionServiceRpc,
    338             ConnectionService ourConnectionServiceImpl) throws RemoteException {
    339         mOutgoingConnectionServiceRpc = outgoingConnectionServiceRpc;
    340         mOutgoingConnectionServiceRpc.asBinder().linkToDeath(mDeathRecipient, 0);
    341         mOurConnectionServiceImpl = ourConnectionServiceImpl;
    342     }
    343 
    344     @Override
    345     public String toString() {
    346         return "[RemoteCS - " + mOutgoingConnectionServiceRpc.asBinder().toString() + "]";
    347     }
    348 
    349     final RemoteConnection createRemoteConnection(
    350             PhoneAccountHandle connectionManagerPhoneAccount,
    351             ConnectionRequest request,
    352             boolean isIncoming) {
    353         final String id = UUID.randomUUID().toString();
    354         final ConnectionRequest newRequest = new ConnectionRequest(
    355                 request.getAccountHandle(),
    356                 request.getAddress(),
    357                 request.getExtras(),
    358                 request.getVideoState());
    359         try {
    360             if (mConnectionById.isEmpty()) {
    361                 mOutgoingConnectionServiceRpc.addConnectionServiceAdapter(mServant.getStub());
    362             }
    363             RemoteConnection connection =
    364                     new RemoteConnection(id, mOutgoingConnectionServiceRpc, newRequest);
    365             mPendingConnections.add(connection);
    366             mConnectionById.put(id, connection);
    367             mOutgoingConnectionServiceRpc.createConnection(
    368                     connectionManagerPhoneAccount,
    369                     id,
    370                     newRequest,
    371                     isIncoming,
    372                     false /* isUnknownCall */);
    373             connection.registerCallback(new RemoteConnection.Callback() {
    374                 @Override
    375                 public void onDestroyed(RemoteConnection connection) {
    376                     mConnectionById.remove(id);
    377                     maybeDisconnectAdapter();
    378                 }
    379             });
    380             return connection;
    381         } catch (RemoteException e) {
    382             return RemoteConnection.failure(
    383                     new DisconnectCause(DisconnectCause.ERROR, e.toString()));
    384         }
    385     }
    386 
    387     private boolean hasConnection(String callId) {
    388         return mConnectionById.containsKey(callId);
    389     }
    390 
    391     private RemoteConnection findConnectionForAction(
    392             String callId, String action) {
    393         if (mConnectionById.containsKey(callId)) {
    394             return mConnectionById.get(callId);
    395         }
    396         Log.w(this, "%s - Cannot find Connection %s", action, callId);
    397         return NULL_CONNECTION;
    398     }
    399 
    400     private RemoteConference findConferenceForAction(
    401             String callId, String action) {
    402         if (mConferenceById.containsKey(callId)) {
    403             return mConferenceById.get(callId);
    404         }
    405         Log.w(this, "%s - Cannot find Conference %s", action, callId);
    406         return NULL_CONFERENCE;
    407     }
    408 
    409     private void maybeDisconnectAdapter() {
    410         if (mConnectionById.isEmpty() && mConferenceById.isEmpty()) {
    411             try {
    412                 mOutgoingConnectionServiceRpc.removeConnectionServiceAdapter(mServant.getStub());
    413             } catch (RemoteException e) {
    414             }
    415         }
    416     }
    417 }
    418