Home | History | Annotate | Download | only in telephony
      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 com.android.services.telephony;
     18 
     19 import java.util.ArrayList;
     20 import java.util.Collection;
     21 import java.util.Collections;
     22 import java.util.HashMap;
     23 import java.util.HashSet;
     24 import java.util.List;
     25 import java.util.Set;
     26 import java.util.stream.Collectors;
     27 
     28 import android.net.Uri;
     29 import android.telecom.Conference;
     30 import android.telecom.ConferenceParticipant;
     31 import android.telecom.Conferenceable;
     32 import android.telecom.Connection;
     33 import android.telecom.DisconnectCause;
     34 import android.telecom.PhoneAccountHandle;
     35 import com.android.phone.PhoneUtils;
     36 
     37 import com.android.internal.telephony.Call;
     38 
     39 /**
     40  * Maintains a list of all the known TelephonyConnections connections and controls GSM and
     41  * default IMS conference call behavior. This functionality is characterized by the support of
     42  * two top-level calls, in contrast to a CDMA conference call which automatically starts a
     43  * conference when there are two calls.
     44  */
     45 final class TelephonyConferenceController {
     46     private static final int TELEPHONY_CONFERENCE_MAX_SIZE = 5;
     47 
     48     private final Connection.Listener mConnectionListener = new Connection.Listener() {
     49         @Override
     50         public void onStateChanged(Connection c, int state) {
     51             Log.v(this, "onStateChange triggered in Conf Controller : connection = "+ c
     52                  + " state = " + state);
     53             recalculate();
     54         }
     55 
     56         /** ${inheritDoc} */
     57         @Override
     58         public void onDisconnected(Connection c, DisconnectCause disconnectCause) {
     59             recalculate();
     60         }
     61 
     62         @Override
     63         public void onDestroyed(Connection connection) {
     64             remove(connection);
     65         }
     66     };
     67 
     68     /** The known connections. */
     69     private final List<TelephonyConnection> mTelephonyConnections = new ArrayList<>();
     70 
     71     private final TelephonyConnectionService mConnectionService;
     72     private boolean mTriggerRecalculate = false;
     73 
     74     public TelephonyConferenceController(TelephonyConnectionService connectionService) {
     75         mConnectionService = connectionService;
     76     }
     77 
     78     /** The TelephonyConference connection object. */
     79     private TelephonyConference mTelephonyConference;
     80 
     81     boolean shouldRecalculate() {
     82         Log.d(this, "shouldRecalculate is " + mTriggerRecalculate);
     83         return mTriggerRecalculate;
     84     }
     85 
     86     void add(TelephonyConnection connection) {
     87         mTelephonyConnections.add(connection);
     88         connection.addConnectionListener(mConnectionListener);
     89         recalculate();
     90     }
     91 
     92     void remove(Connection connection) {
     93         connection.removeConnectionListener(mConnectionListener);
     94         mTelephonyConnections.remove(connection);
     95         recalculate();
     96     }
     97 
     98     void recalculate() {
     99         recalculateConference();
    100         recalculateConferenceable();
    101     }
    102 
    103     private boolean isFullConference(Conference conference) {
    104         return conference.getConnections().size() >= TELEPHONY_CONFERENCE_MAX_SIZE;
    105     }
    106 
    107     private boolean participatesInFullConference(Connection connection) {
    108         return connection.getConference() != null &&
    109                 isFullConference(connection.getConference());
    110     }
    111 
    112     /**
    113      * Calculates the conference-capable state of all GSM connections in this connection service.
    114      */
    115     private void recalculateConferenceable() {
    116         Log.v(this, "recalculateConferenceable : %d", mTelephonyConnections.size());
    117         HashSet<Connection> conferenceableConnections = new HashSet<>(mTelephonyConnections.size());
    118 
    119         // Loop through and collect all calls which are active or holding
    120         for (TelephonyConnection connection : mTelephonyConnections) {
    121             Log.d(this, "recalc - %s %s supportsConf? %s", connection.getState(), connection,
    122                     connection.isConferenceSupported());
    123 
    124             if (connection.isConferenceSupported() && !participatesInFullConference(connection)) {
    125                 switch (connection.getState()) {
    126                     case Connection.STATE_ACTIVE:
    127                         //fall through
    128                     case Connection.STATE_HOLDING:
    129                         conferenceableConnections.add(connection);
    130                         continue;
    131                     default:
    132                         break;
    133                 }
    134             }
    135 
    136             connection.setConferenceableConnections(Collections.<Connection>emptyList());
    137         }
    138 
    139         Log.v(this, "conferenceable: " + conferenceableConnections.size());
    140 
    141         // Go through all the conferenceable connections and add all other conferenceable
    142         // connections that is not the connection itself
    143         for (Connection c : conferenceableConnections) {
    144             List<Connection> connections = conferenceableConnections
    145                     .stream()
    146                     // Filter out this connection from the list of connections
    147                     .filter(connection -> c != connection)
    148                     .collect(Collectors.toList());
    149             c.setConferenceableConnections(connections);
    150         }
    151 
    152         // Set the conference as conferenceable with all of the connections that are not in the
    153         // conference.
    154         if (mTelephonyConference != null && !isFullConference(mTelephonyConference)) {
    155             List<Connection> nonConferencedConnections = mTelephonyConnections
    156                     .stream()
    157                     // Only retrieve Connections that are not in a conference (but support
    158                     // conferences).
    159                     .filter(c -> c.isConferenceSupported() && c.getConference() == null)
    160                     .collect(Collectors.toList());
    161             mTelephonyConference.setConferenceableConnections(nonConferencedConnections);
    162         }
    163         // TODO: Do not allow conferencing of already conferenced connections.
    164     }
    165 
    166     private void recalculateConference() {
    167         Set<Connection> conferencedConnections = new HashSet<>();
    168         int numGsmConnections = 0;
    169 
    170         for (TelephonyConnection connection : mTelephonyConnections) {
    171             com.android.internal.telephony.Connection radioConnection =
    172                 connection.getOriginalConnection();
    173 
    174             if (radioConnection != null) {
    175                 Call.State state = radioConnection.getState();
    176                 Call call = radioConnection.getCall();
    177                 if ((state == Call.State.ACTIVE || state == Call.State.HOLDING) &&
    178                         (call != null && call.isMultiparty())) {
    179 
    180                     numGsmConnections++;
    181                     conferencedConnections.add(connection);
    182                 }
    183             }
    184         }
    185 
    186         Log.d(this, "Recalculate conference calls %s %s.",
    187                 mTelephonyConference, conferencedConnections);
    188 
    189         // Check if all conferenced connections are in Connection Service
    190         boolean allConnInService = true;
    191         Collection<Connection> allConnections = mConnectionService.getAllConnections();
    192         for (Connection connection : conferencedConnections) {
    193             Log.v (this, "Finding connection in Connection Service for " + connection);
    194             if (!allConnections.contains(connection)) {
    195                 allConnInService = false;
    196                 Log.v(this, "Finding connection in Connection Service Failed");
    197                 break;
    198             }
    199         }
    200 
    201         Log.d(this, "Is there a match for all connections in connection service " +
    202             allConnInService);
    203 
    204         // If this is a GSM conference and the number of connections drops below 2, we will
    205         // terminate the conference.
    206         if (numGsmConnections < 2) {
    207             Log.d(this, "not enough connections to be a conference!");
    208 
    209             // No more connections are conferenced, destroy any existing conference.
    210             if (mTelephonyConference != null) {
    211                 Log.d(this, "with a conference to destroy!");
    212                 mTelephonyConference.destroy();
    213                 mTelephonyConference = null;
    214             }
    215         } else {
    216             if (mTelephonyConference != null) {
    217                 List<Connection> existingConnections = mTelephonyConference.getConnections();
    218                 // Remove any that no longer exist
    219                 for (Connection connection : existingConnections) {
    220                     if (connection instanceof TelephonyConnection &&
    221                             !conferencedConnections.contains(connection)) {
    222                         mTelephonyConference.removeConnection(connection);
    223                     }
    224                 }
    225                 if (allConnInService) {
    226                     mTriggerRecalculate = false;
    227                     // Add any new ones
    228                     for (Connection connection : conferencedConnections) {
    229                         if (!existingConnections.contains(connection)) {
    230                             mTelephonyConference.addConnection(connection);
    231                         }
    232                     }
    233                 } else {
    234                     Log.d(this, "Trigger recalculate later");
    235                     mTriggerRecalculate = true;
    236                 }
    237             } else {
    238                 if (allConnInService) {
    239                     mTriggerRecalculate = false;
    240 
    241                     // Get PhoneAccount from one of the conferenced connections and use it to set
    242                     // the phone account on the conference.
    243                     PhoneAccountHandle phoneAccountHandle = null;
    244                     if (!conferencedConnections.isEmpty()) {
    245                         TelephonyConnection telephonyConnection =
    246                                 (TelephonyConnection) conferencedConnections.iterator().next();
    247                         phoneAccountHandle = PhoneUtils.makePstnPhoneAccountHandle(
    248                                 telephonyConnection.getPhone());
    249                     }
    250 
    251                     mTelephonyConference = new TelephonyConference(phoneAccountHandle);
    252                     for (Connection connection : conferencedConnections) {
    253                         Log.d(this, "Adding a connection to a conference call: %s %s",
    254                                 mTelephonyConference, connection);
    255                         mTelephonyConference.addConnection(connection);
    256                     }
    257                     mConnectionService.addConference(mTelephonyConference);
    258                 } else {
    259                     Log.d(this, "Trigger recalculate later");
    260                     mTriggerRecalculate = true;
    261                 }
    262             }
    263             if (mTelephonyConference != null) {
    264                 Connection conferencedConnection = mTelephonyConference.getPrimaryConnection();
    265                 Log.v(this, "Primary Conferenced connection is " + conferencedConnection);
    266                 if (conferencedConnection != null) {
    267                     switch (conferencedConnection.getState()) {
    268                         case Connection.STATE_ACTIVE:
    269                             Log.v(this, "Setting conference to active");
    270                             mTelephonyConference.setActive();
    271                             break;
    272                         case Connection.STATE_HOLDING:
    273                             Log.v(this, "Setting conference to hold");
    274                             mTelephonyConference.setOnHold();
    275                             break;
    276                     }
    277                 }
    278             }
    279         }
    280     }
    281 }
    282