| /* |
| * Copyright (C) 2014 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.services.telephony.sip; |
| |
| import android.app.Notification; |
| import android.app.NotificationChannel; |
| import android.app.NotificationManager; |
| import android.app.PendingIntent; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.net.sip.SipManager; |
| import android.net.sip.SipProfile; |
| import android.telecom.PhoneAccount; |
| import android.telecom.PhoneAccountHandle; |
| import android.telecom.TelecomManager; |
| import android.util.Log; |
| |
| import com.android.phone.R; |
| |
| import java.io.IOException; |
| import java.util.List; |
| import java.util.Objects; |
| import java.util.concurrent.CopyOnWriteArrayList; |
| import java.util.stream.Collectors; |
| |
| /** |
| * Manages the {@link PhoneAccount} entries for SIP calling. |
| */ |
| public final class SipAccountRegistry { |
| private final class AccountEntry { |
| private final SipProfile mProfile; |
| |
| AccountEntry(SipProfile profile) { |
| mProfile = profile; |
| } |
| |
| SipProfile getProfile() { |
| return mProfile; |
| } |
| |
| /** |
| * Stops the SIP service associated with the SIP profile. The {@code SipAccountRegistry} is |
| * informed when the service has been stopped via an intent which triggers |
| * {@link SipAccountRegistry#removeSipProfile(String)}. |
| * |
| * @param sipManager The SIP manager. |
| * @return {@code True} if stop was successful. |
| */ |
| boolean stopSipService(SipManager sipManager) { |
| try { |
| sipManager.close(mProfile.getUriString()); |
| return true; |
| } catch (Exception e) { |
| log("stopSipService, stop failed for profile: " + mProfile.getUriString() + |
| ", exception: " + e); |
| } |
| return false; |
| } |
| } |
| |
| private static final String PREFIX = "[SipAccountRegistry] "; |
| private static final boolean VERBOSE = false; /* STOP SHIP if true */ |
| private static final SipAccountRegistry INSTANCE = new SipAccountRegistry(); |
| private static final String NOTIFICATION_TAG = SipAccountRegistry.class.getSimpleName(); |
| private static final int SIP_ACCOUNTS_REMOVED_NOTIFICATION_ID = 1; |
| |
| private static final String CHANNEL_ID_SIP_ACCOUNTS_REMOVED = "sipAccountsRemoved"; |
| |
| private final List<AccountEntry> mAccounts = new CopyOnWriteArrayList<>(); |
| |
| private NotificationChannel mNotificationChannel; |
| private NotificationManager mNm; |
| |
| private SipAccountRegistry() {} |
| |
| public static SipAccountRegistry getInstance() { |
| return INSTANCE; |
| } |
| |
| /** |
| * Sets up the Account registry and performs any upgrade operations before it is used. |
| */ |
| public void setup(Context context) { |
| setupNotificationChannel(context); |
| verifyAndPurgeInvalidPhoneAccounts(context); |
| startSipProfilesAsync(context); |
| } |
| |
| private void setupNotificationChannel(Context context) { |
| mNotificationChannel = new NotificationChannel( |
| CHANNEL_ID_SIP_ACCOUNTS_REMOVED, |
| context.getText(R.string.notification_channel_sip_account), |
| NotificationManager.IMPORTANCE_HIGH); |
| mNm = context.getSystemService(NotificationManager.class); |
| if (mNm != null) { |
| mNm.createNotificationChannel(mNotificationChannel); |
| } |
| } |
| |
| /** |
| * Checks the existing SIP phone {@link PhoneAccount}s registered with telecom and deletes any |
| * invalid accounts. |
| * |
| * @param context The context. |
| */ |
| void verifyAndPurgeInvalidPhoneAccounts(Context context) { |
| TelecomManager telecomManager = context.getSystemService(TelecomManager.class); |
| SipProfileDb profileDb = new SipProfileDb(context); |
| List<PhoneAccountHandle> accountHandles = telecomManager.getPhoneAccountsSupportingScheme( |
| PhoneAccount.SCHEME_SIP); |
| |
| for (PhoneAccountHandle accountHandle : accountHandles) { |
| String profileName = SipUtil.getSipProfileNameFromPhoneAccount(accountHandle); |
| SipProfile profile = profileDb.retrieveSipProfileFromName(profileName); |
| if (profile == null) { |
| log("verifyAndPurgeInvalidPhoneAccounts, deleting account: " + accountHandle); |
| telecomManager.unregisterPhoneAccount(accountHandle); |
| } |
| } |
| } |
| |
| /** |
| * Starts the SIP service for the specified SIP profile and ensures it has a valid registered |
| * {@link PhoneAccount}. |
| * |
| * @param context The context. |
| * @param sipProfileName The name of the {@link SipProfile} to start, or {@code null} for all. |
| * @param enableProfile Sip account should be enabled |
| */ |
| void startSipService(Context context, String sipProfileName, boolean enabledProfile) { |
| startSipProfilesAsync(context); |
| } |
| |
| /** |
| * Removes a {@link SipProfile} from the account registry. Does not stop/close the associated |
| * SIP service (this method is invoked via an intent from the SipService once a profile has |
| * been stopped/closed). |
| * |
| * @param sipProfileName Name of the SIP profile. |
| */ |
| public void removeSipProfile(String sipProfileName) { |
| AccountEntry accountEntry = getAccountEntry(sipProfileName); |
| |
| if (accountEntry != null) { |
| mAccounts.remove(accountEntry); |
| } |
| } |
| |
| /** |
| * Stops a SIP profile and un-registers its associated {@link android.telecom.PhoneAccount}. |
| * Called after a SIP profile is deleted. The {@link AccountEntry} will be removed when the |
| * service has been stopped. The {@code SipService} fires the {@code ACTION_SIP_REMOVE_PHONE} |
| * intent, which triggers {@link SipAccountRegistry#removeSipProfile(String)} to perform the |
| * removal. |
| * |
| * @param context The context. |
| * @param sipProfileName Name of the SIP profile. |
| */ |
| void stopSipService(Context context, String sipProfileName) { |
| // Stop the sip service for the profile. |
| AccountEntry accountEntry = getAccountEntry(sipProfileName); |
| if (accountEntry != null ) { |
| SipManager sipManager = SipManager.newInstance(context); |
| accountEntry.stopSipService(sipManager); |
| } |
| |
| // Un-register its PhoneAccount. |
| PhoneAccountHandle handle = SipUtil.createAccountHandle(context, sipProfileName); |
| TelecomManager tm = context.getSystemService(TelecomManager.class); |
| tm.unregisterPhoneAccount(handle); |
| } |
| |
| /** |
| * Performs an asynchronous call to |
| * {@link SipAccountRegistry#startSipProfiles(android.content.Context, String)}, starting the |
| * specified SIP profile and registering its {@link android.telecom.PhoneAccount}. |
| * |
| * @param context The context. |
| */ |
| private void startSipProfilesAsync( |
| final Context context) { |
| if (VERBOSE) log("startSipProfiles, start auto registration"); |
| |
| new Thread(new Runnable() { |
| @Override |
| public void run() { |
| startSipProfiles(context); |
| }} |
| ).start(); |
| } |
| |
| /** |
| * Loops through all SIP accounts from the SIP database, starts each service and registers |
| * each with the telecom framework. If a specific sipProfileName is specified, this will only |
| * register the associated SIP account. |
| * |
| * @param context The context. |
| */ |
| private void startSipProfiles(Context context) { |
| SipProfileDb profileDb = new SipProfileDb(context); |
| List<SipProfile> sipProfileList = profileDb.retrieveSipProfileList(); |
| |
| // If there're SIP profiles existing in DB, display a notification and delete all these |
| // profiles. |
| if (!sipProfileList.isEmpty()) { |
| for (SipProfile profile : sipProfileList) { |
| stopSipService(context, profile.getProfileName()); |
| removeSipProfile(profile.getProfileName()); |
| try { |
| profileDb.deleteProfile(profile); |
| } catch (IOException e) { |
| // Ignore |
| } |
| } |
| sendSipAccountsRemovedNotification(context, sipProfileList); |
| } |
| } |
| |
| private void sendSipAccountsRemovedNotification(Context context, List<SipProfile> profiles) { |
| String sipAccounts = profiles.stream().map(p -> p.getProfileName()) |
| .collect(Collectors.joining(",")); |
| |
| Intent intent = new Intent(TelecomManager.ACTION_CHANGE_PHONE_ACCOUNTS); |
| intent.setFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); |
| PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, |
| PendingIntent.FLAG_IMMUTABLE); |
| |
| Notification.Action action = new Notification.Action.Builder(R.drawable.ic_sim_card, |
| context.getString(R.string.sip_accounts_removed_notification_action), |
| pendingIntent).build(); |
| Notification.Builder builder = new Notification.Builder(context) |
| .setSmallIcon(R.drawable.ic_sim_card) |
| .setChannelId(CHANNEL_ID_SIP_ACCOUNTS_REMOVED) |
| .setContentTitle(context.getText(R.string.sip_accounts_removed_notification_title)) |
| .setStyle(new Notification.BigTextStyle() |
| .bigText(context.getString( |
| R.string.sip_accounts_removed_notification_message, |
| sipAccounts))) |
| .setAutoCancel(true) |
| .addAction(action); |
| Notification notification = builder.build(); |
| if (mNm != null) { |
| mNm.notify(NOTIFICATION_TAG, SIP_ACCOUNTS_REMOVED_NOTIFICATION_ID, |
| notification); |
| } else { |
| log("NotificationManager is null when send the notification of removed SIP accounts"); |
| } |
| } |
| |
| /** |
| * Retrieves the {@link AccountEntry} from the registry with the specified name. |
| * |
| * @param sipProfileName Name of the SIP profile to retrieve. |
| * @return The {@link AccountEntry}, or {@code null} is it was not found. |
| */ |
| private AccountEntry getAccountEntry(String sipProfileName) { |
| for (AccountEntry entry : mAccounts) { |
| if (Objects.equals(sipProfileName, entry.getProfile().getProfileName())) { |
| return entry; |
| } |
| } |
| return null; |
| } |
| |
| private void log(String message) { |
| Log.d(SipUtil.LOG_TAG, PREFIX + message); |
| } |
| } |