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