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 android.net.Uri;
     20 import android.os.AsyncResult;
     21 import android.os.Handler;
     22 import android.os.Message;
     23 import android.telecom.AudioState;
     24 import android.telecom.ConferenceParticipant;
     25 import android.telecom.Connection;
     26 import android.telecom.PhoneAccount;
     27 
     28 import com.android.internal.telephony.Call;
     29 import com.android.internal.telephony.CallStateException;
     30 import com.android.internal.telephony.Connection.PostDialListener;
     31 import com.android.internal.telephony.Phone;
     32 import com.android.internal.telephony.imsphone.ImsPhoneConnection;
     33 
     34 import java.lang.Override;
     35 import java.util.Collections;
     36 import java.util.List;
     37 import java.util.Objects;
     38 import java.util.Set;
     39 import java.util.concurrent.ConcurrentHashMap;
     40 
     41 /**
     42  * Base class for CDMA and GSM connections.
     43  */
     44 abstract class TelephonyConnection extends Connection {
     45     private static final int MSG_PRECISE_CALL_STATE_CHANGED = 1;
     46     private static final int MSG_RINGBACK_TONE = 2;
     47     private static final int MSG_HANDOVER_STATE_CHANGED = 3;
     48     private static final int MSG_DISCONNECT = 4;
     49 
     50     private final Handler mHandler = new Handler() {
     51         @Override
     52         public void handleMessage(Message msg) {
     53             switch (msg.what) {
     54                 case MSG_PRECISE_CALL_STATE_CHANGED:
     55                     Log.v(TelephonyConnection.this, "MSG_PRECISE_CALL_STATE_CHANGED");
     56                     updateState();
     57                     break;
     58                 case MSG_HANDOVER_STATE_CHANGED:
     59                     Log.v(TelephonyConnection.this, "MSG_HANDOVER_STATE_CHANGED");
     60                     AsyncResult ar = (AsyncResult) msg.obj;
     61                     com.android.internal.telephony.Connection connection =
     62                          (com.android.internal.telephony.Connection) ar.result;
     63                     if ((connection.getAddress() != null &&
     64                                     mOriginalConnection.getAddress() != null &&
     65                             mOriginalConnection.getAddress().contains(connection.getAddress())) ||
     66                             connection.getStateBeforeHandover() == mOriginalConnection.getState()) {
     67                         Log.d(TelephonyConnection.this, "SettingOriginalConnection " +
     68                                 mOriginalConnection.toString() + " with " + connection.toString());
     69                         setOriginalConnection(connection);
     70                     }
     71                     break;
     72                 case MSG_RINGBACK_TONE:
     73                     Log.v(TelephonyConnection.this, "MSG_RINGBACK_TONE");
     74                     // TODO: This code assumes that there is only one connection in the foreground
     75                     // call, in other words, it punts on network-mediated conference calling.
     76                     if (getOriginalConnection() != getForegroundConnection()) {
     77                         Log.v(TelephonyConnection.this, "handleMessage, original connection is " +
     78                                 "not foreground connection, skipping");
     79                         return;
     80                     }
     81                     setRingbackRequested((Boolean) ((AsyncResult) msg.obj).result);
     82                     break;
     83                 case MSG_DISCONNECT:
     84                     updateState();
     85                     break;
     86             }
     87         }
     88     };
     89 
     90     /**
     91      * A listener/callback mechanism that is specific communication from TelephonyConnections
     92      * to TelephonyConnectionService (for now). It is more specific that Connection.Listener
     93      * because it is only exposed in Telephony.
     94      */
     95     public abstract static class TelephonyConnectionListener {
     96         public void onOriginalConnectionConfigured(TelephonyConnection c) {}
     97     }
     98 
     99     private final PostDialListener mPostDialListener = new PostDialListener() {
    100         @Override
    101         public void onPostDialWait() {
    102             Log.v(TelephonyConnection.this, "onPostDialWait");
    103             if (mOriginalConnection != null) {
    104                 setPostDialWait(mOriginalConnection.getRemainingPostDialString());
    105             }
    106         }
    107 
    108         @Override
    109         public void onPostDialChar(char c) {
    110             Log.v(TelephonyConnection.this, "onPostDialChar: %s", c);
    111             if (mOriginalConnection != null) {
    112                 setNextPostDialWaitChar(c);
    113             }
    114         }
    115     };
    116 
    117     /**
    118      * Listener for listening to events in the {@link com.android.internal.telephony.Connection}.
    119      */
    120     private final com.android.internal.telephony.Connection.Listener mOriginalConnectionListener =
    121             new com.android.internal.telephony.Connection.ListenerBase() {
    122         @Override
    123         public void onVideoStateChanged(int videoState) {
    124             setVideoState(videoState);
    125         }
    126 
    127         /**
    128          * The {@link com.android.internal.telephony.Connection} has reported a change in local
    129          * video capability.
    130          *
    131          * @param capable True if capable.
    132          */
    133         @Override
    134         public void onLocalVideoCapabilityChanged(boolean capable) {
    135             setLocalVideoCapable(capable);
    136         }
    137 
    138         /**
    139          * The {@link com.android.internal.telephony.Connection} has reported a change in remote
    140          * video capability.
    141          *
    142          * @param capable True if capable.
    143          */
    144         @Override
    145         public void onRemoteVideoCapabilityChanged(boolean capable) {
    146             setRemoteVideoCapable(capable);
    147         }
    148 
    149         /**
    150          * The {@link com.android.internal.telephony.Connection} has reported a change in the
    151          * video call provider.
    152          *
    153          * @param videoProvider The video call provider.
    154          */
    155         @Override
    156         public void onVideoProviderChanged(VideoProvider videoProvider) {
    157             setVideoProvider(videoProvider);
    158         }
    159 
    160         /**
    161          * Used by the {@link com.android.internal.telephony.Connection} to report a change in the
    162          * audio quality for the current call.
    163          *
    164          * @param audioQuality The audio quality.
    165          */
    166         @Override
    167         public void onAudioQualityChanged(int audioQuality) {
    168             setAudioQuality(audioQuality);
    169         }
    170 
    171         /**
    172          * Handles a change in the state of conference participant(s), as reported by the
    173          * {@link com.android.internal.telephony.Connection}.
    174          *
    175          * @param participants The participant(s) which changed.
    176          */
    177         @Override
    178         public void onConferenceParticipantsChanged(List<ConferenceParticipant> participants) {
    179             updateConferenceParticipants(participants);
    180         }
    181     };
    182 
    183     private com.android.internal.telephony.Connection mOriginalConnection;
    184     private Call.State mOriginalConnectionState = Call.State.IDLE;
    185 
    186     private boolean mWasImsConnection;
    187 
    188     /**
    189      * Tracks the multiparty state of the ImsCall so that changes in the bit state can be detected.
    190      */
    191     private boolean mIsMultiParty = false;
    192 
    193     /**
    194      * Determines if the {@link TelephonyConnection} has local video capabilities.
    195      * This is used when {@link TelephonyConnection#updateConnectionCapabilities()}} is called,
    196      * ensuring the appropriate capabilities are set.  Since capabilities
    197      * can be rebuilt at any time it is necessary to track the video capabilities between rebuild.
    198      * The capabilities (including video capabilities) are communicated to the telecom
    199      * layer.
    200      */
    201     private boolean mLocalVideoCapable;
    202 
    203     /**
    204      * Determines if the {@link TelephonyConnection} has remote video capabilities.
    205      * This is used when {@link TelephonyConnection#updateConnectionCapabilities()}} is called,
    206      * ensuring the appropriate capabilities are set.  Since capabilities can be rebuilt at any time
    207      * it is necessary to track the video capabilities between rebuild. The capabilities (including
    208      * video capabilities) are communicated to the telecom layer.
    209      */
    210     private boolean mRemoteVideoCapable;
    211 
    212     /**
    213      * Determines the current audio quality for the {@link TelephonyConnection}.
    214      * This is used when {@link TelephonyConnection#updateConnectionCapabilities}} is called to
    215      * indicate whether a call has the {@link Connection#CAPABILITY_HIGH_DEF_AUDIO} capability.
    216      */
    217     private int mAudioQuality;
    218 
    219     /**
    220      * Listeners to our TelephonyConnection specific callbacks
    221      */
    222     private final Set<TelephonyConnectionListener> mTelephonyListeners = Collections.newSetFromMap(
    223             new ConcurrentHashMap<TelephonyConnectionListener, Boolean>(8, 0.9f, 1));
    224 
    225     protected TelephonyConnection(com.android.internal.telephony.Connection originalConnection) {
    226         if (originalConnection != null) {
    227             setOriginalConnection(originalConnection);
    228         }
    229     }
    230 
    231     /**
    232      * Creates a clone of the current {@link TelephonyConnection}.
    233      *
    234      * @return The clone.
    235      */
    236     public abstract TelephonyConnection cloneConnection();
    237 
    238     @Override
    239     public void onAudioStateChanged(AudioState audioState) {
    240         // TODO: update TTY mode.
    241         if (getPhone() != null) {
    242             getPhone().setEchoSuppressionEnabled();
    243         }
    244     }
    245 
    246     @Override
    247     public void onStateChanged(int state) {
    248         Log.v(this, "onStateChanged, state: " + Connection.stateToString(state));
    249     }
    250 
    251     @Override
    252     public void onDisconnect() {
    253         Log.v(this, "onDisconnect");
    254         hangup(android.telephony.DisconnectCause.LOCAL);
    255     }
    256 
    257     /**
    258      * Notifies this Connection of a request to disconnect a participant of the conference managed
    259      * by the connection.
    260      *
    261      * @param endpoint the {@link Uri} of the participant to disconnect.
    262      */
    263     @Override
    264     public void onDisconnectConferenceParticipant(Uri endpoint) {
    265         Log.v(this, "onDisconnectConferenceParticipant %s", endpoint);
    266 
    267         if (mOriginalConnection == null) {
    268             return;
    269         }
    270 
    271         mOriginalConnection.onDisconnectConferenceParticipant(endpoint);
    272     }
    273 
    274     @Override
    275     public void onSeparate() {
    276         Log.v(this, "onSeparate");
    277         if (mOriginalConnection != null) {
    278             try {
    279                 mOriginalConnection.separate();
    280             } catch (CallStateException e) {
    281                 Log.e(this, e, "Call to Connection.separate failed with exception");
    282             }
    283         }
    284     }
    285 
    286     @Override
    287     public void onAbort() {
    288         Log.v(this, "onAbort");
    289         hangup(android.telephony.DisconnectCause.LOCAL);
    290     }
    291 
    292     @Override
    293     public void onHold() {
    294         performHold();
    295     }
    296 
    297     @Override
    298     public void onUnhold() {
    299         performUnhold();
    300     }
    301 
    302     @Override
    303     public void onAnswer(int videoState) {
    304         Log.v(this, "onAnswer");
    305         if (isValidRingingCall() && getPhone() != null) {
    306             try {
    307                 getPhone().acceptCall(videoState);
    308             } catch (CallStateException e) {
    309                 Log.e(this, e, "Failed to accept call.");
    310             }
    311         }
    312     }
    313 
    314     @Override
    315     public void onReject() {
    316         Log.v(this, "onReject");
    317         if (isValidRingingCall()) {
    318             hangup(android.telephony.DisconnectCause.INCOMING_REJECTED);
    319         }
    320         super.onReject();
    321     }
    322 
    323     @Override
    324     public void onPostDialContinue(boolean proceed) {
    325         Log.v(this, "onPostDialContinue, proceed: " + proceed);
    326         if (mOriginalConnection != null) {
    327             if (proceed) {
    328                 mOriginalConnection.proceedAfterWaitChar();
    329             } else {
    330                 mOriginalConnection.cancelPostDial();
    331             }
    332         }
    333     }
    334 
    335     public void performHold() {
    336         Log.v(this, "performHold");
    337         // TODO: Can dialing calls be put on hold as well since they take up the
    338         // foreground call slot?
    339         if (Call.State.ACTIVE == mOriginalConnectionState) {
    340             Log.v(this, "Holding active call");
    341             try {
    342                 Phone phone = mOriginalConnection.getCall().getPhone();
    343                 Call ringingCall = phone.getRingingCall();
    344 
    345                 // Although the method says switchHoldingAndActive, it eventually calls a RIL method
    346                 // called switchWaitingOrHoldingAndActive. What this means is that if we try to put
    347                 // a call on hold while a call-waiting call exists, it'll end up accepting the
    348                 // call-waiting call, which is bad if that was not the user's intention. We are
    349                 // cheating here and simply skipping it because we know any attempt to hold a call
    350                 // while a call-waiting call is happening is likely a request from Telecom prior to
    351                 // accepting the call-waiting call.
    352                 // TODO: Investigate a better solution. It would be great here if we
    353                 // could "fake" hold by silencing the audio and microphone streams for this call
    354                 // instead of actually putting it on hold.
    355                 if (ringingCall.getState() != Call.State.WAITING) {
    356                     phone.switchHoldingAndActive();
    357                 }
    358 
    359                 // TODO: Cdma calls are slightly different.
    360             } catch (CallStateException e) {
    361                 Log.e(this, e, "Exception occurred while trying to put call on hold.");
    362             }
    363         } else {
    364             Log.w(this, "Cannot put a call that is not currently active on hold.");
    365         }
    366     }
    367 
    368     public void performUnhold() {
    369         Log.v(this, "performUnhold");
    370         if (Call.State.HOLDING == mOriginalConnectionState) {
    371             try {
    372                 // Here's the deal--Telephony hold/unhold is weird because whenever there exists
    373                 // more than one call, one of them must always be active. In other words, if you
    374                 // have an active call and holding call, and you put the active call on hold, it
    375                 // will automatically activate the holding call. This is weird with how Telecom
    376                 // sends its commands. When a user opts to "unhold" a background call, telecom
    377                 // issues hold commands to all active calls, and then the unhold command to the
    378                 // background call. This means that we get two commands...each of which reduces to
    379                 // switchHoldingAndActive(). The result is that they simply cancel each other out.
    380                 // To fix this so that it works well with telecom we add a minor hack. If we
    381                 // have one telephony call, everything works as normally expected. But if we have
    382                 // two or more calls, we will ignore all requests to "unhold" knowing that the hold
    383                 // requests already do what we want. If you've read up to this point, I'm very sorry
    384                 // that we are doing this. I didn't think of a better solution that wouldn't also
    385                 // make the Telecom APIs very ugly.
    386 
    387                 if (!hasMultipleTopLevelCalls()) {
    388                     mOriginalConnection.getCall().getPhone().switchHoldingAndActive();
    389                 } else {
    390                     Log.i(this, "Skipping unhold command for %s", this);
    391                 }
    392             } catch (CallStateException e) {
    393                 Log.e(this, e, "Exception occurred while trying to release call from hold.");
    394             }
    395         } else {
    396             Log.w(this, "Cannot release a call that is not already on hold from hold.");
    397         }
    398     }
    399 
    400     public void performConference(TelephonyConnection otherConnection) {
    401         Log.d(this, "performConference - %s", this);
    402         if (getPhone() != null) {
    403             try {
    404                 // We dont use the "other" connection because there is no concept of that in the
    405                 // implementation of calls inside telephony. Basically, you can "conference" and it
    406                 // will conference with the background call.  We know that otherConnection is the
    407                 // background call because it would never have called setConferenceableConnections()
    408                 // otherwise.
    409                 getPhone().conference();
    410             } catch (CallStateException e) {
    411                 Log.e(this, e, "Failed to conference call.");
    412             }
    413         }
    414     }
    415 
    416     /**
    417      * Builds call capabilities common to all TelephonyConnections. Namely, apply IMS-based
    418      * capabilities.
    419      */
    420     protected int buildConnectionCapabilities() {
    421         int callCapabilities = 0;
    422         if (isImsConnection()) {
    423             if (mOriginalConnection.isIncoming()) {
    424                 callCapabilities |= CAPABILITY_SPEED_UP_MT_AUDIO;
    425             }
    426             callCapabilities |= CAPABILITY_SUPPORT_HOLD;
    427             if (getState() == STATE_ACTIVE || getState() == STATE_HOLDING) {
    428                 callCapabilities |= CAPABILITY_HOLD;
    429             }
    430         }
    431         return callCapabilities;
    432     }
    433 
    434     protected final void updateConnectionCapabilities() {
    435         int newCapabilities = buildConnectionCapabilities();
    436         newCapabilities = applyVideoCapabilities(newCapabilities);
    437         newCapabilities = applyAudioQualityCapabilities(newCapabilities);
    438         newCapabilities = applyConferenceTerminationCapabilities(newCapabilities);
    439 
    440         if (getConnectionCapabilities() != newCapabilities) {
    441             setConnectionCapabilities(newCapabilities);
    442         }
    443     }
    444 
    445     protected final void updateAddress() {
    446         updateConnectionCapabilities();
    447         if (mOriginalConnection != null) {
    448             Uri address = getAddressFromNumber(mOriginalConnection.getAddress());
    449             int presentation = mOriginalConnection.getNumberPresentation();
    450             if (!Objects.equals(address, getAddress()) ||
    451                     presentation != getAddressPresentation()) {
    452                 Log.v(this, "updateAddress, address changed");
    453                 setAddress(address, presentation);
    454             }
    455 
    456             String name = mOriginalConnection.getCnapName();
    457             int namePresentation = mOriginalConnection.getCnapNamePresentation();
    458             if (!Objects.equals(name, getCallerDisplayName()) ||
    459                     namePresentation != getCallerDisplayNamePresentation()) {
    460                 Log.v(this, "updateAddress, caller display name changed");
    461                 setCallerDisplayName(name, namePresentation);
    462             }
    463         }
    464     }
    465 
    466     void onRemovedFromCallService() {
    467         // Subclass can override this to do cleanup.
    468     }
    469 
    470     void setOriginalConnection(com.android.internal.telephony.Connection originalConnection) {
    471         Log.v(this, "new TelephonyConnection, originalConnection: " + originalConnection);
    472         clearOriginalConnection();
    473 
    474         mOriginalConnection = originalConnection;
    475         getPhone().registerForPreciseCallStateChanged(
    476                 mHandler, MSG_PRECISE_CALL_STATE_CHANGED, null);
    477         getPhone().registerForHandoverStateChanged(
    478                 mHandler, MSG_HANDOVER_STATE_CHANGED, null);
    479         getPhone().registerForRingbackTone(mHandler, MSG_RINGBACK_TONE, null);
    480         getPhone().registerForDisconnect(mHandler, MSG_DISCONNECT, null);
    481         mOriginalConnection.addPostDialListener(mPostDialListener);
    482         mOriginalConnection.addListener(mOriginalConnectionListener);
    483 
    484         // Set video state and capabilities
    485         setVideoState(mOriginalConnection.getVideoState());
    486         setLocalVideoCapable(mOriginalConnection.isLocalVideoCapable());
    487         setRemoteVideoCapable(mOriginalConnection.isRemoteVideoCapable());
    488         setVideoProvider(mOriginalConnection.getVideoProvider());
    489         setAudioQuality(mOriginalConnection.getAudioQuality());
    490 
    491         if (isImsConnection()) {
    492             mWasImsConnection = true;
    493         }
    494         mIsMultiParty = mOriginalConnection.isMultiparty();
    495 
    496         fireOnOriginalConnectionConfigured();
    497         updateAddress();
    498     }
    499 
    500     /**
    501      * Un-sets the underlying radio connection.
    502      */
    503     void clearOriginalConnection() {
    504         if (mOriginalConnection != null) {
    505             getPhone().unregisterForPreciseCallStateChanged(mHandler);
    506             getPhone().unregisterForRingbackTone(mHandler);
    507             getPhone().unregisterForHandoverStateChanged(mHandler);
    508             getPhone().unregisterForDisconnect(mHandler);
    509             mOriginalConnection = null;
    510         }
    511     }
    512 
    513     protected void hangup(int telephonyDisconnectCode) {
    514         if (mOriginalConnection != null) {
    515             try {
    516                 // Hanging up a ringing call requires that we invoke call.hangup() as opposed to
    517                 // connection.hangup(). Without this change, the party originating the call will not
    518                 // get sent to voicemail if the user opts to reject the call.
    519                 if (isValidRingingCall()) {
    520                     Call call = getCall();
    521                     if (call != null) {
    522                         call.hangup();
    523                     } else {
    524                         Log.w(this, "Attempting to hangup a connection without backing call.");
    525                     }
    526                 } else {
    527                     // We still prefer to call connection.hangup() for non-ringing calls in order
    528                     // to support hanging-up specific calls within a conference call. If we invoked
    529                     // call.hangup() while in a conference, we would end up hanging up the entire
    530                     // conference call instead of the specific connection.
    531                     mOriginalConnection.hangup();
    532                 }
    533             } catch (CallStateException e) {
    534                 Log.e(this, e, "Call to Connection.hangup failed with exception");
    535             }
    536         }
    537     }
    538 
    539     com.android.internal.telephony.Connection getOriginalConnection() {
    540         return mOriginalConnection;
    541     }
    542 
    543     protected Call getCall() {
    544         if (mOriginalConnection != null) {
    545             return mOriginalConnection.getCall();
    546         }
    547         return null;
    548     }
    549 
    550     Phone getPhone() {
    551         Call call = getCall();
    552         if (call != null) {
    553             return call.getPhone();
    554         }
    555         return null;
    556     }
    557 
    558     private boolean hasMultipleTopLevelCalls() {
    559         int numCalls = 0;
    560         Phone phone = getPhone();
    561         if (phone != null) {
    562             if (!phone.getRingingCall().isIdle()) {
    563                 numCalls++;
    564             }
    565             if (!phone.getForegroundCall().isIdle()) {
    566                 numCalls++;
    567             }
    568             if (!phone.getBackgroundCall().isIdle()) {
    569                 numCalls++;
    570             }
    571         }
    572         return numCalls > 1;
    573     }
    574 
    575     private com.android.internal.telephony.Connection getForegroundConnection() {
    576         if (getPhone() != null) {
    577             return getPhone().getForegroundCall().getEarliestConnection();
    578         }
    579         return null;
    580     }
    581 
    582     /**
    583      * Checks to see the original connection corresponds to an active incoming call. Returns false
    584      * if there is no such actual call, or if the associated call is not incoming (See
    585      * {@link Call.State#isRinging}).
    586      */
    587     private boolean isValidRingingCall() {
    588         if (getPhone() == null) {
    589             Log.v(this, "isValidRingingCall, phone is null");
    590             return false;
    591         }
    592 
    593         Call ringingCall = getPhone().getRingingCall();
    594         if (!ringingCall.getState().isRinging()) {
    595             Log.v(this, "isValidRingingCall, ringing call is not in ringing state");
    596             return false;
    597         }
    598 
    599         if (ringingCall.getEarliestConnection() != mOriginalConnection) {
    600             Log.v(this, "isValidRingingCall, ringing call connection does not match");
    601             return false;
    602         }
    603 
    604         Log.v(this, "isValidRingingCall, returning true");
    605         return true;
    606     }
    607 
    608     void updateState() {
    609         if (mOriginalConnection == null) {
    610             return;
    611         }
    612 
    613         Call.State newState = mOriginalConnection.getState();
    614         Log.v(this, "Update state from %s to %s for %s", mOriginalConnectionState, newState, this);
    615         if (mOriginalConnectionState != newState) {
    616             mOriginalConnectionState = newState;
    617             switch (newState) {
    618                 case IDLE:
    619                     break;
    620                 case ACTIVE:
    621                     setActiveInternal();
    622                     break;
    623                 case HOLDING:
    624                     setOnHold();
    625                     break;
    626                 case DIALING:
    627                 case ALERTING:
    628                     setDialing();
    629                     break;
    630                 case INCOMING:
    631                 case WAITING:
    632                     setRinging();
    633                     break;
    634                 case DISCONNECTED:
    635                     setDisconnected(DisconnectCauseUtil.toTelecomDisconnectCause(
    636                             mOriginalConnection.getDisconnectCause()));
    637                     close();
    638                     break;
    639                 case DISCONNECTING:
    640                     break;
    641             }
    642         }
    643         updateConnectionCapabilities();
    644         updateAddress();
    645         updateMultiparty();
    646     }
    647 
    648     /**
    649      * Checks for changes to the multiparty bit.  If a conference has started, informs listeners.
    650      */
    651     private void updateMultiparty() {
    652         if (mOriginalConnection == null) {
    653             return;
    654         }
    655 
    656         if (mIsMultiParty != mOriginalConnection.isMultiparty()) {
    657             mIsMultiParty = mOriginalConnection.isMultiparty();
    658 
    659             if (mIsMultiParty) {
    660                 notifyConferenceStarted();
    661             }
    662         }
    663     }
    664 
    665     private void setActiveInternal() {
    666         if (getState() == STATE_ACTIVE) {
    667             Log.w(this, "Should not be called if this is already ACTIVE");
    668             return;
    669         }
    670 
    671         // When we set a call to active, we need to make sure that there are no other active
    672         // calls. However, the ordering of state updates to connections can be non-deterministic
    673         // since all connections register for state changes on the phone independently.
    674         // To "optimize", we check here to see if there already exists any active calls.  If so,
    675         // we issue an update for those calls first to make sure we only have one top-level
    676         // active call.
    677         if (getConnectionService() != null) {
    678             for (Connection current : getConnectionService().getAllConnections()) {
    679                 if (current != this && current instanceof TelephonyConnection) {
    680                     TelephonyConnection other = (TelephonyConnection) current;
    681                     if (other.getState() == STATE_ACTIVE) {
    682                         other.updateState();
    683                     }
    684                 }
    685             }
    686         }
    687         setActive();
    688     }
    689 
    690     private void close() {
    691         Log.v(this, "close");
    692         if (getPhone() != null) {
    693             getPhone().unregisterForPreciseCallStateChanged(mHandler);
    694             getPhone().unregisterForRingbackTone(mHandler);
    695             getPhone().unregisterForHandoverStateChanged(mHandler);
    696         }
    697         mOriginalConnection = null;
    698         destroy();
    699     }
    700 
    701     /**
    702      * Applies the video capability states to the CallCapabilities bit-mask.
    703      *
    704      * @param capabilities The CallCapabilities bit-mask.
    705      * @return The capabilities with video capabilities applied.
    706      */
    707     private int applyVideoCapabilities(int capabilities) {
    708         int currentCapabilities = capabilities;
    709         if (mRemoteVideoCapable) {
    710             currentCapabilities = applyCapability(currentCapabilities,
    711                     CAPABILITY_SUPPORTS_VT_REMOTE);
    712         } else {
    713             currentCapabilities = removeCapability(currentCapabilities,
    714                     CAPABILITY_SUPPORTS_VT_REMOTE);
    715         }
    716 
    717         if (mLocalVideoCapable) {
    718             currentCapabilities = applyCapability(currentCapabilities,
    719                     CAPABILITY_SUPPORTS_VT_LOCAL);
    720         } else {
    721             currentCapabilities = removeCapability(currentCapabilities,
    722                     CAPABILITY_SUPPORTS_VT_LOCAL);
    723         }
    724         return currentCapabilities;
    725     }
    726 
    727     /**
    728      * Applies the audio capabilities to the {@code CallCapabilities} bit-mask.  A call with high
    729      * definition audio is considered to have the {@code HIGH_DEF_AUDIO} call capability.
    730      *
    731      * @param capabilities The {@code CallCapabilities} bit-mask.
    732      * @return The capabilities with the audio capabilities applied.
    733      */
    734     private int applyAudioQualityCapabilities(int capabilities) {
    735         int currentCapabilities = capabilities;
    736 
    737         if (mAudioQuality ==
    738                 com.android.internal.telephony.Connection.AUDIO_QUALITY_HIGH_DEFINITION) {
    739             currentCapabilities = applyCapability(currentCapabilities, CAPABILITY_HIGH_DEF_AUDIO);
    740         } else {
    741             currentCapabilities = removeCapability(currentCapabilities, CAPABILITY_HIGH_DEF_AUDIO);
    742         }
    743 
    744         return currentCapabilities;
    745     }
    746 
    747     /**
    748      * Applies capabilities specific to conferences termination to the
    749      * {@code CallCapabilities} bit-mask.
    750      *
    751      * @param capabilities The {@code CallCapabilities} bit-mask.
    752      * @return The capabilities with the IMS conference capabilities applied.
    753      */
    754     private int applyConferenceTerminationCapabilities(int capabilities) {
    755         int currentCapabilities = capabilities;
    756 
    757         // An IMS call cannot be individually disconnected or separated from its parent conference.
    758         // If the call was IMS, even if it hands over to GMS, these capabilities are not supported.
    759         if (!mWasImsConnection) {
    760             currentCapabilities |= CAPABILITY_DISCONNECT_FROM_CONFERENCE;
    761             currentCapabilities |= CAPABILITY_SEPARATE_FROM_CONFERENCE;
    762         }
    763 
    764         return currentCapabilities;
    765     }
    766 
    767     /**
    768      * Returns the local video capability state for the connection.
    769      *
    770      * @return {@code True} if the connection has local video capabilities.
    771      */
    772     public boolean isLocalVideoCapable() {
    773         return mLocalVideoCapable;
    774     }
    775 
    776     /**
    777      * Returns the remote video capability state for the connection.
    778      *
    779      * @return {@code True} if the connection has remote video capabilities.
    780      */
    781     public boolean isRemoteVideoCapable() {
    782         return mRemoteVideoCapable;
    783     }
    784 
    785     /**
    786      * Sets whether video capability is present locally.  Used during rebuild of the
    787      * capabilities to set the video call capabilities.
    788      *
    789      * @param capable {@code True} if video capable.
    790      */
    791     public void setLocalVideoCapable(boolean capable) {
    792         mLocalVideoCapable = capable;
    793         updateConnectionCapabilities();
    794     }
    795 
    796     /**
    797      * Sets whether video capability is present remotely.  Used during rebuild of the
    798      * capabilities to set the video call capabilities.
    799      *
    800      * @param capable {@code True} if video capable.
    801      */
    802     public void setRemoteVideoCapable(boolean capable) {
    803         mRemoteVideoCapable = capable;
    804         updateConnectionCapabilities();
    805     }
    806 
    807     /**
    808      * Sets the current call audio quality.  Used during rebuild of the capabilities
    809      * to set or unset the {@link Connection#CAPABILITY_HIGH_DEF_AUDIO} capability.
    810      *
    811      * @param audioQuality The audio quality.
    812      */
    813     public void setAudioQuality(int audioQuality) {
    814         mAudioQuality = audioQuality;
    815         updateConnectionCapabilities();
    816     }
    817 
    818     /**
    819      * Obtains the current call audio quality.
    820      */
    821     public int getAudioQuality() {
    822         return mAudioQuality;
    823     }
    824 
    825     void resetStateForConference() {
    826         if (getState() == Connection.STATE_HOLDING) {
    827             if (mOriginalConnection.getState() == Call.State.ACTIVE) {
    828                 setActive();
    829             }
    830         }
    831     }
    832 
    833     boolean setHoldingForConference() {
    834         if (getState() == Connection.STATE_ACTIVE) {
    835             setOnHold();
    836             return true;
    837         }
    838         return false;
    839     }
    840 
    841     /**
    842      * Whether the original connection is an IMS connection.
    843      * @return {@code True} if the original connection is an IMS connection, {@code false}
    844      *     otherwise.
    845      */
    846     protected boolean isImsConnection() {
    847         return getOriginalConnection() instanceof ImsPhoneConnection;
    848     }
    849 
    850     /**
    851      * Whether the original connection was ever an IMS connection, either before or now.
    852      * @return {@code True} if the original connection was ever an IMS connection, {@code false}
    853      *     otherwise.
    854      */
    855     public boolean wasImsConnection() {
    856         return mWasImsConnection;
    857     }
    858 
    859     private static Uri getAddressFromNumber(String number) {
    860         // Address can be null for blocked calls.
    861         if (number == null) {
    862             number = "";
    863         }
    864         return Uri.fromParts(PhoneAccount.SCHEME_TEL, number, null);
    865     }
    866 
    867     /**
    868      * Applies a capability to a capabilities bit-mask.
    869      *
    870      * @param capabilities The capabilities bit-mask.
    871      * @param capability The capability to apply.
    872      * @return The capabilities bit-mask with the capability applied.
    873      */
    874     private int applyCapability(int capabilities, int capability) {
    875         int newCapabilities = capabilities | capability;
    876         return newCapabilities;
    877     }
    878 
    879     /**
    880      * Removes a capability from a capabilities bit-mask.
    881      *
    882      * @param capabilities The capabilities bit-mask.
    883      * @param capability The capability to remove.
    884      * @return The capabilities bit-mask with the capability removed.
    885      */
    886     private int removeCapability(int capabilities, int capability) {
    887         int newCapabilities = capabilities & ~capability;
    888         return newCapabilities;
    889     }
    890 
    891     /**
    892      * Register a listener for {@link TelephonyConnection} specific triggers.
    893      * @param l The instance of the listener to add
    894      * @return The connection being listened to
    895      */
    896     public final TelephonyConnection addTelephonyConnectionListener(TelephonyConnectionListener l) {
    897         mTelephonyListeners.add(l);
    898         // If we already have an original connection, let's call back immediately.
    899         // This would be the case for incoming calls.
    900         if (mOriginalConnection != null) {
    901             fireOnOriginalConnectionConfigured();
    902         }
    903         return this;
    904     }
    905 
    906     /**
    907      * Remove a listener for {@link TelephonyConnection} specific triggers.
    908      * @param l The instance of the listener to remove
    909      * @return The connection being listened to
    910      */
    911     public final TelephonyConnection removeTelephonyConnectionListener(
    912             TelephonyConnectionListener l) {
    913         if (l != null) {
    914             mTelephonyListeners.remove(l);
    915         }
    916         return this;
    917     }
    918 
    919     /**
    920      * Fire a callback to the various listeners for when the original connection is
    921      * set in this {@link TelephonyConnection}
    922      */
    923     private final void fireOnOriginalConnectionConfigured() {
    924         for (TelephonyConnectionListener l : mTelephonyListeners) {
    925             l.onOriginalConnectionConfigured(this);
    926         }
    927     }
    928 
    929     /**
    930      * Creates a string representation of this {@link TelephonyConnection}.  Primarily intended for
    931      * use in log statements.
    932      *
    933      * @return String representation of the connection.
    934      */
    935     @Override
    936     public String toString() {
    937         StringBuilder sb = new StringBuilder();
    938         sb.append("[TelephonyConnection objId:");
    939         sb.append(System.identityHashCode(this));
    940         sb.append(" type:");
    941         if (isImsConnection()) {
    942             sb.append("ims");
    943         } else if (this instanceof com.android.services.telephony.GsmConnection) {
    944             sb.append("gsm");
    945         } else if (this instanceof CdmaConnection) {
    946             sb.append("cdma");
    947         }
    948         sb.append(" state:");
    949         sb.append(Connection.stateToString(getState()));
    950         sb.append(" capabilities:");
    951         sb.append(capabilitiesToString(getConnectionCapabilities()));
    952         sb.append(" address:");
    953         sb.append(Log.pii(getAddress()));
    954         sb.append(" originalConnection:");
    955         sb.append(mOriginalConnection);
    956         sb.append(" partOfConf:");
    957         if (getConference() == null) {
    958             sb.append("N");
    959         } else {
    960             sb.append("Y");
    961         }
    962         sb.append("]");
    963         return sb.toString();
    964     }
    965 }
    966