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 com.android.phone.PhoneUtils;
     20 
     21 import android.os.Handler;
     22 import android.telecom.Connection;
     23 import android.telecom.DisconnectCause;
     24 import android.telecom.PhoneAccountHandle;
     25 
     26 import java.util.ArrayList;
     27 import java.util.List;
     28 
     29 /**
     30  * Manages CDMA conference calls. CDMA conference calls are much more limited than GSM conference
     31  * calls. Two main points of difference:
     32  * 1) Users cannot manage individual calls within a conference
     33  * 2) Whether a conference call starts off as a conference or as two distinct calls is a matter of
     34  *    physical location (some antennas are different than others). Worst still, there's no
     35  *    indication given to us as to what state they are in.
     36  *
     37  * To make life easier on the user we do the following: Whenever there exist 2 or more calls, we
     38  * say that we are in a conference call with {@link Connection#PROPERTY_GENERIC_CONFERENCE}.
     39  * Generic indicates that this is a simple conference that doesn't support conference management.
     40  * The conference call will also support "MERGE" to begin with and stop supporting it the first time
     41  * we are asked to actually execute a merge. I emphasize when "we are asked" because we get no
     42  * indication whether the merge succeeds from CDMA, we just assume it does. Thats the best we
     43  * can do. Also, we do not kill a conference call once it is created unless all underlying
     44  * connections also go away.
     45  *
     46  * Outgoing CDMA calls made while another call exists would normally trigger a conference to be
     47  * created. To avoid this and make it seem like there is a "dialing" state, we fake it and prevent
     48  * the conference from being created for 3 seconds. This is a more pleasant experience for the user.
     49  */
     50 final class CdmaConferenceController {
     51     private final Connection.Listener mConnectionListener = new Connection.Listener() {
     52                 @Override
     53                 public void onStateChanged(Connection c, int state) {
     54                     recalculateConference();
     55                 }
     56 
     57                 @Override
     58                 public void onDisconnected(Connection c, DisconnectCause disconnectCause) {
     59                     recalculateConference();
     60                 }
     61 
     62                 @Override
     63                 public void onDestroyed(Connection c) {
     64                     remove((CdmaConnection) c);
     65                 }
     66             };
     67 
     68     private static final int ADD_OUTGOING_CONNECTION_DELAY_MILLIS = 6000;
     69 
     70     /** The known CDMA connections. */
     71     private final List<CdmaConnection> mCdmaConnections = new ArrayList<>();
     72 
     73     /**
     74      * Newly added connections.  We keep track of newly added outgoing connections because we do not
     75      * create a conference until a second outgoing call has existing for
     76      * {@link #ADD_OUTGOING_CONNECTION_DELAY_MILLIS} milliseconds.  This allows the UI to show the
     77      * call as "Dialing" for a certain amount of seconds.
     78      */
     79     private final List<CdmaConnection> mPendingOutgoingConnections = new ArrayList<>();
     80 
     81     private final TelephonyConnectionService mConnectionService;
     82 
     83     private final Handler mHandler = new Handler();
     84 
     85     public CdmaConferenceController(TelephonyConnectionService connectionService) {
     86         mConnectionService = connectionService;
     87     }
     88 
     89     /** The CDMA conference connection object. */
     90     private CdmaConference mConference;
     91 
     92     void add(final CdmaConnection connection) {
     93         if (!mCdmaConnections.isEmpty() && connection.isOutgoing()) {
     94             // There already exists a connection, so this will probably result in a conference once
     95             // it is added. For outgoing connections which are added while another connection
     96             // exists, we mark them as "dialing" for a set amount of time to give the user time to
     97             // see their new call as "Dialing" before it turns into a conference call.
     98             // During that time, we also mark the other calls as "held" or else it can cause issues
     99             // due to having an ACTIVE and a DIALING call simultaneously.
    100             connection.forceAsDialing(true);
    101             final List<CdmaConnection> connectionsToReset =
    102                     new ArrayList<>(mCdmaConnections.size());
    103             for (CdmaConnection current : mCdmaConnections) {
    104                 if (current.setHoldingForConference()) {
    105                     connectionsToReset.add(current);
    106                 }
    107             }
    108             mHandler.postDelayed(new Runnable() {
    109                 @Override
    110                 public void run() {
    111                     connection.forceAsDialing(false);
    112                     addInternal(connection);
    113                     for (CdmaConnection current : connectionsToReset) {
    114                         current.resetStateForConference();
    115                     }
    116                 }
    117             }, ADD_OUTGOING_CONNECTION_DELAY_MILLIS);
    118         } else {
    119             // This is the first connection, or it is incoming, so let it flow through.
    120             addInternal(connection);
    121         }
    122     }
    123 
    124     private void addInternal(CdmaConnection connection) {
    125         mCdmaConnections.add(connection);
    126         connection.addConnectionListener(mConnectionListener);
    127         recalculateConference();
    128     }
    129 
    130     private void remove(CdmaConnection connection) {
    131         connection.removeConnectionListener(mConnectionListener);
    132         mCdmaConnections.remove(connection);
    133         recalculateConference();
    134     }
    135 
    136     private void recalculateConference() {
    137         List<CdmaConnection> conferenceConnections = new ArrayList<>(mCdmaConnections.size());
    138         for (CdmaConnection connection : mCdmaConnections) {
    139             // We do not include call-waiting calls in conferences.
    140             if (!connection.isCallWaiting() &&
    141                     connection.getState() != Connection.STATE_DISCONNECTED) {
    142                 conferenceConnections.add(connection);
    143             }
    144         }
    145 
    146         Log.d(this, "recalculating conference calls %d", conferenceConnections.size());
    147         if (conferenceConnections.size() >= 2) {
    148             boolean isNewlyCreated = false;
    149 
    150             CdmaConnection newConnection = mCdmaConnections.get(mCdmaConnections.size() - 1);
    151 
    152             // There are two or more CDMA connections. Do the following:
    153             // 1) Create a new conference connection if it doesn't exist.
    154             if (mConference == null) {
    155                 Log.i(this, "Creating new Cdma conference call");
    156                 PhoneAccountHandle phoneAccountHandle =
    157                         PhoneUtils.makePstnPhoneAccountHandle(newConnection.getPhone());
    158                 mConference = new CdmaConference(phoneAccountHandle);
    159                 isNewlyCreated = true;
    160             }
    161 
    162             if (newConnection.isOutgoing()) {
    163                 // Only an outgoing call can be merged with an ongoing call.
    164                 mConference.updateCapabilities(Connection.CAPABILITY_MERGE_CONFERENCE);
    165             } else {
    166                 // If the most recently added connection was an incoming call, enable
    167                 // swap instead of merge.
    168                 mConference.updateCapabilities(Connection.CAPABILITY_SWAP_CONFERENCE);
    169             }
    170 
    171             // 2) Add any new connections to the conference
    172             List<Connection> existingChildConnections =
    173                     new ArrayList<>(mConference.getConnections());
    174             for (CdmaConnection connection : conferenceConnections) {
    175                 if (!existingChildConnections.contains(connection)) {
    176                     Log.i(this, "Adding connection to conference call: %s", connection);
    177                     mConference.addConnection(connection);
    178                 }
    179                 existingChildConnections.remove(connection);
    180             }
    181 
    182             // 3) Remove any lingering old/disconnected/destroyed connections
    183             for (Connection oldConnection : existingChildConnections) {
    184                 mConference.removeConnection(oldConnection);
    185                 Log.i(this, "Removing connection from conference call: %s", oldConnection);
    186             }
    187 
    188             // 4) Add the conference to the connection service if it is new.
    189             if (isNewlyCreated) {
    190                 Log.d(this, "Adding the conference call");
    191                 mConnectionService.addConference(mConference);
    192             }
    193         } else if (conferenceConnections.isEmpty()) {
    194             // There are no more connection so if we still have a conference, lets remove it.
    195             if (mConference != null) {
    196                 Log.i(this, "Destroying the CDMA conference connection.");
    197                 mConference.destroy();
    198                 mConference = null;
    199             }
    200         }
    201     }
    202 }
    203