1 /* 2 * Copyright (C) 2010 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.app.PendingIntent; 20 import android.content.ComponentName; 21 import android.content.Context; 22 import android.content.Intent; 23 import android.graphics.drawable.Icon; 24 import android.net.Uri; 25 import android.net.sip.SipManager; 26 import android.net.sip.SipProfile; 27 import android.os.Bundle; 28 import android.provider.Settings; 29 import android.telecom.PhoneAccount; 30 import android.telecom.PhoneAccountHandle; 31 import android.telecom.TelecomManager; 32 import android.telephony.TelephonyManager; 33 import android.text.TextUtils; 34 import android.util.Log; 35 36 import com.android.phone.PhoneGlobals; 37 38 import java.io.IOException; 39 import java.util.ArrayList; 40 import java.util.List; 41 42 public class SipUtil { 43 static final String LOG_TAG = "SIP"; 44 static final String EXTRA_INCOMING_CALL_INTENT = 45 "com.android.services.telephony.sip.incoming_call_intent"; 46 static final String EXTRA_PHONE_ACCOUNT = 47 "com.android.services.telephony.sip.phone_account"; 48 static final String PHONE_PACKAGE = "com.android.phone"; 49 SipUtil()50 private SipUtil() { 51 } 52 isVoipSupported(Context context)53 public static boolean isVoipSupported(Context context) { 54 return SipManager.isVoipSupported(context) 55 && context.getResources().getBoolean( 56 com.android.internal.R.bool.config_built_in_sip_phone) 57 && ((TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE)) 58 .isVoiceCapable(); 59 } 60 createIncomingCallPendingIntent( Context context, String sipProfileName)61 static PendingIntent createIncomingCallPendingIntent( 62 Context context, String sipProfileName) { 63 Intent intent = new Intent(context, SipIncomingCallReceiver.class); 64 intent.setAction(SipManager.ACTION_SIP_INCOMING_CALL); 65 intent.putExtra(EXTRA_PHONE_ACCOUNT, SipUtil.createAccountHandle(context, sipProfileName)); 66 return PendingIntent.getBroadcast(context, 0, intent, 67 // Mutable because information associated with the call is passed back here. 68 PendingIntent.FLAG_MUTABLE | PendingIntent.FLAG_UPDATE_CURRENT); 69 } 70 isPhoneIdle(Context context)71 public static boolean isPhoneIdle(Context context) { 72 TelecomManager manager = (TelecomManager) context.getSystemService( 73 Context.TELECOM_SERVICE); 74 if (manager != null) { 75 return !manager.isInCall(); 76 } 77 return true; 78 } 79 80 /** 81 * Creates a {@link PhoneAccountHandle} from the specified SIP profile name. 82 */ createAccountHandle(Context context, String sipProfileName)83 static PhoneAccountHandle createAccountHandle(Context context, String sipProfileName) { 84 return new PhoneAccountHandle( 85 new ComponentName(context, SipConnectionService.class), sipProfileName); 86 } 87 88 /** 89 * Determines the SIP profile name for a specified {@link PhoneAccountHandle}. 90 * 91 * @param phoneAccountHandle The {@link PhoneAccountHandle}. 92 * @return The SIP profile name. 93 */ getSipProfileNameFromPhoneAccount(PhoneAccountHandle phoneAccountHandle)94 static String getSipProfileNameFromPhoneAccount(PhoneAccountHandle phoneAccountHandle) { 95 if (phoneAccountHandle == null) { 96 return null; 97 } 98 99 String sipProfileName = phoneAccountHandle.getId(); 100 if (TextUtils.isEmpty(sipProfileName)) { 101 return null; 102 } 103 return sipProfileName; 104 } 105 106 /** 107 * Creates a PhoneAccount for a SipProfile. 108 * 109 * @param context The context 110 * @param profile The SipProfile. 111 * @return The PhoneAccount. 112 */ createPhoneAccount(Context context, SipProfile profile)113 static PhoneAccount createPhoneAccount(Context context, SipProfile profile) { 114 // Build a URI to represent the SIP account. Does not use SipProfile#getUriString() since 115 // that prototype can include transport information which we do not want to see in the 116 // phone account. 117 String sipAddress = profile.getUserName() + "@" + profile.getSipDomain(); 118 Uri sipUri = Uri.parse(profile.getUriString()); 119 120 PhoneAccountHandle accountHandle = 121 SipUtil.createAccountHandle(context, profile.getProfileName()); 122 123 final ArrayList<String> supportedUriSchemes = new ArrayList<String>(); 124 supportedUriSchemes.add(PhoneAccount.SCHEME_SIP); 125 if (useSipForPstnCalls(context)) { 126 supportedUriSchemes.add(PhoneAccount.SCHEME_TEL); 127 } 128 129 Bundle phoneAccountExtras = new Bundle(); 130 phoneAccountExtras.putBoolean(PhoneAccount.EXTRA_ALWAYS_USE_VOIP_AUDIO_MODE, 131 true); 132 133 PhoneAccount.Builder builder = PhoneAccount.builder(accountHandle, profile.getDisplayName()) 134 .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER 135 | PhoneAccount.CAPABILITY_MULTI_USER) 136 .setAddress(sipUri) 137 .setShortDescription(sipAddress) 138 .setIcon(Icon.createWithResource( 139 context, R.drawable.ic_dialer_sip_black_24dp)) 140 .setExtras(phoneAccountExtras) 141 .setSupportedUriSchemes(supportedUriSchemes); 142 143 return builder.build(); 144 } 145 146 /** 147 * Upon migration from M->N, the SIP Profile database will be moved into DE storage. This will 148 * not be a problem for non-FBE enabled devices, since DE and CE storage is available at the 149 * same time. This will be a problem for backup/restore, however if the SIP Profile DB is 150 * restored onto a new FBE enabled device. 151 * 152 * Checks if the Sip Db is in DE storage. If it is, the Db is moved to CE storage and 153 * deleted. 154 */ possiblyMigrateSipDb(Context context)155 private static void possiblyMigrateSipDb(Context context) { 156 SipProfileDb dbDeStorage = new SipProfileDb(context); 157 dbDeStorage.accessDEStorageForMigration(); 158 List<SipProfile> profilesDeStorage = dbDeStorage.retrieveSipProfileList(); 159 if(profilesDeStorage.size() != 0) { 160 Log.i(LOG_TAG, "Migrating SIP Profiles over!"); 161 SipProfileDb dbCeStorage = new SipProfileDb(context); 162 //Perform Profile Migration 163 for (SipProfile profileToMove : profilesDeStorage) { 164 if (dbCeStorage.retrieveSipProfileFromName( 165 profileToMove.getProfileName()) == null) { 166 try { 167 dbCeStorage.saveProfile(profileToMove); 168 } catch (IOException e) { 169 Log.w(LOG_TAG, "Error Migrating file to CE: " + 170 profileToMove.getProfileName(), e); 171 } 172 } 173 Log.i(LOG_TAG, "(Migration) Deleting SIP profile: " + 174 profileToMove.getProfileName()); 175 try { 176 dbDeStorage.deleteProfile(profileToMove); 177 } catch (IOException e) { 178 Log.w(LOG_TAG, "Error Deleting file: " + 179 profileToMove.getProfileName(), e); 180 } 181 } 182 } 183 // Delete supporting structures if they exist 184 dbDeStorage.cleanupUponMigration(); 185 } 186 187 /** 188 * Migrates the DB files over from CE->DE storage and starts the SipService. 189 */ startSipService()190 public static void startSipService() { 191 Context phoneGlobalsContext = PhoneGlobals.getInstance(); 192 // Migrate SIP database from DE->CE storage if the device has just upgraded. 193 possiblyMigrateSipDb(phoneGlobalsContext); 194 // Wait until boot complete to start SIP so that it has access to CE storage. 195 Intent startSipIntent = new Intent(); 196 startSipIntent.setAction(SipManager.ACTION_START_SIP); 197 startSipIntent.setPackage(PHONE_PACKAGE); 198 phoneGlobalsContext.startService(startSipIntent); 199 } 200 201 /** 202 * Determines if the user has chosen to use SIP for PSTN calls as well as SIP calls. 203 * @param context The context. 204 * @return {@code True} if SIP should be used for PSTN calls. 205 */ useSipForPstnCalls(Context context)206 private static boolean useSipForPstnCalls(Context context) { 207 final SipPreferences sipPreferences = new SipPreferences(context); 208 return sipPreferences.getSipCallOption().equals(Settings.System.SIP_ALWAYS); 209 } 210 211 /** 212 * Updates SIP accounts to indicate whether they are enabled to receive incoming SIP calls. 213 * 214 * @param isEnabled {@code True} if receiving incoming SIP calls. 215 */ useSipToReceiveIncomingCalls(Context context, boolean isEnabled)216 public static void useSipToReceiveIncomingCalls(Context context, boolean isEnabled) { 217 SipProfileDb profileDb = new SipProfileDb(context); 218 219 // Mark all profiles as auto-register if we are now receiving calls. 220 List<SipProfile> sipProfileList = profileDb.retrieveSipProfileList(); 221 for (SipProfile p : sipProfileList) { 222 updateAutoRegistrationFlag(p, profileDb, isEnabled); 223 } 224 } 225 updateAutoRegistrationFlag( SipProfile p, SipProfileDb db, boolean isEnabled)226 private static void updateAutoRegistrationFlag( 227 SipProfile p, SipProfileDb db, boolean isEnabled) { 228 SipProfile newProfile = new SipProfile.Builder(p).setAutoRegistration(isEnabled).build(); 229 230 try { 231 // Note: The profile is updated, but the associated PhoneAccount is left alone since 232 // the only thing that changed is the auto-registration flag, which is not part of the 233 // PhoneAccount. 234 db.deleteProfile(p); 235 db.saveProfile(newProfile); 236 } catch (Exception e) { 237 Log.d(LOG_TAG, "updateAutoRegistrationFlag, exception: " + e); 238 } 239 } 240 } 241