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 = context.getSystemService(TelecomManager.class); 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 tm = context.getSystemService(TelecomManager.class); 192 tm.unregisterPhoneAccount(handle); 193 } 194 195 /** 196 * Causes the SIP service to be restarted for all {@link SipProfile}s. For example, if the user 197 * toggles the "receive calls" option for SIP, this method handles restarting the SIP services 198 * in the new mode. 199 * 200 * @param context The context. 201 */ restartSipService(Context context)202 public void restartSipService(Context context) { 203 startSipProfiles(context, null, false); 204 } 205 206 /** 207 * Performs an asynchronous call to 208 * {@link SipAccountRegistry#startSipProfiles(android.content.Context, String)}, starting the 209 * specified SIP profile and registering its {@link android.telecom.PhoneAccount}. 210 * 211 * @param context The context. 212 * @param sipProfileName Name of the SIP profile. 213 * @param enableProfile Sip account should be enabled. 214 */ startSipProfilesAsync( final Context context, final String sipProfileName, final boolean enableProfile)215 private void startSipProfilesAsync( 216 final Context context, final String sipProfileName, final boolean enableProfile) { 217 if (VERBOSE) log("startSipProfiles, start auto registration"); 218 219 new Thread(new Runnable() { 220 @Override 221 public void run() { 222 startSipProfiles(context, sipProfileName, enableProfile); 223 }} 224 ).start(); 225 } 226 227 /** 228 * Loops through all SIP accounts from the SIP database, starts each service and registers 229 * each with the telecom framework. If a specific sipProfileName is specified, this will only 230 * register the associated SIP account. 231 * 232 * @param context The context. 233 * @param sipProfileName A specific SIP profile Name to start, or {@code null} to start all. 234 * @param enableProfile Sip account should be enabled. 235 */ startSipProfiles(Context context, String sipProfileName, boolean enableProfile)236 private void startSipProfiles(Context context, String sipProfileName, boolean enableProfile) { 237 final SipPreferences sipPreferences = new SipPreferences(context); 238 boolean isReceivingCalls = sipPreferences.isReceivingCallsEnabled(); 239 TelecomManager telecomManager = context.getSystemService(TelecomManager.class); 240 SipManager sipManager = SipManager.newInstance(context); 241 SipProfileDb profileDb = new SipProfileDb(context); 242 List<SipProfile> sipProfileList = profileDb.retrieveSipProfileList(); 243 244 for (SipProfile profile : sipProfileList) { 245 // Register a PhoneAccount for the profile and optionally enable the primary 246 // profile. 247 if (sipProfileName == null || sipProfileName.equals(profile.getProfileName())) { 248 PhoneAccount phoneAccount = SipUtil.createPhoneAccount(context, profile); 249 telecomManager.registerPhoneAccount(phoneAccount); 250 if (enableProfile) { 251 telecomManager.enablePhoneAccount(phoneAccount.getAccountHandle(), true); 252 } 253 startSipServiceForProfile(profile, sipManager, context, isReceivingCalls); 254 } 255 } 256 } 257 258 /** 259 * Starts the SIP service for a sip profile and saves a new {@code AccountEntry} in the 260 * registry. 261 * 262 * @param profile The {@link SipProfile} to start. 263 * @param sipManager The SIP manager. 264 * @param context The context. 265 * @param isReceivingCalls {@code True} if the profile should be started such that it can 266 * receive incoming calls. 267 */ startSipServiceForProfile(SipProfile profile, SipManager sipManager, Context context, boolean isReceivingCalls)268 private void startSipServiceForProfile(SipProfile profile, SipManager sipManager, 269 Context context, boolean isReceivingCalls) { 270 removeSipProfile(profile.getUriString()); 271 272 AccountEntry entry = new AccountEntry(profile); 273 if (entry.startSipService(sipManager, context, isReceivingCalls)) { 274 mAccounts.add(entry); 275 } 276 } 277 278 /** 279 * Retrieves the {@link AccountEntry} from the registry with the specified name. 280 * 281 * @param sipProfileName Name of the SIP profile to retrieve. 282 * @return The {@link AccountEntry}, or {@code null} is it was not found. 283 */ getAccountEntry(String sipProfileName)284 private AccountEntry getAccountEntry(String sipProfileName) { 285 for (AccountEntry entry : mAccounts) { 286 if (Objects.equals(sipProfileName, entry.getProfile().getProfileName())) { 287 return entry; 288 } 289 } 290 return null; 291 } 292 log(String message)293 private void log(String message) { 294 Log.d(SipUtil.LOG_TAG, PREFIX + message); 295 } 296 } 297