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.content.ComponentName;
     20 import android.content.Context;
     21 import android.content.Intent;
     22 import android.net.ConnectivityManager;
     23 import android.net.NetworkInfo;
     24 import android.net.sip.SipAudioCall;
     25 import android.net.sip.SipException;
     26 import android.net.sip.SipManager;
     27 import android.net.sip.SipProfile;
     28 import android.os.Bundle;
     29 import android.os.Handler;
     30 import android.telecom.Connection;
     31 import android.telecom.ConnectionRequest;
     32 import android.telecom.ConnectionService;
     33 import android.telecom.PhoneAccountHandle;
     34 import android.telecom.TelecomManager;
     35 import android.telephony.DisconnectCause;
     36 import android.util.Log;
     37 
     38 import com.android.internal.telephony.CallStateException;
     39 import com.android.internal.telephony.PhoneFactory;
     40 import com.android.internal.telephony.sip.SipPhone;
     41 import com.android.services.telephony.DisconnectCauseUtil;
     42 
     43 import java.util.List;
     44 import java.util.Objects;
     45 
     46 public final class SipConnectionService extends ConnectionService {
     47     private interface IProfileFinderCallback {
     48         void onFound(SipProfile profile);
     49     }
     50 
     51     private static final String PREFIX = "[SipConnectionService] ";
     52     private static final boolean VERBOSE = false; /* STOP SHIP if true */
     53 
     54     private SipProfileDb mSipProfileDb;
     55     private Handler mHandler;
     56 
     57     @Override
     58     public void onCreate() {
     59         mSipProfileDb = new SipProfileDb(this);
     60         mHandler = new Handler();
     61         super.onCreate();
     62     }
     63 
     64     @Override
     65     public Connection onCreateOutgoingConnection(
     66             PhoneAccountHandle connectionManagerAccount,
     67             final ConnectionRequest request) {
     68         if (VERBOSE) log("onCreateOutgoingConnection, request: " + request);
     69 
     70         Bundle extras = request.getExtras();
     71         if (extras != null &&
     72                 extras.getString(TelecomManager.GATEWAY_PROVIDER_PACKAGE) != null) {
     73             return Connection.createFailedConnection(DisconnectCauseUtil.toTelecomDisconnectCause(
     74                     DisconnectCause.CALL_BARRED, "Cannot make a SIP call with a gateway number."));
     75         }
     76 
     77         PhoneAccountHandle accountHandle = request.getAccountHandle();
     78         ComponentName sipComponentName = new ComponentName(this, SipConnectionService.class);
     79         if (!Objects.equals(accountHandle.getComponentName(), sipComponentName)) {
     80             return Connection.createFailedConnection(DisconnectCauseUtil.toTelecomDisconnectCause(
     81                     DisconnectCause.OUTGOING_FAILURE, "Did not match service connection"));
     82         }
     83 
     84         final SipConnection connection = new SipConnection();
     85         connection.setAddress(request.getAddress(), TelecomManager.PRESENTATION_ALLOWED);
     86         connection.setInitializing();
     87         connection.onAddedToCallService();
     88         boolean attemptCall = true;
     89 
     90         if (!SipUtil.isVoipSupported(this)) {
     91             final CharSequence description = getString(R.string.no_voip);
     92             connection.setDisconnected(new android.telecom.DisconnectCause(
     93                     android.telecom.DisconnectCause.ERROR, null, description,
     94                     "VoIP unsupported"));
     95             attemptCall = false;
     96         }
     97 
     98         if (attemptCall && !isNetworkConnected()) {
     99             if (VERBOSE) log("start, network not connected, dropping call");
    100             final boolean wifiOnly = SipManager.isSipWifiOnly(this);
    101             final CharSequence description = getString(wifiOnly ? R.string.no_wifi_available
    102                     : R.string.no_internet_available);
    103             connection.setDisconnected(new android.telecom.DisconnectCause(
    104                     android.telecom.DisconnectCause.ERROR, null, description,
    105                     "Network not connected"));
    106             attemptCall = false;
    107         }
    108 
    109         if (attemptCall) {
    110             // The ID used for SIP-based phone account is the SIP profile Uri. Use it to find
    111             // the actual profile.
    112             String profileName = accountHandle.getId();
    113             findProfile(profileName, new IProfileFinderCallback() {
    114                 @Override
    115                 public void onFound(SipProfile profile) {
    116                     if (profile == null) {
    117                         connection.setDisconnected(DisconnectCauseUtil.toTelecomDisconnectCause(
    118                                 DisconnectCause.OUTGOING_FAILURE, "SIP profile not found."));
    119                         connection.destroy();
    120                     } else {
    121                         com.android.internal.telephony.Connection chosenConnection =
    122                                 createConnectionForProfile(profile, request);
    123                         if (chosenConnection == null) {
    124                             connection.setDisconnected(DisconnectCauseUtil.toTelecomDisconnectCause(
    125                                     DisconnectCause.OUTGOING_FAILURE, "Connection failed."));
    126                             connection.destroy();
    127                         } else {
    128                             if (VERBOSE) log("initializing connection");
    129                             connection.initialize(chosenConnection);
    130                         }
    131                     }
    132                 }
    133             });
    134         }
    135 
    136         return connection;
    137     }
    138 
    139     @Override
    140     public Connection onCreateIncomingConnection(
    141             PhoneAccountHandle connectionManagerAccount,
    142             ConnectionRequest request) {
    143         if (VERBOSE) log("onCreateIncomingConnection, request: " + request);
    144 
    145         if (request.getExtras() == null) {
    146             if (VERBOSE) log("onCreateIncomingConnection, no extras");
    147             return Connection.createFailedConnection(DisconnectCauseUtil.toTelecomDisconnectCause(
    148                     DisconnectCause.ERROR_UNSPECIFIED, "No extras on request."));
    149         }
    150 
    151         Intent sipIntent = (Intent) request.getExtras().getParcelable(
    152                 SipUtil.EXTRA_INCOMING_CALL_INTENT);
    153         if (sipIntent == null) {
    154             if (VERBOSE) log("onCreateIncomingConnection, no SIP intent");
    155             return Connection.createFailedConnection(DisconnectCauseUtil.toTelecomDisconnectCause(
    156                     DisconnectCause.ERROR_UNSPECIFIED, "No SIP intent."));
    157         }
    158 
    159         SipAudioCall sipAudioCall;
    160         try {
    161             sipAudioCall = SipManager.newInstance(this).takeAudioCall(sipIntent, null);
    162         } catch (SipException e) {
    163             log("onCreateIncomingConnection, takeAudioCall exception: " + e);
    164             return Connection.createCanceledConnection();
    165         }
    166 
    167         SipPhone phone = findPhoneForProfile(sipAudioCall.getLocalProfile());
    168         if (phone == null) {
    169             phone = createPhoneForProfile(sipAudioCall.getLocalProfile());
    170         }
    171         if (phone != null) {
    172             com.android.internal.telephony.Connection originalConnection = phone.takeIncomingCall(
    173                     sipAudioCall);
    174             if (VERBOSE) log("onCreateIncomingConnection, new connection: " + originalConnection);
    175             if (originalConnection != null) {
    176                 SipConnection sipConnection = new SipConnection();
    177                 sipConnection.initialize(originalConnection);
    178                 sipConnection.onAddedToCallService();
    179                 return sipConnection;
    180             } else {
    181                 if (VERBOSE) log("onCreateIncomingConnection, takingIncomingCall failed");
    182                 return Connection.createCanceledConnection();
    183             }
    184         }
    185         return Connection.createFailedConnection(DisconnectCauseUtil.toTelecomDisconnectCause(
    186                 DisconnectCause.ERROR_UNSPECIFIED));
    187     }
    188 
    189     private com.android.internal.telephony.Connection createConnectionForProfile(
    190             SipProfile profile,
    191             ConnectionRequest request) {
    192         SipPhone phone = findPhoneForProfile(profile);
    193         if (phone == null) {
    194             phone = createPhoneForProfile(profile);
    195         }
    196         if (phone != null) {
    197             return startCallWithPhone(phone, request);
    198         }
    199         return null;
    200     }
    201 
    202     /**
    203      * Searched for the specified profile in the SIP profile database.  This can take a long time
    204      * in communicating with the database, so it is done asynchronously with a separate thread and a
    205      * callback interface.
    206      */
    207     private void findProfile(final String profileName, final IProfileFinderCallback callback) {
    208         if (VERBOSE) log("findProfile");
    209         new Thread(new Runnable() {
    210             @Override
    211             public void run() {
    212                 SipProfile profileToUse = null;
    213                 List<SipProfile> profileList = mSipProfileDb.retrieveSipProfileList();
    214                 if (profileList != null) {
    215                     for (SipProfile profile : profileList) {
    216                         if (Objects.equals(profileName, profile.getProfileName())) {
    217                             profileToUse = profile;
    218                             break;
    219                         }
    220                     }
    221                 }
    222 
    223                 final SipProfile profileFound = profileToUse;
    224                 mHandler.post(new Runnable() {
    225                     @Override
    226                     public void run() {
    227                         callback.onFound(profileFound);
    228                     }
    229                 });
    230             }
    231         }).start();
    232     }
    233 
    234     private SipPhone findPhoneForProfile(SipProfile profile) {
    235         if (VERBOSE) log("findPhoneForProfile, profile: " + profile);
    236         for (Connection connection : getAllConnections()) {
    237             if (connection instanceof SipConnection) {
    238                 SipPhone phone = ((SipConnection) connection).getPhone();
    239                 if (phone != null && phone.getSipUri().equals(profile.getUriString())) {
    240                     if (VERBOSE) log("findPhoneForProfile, found existing phone: " + phone);
    241                     return phone;
    242                 }
    243             }
    244         }
    245         if (VERBOSE) log("findPhoneForProfile, no phone found");
    246         return null;
    247     }
    248 
    249     private SipPhone createPhoneForProfile(SipProfile profile) {
    250         if (VERBOSE) log("createPhoneForProfile, profile: " + profile);
    251         return PhoneFactory.makeSipPhone(profile.getUriString());
    252     }
    253 
    254     private com.android.internal.telephony.Connection startCallWithPhone(
    255             SipPhone phone, ConnectionRequest request) {
    256         String number = request.getAddress().getSchemeSpecificPart();
    257         if (VERBOSE) log("startCallWithPhone, number: " + number);
    258 
    259         try {
    260             com.android.internal.telephony.Connection originalConnection =
    261                     phone.dial(number, request.getVideoState());
    262             return originalConnection;
    263         } catch (CallStateException e) {
    264             log("startCallWithPhone, exception: " + e);
    265             return null;
    266         }
    267     }
    268 
    269     private boolean isNetworkConnected() {
    270         ConnectivityManager cm =
    271                 (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
    272         if (cm != null) {
    273             NetworkInfo ni = cm.getActiveNetworkInfo();
    274             if (ni != null && ni.isConnected()) {
    275                 return ni.getType() == ConnectivityManager.TYPE_WIFI ||
    276                         !SipManager.isSipWifiOnly(this);
    277             }
    278         }
    279         return false;
    280     }
    281 
    282     private static void log(String msg) {
    283         Log.d(SipUtil.LOG_TAG, PREFIX + msg);
    284     }
    285 }
    286