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