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