Home | History | Annotate | Download | only in sip
      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.sip;
     18 
     19 import android.net.Uri;
     20 import android.os.Bundle;
     21 import android.os.Handler;
     22 import android.os.Message;
     23 import android.telecom.AudioState;
     24 import android.telecom.Connection;
     25 import android.telecom.PhoneAccount;
     26 import android.telecom.TelecomManager;
     27 import android.util.EventLog;
     28 import android.util.Log;
     29 
     30 import com.android.internal.telephony.Call;
     31 import com.android.internal.telephony.CallStateException;
     32 import com.android.internal.telephony.PhoneConstants;
     33 import com.android.internal.telephony.sip.SipPhone;
     34 import com.android.services.telephony.DisconnectCauseUtil;
     35 
     36 import java.util.Objects;
     37 
     38 final class SipConnection extends Connection {
     39     private static final String PREFIX = "[SipConnection] ";
     40     private static final boolean VERBOSE = false; /* STOP SHIP if true */
     41 
     42     private static final int MSG_PRECISE_CALL_STATE_CHANGED = 1;
     43 
     44     private final Handler mHandler = new Handler() {
     45         @Override
     46         public void handleMessage(Message msg) {
     47             switch (msg.what) {
     48                 case MSG_PRECISE_CALL_STATE_CHANGED:
     49                     updateState(false);
     50                     break;
     51             }
     52         }
     53     };
     54 
     55     private com.android.internal.telephony.Connection mOriginalConnection;
     56     private Call.State mOriginalConnectionState = Call.State.IDLE;
     57 
     58     SipConnection() {
     59         if (VERBOSE) log("new SipConnection");
     60         setInitializing();
     61     }
     62 
     63     void initialize(com.android.internal.telephony.Connection connection) {
     64         if (VERBOSE) log("init SipConnection, connection: " + connection);
     65         mOriginalConnection = connection;
     66         if (getPhone() != null) {
     67             getPhone().registerForPreciseCallStateChanged(mHandler, MSG_PRECISE_CALL_STATE_CHANGED,
     68                     null);
     69         }
     70         updateAddress();
     71         setTechnologyTypeExtra();
     72         setInitialized();
     73     }
     74 
     75     @Override
     76     public void onAudioStateChanged(AudioState state) {
     77         if (VERBOSE) log("onAudioStateChanged: " + state);
     78         if (getPhone() != null) {
     79             getPhone().setEchoSuppressionEnabled();
     80         }
     81     }
     82 
     83     @Override
     84     public void onStateChanged(int state) {
     85         if (VERBOSE) log("onStateChanged, state: " + Connection.stateToString(state));
     86     }
     87 
     88     @Override
     89     public void onPlayDtmfTone(char c) {
     90         if (VERBOSE) log("onPlayDtmfTone");
     91         if (getPhone() != null) {
     92             getPhone().startDtmf(c);
     93         }
     94     }
     95 
     96     @Override
     97     public void onStopDtmfTone() {
     98         if (VERBOSE) log("onStopDtmfTone");
     99         if (getPhone() != null) {
    100             getPhone().stopDtmf();
    101         }
    102     }
    103 
    104     @Override
    105     public void onDisconnect() {
    106         if (VERBOSE) log("onDisconnect");
    107         try {
    108             if (getCall() != null && !getCall().isMultiparty()) {
    109                 getCall().hangup();
    110             } else if (mOriginalConnection != null) {
    111                 mOriginalConnection.hangup();
    112             }
    113         } catch (CallStateException e) {
    114             log("onDisconnect, exception: " + e);
    115         }
    116     }
    117 
    118     @Override
    119     public void onSeparate() {
    120         if (VERBOSE) log("onSeparate");
    121         try {
    122             if (mOriginalConnection != null) {
    123                 mOriginalConnection.separate();
    124             }
    125         } catch (CallStateException e) {
    126             log("onSeparate, exception: " + e);
    127         }
    128     }
    129 
    130     @Override
    131     public void onAbort() {
    132         if (VERBOSE) log("onAbort");
    133         onDisconnect();
    134     }
    135 
    136     @Override
    137     public void onHold() {
    138         if (VERBOSE) log("onHold");
    139         try {
    140             if (getPhone() != null && getState() == STATE_ACTIVE
    141                     && getPhone().getRingingCall().getState() != Call.State.WAITING) {
    142                 // Double check with the internal state since a discrepancy in states could mean
    143                 // that the transactions is already in progress from a previous request.
    144                 if (mOriginalConnection != null &&
    145                         mOriginalConnection.getState() == Call.State.ACTIVE) {
    146                     getPhone().switchHoldingAndActive();
    147                 } else {
    148                     log("skipping switch from onHold due to internal state:");
    149                 }
    150             }
    151         } catch (CallStateException e) {
    152             log("onHold, exception: " + e);
    153         }
    154     }
    155 
    156     @Override
    157     public void onUnhold() {
    158         if (VERBOSE) log("onUnhold");
    159         try {
    160             if (getPhone() != null && getState() == STATE_HOLDING &&
    161                     getPhone().getForegroundCall().getState() != Call.State.DIALING) {
    162                 // Double check with the internal state since a discrepancy in states could mean
    163                 // that the transaction is already in progress from a previous request.
    164                 if (mOriginalConnection != null &&
    165                         mOriginalConnection.getState() == Call.State.HOLDING) {
    166                     getPhone().switchHoldingAndActive();
    167                 } else {
    168                     log("skipping switch from onUnHold due to internal state.");
    169                 }
    170             }
    171         } catch (CallStateException e) {
    172             log("onUnhold, exception: " + e);
    173         }
    174     }
    175 
    176     @Override
    177     public void onAnswer(int videoState) {
    178         if (VERBOSE) log("onAnswer");
    179         try {
    180             if (isValidRingingCall() && getPhone() != null) {
    181                 getPhone().acceptCall(videoState);
    182             }
    183         } catch (CallStateException e) {
    184             log("onAnswer, exception: " + e);
    185         } catch (IllegalStateException e) {
    186             // Call could not be answered due to an invalid audio-codec offered by the caller.  We
    187             // will reject the call to stop it from ringing.
    188             log("onAnswer, IllegalStateException: " + e);
    189             EventLog.writeEvent(0x534e4554, "31752213", -1, "Invalid codec.");
    190             onReject();
    191         } catch (IllegalArgumentException e) {
    192             // Call could not be answered due to an error parsing the SDP.  We will reject the call
    193             // to stop it from ringing.
    194             log("onAnswer, IllegalArgumentException: " + e);
    195             EventLog.writeEvent(0x534e4554, "31752213", -1, "Invalid SDP.");
    196             onReject();
    197         }
    198     }
    199 
    200     @Override
    201     public void onReject() {
    202         if (VERBOSE) log("onReject");
    203         try {
    204             if (isValidRingingCall() && getPhone() != null) {
    205                 getPhone().rejectCall();
    206             }
    207         } catch (CallStateException e) {
    208             log("onReject, exception: " + e);
    209         }
    210     }
    211 
    212     @Override
    213     public void onPostDialContinue(boolean proceed) {
    214         if (VERBOSE) log("onPostDialContinue, proceed: " + proceed);
    215         // SIP doesn't have post dial support.
    216     }
    217 
    218     private Call getCall() {
    219         if (mOriginalConnection != null) {
    220             return mOriginalConnection.getCall();
    221         }
    222         return null;
    223     }
    224 
    225     SipPhone getPhone() {
    226         Call call = getCall();
    227         if (call != null) {
    228             return (SipPhone) call.getPhone();
    229         }
    230         return null;
    231     }
    232 
    233     private boolean isValidRingingCall() {
    234         Call call = getCall();
    235         return call != null && call.getState().isRinging() &&
    236                 call.getEarliestConnection() == mOriginalConnection;
    237     }
    238 
    239     private void updateState(boolean force) {
    240         if (mOriginalConnection == null) {
    241             return;
    242         }
    243 
    244         Call.State newState = mOriginalConnection.getState();
    245         if (VERBOSE) log("updateState, " + mOriginalConnectionState + " -> " + newState);
    246         if (force || mOriginalConnectionState != newState) {
    247             mOriginalConnectionState = newState;
    248             switch (newState) {
    249                 case IDLE:
    250                     break;
    251                 case ACTIVE:
    252                     setActive();
    253                     break;
    254                 case HOLDING:
    255                     setOnHold();
    256                     break;
    257                 case DIALING:
    258                 case ALERTING:
    259                     setDialing();
    260                     // For SIP calls, we need to ask the framework to play the ringback for us.
    261                     setRingbackRequested(true);
    262                     break;
    263                 case INCOMING:
    264                 case WAITING:
    265                     setRinging();
    266                     break;
    267                 case DISCONNECTED:
    268                     setDisconnected(DisconnectCauseUtil.toTelecomDisconnectCause(
    269                             mOriginalConnection.getDisconnectCause()));
    270                     close();
    271                     break;
    272                 case DISCONNECTING:
    273                     break;
    274             }
    275             updateCallCapabilities(force);
    276         }
    277     }
    278 
    279     private int buildCallCapabilities() {
    280         int capabilities = CAPABILITY_MUTE | CAPABILITY_SUPPORT_HOLD;
    281         if (getState() == STATE_ACTIVE || getState() == STATE_HOLDING) {
    282             capabilities |= CAPABILITY_HOLD;
    283         }
    284         return capabilities;
    285     }
    286 
    287     void updateCallCapabilities(boolean force) {
    288         int newCallCapabilities = buildCallCapabilities();
    289         if (force || getConnectionCapabilities() != newCallCapabilities) {
    290             setConnectionCapabilities(newCallCapabilities);
    291         }
    292     }
    293 
    294     void onAddedToCallService() {
    295         if (VERBOSE) log("onAddedToCallService");
    296         updateState(true);
    297         updateCallCapabilities(true);
    298         setAudioModeIsVoip(true);
    299         if (mOriginalConnection != null) {
    300             setCallerDisplayName(mOriginalConnection.getCnapName(),
    301                     mOriginalConnection.getCnapNamePresentation());
    302         }
    303     }
    304 
    305     /**
    306      * Updates the handle on this connection based on the original connection.
    307      */
    308     private void updateAddress() {
    309         if (mOriginalConnection != null) {
    310             Uri address = getAddressFromNumber(mOriginalConnection.getAddress());
    311             int presentation = mOriginalConnection.getNumberPresentation();
    312             if (!Objects.equals(address, getAddress()) ||
    313                     presentation != getAddressPresentation()) {
    314                 com.android.services.telephony.Log.v(this, "updateAddress, address changed");
    315                 setAddress(address, presentation);
    316             }
    317 
    318             String name = mOriginalConnection.getCnapName();
    319             int namePresentation = mOriginalConnection.getCnapNamePresentation();
    320             if (!Objects.equals(name, getCallerDisplayName()) ||
    321                     namePresentation != getCallerDisplayNamePresentation()) {
    322                 com.android.services.telephony.Log
    323                         .v(this, "updateAddress, caller display name changed");
    324                 setCallerDisplayName(name, namePresentation);
    325             }
    326         }
    327     }
    328 
    329     private void setTechnologyTypeExtra() {
    330         int phoneType = PhoneConstants.PHONE_TYPE_SIP;
    331         if (getExtras() == null) {
    332             Bundle b = new Bundle();
    333             b.putInt(TelecomManager.EXTRA_CALL_TECHNOLOGY_TYPE, phoneType);
    334             setExtras(b);
    335         } else {
    336             getExtras().putInt(TelecomManager.EXTRA_CALL_TECHNOLOGY_TYPE, phoneType);
    337         }
    338     }
    339 
    340     /**
    341      * Determines the address for an incoming number.
    342      *
    343      * @param number The incoming number.
    344      * @return The Uri representing the number.
    345      */
    346     private static Uri getAddressFromNumber(String number) {
    347         // Address can be null for blocked calls.
    348         if (number == null) {
    349             number = "";
    350         }
    351         return Uri.fromParts(PhoneAccount.SCHEME_SIP, number, null);
    352     }
    353 
    354     private void close() {
    355         if (getPhone() != null) {
    356             getPhone().unregisterForPreciseCallStateChanged(mHandler);
    357         }
    358         mOriginalConnection = null;
    359         destroy();
    360     }
    361 
    362     private static void log(String msg) {
    363         Log.d(SipUtil.LOG_TAG, PREFIX + msg);
    364     }
    365 }
    366