blob: 3678c462f15f240559343e7d990de38446331714 [file] [log] [blame]
/*
* Copyright (C) 2010 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.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.graphics.drawable.Icon;
import android.net.Uri;
import android.net.sip.SipManager;
import android.net.sip.SipProfile;
import android.provider.Settings;
import android.telecom.PhoneAccount;
import android.telecom.PhoneAccountHandle;
import android.telecom.TelecomManager;
import android.text.TextUtils;
import android.util.Log;
import com.android.phone.PhoneGlobals;
import com.android.phone.R;
import com.android.server.sip.SipService;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class SipUtil {
static final String LOG_TAG = "SIP";
static final String EXTRA_INCOMING_CALL_INTENT =
"com.android.services.telephony.sip.incoming_call_intent";
static final String EXTRA_PHONE_ACCOUNT =
"com.android.services.telephony.sip.phone_account";
private SipUtil() {
}
public static boolean isVoipSupported(Context context) {
return SipManager.isVoipSupported(context) &&
context.getResources().getBoolean(
com.android.internal.R.bool.config_built_in_sip_phone) &&
context.getResources().getBoolean(
com.android.internal.R.bool.config_voice_capable);
}
static PendingIntent createIncomingCallPendingIntent(
Context context, String sipProfileName) {
Intent intent = new Intent(context, SipBroadcastReceiver.class);
intent.setAction(SipManager.ACTION_SIP_INCOMING_CALL);
intent.putExtra(EXTRA_PHONE_ACCOUNT, SipUtil.createAccountHandle(context, sipProfileName));
return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
}
public static boolean isPhoneIdle(Context context) {
TelecomManager manager = (TelecomManager) context.getSystemService(
Context.TELECOM_SERVICE);
if (manager != null) {
return !manager.isInCall();
}
return true;
}
/**
* Creates a {@link PhoneAccountHandle} from the specified SIP profile name.
*/
static PhoneAccountHandle createAccountHandle(Context context, String sipProfileName) {
return new PhoneAccountHandle(
new ComponentName(context, SipConnectionService.class), sipProfileName);
}
/**
* Determines the SIP profile name for a specified {@link PhoneAccountHandle}.
*
* @param phoneAccountHandle The {@link PhoneAccountHandle}.
* @return The SIP profile name.
*/
static String getSipProfileNameFromPhoneAccount(PhoneAccountHandle phoneAccountHandle) {
if (phoneAccountHandle == null) {
return null;
}
String sipProfileName = phoneAccountHandle.getId();
if (TextUtils.isEmpty(sipProfileName)) {
return null;
}
return sipProfileName;
}
/**
* Creates a PhoneAccount for a SipProfile.
*
* @param context The context
* @param profile The SipProfile.
* @return The PhoneAccount.
*/
static PhoneAccount createPhoneAccount(Context context, SipProfile profile) {
// Build a URI to represent the SIP account. Does not use SipProfile#getUriString() since
// that prototype can include transport information which we do not want to see in the
// phone account.
String sipAddress = profile.getUserName() + "@" + profile.getSipDomain();
Uri sipUri = Uri.parse(profile.getUriString());
PhoneAccountHandle accountHandle =
SipUtil.createAccountHandle(context, profile.getProfileName());
final ArrayList<String> supportedUriSchemes = new ArrayList<String>();
supportedUriSchemes.add(PhoneAccount.SCHEME_SIP);
if (useSipForPstnCalls(context)) {
supportedUriSchemes.add(PhoneAccount.SCHEME_TEL);
}
PhoneAccount.Builder builder = PhoneAccount.builder(accountHandle, profile.getDisplayName())
.setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER
| PhoneAccount.CAPABILITY_MULTI_USER)
.setAddress(sipUri)
.setShortDescription(sipAddress)
.setIcon(Icon.createWithResource(
context.getResources(), R.drawable.ic_dialer_sip_black_24dp))
.setSupportedUriSchemes(supportedUriSchemes);
return builder.build();
}
/**
* Upon migration from M->N, the SIP Profile database will be moved into DE storage. This will
* not be a problem for non-FBE enabled devices, since DE and CE storage is available at the
* same time. This will be a problem for backup/restore, however if the SIP Profile DB is
* restored onto a new FBE enabled device.
*
* Checks if the Sip Db is in DE storage. If it is, the Db is moved to CE storage and
* deleted.
*/
private static void possiblyMigrateSipDb(Context context) {
SipProfileDb dbDeStorage = new SipProfileDb(context);
dbDeStorage.accessDEStorageForMigration();
List<SipProfile> profilesDeStorage = dbDeStorage.retrieveSipProfileList();
if(profilesDeStorage.size() != 0) {
Log.i(LOG_TAG, "Migrating SIP Profiles over!");
SipProfileDb dbCeStorage = new SipProfileDb(context);
//Perform Profile Migration
for (SipProfile profileToMove : profilesDeStorage) {
if (dbCeStorage.retrieveSipProfileFromName(
profileToMove.getProfileName()) == null) {
try {
dbCeStorage.saveProfile(profileToMove);
} catch (IOException e) {
Log.w(LOG_TAG, "Error Migrating file to CE: " +
profileToMove.getProfileName(), e);
}
}
Log.i(LOG_TAG, "(Migration) Deleting SIP profile: " +
profileToMove.getProfileName());
dbDeStorage.deleteProfile(profileToMove);
}
}
// Delete supporting structures if they exist
dbDeStorage.cleanupUponMigration();
}
/**
* Migrates the DB files over from CE->DE storage and starts the SipService.
*/
public static void startSipService() {
Context phoneGlobalsContext = PhoneGlobals.getInstance();
// Migrate SIP database from DE->CE storage if the device has just upgraded.
possiblyMigrateSipDb(phoneGlobalsContext);
// Wait until boot complete to start SIP so that it has access to CE storage.
SipService.start(phoneGlobalsContext);
}
/**
* Determines if the user has chosen to use SIP for PSTN calls as well as SIP calls.
* @param context The context.
* @return {@code True} if SIP should be used for PSTN calls.
*/
private static boolean useSipForPstnCalls(Context context) {
final SipPreferences sipPreferences = new SipPreferences(context);
return sipPreferences.getSipCallOption().equals(Settings.System.SIP_ALWAYS);
}
/**
* Updates SIP accounts to indicate whether they are enabled to receive incoming SIP calls.
*
* @param isEnabled {@code True} if receiving incoming SIP calls.
*/
public static void useSipToReceiveIncomingCalls(Context context, boolean isEnabled) {
SipProfileDb profileDb = new SipProfileDb(context);
// Mark all profiles as auto-register if we are now receiving calls.
List<SipProfile> sipProfileList = profileDb.retrieveSipProfileList();
for (SipProfile p : sipProfileList) {
updateAutoRegistrationFlag(p, profileDb, isEnabled);
}
}
private static void updateAutoRegistrationFlag(
SipProfile p, SipProfileDb db, boolean isEnabled) {
SipProfile newProfile = new SipProfile.Builder(p).setAutoRegistration(isEnabled).build();
try {
// Note: The profile is updated, but the associated PhoneAccount is left alone since
// the only thing that changed is the auto-registration flag, which is not part of the
// PhoneAccount.
db.deleteProfile(p);
db.saveProfile(newProfile);
} catch (Exception e) {
Log.d(LOG_TAG, "updateAutoRegistrationFlag, exception: " + e);
}
}
}