1 /*
2  * Copyright (C) 2010 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.app.PendingIntent;
20 import android.content.ComponentName;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.graphics.drawable.Icon;
24 import android.net.Uri;
25 import android.net.sip.SipManager;
26 import android.net.sip.SipProfile;
27 import android.os.Bundle;
28 import android.provider.Settings;
29 import android.telecom.PhoneAccount;
30 import android.telecom.PhoneAccountHandle;
31 import android.telecom.TelecomManager;
32 import android.telephony.TelephonyManager;
33 import android.text.TextUtils;
34 import android.util.Log;
35 
36 import com.android.phone.PhoneGlobals;
37 
38 import java.io.IOException;
39 import java.util.ArrayList;
40 import java.util.List;
41 
42 public class SipUtil {
43     static final String LOG_TAG = "SIP";
44     static final String EXTRA_INCOMING_CALL_INTENT =
45             "com.android.services.telephony.sip.incoming_call_intent";
46     static final String EXTRA_PHONE_ACCOUNT =
47             "com.android.services.telephony.sip.phone_account";
48     static final String PHONE_PACKAGE = "com.android.phone";
49 
SipUtil()50     private SipUtil() {
51     }
52 
isVoipSupported(Context context)53     public static boolean isVoipSupported(Context context) {
54         return SipManager.isVoipSupported(context)
55                 && context.getResources().getBoolean(
56                         com.android.internal.R.bool.config_built_in_sip_phone)
57                         && ((TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE))
58                         .isVoiceCapable();
59     }
60 
createIncomingCallPendingIntent( Context context, String sipProfileName)61     static PendingIntent createIncomingCallPendingIntent(
62             Context context, String sipProfileName) {
63         Intent intent = new Intent(context, SipIncomingCallReceiver.class);
64         intent.setAction(SipManager.ACTION_SIP_INCOMING_CALL);
65         intent.putExtra(EXTRA_PHONE_ACCOUNT, SipUtil.createAccountHandle(context, sipProfileName));
66         return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
67     }
68 
isPhoneIdle(Context context)69     public static boolean isPhoneIdle(Context context) {
70         TelecomManager manager = (TelecomManager) context.getSystemService(
71                 Context.TELECOM_SERVICE);
72         if (manager != null) {
73             return !manager.isInCall();
74         }
75         return true;
76     }
77 
78     /**
79      * Creates a {@link PhoneAccountHandle} from the specified SIP profile name.
80      */
createAccountHandle(Context context, String sipProfileName)81     static PhoneAccountHandle createAccountHandle(Context context, String sipProfileName) {
82         return new PhoneAccountHandle(
83                 new ComponentName(context, SipConnectionService.class), sipProfileName);
84     }
85 
86     /**
87      * Determines the SIP profile name for a specified {@link PhoneAccountHandle}.
88      *
89      * @param phoneAccountHandle The {@link PhoneAccountHandle}.
90      * @return The SIP profile name.
91      */
getSipProfileNameFromPhoneAccount(PhoneAccountHandle phoneAccountHandle)92     static String getSipProfileNameFromPhoneAccount(PhoneAccountHandle phoneAccountHandle) {
93         if (phoneAccountHandle == null) {
94             return null;
95         }
96 
97         String sipProfileName = phoneAccountHandle.getId();
98         if (TextUtils.isEmpty(sipProfileName)) {
99             return null;
100         }
101         return sipProfileName;
102     }
103 
104     /**
105      * Creates a PhoneAccount for a SipProfile.
106      *
107      * @param context The context
108      * @param profile The SipProfile.
109      * @return The PhoneAccount.
110      */
createPhoneAccount(Context context, SipProfile profile)111     static PhoneAccount createPhoneAccount(Context context, SipProfile profile) {
112         // Build a URI to represent the SIP account.  Does not use SipProfile#getUriString() since
113         // that prototype can include transport information which we do not want to see in the
114         // phone account.
115         String sipAddress = profile.getUserName() + "@" + profile.getSipDomain();
116         Uri sipUri = Uri.parse(profile.getUriString());
117 
118         PhoneAccountHandle accountHandle =
119                 SipUtil.createAccountHandle(context, profile.getProfileName());
120 
121         final ArrayList<String> supportedUriSchemes = new ArrayList<String>();
122         supportedUriSchemes.add(PhoneAccount.SCHEME_SIP);
123         if (useSipForPstnCalls(context)) {
124             supportedUriSchemes.add(PhoneAccount.SCHEME_TEL);
125         }
126 
127         Bundle phoneAccountExtras = new Bundle();
128         phoneAccountExtras.putBoolean(PhoneAccount.EXTRA_ALWAYS_USE_VOIP_AUDIO_MODE,
129                 true);
130 
131         PhoneAccount.Builder builder = PhoneAccount.builder(accountHandle, profile.getDisplayName())
132                 .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER
133                         | PhoneAccount.CAPABILITY_MULTI_USER)
134                 .setAddress(sipUri)
135                 .setShortDescription(sipAddress)
136                 .setIcon(Icon.createWithResource(
137                         context, R.drawable.ic_dialer_sip_black_24dp))
138                 .setExtras(phoneAccountExtras)
139                 .setSupportedUriSchemes(supportedUriSchemes);
140 
141         return builder.build();
142     }
143 
144     /**
145      * Upon migration from M->N, the SIP Profile database will be moved into DE storage. This will
146      * not be a problem for non-FBE enabled devices, since DE and CE storage is available at the
147      * same time. This will be a problem for backup/restore, however if the SIP Profile DB is
148      * restored onto a new FBE enabled device.
149      *
150      * Checks if the Sip Db is in DE storage. If it is, the Db is moved to CE storage and
151      * deleted.
152      */
possiblyMigrateSipDb(Context context)153     private static void possiblyMigrateSipDb(Context context) {
154         SipProfileDb dbDeStorage = new SipProfileDb(context);
155         dbDeStorage.accessDEStorageForMigration();
156         List<SipProfile> profilesDeStorage = dbDeStorage.retrieveSipProfileList();
157         if(profilesDeStorage.size() != 0) {
158             Log.i(LOG_TAG, "Migrating SIP Profiles over!");
159             SipProfileDb dbCeStorage = new SipProfileDb(context);
160             //Perform Profile Migration
161             for (SipProfile profileToMove : profilesDeStorage) {
162                 if (dbCeStorage.retrieveSipProfileFromName(
163                         profileToMove.getProfileName()) == null) {
164                     try {
165                         dbCeStorage.saveProfile(profileToMove);
166                     } catch (IOException e) {
167                         Log.w(LOG_TAG, "Error Migrating file to CE: " +
168                                 profileToMove.getProfileName(), e);
169                     }
170                 }
171                 Log.i(LOG_TAG, "(Migration) Deleting SIP profile: " +
172                         profileToMove.getProfileName());
173                 try {
174                     dbDeStorage.deleteProfile(profileToMove);
175                 } catch (IOException e) {
176                     Log.w(LOG_TAG, "Error Deleting file: " +
177                             profileToMove.getProfileName(), e);
178                 }
179             }
180         }
181         // Delete supporting structures if they exist
182         dbDeStorage.cleanupUponMigration();
183     }
184 
185     /**
186      * Migrates the DB files over from CE->DE storage and starts the SipService.
187      */
startSipService()188     public static void startSipService() {
189         Context phoneGlobalsContext = PhoneGlobals.getInstance();
190         // Migrate SIP database from DE->CE storage if the device has just upgraded.
191         possiblyMigrateSipDb(phoneGlobalsContext);
192         // Wait until boot complete to start SIP so that it has access to CE storage.
193         Intent startSipIntent = new Intent();
194         startSipIntent.setAction(SipManager.ACTION_START_SIP);
195         startSipIntent.setPackage(PHONE_PACKAGE);
196         phoneGlobalsContext.startService(startSipIntent);
197     }
198 
199     /**
200      * Determines if the user has chosen to use SIP for PSTN calls as well as SIP calls.
201      * @param context The context.
202      * @return {@code True} if SIP should be used for PSTN calls.
203      */
useSipForPstnCalls(Context context)204     private static boolean useSipForPstnCalls(Context context) {
205         final SipPreferences sipPreferences = new SipPreferences(context);
206         return sipPreferences.getSipCallOption().equals(Settings.System.SIP_ALWAYS);
207     }
208 
209     /**
210      * Updates SIP accounts to indicate whether they are enabled to receive incoming SIP calls.
211      *
212      * @param isEnabled {@code True} if receiving incoming SIP calls.
213      */
useSipToReceiveIncomingCalls(Context context, boolean isEnabled)214     public static void useSipToReceiveIncomingCalls(Context context, boolean isEnabled) {
215         SipProfileDb profileDb = new SipProfileDb(context);
216 
217         // Mark all profiles as auto-register if we are now receiving calls.
218         List<SipProfile> sipProfileList = profileDb.retrieveSipProfileList();
219         for (SipProfile p : sipProfileList) {
220             updateAutoRegistrationFlag(p, profileDb, isEnabled);
221         }
222     }
223 
updateAutoRegistrationFlag( SipProfile p, SipProfileDb db, boolean isEnabled)224     private static void updateAutoRegistrationFlag(
225             SipProfile p, SipProfileDb db, boolean isEnabled) {
226         SipProfile newProfile = new SipProfile.Builder(p).setAutoRegistration(isEnabled).build();
227 
228         try {
229             // Note: The profile is updated, but the associated PhoneAccount is left alone since
230             // the only thing that changed is the auto-registration flag, which is not part of the
231             // PhoneAccount.
232             db.deleteProfile(p);
233             db.saveProfile(newProfile);
234         } catch (Exception e) {
235             Log.d(LOG_TAG, "updateAutoRegistrationFlag, exception: " + e);
236         }
237     }
238 }
239