Home | History | Annotate | Download | only in sip
      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.text.TextUtils;
     27 import android.util.Log;
     28 
     29 import java.util.List;
     30 import java.util.Objects;
     31 import java.util.concurrent.CopyOnWriteArrayList;
     32 
     33 /**
     34  * Manages the {@link PhoneAccount} entries for SIP calling.
     35  */
     36 public final class SipAccountRegistry {
     37     private final class AccountEntry {
     38         private final SipProfile mProfile;
     39 
     40         AccountEntry(SipProfile profile) {
     41             mProfile = profile;
     42         }
     43 
     44         SipProfile getProfile() {
     45             return mProfile;
     46         }
     47 
     48         /**
     49          * Starts the SIP service associated with the SIP profile.
     50          *
     51          * @param sipManager The SIP manager.
     52          * @param context The context.
     53          * @param isReceivingCalls {@code True} if the sip service is being started to make and
     54          *          receive calls.  {@code False} if the sip service is being started only for
     55          *          outgoing calls.
     56          * @return {@code True} if the service started successfully.
     57          */
     58         boolean startSipService(SipManager sipManager, Context context, boolean isReceivingCalls) {
     59             if (VERBOSE) log("startSipService, profile: " + mProfile);
     60             try {
     61                 // Stop the Sip service for the profile if it is already running.  This is important
     62                 // if we are changing the state of the "receive calls" option.
     63                 sipManager.close(mProfile.getUriString());
     64 
     65                 // Start the sip service for the profile.
     66                 if (isReceivingCalls) {
     67                     sipManager.open(
     68                             mProfile,
     69                             SipUtil.createIncomingCallPendingIntent(context,
     70                                     mProfile.getUriString()),
     71                             null);
     72                 } else {
     73                     sipManager.open(mProfile);
     74                 }
     75                 return true;
     76             } catch (SipException e) {
     77                 log("startSipService, profile: " + mProfile.getProfileName() +
     78                         ", exception: " + e);
     79             }
     80             return false;
     81         }
     82 
     83         /**
     84          * Stops the SIP service associated with the SIP profile.  The {@code SipAccountRegistry} is
     85          * informed when the service has been stopped via an intent which triggers
     86          * {@link SipAccountRegistry#removeSipProfile(String)}.
     87          *
     88          * @param sipManager The SIP manager.
     89          * @return {@code True} if stop was successful.
     90          */
     91         boolean stopSipService(SipManager sipManager) {
     92             try {
     93                 sipManager.close(mProfile.getUriString());
     94                 return true;
     95             } catch (Exception e) {
     96                 log("stopSipService, stop failed for profile: " + mProfile.getUriString() +
     97                         ", exception: " + e);
     98             }
     99             return false;
    100         }
    101     }
    102 
    103     private static final String PREFIX = "[SipAccountRegistry] ";
    104     private static final boolean VERBOSE = false; /* STOP SHIP if true */
    105     private static final SipAccountRegistry INSTANCE = new SipAccountRegistry();
    106 
    107     private final List<AccountEntry> mAccounts = new CopyOnWriteArrayList<>();
    108 
    109     private SipAccountRegistry() {}
    110 
    111     public static SipAccountRegistry getInstance() {
    112         return INSTANCE;
    113     }
    114 
    115     void setup(Context context) {
    116         startSipProfilesAsync((Context) context, (String) null);
    117     }
    118 
    119     /**
    120      * Starts the SIP service for the specified SIP profile and ensures it has a valid registered
    121      * {@link PhoneAccount}.
    122      *
    123      * @param context The context.
    124      * @param sipUri The Uri of the {@link SipProfile} to start, or {@code null} for all.
    125      */
    126     void startSipService(Context context, String sipUri) {
    127         startSipProfilesAsync((Context) context, (String) sipUri);
    128     }
    129 
    130     /**
    131      * Removes a {@link SipProfile} from the account registry.  Does not stop/close the associated
    132      * SIP service (this method is invoked via an intent from the SipService once a profile has
    133      * been stopped/closed).
    134      *
    135      * @param sipUri The Uri of the {@link SipProfile} to remove from the registry.
    136      */
    137     void removeSipProfile(String sipUri) {
    138         AccountEntry accountEntry = getAccountEntry(sipUri);
    139 
    140         if (accountEntry != null) {
    141             mAccounts.remove(accountEntry);
    142         }
    143     }
    144 
    145     /**
    146      * Stops a SIP profile and un-registers its associated {@link android.telecom.PhoneAccount}.
    147      * Called after a SIP profile is deleted.  The {@link AccountEntry} will be removed when the
    148      * service has been stopped.  The {@code SipService} fires the {@code ACTION_SIP_REMOVE_PHONE}
    149      * intent, which triggers {@link SipAccountRegistry#removeSipProfile(String)} to perform the
    150      * removal.
    151      *
    152      * @param context The context.
    153      * @param sipUri The {@code Uri} of the sip profile.
    154      */
    155     void stopSipService(Context context, String sipUri) {
    156         // Stop the sip service for the profile.
    157         AccountEntry accountEntry = getAccountEntry(sipUri);
    158         if (accountEntry != null ) {
    159             SipManager sipManager = SipManager.newInstance(context);
    160             accountEntry.stopSipService(sipManager);
    161         }
    162 
    163         // Un-register its PhoneAccount.
    164         PhoneAccountHandle handle = SipUtil.createAccountHandle(context, sipUri);
    165         TelecomManager.from(context).unregisterPhoneAccount(handle);
    166     }
    167 
    168     /**
    169      * Causes the SIP service to be restarted for all {@link SipProfile}s.  For example, if the user
    170      * toggles the "receive calls" option for SIP, this method handles restarting the SIP services
    171      * in the new mode.
    172      *
    173      * @param context The context.
    174      */
    175     public void restartSipService(Context context) {
    176         startSipProfiles(context, null);
    177     }
    178 
    179     /**
    180      * Performs an asynchronous call to
    181      * {@link SipAccountRegistry#startSipProfiles(android.content.Context, String)}, starting the
    182      * specified SIP profile and registering its {@link android.telecom.PhoneAccount}.
    183      *
    184      * @param context The context.
    185      * @param sipUri A specific SIP uri to start.
    186      */
    187     private void startSipProfilesAsync(final Context context, final String sipUri) {
    188         if (VERBOSE) log("startSipProfiles, start auto registration");
    189 
    190         new Thread(new Runnable() {
    191             @Override
    192             public void run() {
    193                 startSipProfiles(context, sipUri);
    194             }}
    195         ).start();
    196     }
    197 
    198     /**
    199      * Loops through all SIP accounts from the SIP database, starts each service and registers
    200      * each with the telecom framework. If a specific sipUri is specified, this will only register
    201      * the associated SIP account.
    202      *
    203      * @param context The context.
    204      * @param sipUri A specific SIP uri to start, or {@code null} to start all.
    205      */
    206     private void startSipProfiles(Context context, String sipUri) {
    207         final SipSharedPreferences sipSharedPreferences = new SipSharedPreferences(context);
    208         boolean isReceivingCalls = sipSharedPreferences.isReceivingCallsEnabled();
    209         String primaryProfile = sipSharedPreferences.getPrimaryAccount();
    210         TelecomManager telecomManager = TelecomManager.from(context);
    211         SipManager sipManager = SipManager.newInstance(context);
    212         SipProfileDb profileDb = new SipProfileDb(context);
    213         List<SipProfile> sipProfileList = profileDb.retrieveSipProfileList();
    214 
    215         for (SipProfile profile : sipProfileList) {
    216             // Register a PhoneAccount for the profile and optionally enable the primary
    217             // profile.
    218             if (sipUri == null || Objects.equals(sipUri, profile.getUriString())) {
    219                 PhoneAccount phoneAccount = SipUtil.createPhoneAccount(context, profile);
    220                 telecomManager.registerPhoneAccount(phoneAccount);
    221             }
    222 
    223             if (sipUri == null || Objects.equals(sipUri, profile.getUriString())) {
    224                 startSipServiceForProfile(profile, sipManager, context, isReceivingCalls);
    225             }
    226         }
    227 
    228         if (primaryProfile != null) {
    229             // Remove the primary account shared preference, ensuring the migration does not
    230             // occur again in the future.
    231             sipSharedPreferences.cleanupPrimaryAccountSetting();
    232         }
    233     }
    234 
    235     /**
    236      * Starts the SIP service for a sip profile and saves a new {@code AccountEntry} in the
    237      * registry.
    238      *
    239      * @param profile The {@link SipProfile} to start.
    240      * @param sipManager The SIP manager.
    241      * @param context The context.
    242      * @param isReceivingCalls {@code True} if the profile should be started such that it can
    243      *      receive incoming calls.
    244      */
    245     private void startSipServiceForProfile(SipProfile profile, SipManager sipManager,
    246             Context context, boolean isReceivingCalls) {
    247         removeSipProfile(profile.getUriString());
    248 
    249         AccountEntry entry = new AccountEntry(profile);
    250         if (entry.startSipService(sipManager, context, isReceivingCalls)) {
    251             mAccounts.add(entry);
    252         }
    253     }
    254 
    255     /**
    256      * Retrieves the {@link AccountEntry} from the registry with the specified Uri.
    257      *
    258      * @param sipUri The Uri of the profile to retrieve.
    259      * @return The {@link AccountEntry}, or {@code null} is it was not found.
    260      */
    261     private AccountEntry getAccountEntry(String sipUri) {
    262         for (AccountEntry entry : mAccounts) {
    263             if (Objects.equals(sipUri, entry.getProfile().getUriString())) {
    264                 return entry;
    265             }
    266         }
    267         return null;
    268     }
    269 
    270     private void log(String message) {
    271         Log.d(SipUtil.LOG_TAG, PREFIX + message);
    272     }
    273 }
    274