• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.dialer.telecom;
18 
19 import android.Manifest;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.content.pm.PackageManager;
23 import android.net.Uri;
24 import android.provider.CallLog.Calls;
25 import android.support.annotation.Nullable;
26 import android.support.annotation.VisibleForTesting;
27 import android.support.v4.content.ContextCompat;
28 import android.telecom.PhoneAccount;
29 import android.telecom.PhoneAccountHandle;
30 import android.telecom.TelecomManager;
31 import android.text.TextUtils;
32 import android.util.Pair;
33 import com.android.dialer.common.LogUtil;
34 import java.util.ArrayList;
35 import java.util.List;
36 import java.util.Map;
37 import java.util.concurrent.ConcurrentHashMap;
38 
39 /**
40  * Performs permission checks before calling into TelecomManager. Each method is self-explanatory -
41  * perform the required check and return the fallback default if the permission is missing,
42  * otherwise return the value from TelecomManager.
43  */
44 public abstract class TelecomUtil {
45 
46   private static final String TAG = "TelecomUtil";
47   private static boolean sWarningLogged = false;
48 
49   private static TelecomUtilImpl instance = new TelecomUtilImpl();
50 
51   /**
52    * Cache for {@link #isVoicemailNumber(Context, PhoneAccountHandle, String)}. Both
53    * PhoneAccountHandle and number are cached because multiple numbers might be mapped to true, and
54    * comparing with {@link #getVoicemailNumber(Context, PhoneAccountHandle)} will not suffice.
55    */
56   private static final Map<Pair<PhoneAccountHandle, String>, Boolean> isVoicemailNumberCache =
57       new ConcurrentHashMap<>();
58 
59   @VisibleForTesting(otherwise = VisibleForTesting.NONE)
setInstanceForTesting(TelecomUtilImpl instanceForTesting)60   public static void setInstanceForTesting(TelecomUtilImpl instanceForTesting) {
61     instance = instanceForTesting;
62   }
63 
showInCallScreen(Context context, boolean showDialpad)64   public static void showInCallScreen(Context context, boolean showDialpad) {
65     if (hasReadPhoneStatePermission(context)) {
66       try {
67         getTelecomManager(context).showInCallScreen(showDialpad);
68       } catch (SecurityException e) {
69         // Just in case
70         LogUtil.w(TAG, "TelecomManager.showInCallScreen called without permission.");
71       }
72     }
73   }
74 
silenceRinger(Context context)75   public static void silenceRinger(Context context) {
76     if (hasModifyPhoneStatePermission(context)) {
77       try {
78         getTelecomManager(context).silenceRinger();
79       } catch (SecurityException e) {
80         // Just in case
81         LogUtil.w(TAG, "TelecomManager.silenceRinger called without permission.");
82       }
83     }
84   }
85 
cancelMissedCallsNotification(Context context)86   public static void cancelMissedCallsNotification(Context context) {
87     if (hasModifyPhoneStatePermission(context)) {
88       try {
89         getTelecomManager(context).cancelMissedCallsNotification();
90       } catch (SecurityException e) {
91         LogUtil.w(TAG, "TelecomManager.cancelMissedCalls called without permission.");
92       }
93     }
94   }
95 
getAdnUriForPhoneAccount(Context context, PhoneAccountHandle handle)96   public static Uri getAdnUriForPhoneAccount(Context context, PhoneAccountHandle handle) {
97     if (hasModifyPhoneStatePermission(context)) {
98       try {
99         return getTelecomManager(context).getAdnUriForPhoneAccount(handle);
100       } catch (SecurityException e) {
101         LogUtil.w(TAG, "TelecomManager.getAdnUriForPhoneAccount called without permission.");
102       }
103     }
104     return null;
105   }
106 
handleMmi( Context context, String dialString, @Nullable PhoneAccountHandle handle)107   public static boolean handleMmi(
108       Context context, String dialString, @Nullable PhoneAccountHandle handle) {
109     if (hasModifyPhoneStatePermission(context)) {
110       try {
111         if (handle == null) {
112           return getTelecomManager(context).handleMmi(dialString);
113         } else {
114           return getTelecomManager(context).handleMmi(dialString, handle);
115         }
116       } catch (SecurityException e) {
117         LogUtil.w(TAG, "TelecomManager.handleMmi called without permission.");
118       }
119     }
120     return false;
121   }
122 
123   @Nullable
getDefaultOutgoingPhoneAccount( Context context, String uriScheme)124   public static PhoneAccountHandle getDefaultOutgoingPhoneAccount(
125       Context context, String uriScheme) {
126     if (hasReadPhoneStatePermission(context)) {
127       return getTelecomManager(context).getDefaultOutgoingPhoneAccount(uriScheme);
128     }
129     return null;
130   }
131 
getPhoneAccount(Context context, PhoneAccountHandle handle)132   public static PhoneAccount getPhoneAccount(Context context, PhoneAccountHandle handle) {
133     return getTelecomManager(context).getPhoneAccount(handle);
134   }
135 
getCallCapablePhoneAccounts(Context context)136   public static List<PhoneAccountHandle> getCallCapablePhoneAccounts(Context context) {
137     if (hasReadPhoneStatePermission(context)) {
138       return getTelecomManager(context).getCallCapablePhoneAccounts();
139     }
140     return new ArrayList<>();
141   }
142 
isInCall(Context context)143   public static boolean isInCall(Context context) {
144     return instance.isInCall(context);
145   }
146 
147   /**
148    * {@link TelecomManager#isVoiceMailNumber(PhoneAccountHandle, String)} takes about 10ms, which is
149    * way too slow for regular purposes. This method will cache the result for the life time of the
150    * process. The cache will not be invalidated, for example, if the voicemail number is changed by
151    * setting up apps like Google Voicemail, the result will be wrong. These events are rare.
152    */
isVoicemailNumber( Context context, PhoneAccountHandle accountHandle, String number)153   public static boolean isVoicemailNumber(
154       Context context, PhoneAccountHandle accountHandle, String number) {
155     if (TextUtils.isEmpty(number)) {
156       return false;
157     }
158     Pair<PhoneAccountHandle, String> cacheKey = new Pair<>(accountHandle, number);
159     if (isVoicemailNumberCache.containsKey(cacheKey)) {
160       return isVoicemailNumberCache.get(cacheKey);
161     }
162     boolean result = false;
163     if (hasReadPhoneStatePermission(context)) {
164       result = getTelecomManager(context).isVoiceMailNumber(accountHandle, number);
165     }
166     isVoicemailNumberCache.put(cacheKey, result);
167     return result;
168   }
169 
170   @Nullable
getVoicemailNumber(Context context, PhoneAccountHandle accountHandle)171   public static String getVoicemailNumber(Context context, PhoneAccountHandle accountHandle) {
172     if (hasReadPhoneStatePermission(context)) {
173       return getTelecomManager(context).getVoiceMailNumber(accountHandle);
174     }
175     return null;
176   }
177 
178   /**
179    * Tries to place a call using the {@link TelecomManager}.
180    *
181    * @param context context.
182    * @param intent the call intent.
183    * @return {@code true} if we successfully attempted to place the call, {@code false} if it failed
184    *     due to a permission check.
185    */
placeCall(Context context, Intent intent)186   public static boolean placeCall(Context context, Intent intent) {
187     if (hasCallPhonePermission(context)) {
188       getTelecomManager(context).placeCall(intent.getData(), intent.getExtras());
189       return true;
190     }
191     return false;
192   }
193 
getCallLogUri(Context context)194   public static Uri getCallLogUri(Context context) {
195     return hasReadWriteVoicemailPermissions(context)
196         ? Calls.CONTENT_URI_WITH_VOICEMAIL
197         : Calls.CONTENT_URI;
198   }
199 
hasReadWriteVoicemailPermissions(Context context)200   public static boolean hasReadWriteVoicemailPermissions(Context context) {
201     return isDefaultDialer(context)
202         || (hasPermission(context, Manifest.permission.READ_VOICEMAIL)
203             && hasPermission(context, Manifest.permission.WRITE_VOICEMAIL));
204   }
205 
hasModifyPhoneStatePermission(Context context)206   public static boolean hasModifyPhoneStatePermission(Context context) {
207     return isDefaultDialer(context)
208         || hasPermission(context, Manifest.permission.MODIFY_PHONE_STATE);
209   }
210 
hasReadPhoneStatePermission(Context context)211   public static boolean hasReadPhoneStatePermission(Context context) {
212     return isDefaultDialer(context) || hasPermission(context, Manifest.permission.READ_PHONE_STATE);
213   }
214 
hasCallPhonePermission(Context context)215   public static boolean hasCallPhonePermission(Context context) {
216     return isDefaultDialer(context) || hasPermission(context, Manifest.permission.CALL_PHONE);
217   }
218 
hasPermission(Context context, String permission)219   private static boolean hasPermission(Context context, String permission) {
220     return instance.hasPermission(context, permission);
221   }
222 
getTelecomManager(Context context)223   private static TelecomManager getTelecomManager(Context context) {
224     return (TelecomManager) context.getSystemService(Context.TELECOM_SERVICE);
225   }
226 
isDefaultDialer(Context context)227   public static boolean isDefaultDialer(Context context) {
228     return instance.isDefaultDialer(context);
229   }
230 
231   /** Contains an implementation for {@link TelecomUtil} methods */
232   @VisibleForTesting()
233   public static class TelecomUtilImpl {
234 
isInCall(Context context)235     public boolean isInCall(Context context) {
236       if (hasReadPhoneStatePermission(context)) {
237         return getTelecomManager(context).isInCall();
238       }
239       return false;
240     }
241 
hasPermission(Context context, String permission)242     public boolean hasPermission(Context context, String permission) {
243       return ContextCompat.checkSelfPermission(context, permission)
244           == PackageManager.PERMISSION_GRANTED;
245     }
246 
isDefaultDialer(Context context)247     public boolean isDefaultDialer(Context context) {
248       final boolean result =
249           TextUtils.equals(
250               context.getPackageName(), getTelecomManager(context).getDefaultDialerPackage());
251       if (result) {
252         sWarningLogged = false;
253       } else {
254         if (!sWarningLogged) {
255           // Log only once to prevent spam.
256           LogUtil.w(TAG, "Dialer is not currently set to be default dialer");
257           sWarningLogged = true;
258         }
259       }
260       return result;
261     }
262   }
263 }
264