| /* |
| * 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.os.ResultReceiver; |
| import android.telecomm.Connection; |
| import android.telecomm.ConnectionRequest; |
| import android.telecomm.ConnectionService; |
| import android.telecomm.PhoneAccountHandle; |
| import android.telecomm.PropertyPresentation; |
| 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.sip.SipPhone; |
| |
| 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(); |
| } |
| |
| static PhoneAccountHandle getPhoneAccountHandle(Context context) { |
| return new PhoneAccountHandle( |
| new ComponentName(context, SipConnectionService.class), |
| null /* id */); |
| } |
| |
| @Override |
| public Connection onCreateOutgoingConnection( |
| PhoneAccountHandle connectionManagerAccount, |
| final ConnectionRequest request) { |
| if (VERBOSE) log("onCreateOutgoingConnection, request: " + request); |
| |
| Bundle extras = request.getExtras(); |
| if (extras != null && extras.getString(SipUtil.GATEWAY_PROVIDER_PACKAGE) != null) { |
| return Connection.createFailedConnection( |
| 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( |
| DisconnectCause.OUTGOING_FAILURE, "Did not match service connection"); |
| } |
| |
| |
| final SipConnection connection = new SipConnection(); |
| connection.setHandle(request.getHandle(), PropertyPresentation.ALLOWED); |
| connection.setInitializing(); |
| boolean attemptCall = true; |
| |
| if (!SipUtil.isVoipSupported(this)) { |
| SipProfileChooserDialogs.showNoVoip(this, new ResultReceiver(mHandler) { |
| @Override |
| protected void onReceiveResult(int choice, Bundle resultData) { |
| connection.setDisconnected( |
| DisconnectCause.ERROR_UNSPECIFIED, "VoIP unsupported"); |
| } |
| }); |
| attemptCall = false; |
| } |
| |
| if (attemptCall && !isNetworkConnected()) { |
| if (VERBOSE) log("start, network not connected, dropping call"); |
| SipProfileChooserDialogs.showNoInternetError(this, new ResultReceiver(mHandler) { |
| @Override |
| protected void onReceiveResult(int choice, Bundle resultData) { |
| connection.setDisconnected(DisconnectCause.OUT_OF_SERVICE, null); |
| } |
| }); |
| 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 profileUri = accountHandle.getId(); |
| findProfile(profileUri, new IProfileFinderCallback() { |
| @Override |
| public void onFound(SipProfile profile) { |
| if (profile == null) { |
| connection.setDisconnected( |
| DisconnectCause.OUTGOING_FAILURE, "SIP profile not found."); |
| connection.destroy(); |
| } else { |
| com.android.internal.telephony.Connection chosenConnection = |
| createConnectionForProfile(profile, request); |
| if (chosenConnection == null) { |
| connection.setDisconnected( |
| 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(DisconnectCause.ERROR_UNSPECIFIED, null); |
| } |
| |
| Intent sipIntent = (Intent) request.getExtras().getParcelable( |
| SipUtil.EXTRA_INCOMING_CALL_INTENT); |
| if (sipIntent == null) { |
| if (VERBOSE) log("onCreateIncomingConnection, no SIP intent"); |
| return Connection.createFailedConnection(DisconnectCause.ERROR_UNSPECIFIED, null); |
| } |
| |
| 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); |
| return sipConnection; |
| } else { |
| if (VERBOSE) log("onCreateIncomingConnection, takingIncomingCall failed"); |
| return Connection.createCanceledConnection(); |
| } |
| } |
| return Connection.createFailedConnection(DisconnectCause.ERROR_UNSPECIFIED, null); |
| } |
| |
| @Override |
| public void onConnectionAdded(Connection connection) { |
| if (VERBOSE) log("onConnectionAdded, connection: " + connection); |
| if (connection instanceof SipConnection) { |
| ((SipConnection) connection).onAddedToCallService(); |
| } |
| } |
| |
| @Override |
| public void onConnectionRemoved(Connection connection) { |
| if (VERBOSE) log("onConnectionRemoved, connection: " + connection); |
| } |
| |
| 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 profileUri, 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(profileUri, profile.getUriString())) { |
| 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); |
| try { |
| SipManager.newInstance(this).open(profile); |
| return PhoneFactory.makeSipPhone(profile.getUriString()); |
| } catch (SipException e) { |
| log("createPhoneForProfile, exception: " + e); |
| return null; |
| } |
| } |
| |
| private com.android.internal.telephony.Connection startCallWithPhone( |
| SipPhone phone, ConnectionRequest request) { |
| String number = request.getHandle().getSchemeSpecificPart(); |
| if (VERBOSE) log("startCallWithPhone, number: " + number); |
| |
| try { |
| com.android.internal.telephony.Connection originalConnection = |
| phone.dial(number, request.getVideoState()); |
| 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); |
| } |
| } |