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     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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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 
    289     private void log(String message) {
    290         Log.d(SipUtil.LOG_TAG, PREFIX + message);
    291     }
    292 }
    293