Protect against more Telecom privileged operations

Make sure that all invocations of TelecomManager methods that
could possibly require a permission are protected by a permission
check.

Some of these are overcautious - for example, the UI should never
show the option to return to a call (READ_PHONE_STATE) if we didn't
detect an active call (READ_PHONE_STATE) in the first place, so
it is not strictly necessary to protect against the former. But not
crashing is the most preferable of all options.

Bug: 20266292

Change-Id: Id91dd16e34320a5e607f91dbce9a4296025eeaaf
diff --git a/src/com/android/dialer/DialtactsActivity.java b/src/com/android/dialer/DialtactsActivity.java
index f8fb17f..fe828a3 100644
--- a/src/com/android/dialer/DialtactsActivity.java
+++ b/src/com/android/dialer/DialtactsActivity.java
@@ -34,7 +34,6 @@
 import android.support.v4.view.ViewPager;
 import android.support.v7.app.ActionBar;
 import android.telecom.PhoneAccount;
-import android.telecom.TelecomManager;
 import android.text.Editable;
 import android.text.TextUtils;
 import android.text.TextWatcher;
@@ -370,7 +369,6 @@
             TouchPointManager.getInstance().setPoint((int) ev.getRawX(), (int) ev.getRawY());
         }
         return super.dispatchTouchEvent(ev);
-
     }
 
     @Override
@@ -929,7 +927,7 @@
         final boolean callKey = Intent.ACTION_CALL_BUTTON.equals(intent.getAction());
 
         if (callKey) {
-            getTelecomManager().showInCallScreen(false);
+            TelecomUtil.showInCallScreen(this, false);
             return true;
         }
 
@@ -1340,10 +1338,6 @@
     public void onPageScrollStateChanged(int state) {
     }
 
