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 { onFound(SipProfile profile)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 onCreate()58 public void onCreate() { 59 mSipProfileDb = new SipProfileDb(this); 60 mHandler = new Handler(); 61 super.onCreate(); 62 } 63 64 @Override onCreateOutgoingConnection( PhoneAccountHandle connectionManagerAccount, final ConnectionRequest request)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 onCreateIncomingConnection( PhoneAccountHandle connectionManagerAccount, ConnectionRequest request)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 createConnectionForProfile( SipProfile profile, ConnectionRequest request)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 */ findProfile(final String profileName, final IProfileFinderCallback callback)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 findPhoneForProfile(SipProfile profile)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 createPhoneForProfile(SipProfile profile)249 private SipPhone createPhoneForProfile(SipProfile profile) { 250 if (VERBOSE) log("createPhoneForProfile, profile: " + profile); 251 return PhoneFactory.makeSipPhone(profile.getUriString()); 252 } 253 startCallWithPhone( SipPhone phone, ConnectionRequest request)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 isNetworkConnected()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 log(String msg)282 private static void log(String msg) { 283 Log.d(SipUtil.LOG_TAG, PREFIX + msg); 284 } 285 } 286