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