1 /*
2  * Copyright (C) 2015 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.contacts;
18 
19 import android.content.Context;
20 import android.content.Intent;
21 import android.net.Uri;
22 import android.os.Bundle;
23 import android.os.PersistableBundle;
24 import android.telecom.PhoneAccount;
25 import android.telecom.PhoneAccountHandle;
26 import android.telecom.TelecomManager;
27 import android.telecom.VideoProfile;
28 import android.telephony.CarrierConfigManager;
29 import android.telephony.PhoneNumberUtils;
30 import android.text.TextUtils;
31 import android.util.Log;
32 
33 import com.android.contacts.compat.CompatUtils;
34 import com.android.contacts.compat.PhoneAccountSdkCompat;
35 import com.android.contacts.util.PermissionsUtil;
36 import com.android.contacts.util.PhoneNumberHelper;
37 import com.android.contactsbind.FeedbackHelper;
38 import com.android.contactsbind.experiments.Flags;
39 import com.android.phone.common.PhoneConstants;
40 
41 import java.util.List;
42 
43 /**
44  * Utilities related to calls that can be used by non system apps. These
45  * use {@link Intent#ACTION_CALL} instead of ACTION_CALL_PRIVILEGED.
46  *
47  * The privileged version of this util exists inside Dialer.
48  */
49 public class CallUtil {
50 
51     public static final String TAG = "CallUtil";
52 
53     /**
54      * Indicates that the video calling is not available.
55      */
56     public static final int VIDEO_CALLING_DISABLED = 0;
57 
58     /**
59      * Indicates that video calling is enabled, regardless of presence status.
60      */
61     public static final int VIDEO_CALLING_ENABLED = 1;
62 
63     /**
64      * Indicates that video calling is enabled, but the availability of video call affordances is
65      * determined by the presence status associated with contacts.
66      */
67     public static final int VIDEO_CALLING_PRESENCE = 2;
68 
69     /** {@link PhoneAccount#EXTRA_SUPPORTS_VIDEO_CALLING_FALLBACK} */
70     private static final String EXTRA_SUPPORTS_VIDEO_CALLING_FALLBACK =
71             "android.telecom.extra.SUPPORTS_VIDEO_CALLING_FALLBACK";
72 
73     /** {@link CarrierConfigManager#CONFIG_ALLOW_VIDEO_CALLING_FALLBACK} */
74     private static final String CONFIG_ALLOW_VIDEO_CALLING_FALLBACK =
75             "allow_video_calling_fallback_bool";
76 
77     /**
78      * Return an Intent for making a phone call. Scheme (e.g. tel, sip) will be determined
79      * automatically.
80      */
getCallWithSubjectIntent(String number, PhoneAccountHandle phoneAccountHandle, String callSubject)81     public static Intent getCallWithSubjectIntent(String number,
82             PhoneAccountHandle phoneAccountHandle, String callSubject) {
83 
84         final Intent intent = getCallIntent(getCallUri(number));
85         intent.putExtra(TelecomManager.EXTRA_CALL_SUBJECT, callSubject);
86         if (phoneAccountHandle != null) {
87             intent.putExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, phoneAccountHandle);
88         }
89         return intent;
90     }
91 
92     /**
93      * Return an Intent for making a phone call. Scheme (e.g. tel, sip) will be determined
94      * automatically.
95      */
getCallIntent(String number)96     public static Intent getCallIntent(String number) {
97         Uri uri = getCallUri(number);
98         return PhoneNumberUtils.isEmergencyNumber(number)
99                 ? getCallIntentForEmergencyNumber(uri) : getCallIntent(uri);
100     }
101 
102     /**
103      * Return an Intent to directly start Dialer when calling an emergency number. Scheme is always
104      * PhoneAccount.SCHEME_TEL.
105      */
getCallIntentForEmergencyNumber(Uri uri)106     private static Intent getCallIntentForEmergencyNumber(Uri uri) {
107         return new Intent(Intent.ACTION_DIAL, uri);
108     }
109 
110     /**
111      * Return an Intent for making a phone call. A given Uri will be used as is (without any
112      * quick check).
113      */
getCallIntent(Uri uri)114     public static Intent getCallIntent(Uri uri) {
115         return new Intent(Intent.ACTION_CALL, uri);
116     }
117 
118     /**
119      * A variant of {@link #getCallIntent} for starting a video call.
120      */
getVideoCallIntent(String number, String callOrigin)121     public static Intent getVideoCallIntent(String number, String callOrigin) {
122         final Intent intent = new Intent(Intent.ACTION_CALL, getCallUri(number));
123         intent.putExtra(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE,
124                 VideoProfile.STATE_BIDIRECTIONAL);
125         if (!TextUtils.isEmpty(callOrigin)) {
126             intent.putExtra(PhoneConstants.EXTRA_CALL_ORIGIN, callOrigin);
127         }
128         return intent;
129     }
130 
131     /**
132      * Return Uri with an appropriate scheme, accepting both SIP and usual phone call
133      * numbers.
134      */
getCallUri(String number)135     public static Uri getCallUri(String number) {
136         if (PhoneNumberHelper.isUriNumber(number)) {
137              return Uri.fromParts(PhoneAccount.SCHEME_SIP, number, null);
138         }
139         return Uri.fromParts(PhoneAccount.SCHEME_TEL, number, null);
140     }
141 
142     /**
143      * Determines if video calling is available, and if so whether presence checking is available
144      * as well.
145      *
146      * Returns a bitmask with {@link #VIDEO_CALLING_ENABLED} to indicate that video calling is
147      * available, and {@link #VIDEO_CALLING_PRESENCE} if presence indication is also available.
148      *
149      * @param context The context
150      * @return A bit-mask describing the current video capabilities.
151      */
getVideoCallingAvailability(Context context)152     public static int getVideoCallingAvailability(Context context) {
153         if (!PermissionsUtil.hasPermission(context, android.Manifest.permission.READ_PHONE_STATE)
154                 || !CompatUtils.isVideoCompatible()) {
155             return VIDEO_CALLING_DISABLED;
156         }
157         TelecomManager telecommMgr = (TelecomManager)
158                 context.getSystemService(Context.TELECOM_SERVICE);
159         if (telecommMgr == null) {
160             return VIDEO_CALLING_DISABLED;
161         }
162 
163         try {
164             List<PhoneAccountHandle> accountHandles = telecommMgr.getCallCapablePhoneAccounts();
165             for (PhoneAccountHandle accountHandle : accountHandles) {
166                 PhoneAccount account = telecommMgr.getPhoneAccount(accountHandle);
167                 if (account != null) {
168                     if (account.hasCapabilities(PhoneAccount.CAPABILITY_VIDEO_CALLING)) {
169                         // Builds prior to N do not have presence support.
170                         if (!CompatUtils.isVideoPresenceCompatible()) {
171                             return VIDEO_CALLING_ENABLED;
172                         }
173 
174                         int videoCapabilities = VIDEO_CALLING_ENABLED;
175                         if (account.hasCapabilities(PhoneAccountSdkCompat
176                                 .CAPABILITY_VIDEO_CALLING_RELIES_ON_PRESENCE)) {
177                             videoCapabilities |= VIDEO_CALLING_PRESENCE;
178                         }
179                         return videoCapabilities;
180                     }
181                 }
182             }
183             return VIDEO_CALLING_DISABLED;
184         } catch (SecurityException e) {
185             FeedbackHelper.sendFeedback(context, TAG,
186                     "Security exception when getting call capable phone accounts", e);
187             return VIDEO_CALLING_DISABLED;
188         }
189     }
190 
191     /**
192      * Determines if one of the call capable phone accounts defined supports calling with a subject
193      * specified.
194      *
195      * @param context The context.
196      * @return {@code true} if one of the call capable phone accounts supports calling with a
197      *      subject specified, {@code false} otherwise.
198      */
isCallWithSubjectSupported(Context context)199     public static boolean isCallWithSubjectSupported(Context context) {
200         if (!PermissionsUtil.hasPermission(context, android.Manifest.permission.READ_PHONE_STATE)
201                 || !CompatUtils.isCallSubjectCompatible()) {
202             return false;
203         }
204         TelecomManager telecommMgr = (TelecomManager)
205                 context.getSystemService(Context.TELECOM_SERVICE);
206         if (telecommMgr == null) {
207             return false;
208         }
209 
210         try {
211             List<PhoneAccountHandle> accountHandles = telecommMgr.getCallCapablePhoneAccounts();
212             for (PhoneAccountHandle accountHandle : accountHandles) {
213                 PhoneAccount account = telecommMgr.getPhoneAccount(accountHandle);
214                 if (account != null && account.hasCapabilities(PhoneAccount.CAPABILITY_CALL_SUBJECT)) {
215                     return true;
216                 }
217             }
218             return false;
219         } catch (SecurityException e) {
220             FeedbackHelper.sendFeedback(context, TAG,
221                     "Security exception when getting call capable phone accounts", e);
222             return false;
223         }
224 
225     }
226 
227     /**
228      * Determines if we're able to use Tachyon as a fallback for video calling.
229      *
230      * @param context The context.
231      * @return {@code true} if there exists a call capable phone account which supports using a
232      * fallback for video calling, the carrier configuration supports a fallback, and the
233      * experiment for using a fallback is enabled. Otherwise {@code false} is returned.
234      */
isTachyonEnabled(Context context)235     public static boolean isTachyonEnabled(Context context) {
236         // Need to be able to read phone state, and be on at least N to check PhoneAccount extras.
237         if (!PermissionsUtil.hasPermission(context, android.Manifest.permission.READ_PHONE_STATE)
238                 || !CompatUtils.isNCompatible()) {
239             return false;
240         }
241         TelecomManager telecommMgr = (TelecomManager)
242                 context.getSystemService(Context.TELECOM_SERVICE);
243         if (telecommMgr == null) {
244             return false;
245         }
246         try {
247             List<PhoneAccountHandle> accountHandles = telecommMgr.getCallCapablePhoneAccounts();
248             for (PhoneAccountHandle accountHandle : accountHandles) {
249                 PhoneAccount account = telecommMgr.getPhoneAccount(accountHandle);
250                 if (account == null) {
251                     continue;
252                 }
253                 // Check availability for the device config.
254                 final Bundle accountExtras = account.getExtras();
255                 final boolean deviceEnabled = accountExtras != null && accountExtras.getBoolean(
256                         EXTRA_SUPPORTS_VIDEO_CALLING_FALLBACK);
257                 if (Log.isLoggable(TAG, Log.DEBUG)) {
258                     Log.d(TAG, "Device video fallback config: " + deviceEnabled);
259                 }
260 
261                 // Check availability from carrier config.
262                 final PersistableBundle carrierConfig = context.getSystemService(
263                         CarrierConfigManager.class).getConfig();
264                 final boolean carrierEnabled =
265                         carrierConfig != null && carrierConfig.getBoolean(
266                                 CONFIG_ALLOW_VIDEO_CALLING_FALLBACK);
267                 if (Log.isLoggable(TAG, Log.DEBUG)) {
268                     Log.d(TAG, "Carrier video fallback config: " + carrierEnabled);
269                 }
270 
271                 // Check experiment value.
272                 final boolean experimentEnabled = Flags.getInstance().getBoolean(
273                         Experiments.QUICK_CONTACT_VIDEO_CALL);
274                 if (Log.isLoggable(TAG, Log.DEBUG)) {
275                     Log.d(TAG, "Experiment video fallback config: " + experimentEnabled);
276                 }
277 
278                 // All three checks above must be true to enable Tachyon calling.
279                 return deviceEnabled && carrierEnabled && experimentEnabled;
280             }
281             return false;
282         } catch (SecurityException e) {
283             FeedbackHelper.sendFeedback(context, TAG,
284                     "Security exception when getting call capable phone accounts", e);
285             return false;
286         }
287     }
288 }
289