Disallow non-emergency outgoing calls without CALL_PHONE permission

Bug: 21925398
Change-Id: I9cef6cd2c11f767740d03ebce99b8b54efd1f68a
diff --git a/res/values/strings.xml b/res/values/strings.xml
index b6eb307..8f73377 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -81,7 +81,11 @@
 
     <!-- Message indicating that the user is not allowed to make non-emergency outgoing phone calls
          due to a user restriction -->
-    <string name="outgoing_call_not_allowed">Only emergency calls are allowed by the device owner.</string>
+    <string name="outgoing_call_not_allowed_user_restriction">Only emergency calls are allowed by the device owner.</string>
+
+    <!-- Message indicating that the user is not allowed to make non-emergency outgoing phone calls
+         due to the lack of the CALL_PHONE permission -->
+    <string name="outgoing_call_not_allowed_no_permission">This application cannot make outgoing calls without the Phone permission.</string>
 
     <!-- Call failure message displayed in an error dialog used to indicate that a phone number was not provided -->
     <string name="outgoing_call_error_no_phone_number_supplied">To place a call, enter a valid number.</string>
diff --git a/src/com/android/server/telecom/TelecomServiceImpl.java b/src/com/android/server/telecom/TelecomServiceImpl.java
index 17f18b1..b11c8ec 100644
--- a/src/com/android/server/telecom/TelecomServiceImpl.java
+++ b/src/com/android/server/telecom/TelecomServiceImpl.java
@@ -795,6 +795,9 @@
                         + " is not allowed to place phone calls");
             }
 
+            final boolean hasCallAppOp = mAppOpsManager.noteOp(AppOpsManager.OP_CALL_PHONE,
+                    Binder.getCallingUid(), callingPackage) == AppOpsManager.MODE_ALLOWED;
+
             synchronized (mLock) {
                 final UserHandle userHandle = Binder.getCallingUserHandle();
                 long token = Binder.clearCallingIdentity();
@@ -802,7 +805,7 @@
                     final Intent intent = new Intent(Intent.ACTION_CALL, handle);
                     intent.putExtras(extras);
                     new UserCallIntentProcessor(mContext, userHandle).processIntent(intent,
-                            callingPackage);
+                            callingPackage, hasCallAppOp);
                 } finally {
                     Binder.restoreCallingIdentity(token);
                 }
diff --git a/src/com/android/server/telecom/components/UserCallActivity.java b/src/com/android/server/telecom/components/UserCallActivity.java
index ae9004c..d88e09e 100644
--- a/src/com/android/server/telecom/components/UserCallActivity.java
+++ b/src/com/android/server/telecom/components/UserCallActivity.java
@@ -58,8 +58,12 @@
         verifyCallAction(intent);
         final UserManager userManager = (UserManager) getSystemService(Context.USER_SERVICE);
         final UserHandle userHandle = new UserHandle(userManager.getUserHandle());
+        // Once control flow has passed to this activity, it is no longer guaranteed that we can
+        // accurately determine whether the calling package has the CALL_PHONE runtime permission.
+        // At this point in time we trust that the ActivityManager has already performed this
+        // validation before starting this activity.
         new UserCallIntentProcessor(this, userHandle).processIntent(getIntent(),
-                getCallingPackage());
+                getCallingPackage(), true /* hasCallAppOp*/);
         finish();
     }
 
diff --git a/src/com/android/server/telecom/components/UserCallIntentProcessor.java b/src/com/android/server/telecom/components/UserCallIntentProcessor.java
index 52708ac..5cef4e8 100644
--- a/src/com/android/server/telecom/components/UserCallIntentProcessor.java
+++ b/src/com/android/server/telecom/components/UserCallIntentProcessor.java
@@ -21,8 +21,10 @@
 import com.android.server.telecom.R;
 import com.android.server.telecom.TelephonyUtil;
 
+import android.app.AppOpsManager;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.PackageManager;
 import android.net.Uri;
 import android.os.UserHandle;
 import android.os.UserManager;
@@ -68,7 +70,7 @@
      *
      * @param intent The intent.
      */
-    public void processIntent(Intent intent, String callingPackageName) {
+    public void processIntent(Intent intent, String callingPackageName, boolean hasCallAppOp) {
         // Ensure call intents are not processed on devices that are not capable of calling.
         if (!isVoiceCapable()) {
             return;
@@ -79,11 +81,12 @@
         if (Intent.ACTION_CALL.equals(action) ||
                 Intent.ACTION_CALL_PRIVILEGED.equals(action) ||
                 Intent.ACTION_CALL_EMERGENCY.equals(action)) {
-            processOutgoingCallIntent(intent, callingPackageName);
+            processOutgoingCallIntent(intent, callingPackageName, hasCallAppOp);
         }
     }
 
-    private void processOutgoingCallIntent(Intent intent, String callingPackageName) {
+    private void processOutgoingCallIntent(Intent intent, String callingPackageName,
+            boolean hasCallAppOp) {
         Uri handle = intent.getData();
         String scheme = handle.getScheme();
         String uriString = handle.getSchemeSpecificPart();
@@ -93,17 +96,31 @@
                     PhoneAccount.SCHEME_SIP : PhoneAccount.SCHEME_TEL, uriString, null);
         }
 
-        UserManager userManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+        final UserManager userManager =
+                (UserManager) mContext.getSystemService(Context.USER_SERVICE);
         if (userManager.hasUserRestriction(UserManager.DISALLOW_OUTGOING_CALLS, mUserHandle)
                 && !TelephonyUtil.shouldProcessAsEmergency(mContext, handle)) {
             // Only emergency calls are allowed for users with the DISALLOW_OUTGOING_CALLS
             // restriction.
-            showErrorDialogForRestrictedOutgoingCall(mContext);
+            showErrorDialogForRestrictedOutgoingCall(mContext,
+                    R.string.outgoing_call_not_allowed_user_restriction);
             Log.w(this, "Rejecting non-emergency phone call due to DISALLOW_OUTGOING_CALLS "
                     + "restriction");
             return;
         }
 
+        PackageManager packageManager = mContext.getPackageManager();
+        final boolean callDisallowed = !hasCallAppOp ||
+                packageManager.checkPermission(android.Manifest.permission.CALL_PHONE,
+                        callingPackageName) != PackageManager.PERMISSION_GRANTED;
+        if (callDisallowed && !TelephonyUtil.shouldProcessAsEmergency(mContext, handle)) {
+            showErrorDialogForRestrictedOutgoingCall(mContext,
+                    R.string.outgoing_call_not_allowed_no_permission);
+            Log.w(this, "Rejecting non-emergency phone call because "
+                    + android.Manifest.permission.CALL_PHONE + " permission is not granted.");
+            return;
+        }
+
         int videoState = intent.getIntExtra(
                 TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE,
                 VideoProfile.STATE_AUDIO_ONLY);
@@ -174,11 +191,10 @@
         return true;
     }
 
-    private static void showErrorDialogForRestrictedOutgoingCall(Context context) {
+    private static void showErrorDialogForRestrictedOutgoingCall(Context context, int stringId) {
         final Intent intent = new Intent(context, ErrorDialogActivity.class);
         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-        intent.putExtra(ErrorDialogActivity.ERROR_MESSAGE_ID_EXTRA,
-                R.string.outgoing_call_not_allowed);
+        intent.putExtra(ErrorDialogActivity.ERROR_MESSAGE_ID_EXTRA, stringId);
         context.startActivityAsUser(intent, UserHandle.CURRENT);
     }
 }