| /* |
| * Copyright (C) 2014 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.services.telephony.sip; |
| |
| import android.content.ComponentName; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.net.ConnectivityManager; |
| import android.net.NetworkInfo; |
| import android.net.sip.SipAudioCall; |
| import android.net.sip.SipException; |
| import android.net.sip.SipManager; |
| import android.net.sip.SipProfile; |
| import android.os.Bundle; |
| import android.os.Handler; |
| import android.telecom.Connection; |
| import android.telecom.ConnectionRequest; |
| import android.telecom.ConnectionService; |
| import android.telecom.PhoneAccountHandle; |
| import android.telecom.TelecomManager; |
| import android.telephony.DisconnectCause; |
| import android.util.Log; |
| |
| import com.android.internal.telephony.CallStateException; |
| import com.android.internal.telephony.PhoneFactory; |
| import com.android.internal.telephony.PhoneInternalInterface; |
| import com.android.internal.telephony.sip.SipPhone; |
| import com.android.services.telephony.DisconnectCauseUtil; |
| |
| import java.util.List; |
| import java.util.Objects; |
| |
| public final class SipConnectionService extends ConnectionService { |
| private interface IProfileFinderCallback { |
| void onFound(SipProfile profile); |
| } |
| |
| private static final String PREFIX = "[SipConnectionService] "; |
| private static final boolean VERBOSE = false; /* STOP SHIP if true */ |
| |
| private SipProfileDb mSipProfileDb; |
| private Handler mHandler; |
| |
| @Override |
| public void onCreate() { |
| mSipProfileDb = new SipProfileDb(this); |
| mHandler = new Handler(); |
| super.onCreate(); |
| } |
| |
| @Override |
| public Connection onCreateOutgoingConnection( |
| PhoneAccountHandle connectionManagerAccount, |
| final ConnectionRequest request) { |
| if (VERBOSE) log("onCreateOutgoingConnection, request: " + request); |
| |
| Bundle extras = request.getExtras(); |
| if (extras != null && |
| extras.getString(TelecomManager.GATEWAY_PROVIDER_PACKAGE) != null) { |
| return Connection.createFailedConnection(DisconnectCauseUtil.toTelecomDisconnectCause( |
| DisconnectCause.CALL_BARRED, "Cannot make a SIP call with a gateway number.")); |
| } |
| |
| PhoneAccountHandle accountHandle = request.getAccountHandle(); |
| ComponentName sipComponentName = new ComponentName(this, SipConnectionService.class); |
| if (!Objects.equals(accountHandle.getComponentName(), sipComponentName)) { |
| return Connection.createFailedConnection(DisconnectCauseUtil.toTelecomDisconnectCause( |
| DisconnectCause.OUTGOING_FAILURE, "Did not match service connection")); |
| } |
| |
| final SipConnection connection = new SipConnection(); |
| connection.setAddress(request.getAddress(), TelecomManager.PRESENTATION_ALLOWED); |
| connection.setInitializing(); |
| connection.onAddedToCallService(); |
| boolean attemptCall = true; |
| |
| if (!SipUtil.isVoipSupported(this)) { |
| final CharSequence description = getString(R.string.no_voip); |
| connection.setDisconnected(new android.telecom.DisconnectCause( |
| android.telecom.DisconnectCause.ERROR, null, description, |
| "VoIP unsupported")); |
| attemptCall = false; |
| } |
| |
| if (attemptCall && !isNetworkConnected()) { |
| if (VERBOSE) log("start, network not connected, dropping call"); |
| final boolean wifiOnly = SipManager.isSipWifiOnly(this); |
| final CharSequence description = getString(wifiOnly ? R.string.no_wifi_available |
| : R.string.no_internet_available); |
| connection.setDisconnected(new android.telecom.DisconnectCause( |
| android.telecom.DisconnectCause.ERROR, null, description, |
| "Network not connected")); |
| attemptCall = false; |
| } |
| |
| if (attemptCall) { |
| // The ID used for SIP-based phone account is the SIP profile Uri. Use it to find |
| // the actual profile. |
| String profileName = accountHandle.getId(); |
| findProfile(profileName, new IProfileFinderCallback() { |
| @Override |
| public void onFound(SipProfile profile) { |
| if (profile == null) { |
| connection.setDisconnected(DisconnectCauseUtil.toTelecomDisconnectCause( |
| DisconnectCause.OUTGOING_FAILURE, "SIP profile not found.")); |
| connection.destroy(); |
| } else { |
| com.android.internal.telephony.Connection chosenConnection = |
| createConnectionForProfile(profile, request); |
| if (chosenConnection == null) { |
| connection.setDisconnected(DisconnectCauseUtil.toTelecomDisconnectCause( |
| DisconnectCause.OUTGOING_FAILURE, "Connection failed.")); |
| connection.destroy(); |
| } else { |
| if (VERBOSE) log("initializing connection"); |
| connection.initialize(chosenConnection); |
| } |
| } |
| } |
| }); |
| } |
| |
| return connection; |
| } |
| |
| @Override |
| public Connection onCreateIncomingConnection( |
| PhoneAccountHandle connectionManagerAccount, |
| ConnectionRequest request) { |
| if (VERBOSE) log("onCreateIncomingConnection, request: " + request); |
| |
| if (request.getExtras() == null) { |
| if (VERBOSE) log("onCreateIncomingConnection, no extras"); |
| return Connection.createFailedConnection(DisconnectCauseUtil.toTelecomDisconnectCause( |
| DisconnectCause.ERROR_UNSPECIFIED, "No extras on request.")); |
| } |
| |
| Intent sipIntent = (Intent) request.getExtras().getParcelable( |
| SipUtil.EXTRA_INCOMING_CALL_INTENT); |
| if (sipIntent == null) { |
| if (VERBOSE) log("onCreateIncomingConnection, no SIP intent"); |
| return Connection.createFailedConnection(DisconnectCauseUtil.toTelecomDisconnectCause( |
| DisconnectCause.ERROR_UNSPECIFIED, "No SIP intent.")); |
| } |
| |
| SipAudioCall sipAudioCall; |
| try { |
| sipAudioCall = SipManager.newInstance(this).takeAudioCall(sipIntent, null); |
| } catch (SipException e) { |
| log("onCreateIncomingConnection, takeAudioCall exception: " + e); |
| return Connection.createCanceledConnection(); |
| } |
| |
| SipPhone phone = findPhoneForProfile(sipAudioCall.getLocalProfile()); |
| if (phone == null) { |
| phone = createPhoneForProfile(sipAudioCall.getLocalProfile()); |
| } |
| if (phone != null) { |
| com.android.internal.telephony.Connection originalConnection = phone.takeIncomingCall( |
| sipAudioCall); |
| if (VERBOSE) log("onCreateIncomingConnection, new connection: " + originalConnection); |
| if (originalConnection != null) { |
| SipConnection sipConnection = new SipConnection(); |
| sipConnection.initialize(originalConnection); |
| sipConnection.onAddedToCallService(); |
| return sipConnection; |
| } else { |
| if (VERBOSE) log("onCreateIncomingConnection, takingIncomingCall failed"); |
| return Connection.createCanceledConnection(); |
| } |
| } |
| return Connection.createFailedConnection(DisconnectCauseUtil.toTelecomDisconnectCause( |
| DisconnectCause.ERROR_UNSPECIFIED)); |
| } |
| |
| private com.android.internal.telephony.Connection createConnectionForProfile( |
| SipProfile profile, |
| ConnectionRequest request) { |
| SipPhone phone = findPhoneForProfile(profile); |
| if (phone == null) { |
| phone = createPhoneForProfile(profile); |
| } |
| if (phone != null) { |
| return startCallWithPhone(phone, request); |
| } |
| return null; |
| } |
| |
| /** |
| * Searched for the specified profile in the SIP profile database. This can take a long time |
| * in communicating with the database, so it is done asynchronously with a separate thread and a |
| * callback interface. |
| */ |
| private void findProfile(final String profileName, final IProfileFinderCallback callback) { |
| if (VERBOSE) log("findProfile"); |
| new Thread(new Runnable() { |
| @Override |
| public void run() { |
| SipProfile profileToUse = null; |
| List<SipProfile> profileList = mSipProfileDb.retrieveSipProfileList(); |
| if (profileList != null) { |
| for (SipProfile profile : profileList) { |
| if (Objects.equals(profileName, profile.getProfileName())) { |
| profileToUse = profile; |
| break; |
| } |
| } |
| } |
| |
| final SipProfile profileFound = profileToUse; |
| mHandler.post(new Runnable() { |
| @Override |
| public void run() { |
| callback.onFound(profileFound); |
| } |
| }); |
| } |
| }).start(); |
| } |
| |
| private SipPhone findPhoneForProfile(SipProfile profile) { |
| if (VERBOSE) log("findPhoneForProfile, profile: " + profile); |
| for (Connection connection : getAllConnections()) { |
| if (connection instanceof SipConnection) { |
| SipPhone phone = ((SipConnection) connection).getPhone(); |
| if (phone != null && phone.getSipUri().equals(profile.getUriString())) { |
| if (VERBOSE) log("findPhoneForProfile, found existing phone: " + phone); |
| return phone; |
| } |
| } |
| } |
| if (VERBOSE) log("findPhoneForProfile, no phone found"); |
| return null; |
| } |
| |
| private SipPhone createPhoneForProfile(SipProfile profile) { |
| if (VERBOSE) log("createPhoneForProfile, profile: " + profile); |
| return PhoneFactory.makeSipPhone(profile.getUriString()); |
| } |
| |
| private com.android.internal.telephony.Connection startCallWithPhone( |
| SipPhone phone, ConnectionRequest request) { |
| String number = request.getAddress().getSchemeSpecificPart(); |
| if (VERBOSE) log("startCallWithPhone, number: " + number); |
| |
| try { |
| com.android.internal.telephony.Connection originalConnection = |
| phone.dial(number, new PhoneInternalInterface.DialArgs.Builder<>() |
| .setVideoState(request.getVideoState()) |
| .build()); |
| return originalConnection; |
| } catch (CallStateException e) { |
| log("startCallWithPhone, exception: " + e); |
| return null; |
| } |
| } |
| |
| private boolean isNetworkConnected() { |
| ConnectivityManager cm = |
| (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); |
| if (cm != null) { |
| NetworkInfo ni = cm.getActiveNetworkInfo(); |
| if (ni != null && ni.isConnected()) { |
| return ni.getType() == ConnectivityManager.TYPE_WIFI || |
| !SipManager.isSipWifiOnly(this); |
| } |
| } |
| return false; |
| } |
| |
| private static void log(String msg) { |
| Log.d(SipUtil.LOG_TAG, PREFIX + msg); |
| } |
| } |