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.app.Notification; 20 import android.app.NotificationChannel; 21 import android.app.NotificationManager; 22 import android.app.PendingIntent; 23 import android.content.Context; 24 import android.content.Intent; 25 import android.net.sip.SipManager; 26 import android.net.sip.SipProfile; 27 import android.telecom.PhoneAccount; 28 import android.telecom.PhoneAccountHandle; 29 import android.telecom.TelecomManager; 30 import android.util.Log; 31 32 import com.android.phone.R; 33 34 import java.io.IOException; 35 import java.util.List; 36 import java.util.Objects; 37 import java.util.concurrent.CopyOnWriteArrayList; 38 import java.util.stream.Collectors; 39 40 /** 41 * Manages the {@link PhoneAccount} entries for SIP calling. 42 */ 43 public final class SipAccountRegistry { 44 private final class AccountEntry { 45 private final SipProfile mProfile; 46 AccountEntry(SipProfile profile)47 AccountEntry(SipProfile profile) { 48 mProfile = profile; 49 } 50 getProfile()51 SipProfile getProfile() { 52 return mProfile; 53 } 54 55 /** 56 * Stops the SIP service associated with the SIP profile. The {@code SipAccountRegistry} is 57 * informed when the service has been stopped via an intent which triggers 58 * {@link SipAccountRegistry#removeSipProfile(String)}. 59 * 60 * @param sipManager The SIP manager. 61 * @return {@code True} if stop was successful. 62 */ stopSipService(SipManager sipManager)63 boolean stopSipService(SipManager sipManager) { 64 try { 65 sipManager.close(mProfile.getUriString()); 66 return true; 67 } catch (Exception e) { 68 log("stopSipService, stop failed for profile: " + mProfile.getUriString() + 69 ", exception: " + e); 70 } 71 return false; 72 } 73 } 74 75 private static final String PREFIX = "[SipAccountRegistry] "; 76 private static final boolean VERBOSE = false; /* STOP SHIP if true */ 77 private static final SipAccountRegistry INSTANCE = new SipAccountRegistry(); 78 private static final String NOTIFICATION_TAG = SipAccountRegistry.class.getSimpleName(); 79 private static final int SIP_ACCOUNTS_REMOVED_NOTIFICATION_ID = 1; 80 81 private static final String CHANNEL_ID_SIP_ACCOUNTS_REMOVED = "sipAccountsRemoved"; 82 83 private final List<AccountEntry> mAccounts = new CopyOnWriteArrayList<>(); 84 85 private NotificationChannel mNotificationChannel; 86 private NotificationManager mNm; 87 SipAccountRegistry()88 private SipAccountRegistry() {} 89 getInstance()90 public static SipAccountRegistry getInstance() { 91 return INSTANCE; 92 } 93 94 /** 95 * Sets up the Account registry and performs any upgrade operations before it is used. 96 */ setup(Context context)97 public void setup(Context context) { 98 setupNotificationChannel(context); 99 verifyAndPurgeInvalidPhoneAccounts(context); 100 startSipProfilesAsync(context); 101 } 102 setupNotificationChannel(Context context)103 private void setupNotificationChannel(Context context) { 104 mNotificationChannel = new NotificationChannel( 105 CHANNEL_ID_SIP_ACCOUNTS_REMOVED, 106 context.getText(R.string.notification_channel_sip_account), 107 NotificationManager.IMPORTANCE_HIGH); 108 mNm = context.getSystemService(NotificationManager.class); 109 if (mNm != null) { 110 mNm.createNotificationChannel(mNotificationChannel); 111 } 112 } 113 114 /** 115 * Checks the existing SIP phone {@link PhoneAccount}s registered with telecom and deletes any 116 * invalid accounts. 117 * 118 * @param context The context. 119 */ verifyAndPurgeInvalidPhoneAccounts(Context context)120 void verifyAndPurgeInvalidPhoneAccounts(Context context) { 121 TelecomManager telecomManager = context.getSystemService(TelecomManager.class); 122 SipProfileDb profileDb = new SipProfileDb(context); 123 List<PhoneAccountHandle> accountHandles = telecomManager.getPhoneAccountsSupportingScheme( 124 PhoneAccount.SCHEME_SIP); 125 126 for (PhoneAccountHandle accountHandle : accountHandles) { 127 String profileName = SipUtil.getSipProfileNameFromPhoneAccount(accountHandle); 128 SipProfile profile = profileDb.retrieveSipProfileFromName(profileName); 129 if (profile == null) { 130 log("verifyAndPurgeInvalidPhoneAccounts, deleting account: " + accountHandle); 131 telecomManager.unregisterPhoneAccount(accountHandle); 132 } 133 } 134 } 135 136 /** 137 * Starts the SIP service for the specified SIP profile and ensures it has a valid registered 138 * {@link PhoneAccount}. 139 * 140 * @param context The context. 141 * @param sipProfileName The name of the {@link SipProfile} to start, or {@code null} for all. 142 * @param enableProfile Sip account should be enabled 143 */ startSipService(Context context, String sipProfileName, boolean enabledProfile)144 void startSipService(Context context, String sipProfileName, boolean enabledProfile) { 145 startSipProfilesAsync(context); 146 } 147 148 /** 149 * Removes a {@link SipProfile} from the account registry. Does not stop/close the associated 150 * SIP service (this method is invoked via an intent from the SipService once a profile has 151 * been stopped/closed). 152 * 153 * @param sipProfileName Name of the SIP profile. 154 */ removeSipProfile(String sipProfileName)155 public void removeSipProfile(String sipProfileName) { 156 AccountEntry accountEntry = getAccountEntry(sipProfileName); 157 158 if (accountEntry != null) { 159 mAccounts.remove(accountEntry); 160 } 161 } 162 163 /** 164 * Stops a SIP profile and un-registers its associated {@link android.telecom.PhoneAccount}. 165 * Called after a SIP profile is deleted. The {@link AccountEntry} will be removed when the 166 * service has been stopped. The {@code SipService} fires the {@code ACTION_SIP_REMOVE_PHONE} 167 * intent, which triggers {@link SipAccountRegistry#removeSipProfile(String)} to perform the 168 * removal. 169 * 170 * @param context The context. 171 * @param sipProfileName Name of the SIP profile. 172 */ stopSipService(Context context, String sipProfileName)173 void stopSipService(Context context, String sipProfileName) { 174 // Stop the sip service for the profile. 175 AccountEntry accountEntry = getAccountEntry(sipProfileName); 176 if (accountEntry != null ) { 177 SipManager sipManager = SipManager.newInstance(context); 178 accountEntry.stopSipService(sipManager); 179 } 180 181 // Un-register its PhoneAccount. 182 PhoneAccountHandle handle = SipUtil.createAccountHandle(context, sipProfileName); 183 TelecomManager tm = context.getSystemService(TelecomManager.class); 184 tm.unregisterPhoneAccount(handle); 185 } 186 187 /** 188 * Performs an asynchronous call to 189 * {@link SipAccountRegistry#startSipProfiles(android.content.Context, String)}, starting the 190 * specified SIP profile and registering its {@link android.telecom.PhoneAccount}. 191 * 192 * @param context The context. 193 */ startSipProfilesAsync( final Context context)194 private void startSipProfilesAsync( 195 final Context context) { 196 if (VERBOSE) log("startSipProfiles, start auto registration"); 197 198 new Thread(new Runnable() { 199 @Override 200 public void run() { 201 startSipProfiles(context); 202 }} 203 ).start(); 204 } 205 206 /** 207 * Loops through all SIP accounts from the SIP database, starts each service and registers 208 * each with the telecom framework. If a specific sipProfileName is specified, this will only 209 * register the associated SIP account. 210 * 211 * @param context The context. 212 */ startSipProfiles(Context context)213 private void startSipProfiles(Context context) { 214 SipProfileDb profileDb = new SipProfileDb(context); 215 List<SipProfile> sipProfileList = profileDb.retrieveSipProfileList(); 216 217 // If there're SIP profiles existing in DB, display a notification and delete all these 218 // profiles. 219 if (!sipProfileList.isEmpty()) { 220 for (SipProfile profile : sipProfileList) { 221 stopSipService(context, profile.getProfileName()); 222 removeSipProfile(profile.getProfileName()); 223 try { 224 profileDb.deleteProfile(profile); 225 } catch (IOException e) { 226 // Ignore 227 } 228 } 229 sendSipAccountsRemovedNotification(context, sipProfileList); 230 } 231 } 232 sendSipAccountsRemovedNotification(Context context, List<SipProfile> profiles)233 private void sendSipAccountsRemovedNotification(Context context, List<SipProfile> profiles) { 234 String sipAccounts = profiles.stream().map(p -> p.getProfileName()) 235 .collect(Collectors.joining(",")); 236 237 Intent intent = new Intent(TelecomManager.ACTION_CHANGE_PHONE_ACCOUNTS); 238 intent.setFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); 239 PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 240 PendingIntent.FLAG_IMMUTABLE); 241 242 Notification.Action action = new Notification.Action.Builder(R.drawable.ic_sim_card, 243 context.getString(R.string.sip_accounts_removed_notification_action), 244 pendingIntent).build(); 245 Notification.Builder builder = new Notification.Builder(context) 246 .setSmallIcon(R.drawable.ic_sim_card) 247 .setChannelId(CHANNEL_ID_SIP_ACCOUNTS_REMOVED) 248 .setContentTitle(context.getText(R.string.sip_accounts_removed_notification_title)) 249 .setStyle(new Notification.BigTextStyle() 250 .bigText(context.getString( 251 R.string.sip_accounts_removed_notification_message, 252 sipAccounts))) 253 .setAutoCancel(true) 254 .addAction(action); 255 Notification notification = builder.build(); 256 if (mNm != null) { 257 mNm.notify(NOTIFICATION_TAG, SIP_ACCOUNTS_REMOVED_NOTIFICATION_ID, 258 notification); 259 } else { 260 log("NotificationManager is null when send the notification of removed SIP accounts"); 261 } 262 } 263 264 /** 265 * Retrieves the {@link AccountEntry} from the registry with the specified name. 266 * 267 * @param sipProfileName Name of the SIP profile to retrieve. 268 * @return The {@link AccountEntry}, or {@code null} is it was not found. 269 */ getAccountEntry(String sipProfileName)270 private AccountEntry getAccountEntry(String sipProfileName) { 271 for (AccountEntry entry : mAccounts) { 272 if (Objects.equals(sipProfileName, entry.getProfile().getProfileName())) { 273 return entry; 274 } 275 } 276 return null; 277 } 278 log(String message)279 private void log(String message) { 280 Log.d(SipUtil.LOG_TAG, PREFIX + message); 281 } 282 } 283