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.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 
     39         AccountEntry(SipProfile profile) {
     40             mProfile = profile;
     41         }
     42 
     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          */
     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          */
     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 
    108     private SipAccountRegistry() {}
    109 
    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      */
    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      */
    128     void verifyAndPurgeInvalidPhoneAccounts(Context context) {
    129         TelecomManager telecomManager = TelecomManager.from(context);
    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      */
    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      */
    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      */
    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.from(context).unregisterPhoneAccount(handle);
    192     }
    193 
    194     /**
    195      * Causes the SIP service to be restarted for all {@link SipProfile}s.  For example, if the user
    196      * toggles the "receive calls" option for SIP, this method handles restarting the SIP services
    197      * in the new mode.
    198      *
    199      * @param context The context.
    200      */
    201     public void restartSipService(Context context) {
    202         startSipProfiles(context, null, false);
    203     }
    204 
    205     /**
    206      * Performs an asynchronous call to
    207      * {@link SipAccountRegistry#startSipProfiles(android.content.Context, String)}, starting the
    208      * specified SIP profile and registering its {@link android.telecom.PhoneAccount}.
    209      *
    210      * @param context The context.
    211      * @param sipProfileName Name of the SIP profile.
    212      * @param enableProfile Sip account should be enabled.
    213      */
    214     private void startSipProfilesAsync(
    215             final Context context, final String sipProfileName, final boolean enableProfile) {
    216         if (VERBOSE) log("startSipProfiles, start auto registration");
    217 
    218         new Thread(new Runnable() {
    219             @Override
    220             public void run() {
    221                 startSipProfiles(context, sipProfileName, enableProfile);
    222             }}
    223         ).start();
    224     }
    225 
    226     /**
    227      * Loops through all SIP accounts from the SIP database, starts each service and registers
    228      * each with the telecom framework. If a specific sipProfileName is specified, this will only
    229      * register the associated SIP account.
    230      *
    231      * @param context The context.
    232      * @param sipProfileName A specific SIP profile Name to start, or {@code null} to start all.
    233      * @param enableProfile Sip account should be enabled.
    234      */
    235     private void startSipProfiles(Context context, String sipProfileName, boolean enableProfile) {
    236         final SipPreferences sipPreferences = new SipPreferences(context);
    237         boolean isReceivingCalls = sipPreferences.isReceivingCallsEnabled();
    238         TelecomManager telecomManager = TelecomManager.from(context);
    239         SipManager sipManager = SipManager.newInstance(context);
    240         SipProfileDb profileDb = new SipProfileDb(context);
    241         List<SipProfile> sipProfileList = profileDb.retrieveSipProfileList();
    242 
    243         for (SipProfile profile : sipProfileList) {
    244             // Register a PhoneAccount for the profile and optionally enable the primary
    245             // profile.
    246             if (sipProfileName == null || sipProfileName.equals(profile.getProfileName())) {
    247                 PhoneAccount phoneAccount = SipUtil.createPhoneAccount(context, profile);
    248                 telecomManager.registerPhoneAccount(phoneAccount);
    249                 if (enableProfile) {
    250                     telecomManager.enablePhoneAccount(phoneAccount.getAccountHandle(), true);
    251                 }
    252                 startSipServiceForProfile(profile, sipManager, context, isReceivingCalls);
    253             }
    254         }
    255     }
    256 
    257     /**
    258      * Starts the SIP service for a sip profile and saves a new {@code AccountEntry} in the
    259      * registry.
    260      *
    261      * @param profile The {@link SipProfile} to start.
    262      * @param sipManager The SIP manager.
    263      * @param context The context.
    264      * @param isReceivingCalls {@code True} if the profile should be started such that it can
    265      *      receive incoming calls.
    266      */
    267     private void startSipServiceForProfile(SipProfile profile, SipManager sipManager,
    268             Context context, boolean isReceivingCalls) {
    269         removeSipProfile(profile.getUriString());
    270 
    271         AccountEntry entry = new AccountEntry(profile);
    272         if (entry.startSipService(sipManager, context, isReceivingCalls)) {
    273             mAccounts.add(entry);
    274         }
    275     }
    276 
    277     /**
    278      * Retrieves the {@link AccountEntry} from the registry with the specified name.
    279      *
    280      * @param sipProfileName Name of the SIP profile to retrieve.
    281      * @return The {@link AccountEntry}, or {@code null} is it was not found.
    282      */
    283     private AccountEntry getAccountEntry(String sipProfileName) {
    284         for (AccountEntry entry : mAccounts) {
    285             if (Objects.equals(sipProfileName, entry.getProfile().getProfileName())) {
    286                 return entry;
    287             }
    288         }
    289         return null;
    290     }
    291 
    292     private void log(String message) {
    293         Log.d(SipUtil.LOG_TAG, PREFIX + message);
    294     }
    295 }
    296