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