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