-    private TelecomManager getTelecomManager() {
-        return (TelecomManager) getSystemService(Context.TELECOM_SERVICE);
-    }
-
     @Override
     public boolean isActionBarShowing() {
         return mActionBarController.isActionBarShowing();
diff --git a/src/com/android/dialer/SpecialCharSequenceMgr.java b/src/com/android/dialer/SpecialCharSequenceMgr.java
index 31aa5c3..994829a 100644
--- a/src/com/android/dialer/SpecialCharSequenceMgr.java
+++ b/src/com/android/dialer/SpecialCharSequenceMgr.java
@@ -32,7 +32,6 @@
 import android.provider.Settings;
 import android.telecom.PhoneAccount;
 import android.telecom.PhoneAccountHandle;
-import android.telecom.TelecomManager;
 import android.telephony.PhoneNumberUtils;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
@@ -48,7 +47,6 @@
 import com.android.dialer.calllog.PhoneAccountUtils;
 import com.android.dialer.util.TelecomUtil;
 
-import java.util.Arrays;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -91,13 +89,13 @@
     private static QueryHandler sPreviousAdnQueryHandler;
 
     public static class HandleAdnEntryAccountSelectedCallback extends SelectPhoneAccountListener{
-        final private TelecomManager mTelecomManager;
+        final private Context mContext;
         final private QueryHandler mQueryHandler;
         final private SimContactQueryCookie mCookie;
 
-        public HandleAdnEntryAccountSelectedCallback(TelecomManager telecomManager,
+        public HandleAdnEntryAccountSelectedCallback(Context context,
                 QueryHandler queryHandler, SimContactQueryCookie cookie) {
-            mTelecomManager = telecomManager;
+            mContext = context;
             mQueryHandler = queryHandler;
             mCookie = cookie;
         }
@@ -105,7 +103,7 @@
         @Override
         public void onPhoneAccountSelected(PhoneAccountHandle selectedAccountHandle,
                 boolean setDefault) {
-            Uri uri = mTelecomManager.getAdnUriForPhoneAccount(selectedAccountHandle);
+            Uri uri = TelecomUtil.getAdnUriForPhoneAccount(mContext, selectedAccountHandle);
             handleAdnQuery(mQueryHandler, mCookie, uri);
             // TODO: Show error dialog if result isn't valid.
         }
@@ -193,6 +191,7 @@
      * and query cancel handler implemented in {@link SimContactQueryCookie}.
      */
     static boolean handleAdnEntry(Context context, String input, EditText textField) {
+        context = context.getApplicationContext();
         /* ADN entries are of the form "N(N)(N)#" */
         TelephonyManager telephonyManager =
                 (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
@@ -245,20 +244,19 @@
                 sc.progressDialog.getWindow().addFlags(
                         WindowManager.LayoutParams.FLAG_BLUR_BEHIND);
 
-                final TelecomManager telecomManager =
-                        (TelecomManager) context.getSystemService(Context.TELECOM_SERVICE);
                 List<PhoneAccountHandle> subscriptionAccountHandles =
                         PhoneAccountUtils.getSubscriptionPhoneAccounts(context);
 
                 boolean hasUserSelectedDefault = subscriptionAccountHandles.contains(
-                        telecomManager.getDefaultOutgoingPhoneAccount(PhoneAccount.SCHEME_TEL));
+                        TelecomUtil.getDefaultOutgoingPhoneAccount(context,
+                                PhoneAccount.SCHEME_TEL));
 
                 if (subscriptionAccountHandles.size() == 1 || hasUserSelectedDefault) {
-                    Uri uri = telecomManager.getAdnUriForPhoneAccount(null);
+                    Uri uri = TelecomUtil.getAdnUriForPhoneAccount(context, null);
                     handleAdnQuery(handler, sc, uri);
                 } else if (subscriptionAccountHandles.size() > 1){
                     SelectPhoneAccountListener callback =
-                            new HandleAdnEntryAccountSelectedCallback(telecomManager, handler, sc);
+                            new HandleAdnEntryAccountSelectedCallback(context, handler, sc);
 
                     DialogFragment dialogFragment = SelectPhoneAccountDialogFragment.newInstance(
                             subscriptionAccountHandles, callback);
@@ -299,12 +297,10 @@
 
     static boolean handlePinEntry(final Context context, final String input) {
         if ((input.startsWith("**04") || input.startsWith("**05")) && input.endsWith("#")) {
-            final TelecomManager telecomManager =
-                    (TelecomManager) context.getSystemService(Context.TELECOM_SERVICE);
             List<PhoneAccountHandle> subscriptionAccountHandles =
                     PhoneAccountUtils.getSubscriptionPhoneAccounts(context);
             boolean hasUserSelectedDefault = subscriptionAccountHandles.contains(
-                    telecomManager.getDefaultOutgoingPhoneAccount(PhoneAccount.SCHEME_TEL));
+                    TelecomUtil.getDefaultOutgoingPhoneAccount(context, PhoneAccount.SCHEME_TEL));
 
             if (subscriptionAccountHandles.size() == 1 || hasUserSelectedDefault) {
                 // Don't bring up the dialog for single-SIM or if the default outgoing account is
diff --git a/src/com/android/dialer/calllog/PhoneAccountUtils.java b/src/com/android/dialer/calllog/PhoneAccountUtils.java
index 143d13e..ceadabc 100644
--- a/src/com/android/dialer/calllog/PhoneAccountUtils.java
+++ b/src/com/android/dialer/calllog/PhoneAccountUtils.java
@@ -23,6 +23,9 @@
 import android.telecom.TelecomManager;
 import android.text.TextUtils;
 
+import com.android.contacts.common.util.PermissionsUtil;
+import com.android.dialer.util.TelecomUtil;
+
 import java.util.ArrayList;
 import java.util.List;
 
@@ -38,7 +41,8 @@
                 (TelecomManager) context.getSystemService(Context.TELECOM_SERVICE);
 
         List<PhoneAccountHandle> subscriptionAccountHandles = new ArrayList<PhoneAccountHandle>();
-        List<PhoneAccountHandle> accountHandles = telecomManager.getCallCapablePhoneAccounts();
+        final List<PhoneAccountHandle> accountHandles =
+                TelecomUtil.getCallCapablePhoneAccounts(context);
         for (PhoneAccountHandle accountHandle : accountHandles) {
             PhoneAccount account = telecomManager.getPhoneAccount(accountHandle);
             if (account.hasCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)) {
diff --git a/src/com/android/dialer/dialpad/DialpadFragment.java b/src/com/android/dialer/dialpad/DialpadFragment.java
index 54e4b89..9c77f30 100644
--- a/src/com/android/dialer/dialpad/DialpadFragment.java
+++ b/src/com/android/dialer/dialpad/DialpadFragment.java
@@ -42,7 +42,6 @@
 import android.provider.Settings;
 import android.telecom.PhoneAccount;
 import android.telecom.PhoneAccountHandle;
-import android.telecom.TelecomManager;
 import android.telephony.PhoneNumberUtils;
 import android.telephony.TelephonyManager;
 import android.text.Editable;
@@ -274,10 +273,6 @@
         return (TelephonyManager) getActivity().getSystemService(Context.TELEPHONY_SERVICE);
     }
 
-    private TelecomManager getTelecomManager() {
-        return (TelecomManager) getActivity().getSystemService(Context.TELECOM_SERVICE);
-    }
-
     @Override
     public void beforeTextChanged(CharSequence s, int start, int count, int after) {
         mWasEmptyBeforeTextChange = TextUtils.isEmpty(s);
@@ -980,7 +975,7 @@
                     List<PhoneAccountHandle> subscriptionAccountHandles =
                             PhoneAccountUtils.getSubscriptionPhoneAccounts(getActivity());
                     boolean hasUserSelectedDefault = subscriptionAccountHandles.contains(
-                            getTelecomManager().getDefaultOutgoingPhoneAccount(
+                            TelecomUtil.getDefaultOutgoingPhoneAccount(getActivity(),
                                     PhoneAccount.SCHEME_VOICEMAIL));
                     boolean needsAccountDisambiguation = subscriptionAccountHandles.size() > 1
                             && !hasUserSelectedDefault;
@@ -1462,7 +1457,7 @@
      * or "return to call" from the dialpad chooser.
      */
     private void returnToInCallScreen(boolean showDialpad) {
-        getTelecomManager().showInCallScreen(showDialpad);
+        TelecomUtil.showInCallScreen(getActivity(), showDialpad);
 
         // Finally, finish() ourselves so that we don't stay on the
         // activity stack.
@@ -1579,20 +1574,19 @@
      *
      * @return true if voicemail is enabled and accessible. Note that this can be false
      * "temporarily" after the app boot.
-     * @see TelecomManager#getVoiceMailNumber(PhoneAccountHandle)
      */
     private boolean isVoicemailAvailable() {
         try {
             PhoneAccountHandle defaultUserSelectedAccount =
-                    getTelecomManager().getDefaultOutgoingPhoneAccount(
+                    TelecomUtil.getDefaultOutgoingPhoneAccount(getActivity(),
                             PhoneAccount.SCHEME_VOICEMAIL);
             if (defaultUserSelectedAccount == null) {
                 // In a single-SIM phone, there is no default outgoing phone account selected by
                 // the user, so just call TelephonyManager#getVoicemailNumber directly.
                 return !TextUtils.isEmpty(getTelephonyManager().getVoiceMailNumber());
             } else {
-                return !TextUtils.isEmpty(
-                        getTelecomManager().getVoiceMailNumber(defaultUserSelectedAccount));
+                return !TextUtils.isEmpty(TelecomUtil.getVoicemailNumber(getActivity(),
+                        defaultUserSelectedAccount));
             }
         } catch (SecurityException se) {
             // Possibly no READ_PHONE_STATE privilege.
diff --git a/src/com/android/dialer/util/DialerUtils.java b/src/com/android/dialer/util/DialerUtils.java
index 8870f76..3d5c257 100644
--- a/src/com/android/dialer/util/DialerUtils.java
+++ b/src/com/android/dialer/util/DialerUtils.java
@@ -86,11 +86,10 @@
                     extras.putParcelable(TouchPointManager.TOUCH_POINT, touchPoint);
                     intent.putExtra(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS, extras);
                 }
-                final TelecomManager tm =
-                        (TelecomManager) context.getSystemService(Context.TELECOM_SERVICE);
-                if (TelecomUtil.hasCallPhonePermission(context)) {
-                    tm.placeCall(intent.getData(), intent.getExtras());
-                } else {
+
+                final boolean hasCallPermission = TelecomUtil.placeCall(context, intent.getData(),
+                        intent.getExtras());
+                if (!hasCallPermission) {
                     // TODO: Make calling activity show request permission dialog and handle
                     // callback results appropriately.
                     Toast.makeText(context, "Cannot place call without Phone permission",
diff --git a/src/com/android/dialer/util/PhoneNumberUtil.java b/src/com/android/dialer/util/PhoneNumberUtil.java
index 539d8b9..2699803 100644
--- a/src/com/android/dialer/util/PhoneNumberUtil.java
+++ b/src/com/android/dialer/util/PhoneNumberUtil.java
@@ -19,7 +19,6 @@
 import android.content.Context;
 import android.provider.CallLog;
 import android.telecom.PhoneAccountHandle;
-import android.telecom.TelecomManager;
 import android.text.TextUtils;
 import android.util.Log;
 import android.util.Pair;
@@ -55,10 +54,7 @@
         if (TextUtils.isEmpty(number)) {
             return false;
         }
-
-        final TelecomManager telecomManager =
-                (TelecomManager) context.getSystemService(Context.TELECOM_SERVICE);
-        return telecomManager.isVoiceMailNumber(accountHandle, number.toString());
+        return TelecomUtil.isVoicemailNumber(context, accountHandle, number.toString());
     }
 
     /**
diff --git a/src/com/android/dialer/util/TelecomUtil.java b/src/com/android/dialer/util/TelecomUtil.java
index 43b9a72..bab1ade 100644
--- a/src/com/android/dialer/util/TelecomUtil.java
+++ b/src/com/android/dialer/util/TelecomUtil.java
@@ -20,6 +20,7 @@
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.net.Uri;
+import android.os.Bundle;
 import android.provider.CallLog.Calls;
 import android.telecom.PhoneAccountHandle;
 import android.telecom.TelecomManager;
@@ -29,10 +30,27 @@
 import java.util.ArrayList;
 import java.util.List;
 
+/**
+ * Performs permission checks before calling into TelecomManager. Each method is self-explanatory -
+ * perform the required check and return the fallback default if the permission is missing,
+ * otherwise return the value from TelecomManager.
+ *
+ */
 public class TelecomUtil {
     private static final String TAG = "TelecomUtil";
     private static boolean sWarningLogged = false;
 
+    public static void showInCallScreen(Context context, boolean showDialpad) {
+        if (hasReadPhoneStatePermission(context)) {
+            try {
+                getTelecomManager(context).showInCallScreen(showDialpad);
+            } catch (SecurityException e) {
+                // Just in case
+                Log.w(TAG, "TelecomManager.showInCallScreen called without permission.");
+            }
+        }
+    }
+
     public static void silenceRinger(Context context) {
         if (hasModifyPhoneStatePermission(context)) {
             try {
@@ -81,6 +99,14 @@
         return false;
     }
 
+    public static PhoneAccountHandle getDefaultOutgoingPhoneAccount(Context context,
+            String uriScheme) {
+        if (hasReadPhoneStatePermission(context)) {
+            return getTelecomManager(context).getDefaultOutgoingPhoneAccount(uriScheme);
+        }
+        return null;
+    }
+
     public static List<PhoneAccountHandle> getCallCapablePhoneAccounts(Context context) {
         if (hasReadPhoneStatePermission(context)) {
             return getTelecomManager(context).getCallCapablePhoneAccounts();
@@ -95,6 +121,39 @@
         return false;
     }
 
+    public static boolean isVoicemailNumber(Context context, PhoneAccountHandle accountHandle,
+            String number) {
+        if (hasReadPhoneStatePermission(context)) {
+            return getTelecomManager(context).isVoiceMailNumber(accountHandle, number);
+        }
+        return false;
+    }
+
+    public static String getVoicemailNumber(Context context, PhoneAccountHandle accountHandle) {
+        if (hasReadPhoneStatePermission(context)) {
+            return getTelecomManager(context).getVoiceMailNumber(accountHandle);
+        }
+        return null;
+    }
+
+    /**
+     * Tries to place a call using the {@link TelecomManager}.
+     *
+     * @param context a valid context.
+     * @param address Handle to call.
+     * @param extras Bundle of extras to attach to the call intent.
+     *
+     * @return {@code true} if we successfully attempted to place the call, {@code false} if it
+     *         failed due to a permission check.
+     */
+    public static boolean placeCall(Context context, Uri address, Bundle extras) {
+        if (hasCallPhonePermission(context)) {
+            getTelecomManager(context).placeCall(address, extras);
+            return true;
+        }
+        return false;
+    }
+
     public static Uri getCallLogUri(Context context) {
         return hasReadWriteVoicemailPermissions(context) ? Calls.CONTENT_URI_WITH_VOICEMAIL
                 : Calls.CONTENT_URI;