1 /* 2 * Copyright (C) 2014 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.content.Context; 20 import android.net.sip.SipException; 21 import android.net.sip.SipManager; 22 import android.net.sip.SipProfile; 23 import android.telecom.PhoneAccount; 24 import android.telecom.PhoneAccountHandle; 25 import android.telecom.TelecomManager; 26 import android.util.Log; 27 28 import java.util.List; 29 import java.util.Objects; 30 import java.util.concurrent.CopyOnWriteArrayList; 31 32 /** 33 * Manages the {@link PhoneAccount} entries for SIP calling. 34 */ 35 public final class SipAccountRegistry { 36 private final class AccountEntry { 37 private final SipProfile mProfile; 38 39 AccountEntry(SipProfile profile) { 40 mProfile = profile; 41 } 42 43 SipProfile getProfile() { 44 return mProfile; 45 } 46 47 /** 48 * Starts the SIP service associated with the SIP profile. 49 * 50 * @param sipManager The SIP manager. 51 * @param context The context. 52 * @param isReceivingCalls {@code True} if the sip service is being started to make and 53 * receive calls. {@code False} if the sip service is being started only for 54 * outgoing calls. 55 * @return {@code True} if the service started successfully. 56 */ 57 boolean startSipService(SipManager sipManager, Context context, boolean isReceivingCalls) { 58 if (VERBOSE) log("startSipService, profile: " + mProfile); 59 try { 60 // Stop the Sip service for the profile if it is already running. This is important 61 // if we are changing the state of the "receive calls" option. 62 sipManager.close(mProfile.getUriString()); 63 64 // Start the sip service for the profile. 65 if (isReceivingCalls) { 66 sipManager.open( 67 mProfile, 68 SipUtil.createIncomingCallPendingIntent(context, 69 mProfile.getProfileName()), 70 null); 71 } else { 72 sipManager.open(mProfile); 73 } 74 return true; 75 } catch (SipException e) { 76 log("startSipService, profile: " + mProfile.getProfileName() + 77 ", exception: " + e); 78 } 79 return false; 80 } 81 82 /** 83 * Stops the SIP service associated with the SIP profile. The {@code SipAccountRegistry} is 84 * informed when the service has been stopped via an intent which triggers 85 * {@link SipAccountRegistry#removeSipProfile(String)}. 86 * 87 * @param sipManager The SIP manager. 88 * @return {@code True} if stop was successful. 89 */ 90 boolean stopSipService(SipManager sipManager) { 91 try { 92 sipManager.close(mProfile.getUriString()); 93 return true; 94 } catch (Exception e) { 95 log("stopSipService, stop failed for profile: " + mProfile.getUriString() + 96 ", exception: " + e); 97 } 98 return false; 99 } 100 } 101 102 private static final String PREFIX = "[SipAccountRegistry] "; 103 private static final boolean VERBOSE = false; /* STOP SHIP if true */ 104 private static final SipAccountRegistry INSTANCE = new SipAccountRegistry(); 105 106 private final List<AccountEntry> mAccounts = new CopyOnWriteArrayList<>(); 107 108 private SipAccountRegistry() {} 109 110 public static SipAccountRegistry getInstance() { 111 return INSTANCE; 112 } 113 114 void setup(Context context) { 115 verifyAndPurgeInvalidPhoneAccounts(context); 116 startSipProfilesAsync(context, (String) null, false); 117 } 118 119 /** 120 * Checks the existing SIP phone {@link PhoneAccount}s registered with telecom and deletes any 121 * invalid accounts. 122 * 123 * @param context The context. 124 */ 125 void verifyAndPurgeInvalidPhoneAccounts(Context context) { 126 TelecomManager telecomManager = TelecomManager.from(context); 127 SipProfileDb profileDb = new SipProfileDb(context); 128 List<PhoneAccountHandle> accountHandles = telecomManager.getPhoneAccountsSupportingScheme( 129 PhoneAccount.SCHEME_SIP); 130 131 for (PhoneAccountHandle accountHandle : accountHandles) { 132 String profileName = SipUtil.getSipProfileNameFromPhoneAccount(accountHandle); 133 SipProfile profile = profileDb.retrieveSipProfileFromName(profileName); 134 if (profile == null) { 135 log("verifyAndPurgeInvalidPhoneAccounts, deleting account: " + accountHandle); 136 telecomManager.unregisterPhoneAccount(accountHandle); 137 } 138 } 139 } 140 141 /** 142 * Starts the SIP service for the specified SIP profile and ensures it has a valid registered 143 * {@link PhoneAccount}. 144 * 145 * @param context The context. 146 * @param sipProfileName The name of the {@link SipProfile} to start, or {@code null} for all. 147 * @param enableProfile Sip account should be enabled 148 */ 149 void startSipService(Context context, String sipProfileName, boolean enableProfile) { 150 startSipProfilesAsync(context, sipProfileName, enableProfile); 151 } 152 153 /** 154 * Removes a {@link SipProfile} from the account registry. Does not stop/close the associated 155 * SIP service (this method is invoked via an intent from the SipService once a profile has 156 * been stopped/closed). 157 * 158 * @param sipProfileName Name of the SIP profile. 159 */ 160 void removeSipProfile(String sipProfileName) { 161 AccountEntry accountEntry = getAccountEntry(sipProfileName); 162 163 if (accountEntry != null) { 164 mAccounts.remove(accountEntry); 165 } 166 } 167 168 /** 169 * Stops a SIP profile and un-registers its associated {@link android.telecom.PhoneAccount}. 170 * Called after a SIP profile is deleted. The {@link AccountEntry} will be removed when the 171 * service has been stopped. The {@code SipService} fires the {@code ACTION_SIP_REMOVE_PHONE} 172 * intent, which triggers {@link SipAccountRegistry#removeSipProfile(String)} to perform the 173 * removal. 174 * 175 * @param context The context. 176 * @param sipProfileName Name of the SIP profile. 177 */ 178 void stopSipService(Context context, String sipProfileName) { 179 // Stop the sip service for the profile. 180 AccountEntry accountEntry = getAccountEntry(sipProfileName); 181 if (accountEntry != null ) { 182 SipManager sipManager = SipManager.newInstance(context); 183 accountEntry.stopSipService(sipManager); 184 } 185 186 // Un-register its PhoneAccount. 187 PhoneAccountHandle handle = SipUtil.createAccountHandle(context, sipProfileName); 188 TelecomManager.from(context).unregisterPhoneAccount(handle); 189 } 190 191 /** 192 * Causes the SIP service to be restarted for all {@link SipProfile}s. For example, if the user 193 * toggles the "receive calls" option for SIP, this method handles restarting the SIP services 194 * in the new mode. 195 * 196 * @param context The context. 197 */ 198 public void restartSipService(Context context) { 199 startSipProfiles(context, null, false); 200 } 201 202 /** 203 * Performs an asynchronous call to 204 * {@link SipAccountRegistry#startSipProfiles(android.content.Context, String)}, starting the 205 * specified SIP profile and registering its {@link android.telecom.PhoneAccount}. 206 * 207 * @param context The context. 208 * @param sipProfileName Name of the SIP profile. 209 * @param enableProfile Sip account should be enabled. 210 */ 211 private void startSipProfilesAsync( 212 final Context context, final String sipProfileName, final boolean enableProfile) { 213 if (VERBOSE) log("startSipProfiles, start auto registration"); 214 215 new Thread(new Runnable() { 216 @Override 217 public void run() { 218 startSipProfiles(context, sipProfileName, enableProfile); 219 }} 220 ).start(); 221 } 222 223 /** 224 * Loops through all SIP accounts from the SIP database, starts each service and registers 225 * each with the telecom framework. If a specific sipProfileName is specified, this will only 226 * register the associated SIP account. 227 * 228 * @param context The context. 229 * @param sipProfileName A specific SIP profile Name to start, or {@code null} to start all. 230 * @param enableProfile Sip account should be enabled. 231 */ 232 private void startSipProfiles(Context context, String sipProfileName, boolean enableProfile) { 233 final SipPreferences sipPreferences = new SipPreferences(context); 234 boolean isReceivingCalls = sipPreferences.isReceivingCallsEnabled(); 235 TelecomManager telecomManager = TelecomManager.from(context); 236 SipManager sipManager = SipManager.newInstance(context); 237 SipProfileDb profileDb = new SipProfileDb(context); 238 List<SipProfile> sipProfileList = profileDb.retrieveSipProfileList(); 239 240 for (SipProfile profile : sipProfileList) { 241 // Register a PhoneAccount for the profile and optionally enable the primary 242 // profile. 243 if (sipProfileName == null || sipProfileName.equals(profile.getProfileName())) { 244 PhoneAccount phoneAccount = SipUtil.createPhoneAccount(context, profile); 245 telecomManager.registerPhoneAccount(phoneAccount); 246 if (enableProfile) { 247 telecomManager.enablePhoneAccount(phoneAccount.getAccountHandle(), true); 248 } 249 startSipServiceForProfile(profile, sipManager, context, isReceivingCalls); 250 } 251 } 252 } 253 254 /** 255 * Starts the SIP service for a sip profile and saves a new {@code AccountEntry} in the 256 * registry. 257 * 258 * @param profile The {@link SipProfile} to start. 259 * @param sipManager The SIP manager. 260 * @param context The context. 261 * @param isReceivingCalls {@code True} if the profile should be started such that it can 262 * receive incoming calls. 263 */ 264 private void startSipServiceForProfile(SipProfile profile, SipManager sipManager, 265 Context context, boolean isReceivingCalls) { 266 removeSipProfile(profile.getUriString()); 267 268 AccountEntry entry = new AccountEntry(profile); 269 if (entry.startSipService(sipManager, context, isReceivingCalls)) { 270 mAccounts.add(entry); 271 } 272 } 273 274 /** 275 * Retrieves the {@link AccountEntry} from the registry with the specified name. 276 * 277 * @param sipProfileName Name of the SIP profile to retrieve. 278 * @return The {@link AccountEntry}, or {@code null} is it was not found. 279 */ 280 private AccountEntry getAccountEntry(String sipProfileName) { 281 for (AccountEntry entry : mAccounts) { 282 if (Objects.equals(sipProfileName, entry.getProfile().getProfileName())) { 283 return entry; 284 } 285 } 286 return null; 287 } 288 289 private void log(String message) { 290 Log.d(SipUtil.LOG_TAG, PREFIX + message); 291 } 292 } 293