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