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 AccountEntry(SipProfile profile)39 AccountEntry(SipProfile profile) { 40 mProfile = profile; 41 } 42 getProfile()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 */ startSipService(SipManager sipManager, Context context, boolean isReceivingCalls)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 */ stopSipService(SipManager sipManager)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 SipAccountRegistry()108 private SipAccountRegistry() {} 109 getInstance()110 public static SipAccountRegistry getInstance() { 111 return INSTANCE; 112 } 113 114 /** 115 * Sets up the Account registry and performs any upgrade operations before it is used. 116 */ setup(Context context)117 public void setup(Context context) { 118 verifyAndPurgeInvalidPhoneAccounts(context); 119 startSipProfilesAsync(context, (String) null, false); 120 } 121 122 /** 123 * Checks the existing SIP phone {@link PhoneAccount}s registered with telecom and deletes any 124 * invalid accounts. 125 * 126 * @param context The context. 127 */ verifyAndPurgeInvalidPhoneAccounts(Context context)128 void verifyAndPurgeInvalidPhoneAccounts(Context context) { 129 TelecomManager telecomManager = TelecomManager.from(context); 130 SipProfileDb profileDb = new SipProfileDb(context); 131 List<PhoneAccountHandle> accountHandles = telecomManager.getPhoneAccountsSupportingScheme( 132 PhoneAccount.SCHEME_SIP); 133 134 for (PhoneAccountHandle accountHandle : accountHandles) { 135 String profileName = SipUtil.getSipProfileNameFromPhoneAccount(accountHandle); 136 SipProfile profile = profileDb.retrieveSipProfileFromName(profileName); 137 if (profile == null) { 138 log("verifyAndPurgeInvalidPhoneAccounts, deleting account: " + accountHandle); 139 telecomManager.unregisterPhoneAccount(accountHandle); 140 } 141 } 142 } 143 144 /** 145 * Starts the SIP service for the specified SIP profile and ensures it has a valid registered 146 * {@link PhoneAccount}. 147 * 148 * @param context The context. 149 * @param sipProfileName The name of the {@link SipProfile} to start, or {@code null} for all. 150 * @param enableProfile Sip account should be enabled 151 */ startSipService(Context context, String sipProfileName, boolean enableProfile)152 void startSipService(Context context, String sipProfileName, boolean enableProfile) { 153 startSipProfilesAsync(context, sipProfileName, enableProfile); 154 } 155 156 /** 157 * Removes a {@link SipProfile} from the account registry. Does not stop/close the associated 158 * SIP service (this method is invoked via an intent from the SipService once a profile has 159 * been stopped/closed). 160 * 161 * @param sipProfileName Name of the SIP profile. 162 */ removeSipProfile(String sipProfileName)163 public void removeSipProfile(String sipProfileName) { 164 AccountEntry accountEntry = getAccountEntry(sipProfileName); 165 166 if (accountEntry != null) { 167 mAccounts.remove(accountEntry); 168 } 169 } 170 171 /** 172 * Stops a SIP profile and un-registers its associated {@link android.telecom.PhoneAccount}. 173 * Called after a SIP profile is deleted. The {@link AccountEntry} will be removed when the 174 * service has been stopped. The {@code SipService} fires the {@code ACTION_SIP_REMOVE_PHONE} 175 * intent, which triggers {@link SipAccountRegistry#removeSipProfile(String)} to perform the 176 * removal. 177 * 178 * @param context The context. 179 * @param sipProfileName Name of the SIP profile. 180 */ stopSipService(Context context, String sipProfileName)181 void stopSipService(Context context, String sipProfileName) { 182 // Stop the sip service for the profile. 183 AccountEntry accountEntry = getAccountEntry(sipProfileName); 184 if (accountEntry != null ) { 185 SipManager sipManager = SipManager.newInstance(context); 186 accountEntry.stopSipService(sipManager); 187 } 188 189 // Un-register its PhoneAccount. 190 PhoneAccountHandle handle = SipUtil.createAccountHandle(context, sipProfileName); 191 TelecomManager.from(context).unregisterPhoneAccount(handle); 192 } 193 194 /** 195 * Causes the SIP service to be restarted for all {@link SipProfile}s. For example, if the user 196 * toggles the "receive calls" option for SIP, this method handles restarting the SIP services 197 * in the new mode. 198 * 199 * @param context The context. 200 */ restartSipService(Context context)201 public void restartSipService(Context context) { 202 startSipProfiles(context, null, false); 203 } 204 205 /** 206 * Performs an asynchronous call to 207 * {@link SipAccountRegistry#startSipProfiles(android.content.Context, String)}, starting the 208 * specified SIP profile and registering its {@link android.telecom.PhoneAccount}. 209 * 210 * @param context The context. 211 * @param sipProfileName Name of the SIP profile. 212 * @param enableProfile Sip account should be enabled. 213 */ startSipProfilesAsync( final Context context, final String sipProfileName, final boolean enableProfile)214 private void startSipProfilesAsync( 215 final Context context, final String sipProfileName, final boolean enableProfile) { 216 if (VERBOSE) log("startSipProfiles, start auto registration"); 217 218 new Thread(new Runnable() { 219 @Override 220 public void run() { 221 startSipProfiles(context, sipProfileName, enableProfile); 222 }} 223 ).start(); 224 } 225 226 /** 227 * Loops through all SIP accounts from the SIP database, starts each service and registers 228 * each with the telecom framework. If a specific sipProfileName is specified, this will only 229 * register the associated SIP account. 230 * 231 * @param context The context. 232 * @param sipProfileName A specific SIP profile Name to start, or {@code null} to start all. 233 * @param enableProfile Sip account should be enabled. 234 */ startSipProfiles(Context context, String sipProfileName, boolean enableProfile)235 private void startSipProfiles(Context context, String sipProfileName, boolean enableProfile) { 236 final SipPreferences sipPreferences = new SipPreferences(context); 237 boolean isReceivingCalls = sipPreferences.isReceivingCallsEnabled(); 238 TelecomManager telecomManager = TelecomManager.from(context); 239 SipManager sipManager = SipManager.newInstance(context); 240 SipProfileDb profileDb = new SipProfileDb(context); 241 List<SipProfile> sipProfileList = profileDb.retrieveSipProfileList(); 242 243 for (SipProfile profile : sipProfileList) { 244 // Register a PhoneAccount for the profile and optionally enable the primary 245 // profile. 246 if (sipProfileName == null || sipProfileName.equals(profile.getProfileName())) { 247 PhoneAccount phoneAccount = SipUtil.createPhoneAccount(context, profile); 248 telecomManager.registerPhoneAccount(phoneAccount); 249 if (enableProfile) { 250 telecomManager.enablePhoneAccount(phoneAccount.getAccountHandle(), true); 251 } 252 startSipServiceForProfile(profile, sipManager, context, isReceivingCalls); 253 } 254 } 255 } 256 257 /** 258 * Starts the SIP service for a sip profile and saves a new {@code AccountEntry} in the 259 * registry. 260 * 261 * @param profile The {@link SipProfile} to start. 262 * @param sipManager The SIP manager. 263 * @param context The context. 264 * @param isReceivingCalls {@code True} if the profile should be started such that it can 265 * receive incoming calls. 266 */ startSipServiceForProfile(SipProfile profile, SipManager sipManager, Context context, boolean isReceivingCalls)267 private void startSipServiceForProfile(SipProfile profile, SipManager sipManager, 268 Context context, boolean isReceivingCalls) { 269 removeSipProfile(profile.getUriString()); 270 271 AccountEntry entry = new AccountEntry(profile); 272 if (entry.startSipService(sipManager, context, isReceivingCalls)) { 273 mAccounts.add(entry); 274 } 275 } 276 277 /** 278 * Retrieves the {@link AccountEntry} from the registry with the specified name. 279 * 280 * @param sipProfileName Name of the SIP profile to retrieve. 281 * @return The {@link AccountEntry}, or {@code null} is it was not found. 282 */ getAccountEntry(String sipProfileName)283 private AccountEntry getAccountEntry(String sipProfileName) { 284 for (AccountEntry entry : mAccounts) { 285 if (Objects.equals(sipProfileName, entry.getProfile().getProfileName())) { 286 return entry; 287 } 288 } 289 return null; 290 } 291 log(String message)292 private void log(String message) { 293 Log.d(SipUtil.LOG_TAG, PREFIX + message); 294 } 295 } 296