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