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 
setup(Context context)114     void setup(Context context) {
115         verifyAndPurgeInvalidPhoneAccounts(context);
116         startSipProfilesAsync(context, (String) null, false);
117     }
118 
119     /**
120      * Checks the existing SIP phone {@link PhoneAccount}s registered with telecom and deletes any
121      * invalid accounts.
122      *
123      * @param context The context.
124      */
verifyAndPurgeInvalidPhoneAccounts(Context context)125     void verifyAndPurgeInvalidPhoneAccounts(Context context) {
126         TelecomManager telecomManager = TelecomManager.from(context);
127         SipProfileDb profileDb = new SipProfileDb(context);
128         List<PhoneAccountHandle> accountHandles = telecomManager.getPhoneAccountsSupportingScheme(
129                 PhoneAccount.SCHEME_SIP);
130 
131         for (PhoneAccountHandle accountHandle : accountHandles) {
132             String profileName = SipUtil.getSipProfileNameFromPhoneAccount(accountHandle);
133             SipProfile profile = profileDb.retrieveSipProfileFromName(profileName);
134             if (profile == null) {
135                 log("verifyAndPurgeInvalidPhoneAccounts, deleting account: " + accountHandle);
136                 telecomManager.unregisterPhoneAccount(accountHandle);
137             }
138         }
139     }
140 
141     /**
142      * Starts the SIP service for the specified SIP profile and ensures it has a valid registered
143      * {@link PhoneAccount}.
144      *
145      * @param context The context.
146      * @param sipProfileName The name of the {@link SipProfile} to start, or {@code null} for all.
147      * @param enableProfile Sip account should be enabled
148      */
startSipService(Context context, String sipProfileName, boolean enableProfile)149     void startSipService(Context context, String sipProfileName, boolean enableProfile) {
150         startSipProfilesAsync(context, sipProfileName, enableProfile);
151     }
152 
153     /**
154      * Removes a {@link SipProfile} from the account registry.  Does not stop/close the associated
155      * SIP service (this method is invoked via an intent from the SipService once a profile has
156      * been stopped/closed).
157      *
158      * @param sipProfileName Name of the SIP profile.
159      */
removeSipProfile(String sipProfileName)160     void removeSipProfile(String sipProfileName) {
161         AccountEntry accountEntry = getAccountEntry(sipProfileName);
162 
163         if (accountEntry != null) {
164             mAccounts.remove(accountEntry);
165         }
166     }
167 
168     /**
169      * Stops a SIP profile and un-registers its associated {@link android.telecom.PhoneAccount}.
170      * Called after a SIP profile is deleted.  The {@link AccountEntry} will be removed when the
171      * service has been stopped.  The {@code SipService} fires the {@code ACTION_SIP_REMOVE_PHONE}
172      * intent, which triggers {@link SipAccountRegistry#removeSipProfile(String)} to perform the
173      * removal.
174      *
175      * @param context The context.
176      * @param sipProfileName Name of the SIP profile.
177      */
stopSipService(Context context, String sipProfileName)178     void stopSipService(Context context, String sipProfileName) {
179         // Stop the sip service for the profile.
180         AccountEntry accountEntry = getAccountEntry(sipProfileName);
181         if (accountEntry != null ) {
182             SipManager sipManager = SipManager.newInstance(context);
183             accountEntry.stopSipService(sipManager);
184         }
185 
186         // Un-register its PhoneAccount.
187         PhoneAccountHandle handle = SipUtil.createAccountHandle(context, sipProfileName);
188         TelecomManager.from(context).unregisterPhoneAccount(handle);
189     }
190 
191     /**
192      * Causes the SIP service to be restarted for all {@link SipProfile}s.  For example, if the user
193      * toggles the "receive calls" option for SIP, this method handles restarting the SIP services
194      * in the new mode.
195      *
196      * @param context The context.
197      */
restartSipService(Context context)198     public void restartSipService(Context context) {
199         startSipProfiles(context, null, false);
200     }
201 
202     /**
203      * Performs an asynchronous call to
204      * {@link SipAccountRegistry#startSipProfiles(android.content.Context, String)}, starting the
205      * specified SIP profile and registering its {@link android.telecom.PhoneAccount}.
206      *
207      * @param context The context.
208      * @param sipProfileName Name of the SIP profile.
209      * @param enableProfile Sip account should be enabled.
210      */
startSipProfilesAsync( final Context context, final String sipProfileName, final boolean enableProfile)211     private void startSipProfilesAsync(
212             final Context context, final String sipProfileName, final boolean enableProfile) {
213         if (VERBOSE) log("startSipProfiles, start auto registration");
214 
215         new Thread(new Runnable() {
216             @Override
217             public void run() {
218                 startSipProfiles(context, sipProfileName, enableProfile);
219             }}
220         ).start();
221     }
222 
223     /**
224      * Loops through all SIP accounts from the SIP database, starts each service and registers
225      * each with the telecom framework. If a specific sipProfileName is specified, this will only
226      * register the associated SIP account.
227      *
228      * @param context The context.
229      * @param sipProfileName A specific SIP profile Name to start, or {@code null} to start all.
230      * @param enableProfile Sip account should be enabled.
231      */
startSipProfiles(Context context, String sipProfileName, boolean enableProfile)232     private void startSipProfiles(Context context, String sipProfileName, boolean enableProfile) {
233         final SipPreferences sipPreferences = new SipPreferences(context);
234         boolean isReceivingCalls = sipPreferences.isReceivingCallsEnabled();
235         TelecomManager telecomManager = TelecomManager.from(context);
236         SipManager sipManager = SipManager.newInstance(context);
237         SipProfileDb profileDb = new SipProfileDb(context);
238         List<SipProfile> sipProfileList = profileDb.retrieveSipProfileList();
239 
240         for (SipProfile profile : sipProfileList) {
241             // Register a PhoneAccount for the profile and optionally enable the primary
242             // profile.
243             if (sipProfileName == null || sipProfileName.equals(profile.getProfileName())) {
244                 PhoneAccount phoneAccount = SipUtil.createPhoneAccount(context, profile);
245                 telecomManager.registerPhoneAccount(phoneAccount);
246                 if (enableProfile) {
247                     telecomManager.enablePhoneAccount(phoneAccount.getAccountHandle(), true);
248                 }
249                 startSipServiceForProfile(profile, sipManager, context, isReceivingCalls);
250             }
251         }
252     }
253 
254     /**
255      * Starts the SIP service for a sip profile and saves a new {@code AccountEntry} in the
256      * registry.
257      *
258      * @param profile The {@link SipProfile} to start.
259      * @param sipManager The SIP manager.
260      * @param context The context.
261      * @param isReceivingCalls {@code True} if the profile should be started such that it can
262      *      receive incoming calls.
263      */
startSipServiceForProfile(SipProfile profile, SipManager sipManager, Context context, boolean isReceivingCalls)264     private void startSipServiceForProfile(SipProfile profile, SipManager sipManager,
265             Context context, boolean isReceivingCalls) {
266         removeSipProfile(profile.getUriString());
267 
268         AccountEntry entry = new AccountEntry(profile);
269         if (entry.startSipService(sipManager, context, isReceivingCalls)) {
270             mAccounts.add(entry);
271         }
272     }
273 
274     /**
275      * Retrieves the {@link AccountEntry} from the registry with the specified name.
276      *
277      * @param sipProfileName Name of the SIP profile to retrieve.
278      * @return The {@link AccountEntry}, or {@code null} is it was not found.
279      */
getAccountEntry(String sipProfileName)280     private AccountEntry getAccountEntry(String sipProfileName) {
281         for (AccountEntry entry : mAccounts) {
282             if (Objects.equals(sipProfileName, entry.getProfile().getProfileName())) {
283                 return entry;
284             }
285         }
286         return null;
287     }
288 
log(String message)289     private void log(String message) {
290         Log.d(SipUtil.LOG_TAG, PREFIX + message);
291     }
292 }
293