Home | History | Annotate | Download | only in phone
      1 /*
      2  * Copyright (C) 2013 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.phone;
     18 
     19 import android.os.AsyncResult;
     20 import android.os.Handler;
     21 import android.os.Message;
     22 import android.os.SystemProperties;
     23 import android.telephony.PhoneNumberUtils;
     24 import android.text.TextUtils;
     25 import android.util.Log;
     26 
     27 import com.android.internal.telephony.CallManager;
     28 import com.android.internal.telephony.Connection;
     29 import com.android.internal.telephony.Phone;
     30 import com.android.internal.telephony.PhoneConstants;
     31 import com.android.internal.telephony.TelephonyCapabilities;
     32 import com.android.internal.telephony.cdma.CdmaCallWaitingNotification;
     33 import com.android.phone.CallGatewayManager.RawGatewayInfo;
     34 import com.android.services.telephony.common.Call;
     35 import com.android.services.telephony.common.Call.Capabilities;
     36 import com.android.services.telephony.common.Call.State;
     37 
     38 import com.google.android.collect.Maps;
     39 import com.google.android.collect.Sets;
     40 import com.google.common.base.Preconditions;
     41 import com.google.common.collect.ImmutableMap;
     42 import com.google.common.collect.ImmutableSortedSet;
     43 import com.google.common.collect.Lists;
     44 
     45 import java.util.ArrayList;
     46 import java.util.Collections;
     47 import java.util.HashMap;
     48 import java.util.List;
     49 import java.util.Map.Entry;
     50 import java.util.Set;
     51 import java.util.concurrent.atomic.AtomicInteger;
     52 
     53 /**
     54  * Creates a Call model from Call state and data received from the telephony
     55  * layer. The telephony layer maintains 3 conceptual objects: Phone, Call,
     56  * Connection.
     57  *
     58  * Phone represents the radio and there is an implementation per technology
     59  * type such as GSMPhone, SipPhone, CDMAPhone, etc. Generally, we will only ever
     60  * deal with one instance of this object for the lifetime of this class.
     61  *
     62  * There are 3 Call instances that exist for the lifetime of this class which
     63  * are created by CallTracker. The three are RingingCall, ForegroundCall, and
     64  * BackgroundCall.
     65  *
     66  * A Connection most closely resembles what the layperson would consider a call.
     67  * A Connection is created when a user dials and it is "owned" by one of the
     68  * three Call instances.  Which of the three Calls owns the Connection changes
     69  * as the Connection goes between ACTIVE, HOLD, RINGING, and other states.
     70  *
     71  * This class models a new Call class from Connection objects received from
     72  * the telephony layer. We use Connection references as identifiers for a call;
     73  * new reference = new call.
     74  *
     75  * TODO: Create a new Call class to replace the simple call Id ints
     76  * being used currently.
     77  *
     78  * The new Call models are parcellable for transfer via the CallHandlerService
     79  * API.
     80  */
     81 public class CallModeler extends Handler {
     82 
     83     private static final String TAG = CallModeler.class.getSimpleName();
     84     private static final boolean DBG =
     85             (PhoneGlobals.DBG_LEVEL >= 1) && (SystemProperties.getInt("ro.debuggable", 0) == 1);
     86 
     87     private static final int CALL_ID_START_VALUE = 1;
     88 
     89     private final CallStateMonitor mCallStateMonitor;
     90     private final CallManager mCallManager;
     91     private final CallGatewayManager mCallGatewayManager;
     92     private final HashMap<Connection, Call> mCallMap = Maps.newHashMap();
     93     private final HashMap<Connection, Call> mConfCallMap = Maps.newHashMap();
     94     private final AtomicInteger mNextCallId = new AtomicInteger(CALL_ID_START_VALUE);
     95     private final ArrayList<Listener> mListeners = new ArrayList<Listener>();
     96     private Connection mCdmaIncomingConnection;
     97     private Connection mCdmaOutgoingConnection;
     98 
     99     public CallModeler(CallStateMonitor callStateMonitor, CallManager callManager,
    100             CallGatewayManager callGatewayManager) {
    101         mCallStateMonitor = callStateMonitor;
    102         mCallManager = callManager;
    103         mCallGatewayManager = callGatewayManager;
    104 
    105         mCallStateMonitor.addListener(this);
    106     }
    107 
    108     @Override
    109     public void handleMessage(Message msg) {
    110         switch(msg.what) {
    111             case CallStateMonitor.PHONE_NEW_RINGING_CONNECTION:
    112                 // We let the CallNotifier handle the new ringing connection first. When the custom
    113                 // ringtone and send_to_voicemail settings are retrieved, CallNotifier will directly
    114                 // call CallModeler's onNewRingingConnection.
    115                 break;
    116             case CallStateMonitor.PHONE_DISCONNECT:
    117                 onDisconnect((Connection) ((AsyncResult) msg.obj).result);
    118                 break;
    119             case CallStateMonitor.PHONE_UNKNOWN_CONNECTION_APPEARED:
    120                 // fall through
    121             case CallStateMonitor.PHONE_STATE_CHANGED:
    122                 onPhoneStateChanged((AsyncResult) msg.obj);
    123                 break;
    124             case CallStateMonitor.PHONE_ON_DIAL_CHARS:
    125                 onPostDialChars((AsyncResult) msg.obj, (char) msg.arg1);
    126                 break;
    127             default:
    128                 break;
    129         }
    130     }
    131 
    132     public void addListener(Listener listener) {
    133         Preconditions.checkNotNull(listener);
    134         Preconditions.checkNotNull(mListeners);
    135         if (!mListeners.contains(listener)) {
    136             mListeners.add(listener);
    137         }
    138     }
    139 
    140     public List<Call> getFullList() {
    141         final List<Call> calls =
    142                 Lists.newArrayListWithCapacity(mCallMap.size() + mConfCallMap.size());
    143         calls.addAll(mCallMap.values());
    144         calls.addAll(mConfCallMap.values());
    145         return calls;
    146     }
    147 
    148     public CallResult getCallWithId(int callId) {
    149         // max 8 connections, so this should be fast even through we are traversing the entire map.
    150         for (Entry<Connection, Call> entry : mCallMap.entrySet()) {
    151             if (entry.getValue().getCallId() == callId) {
    152                 return new CallResult(entry.getValue(), entry.getKey());
    153             }
    154         }
    155 
    156         for (Entry<Connection, Call> entry : mConfCallMap.entrySet()) {
    157             if (entry.getValue().getCallId() == callId) {
    158                 return new CallResult(entry.getValue(), entry.getKey());
    159             }
    160         }
    161         return null;
    162     }
    163 
    164     public boolean hasLiveCall() {
    165         return hasLiveCallInternal(mCallMap) ||
    166             hasLiveCallInternal(mConfCallMap);
    167     }
    168 
    169     public void onCdmaCallWaiting(CdmaCallWaitingNotification callWaitingInfo) {
    170         // We dont get the traditional onIncomingCall notification for cdma call waiting,
    171         // but the Connection does actually exist.  We need to find it in the set of ringing calls
    172         // and pass it through our normal incoming logic.
    173         final com.android.internal.telephony.Call teleCall =
    174             mCallManager.getFirstActiveRingingCall();
    175 
    176         if (teleCall.getState() == com.android.internal.telephony.Call.State.WAITING) {
    177             Connection connection = teleCall.getLatestConnection();
    178 
    179             if (connection != null) {
    180                 String number = connection.getAddress();
    181                 if (number != null && number.equals(callWaitingInfo.number)) {
    182                     Call call = onNewRingingConnection(connection);
    183                     mCdmaIncomingConnection = connection;
    184                     return;
    185                 }
    186             }
    187         }
    188 
    189         Log.e(TAG, "CDMA Call waiting notification without a matching connection.");
    190     }
    191 
    192     public void onCdmaCallWaitingReject() {
    193         // Cdma call was rejected...
    194         if (mCdmaIncomingConnection != null) {
    195             onDisconnect(mCdmaIncomingConnection);
    196             mCdmaIncomingConnection = null;
    197         } else {
    198             Log.e(TAG, "CDMA Call waiting rejection without an incoming call.");
    199         }
    200     }
    201 
    202     /**
    203      * CDMA Calls have no sense of "dialing" state. For outgoing calls 3way calls we want to
    204      * mimick this state so that the the UI can notify the user that there is a "dialing"
    205      * call.
    206      */
    207     public void setCdmaOutgoing3WayCall(Connection connection) {
    208         boolean wasSet = mCdmaOutgoingConnection != null;
    209 
    210         mCdmaOutgoingConnection = connection;
    211 
    212         // If we reset the connection, that mean we can now tell the user that the call is actually
    213         // part of the conference call and move it out of the dialing state. To do this, issue a
    214         // new update completely.
    215         if (wasSet && mCdmaOutgoingConnection == null) {
    216             onPhoneStateChanged(null);
    217         }
    218     }
    219 
    220     private boolean hasLiveCallInternal(HashMap<Connection, Call> map) {
    221         for (Call call : map.values()) {
    222             final int state = call.getState();
    223             if (state == Call.State.ACTIVE ||
    224                     state == Call.State.CALL_WAITING ||
    225                     state == Call.State.CONFERENCED ||
    226                     state == Call.State.DIALING ||
    227                     state == Call.State.REDIALING ||
    228                     state == Call.State.INCOMING ||
    229                     state == Call.State.ONHOLD ||
    230                     state == Call.State.DISCONNECTING) {
    231                 return true;
    232             }
    233         }
    234         return false;
    235     }
    236 
    237     public boolean hasOutstandingActiveOrDialingCall() {
    238         return hasOutstandingActiveOrDialingCallInternal(mCallMap) ||
    239                 hasOutstandingActiveOrDialingCallInternal(mConfCallMap);
    240     }
    241 
    242     private static boolean hasOutstandingActiveOrDialingCallInternal(
    243             HashMap<Connection, Call> map) {
    244         for (Call call : map.values()) {
    245             final int state = call.getState();
    246             if (state == Call.State.ACTIVE || Call.State.isDialing(state)) {
    247                 return true;
    248             }
    249         }
    250 
    251         return false;
    252     }
    253 
    254 
    255     /**
    256      * Handles the POST_ON_DIAL_CHARS message from the Phone (see our call to
    257      * mPhone.setOnPostDialCharacter() above.)
    258      *
    259      * TODO: NEED TO TEST THIS SEQUENCE now that we no longer handle "dialable" key events here in
    260      * the InCallScreen: we do directly to the Dialer UI instead.  Similarly, we may now need to go
    261      * directly to the Dialer to handle POST_ON_DIAL_CHARS too.
    262      */
    263     private void onPostDialChars(AsyncResult r, char ch) {
    264         final Connection c = (Connection) r.result;
    265 
    266         if (c != null) {
    267             final Connection.PostDialState state = (Connection.PostDialState) r.userObj;
    268 
    269             switch (state) {
    270                 case WAIT:
    271                     final Call call = getCallFromMap(mCallMap, c, false);
    272                     if (call == null) {
    273                         Log.i(TAG, "Call no longer exists. Skipping onPostDialWait().");
    274                     } else {
    275                         for (Listener mListener : mListeners) {
    276                             mListener.onPostDialAction(state, call.getCallId(),
    277                                     c.getRemainingPostDialString(), ch);
    278                         }
    279                     }
    280                     break;
    281                 default:
    282                     // This is primarily to cause the DTMFTonePlayer to play local tones.
    283                     // Other listeners simply perform no-ops.
    284                     for (Listener mListener : mListeners) {
    285                         mListener.onPostDialAction(state, 0, "", ch);
    286                     }
    287                     break;
    288             }
    289         }
    290     }
    291 
    292     /* package */ Call onNewRingingConnection(Connection conn) {
    293         Log.i(TAG, "onNewRingingConnection");
    294         final Call call = getCallFromMap(mCallMap, conn, true);
    295 
    296         if (call != null) {
    297             updateCallFromConnection(call, conn, false);
    298 
    299             for (int i = 0; i < mListeners.size(); ++i) {
    300                 mListeners.get(i).onIncoming(call);
    301             }
    302         }
    303 
    304         PhoneGlobals.getInstance().updateWakeState();
    305         return call;
    306     }
    307 
    308     private void onDisconnect(Connection conn) {
    309         Log.i(TAG, "onDisconnect");
    310         final Call call = getCallFromMap(mCallMap, conn, false);
    311 
    312         if (call != null) {
    313             final boolean wasConferenced = call.getState() == State.CONFERENCED;
    314 
    315             updateCallFromConnection(call, conn, false);
    316 
    317             for (int i = 0; i < mListeners.size(); ++i) {
    318                 mListeners.get(i).onDisconnect(call);
    319             }
    320 
    321             // If it was a conferenced call, we need to run the entire update
    322             // to make the proper changes to parent conference calls.
    323             if (wasConferenced) {
    324                 onPhoneStateChanged(null);
    325             }
    326 
    327             mCallMap.remove(conn);
    328         }
    329 
    330         mCallManager.clearDisconnected();
    331         PhoneGlobals.getInstance().updateWakeState();
    332     }
    333 
    334     /**
    335      * Called when the phone state changes.
    336      */
    337     private void onPhoneStateChanged(AsyncResult r) {
    338         Log.i(TAG, "onPhoneStateChanged: ");
    339         final List<Call> updatedCalls = Lists.newArrayList();
    340         doUpdate(false, updatedCalls);
    341 
    342         if (updatedCalls.size() > 0) {
    343             for (int i = 0; i < mListeners.size(); ++i) {
    344                 mListeners.get(i).onUpdate(updatedCalls);
    345             }
    346         }
    347 
    348         PhoneGlobals.getInstance().updateWakeState();
    349     }
    350 
    351     /**
    352      * Go through the Calls from CallManager and return the list of calls that were updated.
    353      * Method also finds any orphaned Calls (Connection objects no longer returned by telephony as
    354      * either ringing, foreground, or background).  For each orphaned call, it sets the call state
    355      * to IDLE and adds it to the list of calls to update.
    356      *
    357      * @param fullUpdate Add all calls to out parameter including those that have no updates.
    358      * @param out List to populate with Calls that have been updated.
    359      */
    360     private void doUpdate(boolean fullUpdate, List<Call> out) {
    361         final List<com.android.internal.telephony.Call> telephonyCalls = Lists.newArrayList();
    362         telephonyCalls.addAll(mCallManager.getRingingCalls());
    363         telephonyCalls.addAll(mCallManager.getForegroundCalls());
    364         telephonyCalls.addAll(mCallManager.getBackgroundCalls());
    365 
    366         // orphanedConnections starts out including all connections we know about.
    367         // As we iterate through the connections we get from the telephony layer we
    368         // prune this Set down to only the connections we have but telephony no longer
    369         // recognizes.
    370         final Set<Connection> orphanedConnections = Sets.newHashSet();
    371         orphanedConnections.addAll(mCallMap.keySet());
    372         orphanedConnections.addAll(mConfCallMap.keySet());
    373 
    374         // Cycle through all the Connections on all the Calls. Update our Call objects
    375         // to reflect any new state and send the updated Call objects to the handler service.
    376         for (com.android.internal.telephony.Call telephonyCall : telephonyCalls) {
    377 
    378             for (Connection connection : telephonyCall.getConnections()) {
    379                 if (DBG) Log.d(TAG, "connection: " + connection + connection.getState());
    380 
    381                 if (orphanedConnections.contains(connection)) {
    382                     orphanedConnections.remove(connection);
    383                 }
    384 
    385                 // We only send updates for live calls which are not incoming (ringing).
    386                 // Disconnected and incoming calls are handled by onDisconnect and
    387                 // onNewRingingConnection.
    388                 final boolean shouldUpdate =
    389                         connection.getState() !=
    390                                 com.android.internal.telephony.Call.State.DISCONNECTED &&
    391                         connection.getState() !=
    392                                 com.android.internal.telephony.Call.State.IDLE &&
    393                         !connection.getState().isRinging();
    394 
    395                 final boolean isDisconnecting = connection.getState() ==
    396                                 com.android.internal.telephony.Call.State.DISCONNECTING;
    397 
    398                 // For disconnecting calls, we still need to send the update to the UI but we do
    399                 // not create a new call if the call did not exist.
    400                 final boolean shouldCreate = shouldUpdate && !isDisconnecting;
    401 
    402                 // New connections return a Call with INVALID state, which does not translate to
    403                 // a state in the internal.telephony.Call object.  This ensures that staleness
    404                 // check below fails and we always add the item to the update list if it is new.
    405                 final Call call = getCallFromMap(mCallMap, connection, shouldCreate /* create */);
    406 
    407                 if (call == null || !shouldUpdate) {
    408                     if (DBG) Log.d(TAG, "update skipped");
    409                     continue;
    410                 }
    411 
    412                 boolean changed = updateCallFromConnection(call, connection, false);
    413 
    414                 if (fullUpdate || changed) {
    415                     out.add(call);
    416                 }
    417             }
    418 
    419             // We do a second loop to address conference call scenarios.  We do this as a separate
    420             // loop to ensure all child calls are up to date before we start updating the parent
    421             // conference calls.
    422             for (Connection connection : telephonyCall.getConnections()) {
    423                 updateForConferenceCalls(connection, out);
    424             }
    425         }
    426 
    427         // Iterate through orphaned connections, set them to idle, and remove
    428         // them from our internal structures.
    429         for (Connection orphanedConnection : orphanedConnections) {
    430             if (mCallMap.containsKey(orphanedConnection)) {
    431                 final Call call = mCallMap.get(orphanedConnection);
    432                 call.setState(Call.State.IDLE);
    433                 out.add(call);
    434 
    435                 mCallMap.remove(orphanedConnection);
    436             }
    437 
    438             if (mConfCallMap.containsKey(orphanedConnection)) {
    439                 final Call call = mCallMap.get(orphanedConnection);
    440                 call.setState(Call.State.IDLE);
    441                 out.add(call);
    442 
    443                 mConfCallMap.remove(orphanedConnection);
    444             }
    445         }
    446     }
    447 
    448     /**
    449      * Checks to see if the connection is the first connection in a conference call.
    450      * If it is a conference call, we will create a new Conference Call object or
    451      * update the existing conference call object for that connection.
    452      * If it is not a conference call but a previous associated conference call still exists,
    453      * we mark it as idle and remove it from the map.
    454      * In both cases above, we add the Calls to be updated to the UI.
    455      * @param connection The connection object to check.
    456      * @param updatedCalls List of 'updated' calls that will be sent to the UI.
    457      */
    458     private boolean updateForConferenceCalls(Connection connection, List<Call> updatedCalls) {
    459         // We consider this connection a conference connection if the call it
    460         // belongs to is a multiparty call AND it is the first live connection.
    461         final boolean isConferenceCallConnection = isPartOfLiveConferenceCall(connection) &&
    462                 getEarliestLiveConnection(connection.getCall()) == connection;
    463 
    464         boolean changed = false;
    465 
    466         // If this connection is the main connection for the conference call, then create or update
    467         // a Call object for that conference call.
    468         if (isConferenceCallConnection) {
    469             final Call confCall = getCallFromMap(mConfCallMap, connection, true);
    470             changed = updateCallFromConnection(confCall, connection, true);
    471 
    472             if (changed) {
    473                 updatedCalls.add(confCall);
    474             }
    475 
    476             if (DBG) Log.d(TAG, "Updating a conference call: " + confCall);
    477 
    478         // It is possible that through a conference call split, there may be lingering conference
    479         // calls where this connection was the main connection.  We clean those up here.
    480         } else {
    481             final Call oldConfCall = getCallFromMap(mConfCallMap, connection, false);
    482 
    483             // We found a conference call for this connection, which is no longer a conference call.
    484             // Kill it!
    485             if (oldConfCall != null) {
    486                 if (DBG) Log.d(TAG, "Cleaning up an old conference call: " + oldConfCall);
    487                 mConfCallMap.remove(connection);
    488                 oldConfCall.setState(State.IDLE);
    489                 changed = true;
    490 
    491                 // add to the list of calls to update
    492                 updatedCalls.add(oldConfCall);
    493             }
    494         }
    495 
    496         return changed;
    497     }
    498 
    499     private Connection getEarliestLiveConnection(com.android.internal.telephony.Call call) {
    500         final List<Connection> connections = call.getConnections();
    501         final int size = connections.size();
    502         Connection earliestConn = null;
    503         long earliestTime = Long.MAX_VALUE;
    504         for (int i = 0; i < size; i++) {
    505             final Connection connection = connections.get(i);
    506             if (!connection.isAlive()) continue;
    507             final long time = connection.getCreateTime();
    508             if (time < earliestTime) {
    509                 earliestTime = time;
    510                 earliestConn = connection;
    511             }
    512         }
    513         return earliestConn;
    514     }
    515 
    516     /**
    517      * Sets the new call state onto the call and performs some additional logic
    518      * associated with setting the state.
    519      */
    520     private void setNewState(Call call, int newState, Connection connection) {
    521         Preconditions.checkState(call.getState() != newState);
    522 
    523         // When starting an outgoing call, we need to grab gateway information
    524         // for the call, if available, and set it.
    525         final RawGatewayInfo info = mCallGatewayManager.getGatewayInfo(connection);
    526 
    527         if (Call.State.isDialing(newState)) {
    528             if (!info.isEmpty()) {
    529                 call.setGatewayNumber(info.getFormattedGatewayNumber());
    530                 call.setGatewayPackage(info.packageName);
    531             }
    532         } else if (!Call.State.isConnected(newState)) {
    533             mCallGatewayManager.clearGatewayData(connection);
    534         }
    535 
    536         call.setState(newState);
    537     }
    538 
    539     /**
    540      * Updates the Call properties to match the state of the connection object
    541      * that it represents.
    542      * @param call The call object to update.
    543      * @param connection The connection object from which to update call.
    544      * @param isForConference There are slight differences in how we populate data for conference
    545      *     calls. This boolean tells us which method to use.
    546      */
    547     private boolean updateCallFromConnection(Call call, Connection connection,
    548             boolean isForConference) {
    549         boolean changed = false;
    550 
    551         final int newState = translateStateFromTelephony(connection, isForConference);
    552 
    553         if (call.getState() != newState) {
    554             setNewState(call, newState, connection);
    555             changed = true;
    556         }
    557 
    558         final Call.DisconnectCause newDisconnectCause =
    559                 translateDisconnectCauseFromTelephony(connection.getDisconnectCause());
    560         if (call.getDisconnectCause() != newDisconnectCause) {
    561             call.setDisconnectCause(newDisconnectCause);
    562             changed = true;
    563         }
    564 
    565         final long oldConnectTime = call.getConnectTime();
    566         if (oldConnectTime != connection.getConnectTime()) {
    567             call.setConnectTime(connection.getConnectTime());
    568             changed = true;
    569         }
    570 
    571         if (!isForConference) {
    572             // Number
    573             final String oldNumber = call.getNumber();
    574             String newNumber = connection.getAddress();
    575             RawGatewayInfo info = mCallGatewayManager.getGatewayInfo(connection);
    576             if (!info.isEmpty()) {
    577                 newNumber = info.trueNumber;
    578             }
    579             if (TextUtils.isEmpty(oldNumber) || !oldNumber.equals(newNumber)) {
    580                 call.setNumber(newNumber);
    581                 changed = true;
    582             }
    583 
    584             // Number presentation
    585             final int newNumberPresentation = connection.getNumberPresentation();
    586             if (call.getNumberPresentation() != newNumberPresentation) {
    587                 call.setNumberPresentation(newNumberPresentation);
    588                 changed = true;
    589             }
    590 
    591             // Name
    592             final String oldCnapName = call.getCnapName();
    593             if (TextUtils.isEmpty(oldCnapName) || !oldCnapName.equals(connection.getCnapName())) {
    594                 call.setCnapName(connection.getCnapName());
    595                 changed = true;
    596             }
    597 
    598             // Name Presentation
    599             final int newCnapNamePresentation = connection.getCnapNamePresentation();
    600             if (call.getCnapNamePresentation() != newCnapNamePresentation) {
    601                 call.setCnapNamePresentation(newCnapNamePresentation);
    602                 changed = true;
    603             }
    604         } else {
    605 
    606             // update the list of children by:
    607             // 1) Saving the old set
    608             // 2) Removing all children
    609             // 3) Adding the correct children into the Call
    610             // 4) Comparing the new children set with the old children set
    611             ImmutableSortedSet<Integer> oldSet = call.getChildCallIds();
    612             call.removeAllChildren();
    613 
    614             if (connection.getCall() != null) {
    615                 for (Connection childConn : connection.getCall().getConnections()) {
    616                     final Call childCall = getCallFromMap(mCallMap, childConn, false);
    617                     if (childCall != null && childConn.isAlive()) {
    618                         call.addChildId(childCall.getCallId());
    619                     }
    620                 }
    621             }
    622             changed |= !oldSet.equals(call.getChildCallIds());
    623         }
    624 
    625         /**
    626          * !!! Uses values from connection and call collected above so this part must be last !!!
    627          */
    628         final int newCapabilities = getCapabilitiesFor(connection, call, isForConference);
    629         if (call.getCapabilities() != newCapabilities) {
    630             call.setCapabilities(newCapabilities);
    631             changed = true;
    632         }
    633 
    634         return changed;
    635     }
    636 
    637     /**
    638      * Returns a mask of capabilities for the connection such as merge, hold, etc.
    639      */
    640     private int getCapabilitiesFor(Connection connection, Call call, boolean isForConference) {
    641         final boolean callIsActive = (call.getState() == Call.State.ACTIVE);
    642         final Phone phone = connection.getCall().getPhone();
    643 
    644         boolean canAddCall = false;
    645         boolean canMergeCall = false;
    646         boolean canSwapCall = false;
    647         boolean canRespondViaText = false;
    648         boolean canMute = false;
    649 
    650         final boolean supportHold = PhoneUtils.okToSupportHold(mCallManager);
    651         final boolean canHold = (supportHold ? PhoneUtils.okToHoldCall(mCallManager) : false);
    652         final boolean genericConf = isForConference &&
    653                 (connection.getCall().getPhone().getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA);
    654 
    655         // only applies to active calls
    656         if (callIsActive) {
    657             canMergeCall = PhoneUtils.okToMergeCalls(mCallManager);
    658             canSwapCall = PhoneUtils.okToSwapCalls(mCallManager);
    659         }
    660 
    661         canAddCall = PhoneUtils.okToAddCall(mCallManager);
    662 
    663         // "Mute": only enabled when the foreground call is ACTIVE.
    664         // (It's meaningless while on hold, or while DIALING/ALERTING.)
    665         // It's also explicitly disabled during emergency calls or if
    666         // emergency callback mode (ECM) is active.
    667         boolean isEmergencyCall = false;
    668         if (connection != null) {
    669             isEmergencyCall = PhoneNumberUtils.isLocalEmergencyNumber(connection.getAddress(),
    670                     phone.getContext());
    671         }
    672         boolean isECM = PhoneUtils.isPhoneInEcm(phone);
    673         if (isEmergencyCall || isECM) {  // disable "Mute" item
    674             canMute = false;
    675         } else {
    676             canMute = callIsActive;
    677         }
    678 
    679         canRespondViaText = RejectWithTextMessageManager.allowRespondViaSmsForCall(call,
    680                 connection);
    681 
    682         // special rules section!
    683         // CDMA always has Add
    684         if (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA) {
    685             canAddCall = true;
    686         }
    687 
    688         int retval = 0x0;
    689         if (canHold) {
    690             retval |= Capabilities.HOLD;
    691         }
    692         if (supportHold) {
    693             retval |= Capabilities.SUPPORT_HOLD;
    694         }
    695         if (canAddCall) {
    696             retval |= Capabilities.ADD_CALL;
    697         }
    698         if (canMergeCall) {
    699             retval |= Capabilities.MERGE_CALLS;
    700         }
    701         if (canSwapCall) {
    702             retval |= Capabilities.SWAP_CALLS;
    703         }
    704         if (canRespondViaText) {
    705             retval |= Capabilities.RESPOND_VIA_TEXT;
    706         }
    707         if (canMute) {
    708             retval |= Capabilities.MUTE;
    709         }
    710         if (genericConf) {
    711             retval |= Capabilities.GENERIC_CONFERENCE;
    712         }
    713 
    714         return retval;
    715     }
    716 
    717     /**
    718      * Returns true if the Connection is part of a multiparty call.
    719      * We do this by checking the isMultiparty() method of the telephony.Call object and also
    720      * checking to see if more than one of it's children is alive.
    721      */
    722     private boolean isPartOfLiveConferenceCall(Connection connection) {
    723         if (connection.getCall() != null && connection.getCall().isMultiparty()) {
    724             int count = 0;
    725             for (Connection currConn : connection.getCall().getConnections()) {
    726 
    727                 // Only count connections which are alive and never cound the special
    728                 // "dialing" 3way call for CDMA calls.
    729                 if (currConn.isAlive() && currConn != mCdmaOutgoingConnection) {
    730                     count++;
    731                     if (count >= 2) {
    732                         return true;
    733                     }
    734                 }
    735             }
    736         }
    737         return false;
    738     }
    739 
    740     private int translateStateFromTelephony(Connection connection, boolean isForConference) {
    741 
    742         com.android.internal.telephony.Call.State connState = connection.getState();
    743 
    744         // For the "fake" outgoing CDMA call, we need to always treat it as an outgoing call.
    745         if (mCdmaOutgoingConnection == connection) {
    746             connState = com.android.internal.telephony.Call.State.DIALING;
    747         }
    748 
    749         int retval = State.IDLE;
    750         switch (connState) {
    751             case ACTIVE:
    752                 retval = State.ACTIVE;
    753                 break;
    754             case INCOMING:
    755                 retval = State.INCOMING;
    756                 break;
    757             case DIALING:
    758             case ALERTING:
    759                 if (PhoneGlobals.getInstance().notifier.getIsCdmaRedialCall()) {
    760                     retval = State.REDIALING;
    761                 } else {
    762                     retval = State.DIALING;
    763                 }
    764                 break;
    765             case WAITING:
    766                 retval = State.CALL_WAITING;
    767                 break;
    768             case HOLDING:
    769                 retval = State.ONHOLD;
    770                 break;
    771             case DISCONNECTING:
    772                 retval = State.DISCONNECTING;
    773                 break;
    774             case DISCONNECTED:
    775                 retval = State.DISCONNECTED;
    776             default:
    777         }
    778 
    779         // If we are dealing with a potential child call (not the parent conference call),
    780         // the check to see if we have to set the state to CONFERENCED.
    781         if (!isForConference) {
    782             // if the connection is part of a multiparty call, and it is live,
    783             // annotate it with CONFERENCED state instead.
    784             if (isPartOfLiveConferenceCall(connection) && connection.isAlive()) {
    785                 return State.CONFERENCED;
    786             }
    787         }
    788 
    789         return retval;
    790     }
    791 
    792     private final ImmutableMap<Connection.DisconnectCause, Call.DisconnectCause> CAUSE_MAP =
    793             ImmutableMap.<Connection.DisconnectCause, Call.DisconnectCause>builder()
    794                 .put(Connection.DisconnectCause.BUSY, Call.DisconnectCause.BUSY)
    795                 .put(Connection.DisconnectCause.CALL_BARRED, Call.DisconnectCause.CALL_BARRED)
    796                 .put(Connection.DisconnectCause.CDMA_ACCESS_BLOCKED,
    797                         Call.DisconnectCause.CDMA_ACCESS_BLOCKED)
    798                 .put(Connection.DisconnectCause.CDMA_ACCESS_FAILURE,
    799                         Call.DisconnectCause.CDMA_ACCESS_FAILURE)
    800                 .put(Connection.DisconnectCause.CDMA_DROP, Call.DisconnectCause.CDMA_DROP)
    801                 .put(Connection.DisconnectCause.CDMA_INTERCEPT, Call.DisconnectCause.CDMA_INTERCEPT)
    802                 .put(Connection.DisconnectCause.CDMA_LOCKED_UNTIL_POWER_CYCLE,
    803                         Call.DisconnectCause.CDMA_LOCKED_UNTIL_POWER_CYCLE)
    804                 .put(Connection.DisconnectCause.CDMA_NOT_EMERGENCY,
    805                         Call.DisconnectCause.CDMA_NOT_EMERGENCY)
    806                 .put(Connection.DisconnectCause.CDMA_PREEMPTED, Call.DisconnectCause.CDMA_PREEMPTED)
    807                 .put(Connection.DisconnectCause.CDMA_REORDER, Call.DisconnectCause.CDMA_REORDER)
    808                 .put(Connection.DisconnectCause.CDMA_RETRY_ORDER,
    809                         Call.DisconnectCause.CDMA_RETRY_ORDER)
    810                 .put(Connection.DisconnectCause.CDMA_SO_REJECT, Call.DisconnectCause.CDMA_SO_REJECT)
    811                 .put(Connection.DisconnectCause.CONGESTION, Call.DisconnectCause.CONGESTION)
    812                 .put(Connection.DisconnectCause.CS_RESTRICTED, Call.DisconnectCause.CS_RESTRICTED)
    813                 .put(Connection.DisconnectCause.CS_RESTRICTED_EMERGENCY,
    814                         Call.DisconnectCause.CS_RESTRICTED_EMERGENCY)
    815                 .put(Connection.DisconnectCause.CS_RESTRICTED_NORMAL,
    816                         Call.DisconnectCause.CS_RESTRICTED_NORMAL)
    817                 .put(Connection.DisconnectCause.ERROR_UNSPECIFIED,
    818                         Call.DisconnectCause.ERROR_UNSPECIFIED)
    819                 .put(Connection.DisconnectCause.FDN_BLOCKED, Call.DisconnectCause.FDN_BLOCKED)
    820                 .put(Connection.DisconnectCause.ICC_ERROR, Call.DisconnectCause.ICC_ERROR)
    821                 .put(Connection.DisconnectCause.INCOMING_MISSED,
    822                         Call.DisconnectCause.INCOMING_MISSED)
    823                 .put(Connection.DisconnectCause.INCOMING_REJECTED,
    824                         Call.DisconnectCause.INCOMING_REJECTED)
    825                 .put(Connection.DisconnectCause.INVALID_CREDENTIALS,
    826                         Call.DisconnectCause.INVALID_CREDENTIALS)
    827                 .put(Connection.DisconnectCause.INVALID_NUMBER,
    828                         Call.DisconnectCause.INVALID_NUMBER)
    829                 .put(Connection.DisconnectCause.LIMIT_EXCEEDED, Call.DisconnectCause.LIMIT_EXCEEDED)
    830                 .put(Connection.DisconnectCause.LOCAL, Call.DisconnectCause.LOCAL)
    831                 .put(Connection.DisconnectCause.LOST_SIGNAL, Call.DisconnectCause.LOST_SIGNAL)
    832                 .put(Connection.DisconnectCause.MMI, Call.DisconnectCause.MMI)
    833                 .put(Connection.DisconnectCause.NORMAL, Call.DisconnectCause.NORMAL)
    834                 .put(Connection.DisconnectCause.NOT_DISCONNECTED,
    835                         Call.DisconnectCause.NOT_DISCONNECTED)
    836                 .put(Connection.DisconnectCause.NUMBER_UNREACHABLE,
    837                         Call.DisconnectCause.NUMBER_UNREACHABLE)
    838                 .put(Connection.DisconnectCause.OUT_OF_NETWORK, Call.DisconnectCause.OUT_OF_NETWORK)
    839                 .put(Connection.DisconnectCause.OUT_OF_SERVICE, Call.DisconnectCause.OUT_OF_SERVICE)
    840                 .put(Connection.DisconnectCause.POWER_OFF, Call.DisconnectCause.POWER_OFF)
    841                 .put(Connection.DisconnectCause.SERVER_ERROR, Call.DisconnectCause.SERVER_ERROR)
    842                 .put(Connection.DisconnectCause.SERVER_UNREACHABLE,
    843                         Call.DisconnectCause.SERVER_UNREACHABLE)
    844                 .put(Connection.DisconnectCause.TIMED_OUT, Call.DisconnectCause.TIMED_OUT)
    845                 .put(Connection.DisconnectCause.UNOBTAINABLE_NUMBER,
    846                         Call.DisconnectCause.UNOBTAINABLE_NUMBER)
    847                 .build();
    848 
    849     private Call.DisconnectCause translateDisconnectCauseFromTelephony(
    850             Connection.DisconnectCause causeSource) {
    851 
    852         if (CAUSE_MAP.containsKey(causeSource)) {
    853             return CAUSE_MAP.get(causeSource);
    854         }
    855 
    856         return Call.DisconnectCause.UNKNOWN;
    857     }
    858 
    859     /**
    860      * Gets an existing callId for a connection, or creates one if none exists.
    861      * This function does NOT set any of the Connection data onto the Call class.
    862      * A separate call to updateCallFromConnection must be made for that purpose.
    863      */
    864     private Call getCallFromMap(HashMap<Connection, Call> map, Connection conn,
    865             boolean createIfMissing) {
    866         Call call = null;
    867 
    868         // Find the call id or create if missing and requested.
    869         if (conn != null) {
    870             if (map.containsKey(conn)) {
    871                 call = map.get(conn);
    872             } else if (createIfMissing) {
    873                 call = createNewCall();
    874                 map.put(conn, call);
    875             }
    876         }
    877         return call;
    878     }
    879 
    880     /**
    881      * Creates a brand new connection for the call.
    882      */
    883     private Call createNewCall() {
    884         int callId;
    885         int newNextCallId;
    886         do {
    887             callId = mNextCallId.get();
    888 
    889             // protect against overflow
    890             newNextCallId = (callId == Integer.MAX_VALUE ?
    891                     CALL_ID_START_VALUE : callId + 1);
    892 
    893             // Keep looping if the change was not atomic OR the value is already taken.
    894             // The call to containsValue() is linear, however, most devices support a
    895             // maximum of 7 connections so it's not expensive.
    896         } while (!mNextCallId.compareAndSet(callId, newNextCallId));
    897 
    898         return new Call(callId);
    899     }
    900 
    901     /**
    902      * Listener interface for changes to Calls.
    903      */
    904     public interface Listener {
    905         void onDisconnect(Call call);
    906         void onIncoming(Call call);
    907         void onUpdate(List<Call> calls);
    908         void onPostDialAction(Connection.PostDialState state, int callId, String remainingChars,
    909                 char c);
    910     }
    911 
    912     /**
    913      * Result class for accessing a call by connection.
    914      */
    915     public static class CallResult {
    916         public Call mCall;
    917         public Call mActionableCall;
    918         public Connection mConnection;
    919 
    920         private CallResult(Call call, Connection connection) {
    921             this(call, call, connection);
    922         }
    923 
    924         private CallResult(Call call, Call actionableCall, Connection connection) {
    925             mCall = call;
    926             mActionableCall = actionableCall;
    927             mConnection = connection;
    928         }
    929 
    930         public Call getCall() {
    931             return mCall;
    932         }
    933 
    934         // The call that should be used for call actions like hanging up.
    935         public Call getActionableCall() {
    936             return mActionableCall;
    937         }
    938 
    939         public Connection getConnection() {
    940             return mConnection;
    941         }
    942     }
    943 }
    944