block call forwarding MMI codes from apps

Block call forwarding MMI codes from apps unless they are the
default dialer or system dialer

Also trampoline to default dialer using ACTION_DIAL

Bug: 268341970
Test: atest as well as manual test using adb shell
Change-Id: I4da38ff24595d4444e03ae363d3c8a51cc4087cb
diff --git a/src/com/android/server/telecom/CallIntentProcessor.java b/src/com/android/server/telecom/CallIntentProcessor.java
index 7f864b8..7953324 100644
--- a/src/com/android/server/telecom/CallIntentProcessor.java
+++ b/src/com/android/server/telecom/CallIntentProcessor.java
@@ -182,9 +182,10 @@
         boolean isPrivilegedDialer = defaultDialerCache.isDefaultOrSystemDialer(callingPackage,
                 initiatingUser.getIdentifier());
 
+
         NewOutgoingCallIntentBroadcaster broadcaster = new NewOutgoingCallIntentBroadcaster(
                 context, callsManager, intent, callsManager.getPhoneNumberUtilsAdapter(),
-                isPrivilegedDialer, defaultDialerCache);
+                isPrivilegedDialer, defaultDialerCache, new MmiUtils());
 
         // If the broadcaster comes back with an immediate error, disconnect and show a dialog.
         NewOutgoingCallIntentBroadcaster.CallDisposition disposition = broadcaster.evaluateCall();
diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java
index bc16ea3..ccc8e59 100644
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -477,6 +477,7 @@
 
     private AnomalyReporterAdapter mAnomalyReporter = new AnomalyReporterAdapterImpl();
 
+    private final MmiUtils mMmiUtils = new MmiUtils();
     /**
      * Listener to PhoneAccountRegistrar events.
      */
@@ -1863,7 +1864,7 @@
         CompletableFuture<Call> makeRoomForCall = setAccountHandle.thenComposeAsync(
                 potentialPhoneAccounts -> {
                     Log.i(CallsManager.this, "make room for outgoing call stage");
-                    if (isPotentialInCallMMICode(handle) && !isSelfManaged) {
+                    if (mMmiUtils.isPotentialInCallMMICode(handle) && !isSelfManaged) {
                         return CompletableFuture.completedFuture(finalCall);
                     }
                     // If a call is being reused, then it has already passed the
@@ -2106,7 +2107,7 @@
                     setIntentExtrasAndStartTime(callToUse, extras);
                     setCallSourceToAnalytics(callToUse, originalIntent);
 
-                    if (isPotentialMMICode(handle) && !isSelfManaged) {
+                    if (mMmiUtils.isPotentialMMICode(handle) && !isSelfManaged) {
                         // Do not add the call if it is a potential MMI code.
                         callToUse.addListener(this);
                     } else if (!mCalls.contains(callToUse)) {
@@ -4420,37 +4421,6 @@
         }
     }
 
-    private boolean isPotentialMMICode(Uri handle) {
-        return (handle != null && handle.getSchemeSpecificPart() != null
-                && handle.getSchemeSpecificPart().contains("#"));
-    }
-
-    /**
-     * Determines if a dialed number is potentially an In-Call MMI code.  In-Call MMI codes are
-     * MMI codes which can be dialed when one or more calls are in progress.
-     * <P>
-     * Checks for numbers formatted similar to the MMI codes defined in:
-     * {@link com.android.internal.telephony.Phone#handleInCallMmiCommands(String)}
-     *
-     * @param handle The URI to call.
-     * @return {@code True} if the URI represents a number which could be an in-call MMI code.
-     */
-    private boolean isPotentialInCallMMICode(Uri handle) {
-        if (handle != null && handle.getSchemeSpecificPart() != null &&
-                handle.getScheme() != null &&
-                handle.getScheme().equals(PhoneAccount.SCHEME_TEL)) {
-
-            String dialedNumber = handle.getSchemeSpecificPart();
-            return (dialedNumber.equals("0") ||
-                    (dialedNumber.startsWith("1") && dialedNumber.length() <= 2) ||
-                    (dialedNumber.startsWith("2") && dialedNumber.length() <= 2) ||
-                    dialedNumber.equals("3") ||
-                    dialedNumber.equals("4") ||
-                    dialedNumber.equals("5"));
-        }
-        return false;
-    }
-
     /**
      * Determines if there are any ongoing self managed calls for the given package/user.
      * @param packageName The package name to check.
@@ -5523,8 +5493,10 @@
     * @param call The call.
     */
     private void maybeShowErrorDialogOnDisconnect(Call call) {
-        if (call.getState() == CallState.DISCONNECTED && (isPotentialMMICode(call.getHandle())
-                || isPotentialInCallMMICode(call.getHandle())) && !mCalls.contains(call)) {
+        if (call.getState() == CallState.DISCONNECTED && (mMmiUtils.isPotentialMMICode(
+                call.getHandle())
+                || mMmiUtils.isPotentialInCallMMICode(call.getHandle())) && !mCalls.contains(
+                call)) {
             DisconnectCause disconnectCause = call.getDisconnectCause();
             if (!TextUtils.isEmpty(disconnectCause.getDescription()) && ((disconnectCause.getCode()
                     == DisconnectCause.ERROR) || (disconnectCause.getCode()
diff --git a/src/com/android/server/telecom/MmiUtils.java b/src/com/android/server/telecom/MmiUtils.java
new file mode 100644
index 0000000..11f6d59
--- /dev/null
+++ b/src/com/android/server/telecom/MmiUtils.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.telecom;
+
+import android.net.Uri;
+import android.telecom.PhoneAccount;
+
+import java.util.HashSet;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class MmiUtils {
+    // See TS 22.030 6.5.2 "Structure of the MMI"
+
+    private static Pattern sPatternSuppService = Pattern.compile(
+            "((\\*|#|\\*#|\\*\\*|##)(\\d{2,3})(\\*([^*#]*)(\\*([^*#]*)(\\*([^*#]*)(\\*([^*#]*))?)?)?)?#)(.*)");
+        /*       1  2                    3          4  5       6   7         8    9     10  11
+              12
+
+                 1 = Full string up to and including #
+                 2 = action (activation/interrogation/registration/erasure)
+                 3 = service code
+                 5 = SIA
+                 7 = SIB
+                 9 = SIC
+                 10 = dialing number
+        */
+    //regex groups
+    static final int MATCH_GROUP_POUND_STRING = 1;
+    static final int MATCH_GROUP_ACTION = 2; //(activation/interrogation/registration/erasure)
+    static final int MATCH_GROUP_SERVICE_CODE = 3;
+    static final int MATCH_GROUP_SIA = 5;
+    static final int MATCH_GROUP_SIB = 7;
+    static final int MATCH_GROUP_SIC = 9;
+    static final int MATCH_GROUP_PWD_CONFIRM = 11;
+    static final int MATCH_GROUP_DIALING_NUMBER = 12;
+    // Call Forwarding service codes
+    static final String SC_CFU = "21";
+    static final String SC_CFB = "67";
+    static final String SC_CFNRy = "61";
+    static final String SC_CFNR = "62";
+    static final String SC_CF_All = "002";
+    static final String SC_CF_All_Conditional = "004";
+
+    //see: https://nationalnanpa.com/number_resource_info/vsc_assignments.html
+    @SuppressWarnings("DoubleBraceInitialization")
+    private static Set<String> sDangerousVerticalServiceCodes = new HashSet<String>()
+    {{
+        add("*09"); //Selective Call Blocking/Reporting
+        add("*42"); //Change Forward-To Number for Cust Programmable Call Forwarding Don't Answer
+        add("*56"); //Change Forward-To Number for ISDN Call Forwarding
+        add("*60"); //Selective Call Rejection Activation
+        add("*63"); //Selective Call Forwarding Activation
+        add("*64"); //Selective Call Acceptance Activation
+        add("*68"); //Call Forwarding Busy Line/Don't Answer Activation
+        add("*72"); //Call Forwarding Activation
+        add("*77"); //Anonymous Call Rejection Activation
+        add("*78"); //Do Not Disturb Activation
+    }};
+    private final int mMinLenInDangerousSet;
+    private final int mMaxLenInDangerousSet;
+
+    public MmiUtils() {
+        mMinLenInDangerousSet = sDangerousVerticalServiceCodes.stream()
+                .mapToInt(String::length)
+                .min()
+                .getAsInt();
+        mMaxLenInDangerousSet = sDangerousVerticalServiceCodes.stream()
+                .mapToInt(String::length)
+                .max()
+                .getAsInt();
+    }
+
+    /**
+     * Determines if the Uri represents a call forwarding related mmi code
+     *
+     * @param handle The URI to call.
+     * @return {@code True} if the URI represents a call forwarding related MMI
+     */
+    private static boolean isCallForwardingMmiCode(Uri handle) {
+        Matcher m;
+        String dialString = handle.getSchemeSpecificPart();
+        m = sPatternSuppService.matcher(dialString);
+
+        if (m.matches()) {
+            String sc = m.group(MATCH_GROUP_SERVICE_CODE);
+            return sc != null &&
+                    (sc.equals(SC_CFU)
+                            || sc.equals(SC_CFB) || sc.equals(SC_CFNRy)
+                            || sc.equals(SC_CFNR) || sc.equals(SC_CF_All)
+                            || sc.equals(SC_CF_All_Conditional));
+        }
+
+        return false;
+
+    }
+
+    private static boolean isTelScheme(Uri handle) {
+        return (handle != null && handle.getSchemeSpecificPart() != null &&
+                handle.getScheme() != null &&
+                handle.getScheme().equals(PhoneAccount.SCHEME_TEL));
+    }
+
+    private boolean isDangerousVerticalServiceCode(Uri handle) {
+        if (isTelScheme(handle)) {
+            String dialedNumber = handle.getSchemeSpecificPart();
+            if (dialedNumber.length() >= mMinLenInDangerousSet && dialedNumber.charAt(0) == '*') {
+                //we only check vertical codes defined by The North American Numbering Plan Admin
+                //see: https://nationalnanpa.com/number_resource_info/vsc_assignments.html
+                //only two or 3-digit codes are valid as of today, but the code is generic enough.
+                for (int prefixLen = mMaxLenInDangerousSet; prefixLen <= mMaxLenInDangerousSet;
+                        prefixLen++) {
+                    String prefix = dialedNumber.substring(0, prefixLen);
+                    if (sDangerousVerticalServiceCodes.contains(prefix)) {
+                        return true;
+                    }
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Determines if a dialed number is potentially an In-Call MMI code.  In-Call MMI codes are
+     * MMI codes which can be dialed when one or more calls are in progress.
+     * <P>
+     * Checks for numbers formatted similar to the MMI codes defined in:
+     * {@link com.android.internal.telephony.Phone#handleInCallMmiCommands(String)}
+     *
+     * @param handle The URI to call.
+     * @return {@code True} if the URI represents a number which could be an in-call MMI code.
+     */
+    public boolean isPotentialInCallMMICode(Uri handle) {
+        if (isTelScheme(handle)) {
+            String dialedNumber = handle.getSchemeSpecificPart();
+            return (dialedNumber.equals("0") ||
+                    (dialedNumber.startsWith("1") && dialedNumber.length() <= 2) ||
+                    (dialedNumber.startsWith("2") && dialedNumber.length() <= 2) ||
+                    dialedNumber.equals("3") ||
+                    dialedNumber.equals("4") ||
+                    dialedNumber.equals("5"));
+        }
+        return false;
+    }
+
+    public boolean isPotentialMMICode(Uri handle) {
+        return (handle != null && handle.getSchemeSpecificPart() != null
+                && handle.getSchemeSpecificPart().contains("#"));
+    }
+
+    /**
+     * Determines if the Uri represents a dangerous MMI code or Vertical Service code. Dangerous
+     * codes are ones, for which,
+     * we normally expect the user to be aware that an application has dialed them
+     *
+     * @param handle The URI to call.
+     * @return {@code True} if the URI represents a dangerous code
+     */
+    public boolean isDangerousMmiOrVerticalCode(Uri handle) {
+        if (isPotentialMMICode(handle)) {
+            return isCallForwardingMmiCode(handle);
+            //since some dangerous mmi codes could be carrier specific, in the future,
+            //we can add a carrier config item which can list carrier specific dangerous mmi codes
+        } else if (isDangerousVerticalServiceCode(handle)) {
+            return true;
+        }
+        return false;
+    }
+}
diff --git a/src/com/android/server/telecom/NewOutgoingCallIntentBroadcaster.java b/src/com/android/server/telecom/NewOutgoingCallIntentBroadcaster.java
index 41aa2fb..8426d1f 100644
--- a/src/com/android/server/telecom/NewOutgoingCallIntentBroadcaster.java
+++ b/src/com/android/server/telecom/NewOutgoingCallIntentBroadcaster.java
@@ -16,14 +16,12 @@
 
 package com.android.server.telecom;
 
-import android.app.AppOpsManager;
-
 import android.app.Activity;
+import android.app.AppOpsManager;
 import android.app.BroadcastOptions;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
-import android.content.res.Resources;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.Trace;
@@ -78,6 +76,7 @@
     private final PhoneNumberUtilsAdapter mPhoneNumberUtilsAdapter;
     private final TelecomSystem.SyncRoot mLock;
     private final DefaultDialerCache mDefaultDialerCache;
+    private final MmiUtils mMmiUtils;
 
     /*
      * Whether or not the outgoing call intent originated from the default phone application. If
@@ -101,7 +100,7 @@
     @VisibleForTesting
     public NewOutgoingCallIntentBroadcaster(Context context, CallsManager callsManager,
             Intent intent, PhoneNumberUtilsAdapter phoneNumberUtilsAdapter,
-            boolean isDefaultPhoneApp, DefaultDialerCache defaultDialerCache) {
+            boolean isDefaultPhoneApp, DefaultDialerCache defaultDialerCache, MmiUtils mmiUtils) {
         mContext = context;
         mCallsManager = callsManager;
         mIntent = intent;
@@ -109,6 +108,7 @@
         mIsDefaultOrSystemPhoneApp = isDefaultPhoneApp;
         mLock = mCallsManager.getLock();
         mDefaultDialerCache = defaultDialerCache;
+        mMmiUtils = mmiUtils;
     }
 
     /**
@@ -291,6 +291,16 @@
                     result.callImmediately = true;
                     result.requestRedirection = false;
                 }
+            } else if (mMmiUtils.isDangerousMmiOrVerticalCode(intent.getData())) {
+                if (!mIsDefaultOrSystemPhoneApp) {
+                    Log.w(this,
+                            "Potentially dangerous MMI code %s with CALL Intent %s can only be "
+                                    + "sent if caller is the system or default dialer",
+                            number, intent);
+                    launchSystemDialer(intent.getData());
+                    result.disconnectCause = DisconnectCause.OUTGOING_CANCELED;
+                    return result;
+                }
             }
         } else if (Intent.ACTION_CALL_EMERGENCY.equals(action)) {
             if (!isEmergencyNumber) {
diff --git a/tests/src/com/android/server/telecom/tests/MmiUtilsTest.java b/tests/src/com/android/server/telecom/tests/MmiUtilsTest.java
new file mode 100644
index 0000000..ed74637
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/MmiUtilsTest.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.telecom.tests;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.net.Uri;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.server.telecom.MmiUtils;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class MmiUtilsTest extends TelecomTestCase {
+
+    private static final String[] sDangerousDialStrings = {
+        "*21*1234567#", // fwd unconditionally to 1234567,
+        "*67*1234567#", // fwd to 1234567 when line is busy
+        "*61*1234567#", // fwd to 1234567 when no one picks up
+        "*62*1234567#", // fwd to 1234567 when out of range
+        "*004*1234567#", // fwd to 1234567 when busy, not pickup up, out of range
+        "*004*1234567#", // fwd to 1234567 conditionally
+        "**21*1234567#", // fwd unconditionally to 1234567
+
+        // north american vertical service codes
+
+        "*094565678", // Selective Call Blocking/Reporting
+        "*4278889", // Change Forward-To Number for Customer Programmable Call Forwarding Don't
+                    // Answer
+        "*5644456", // Change Forward-To Number for ISDN Call Forwarding
+        "*6045677", // Selective Call Rejection Activation
+        "*635678", // Selective Call Forwarding Activation
+        "*64678899", // Selective Call Acceptance Activation
+        "*683456", // Call Forwarding Busy Line/Don't Answer Activation
+        "*721234", // Call Forwarding Activation
+        "*77", // Anonymous Call Rejection Activation
+        "*78", // Do Not Disturb Activation
+    };
+
+    private MmiUtils mMmiUtils = new MmiUtils();
+    private static final String[] sNonDangerousDialStrings = {"*6712345678", "*272", "*272911"};
+
+    @Override
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+    }
+
+    @Override
+    @After
+    public void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    @SmallTest
+    @Test
+    public void testDangerousDialStringsDetected() throws Exception {
+        for (String s : sDangerousDialStrings) {
+            Uri.Builder b = new Uri.Builder();
+            b.scheme("tel").opaquePart(s);
+            assertTrue(mMmiUtils.isDangerousMmiOrVerticalCode(b.build()));
+        }
+    }
+
+    @SmallTest
+    @Test
+    public void testNonDangerousDialStringsNotDetected() throws Exception {
+        for (String s : sNonDangerousDialStrings) {
+            Uri.Builder b = new Uri.Builder();
+            b.scheme("tel").opaquePart(s);
+            assertFalse(mMmiUtils.isDangerousMmiOrVerticalCode(b.build()));
+        }
+    }
+}
diff --git a/tests/src/com/android/server/telecom/tests/NewOutgoingCallIntentBroadcasterTest.java b/tests/src/com/android/server/telecom/tests/NewOutgoingCallIntentBroadcasterTest.java
index 169aeb2..f2bcf18 100644
--- a/tests/src/com/android/server/telecom/tests/NewOutgoingCallIntentBroadcasterTest.java
+++ b/tests/src/com/android/server/telecom/tests/NewOutgoingCallIntentBroadcasterTest.java
@@ -56,6 +56,7 @@
 import com.android.server.telecom.Call;
 import com.android.server.telecom.CallsManager;
 import com.android.server.telecom.DefaultDialerCache;
+import com.android.server.telecom.MmiUtils;
 import com.android.server.telecom.NewOutgoingCallIntentBroadcaster;
 import com.android.server.telecom.PhoneAccountRegistrar;
 import com.android.server.telecom.PhoneNumberUtilsAdapter;
@@ -93,6 +94,7 @@
     @Mock private RoleManagerAdapter mRoleManagerAdapter;
     @Mock private DefaultDialerCache mDefaultDialerCache;
 
+    @Mock private MmiUtils mMmiUtils;
     private PhoneNumberUtilsAdapter mPhoneNumberUtilsAdapter = new PhoneNumberUtilsAdapterImpl();
 
     @Override
@@ -261,6 +263,58 @@
         assertEquals(Intent.FLAG_ACTIVITY_NEW_TASK, dialerIntent.getFlags());
     }
 
+    @Test
+    public void testDangerousMmiCodeWithNonDefaultDialer() {
+        Uri handle = Uri.parse("tel:*21*1234567#");
+        doReturn(true).when(mMmiUtils).isDangerousMmiOrVerticalCode(handle);
+        Intent intent = new Intent(Intent.ACTION_CALL, handle);
+
+        String ui_package_string = "sample_string_1";
+        String dialer_default_class_string = "sample_string_2";
+        mComponentContextFixture.putResource(com.android.internal.R.string.config_defaultDialer,
+                ui_package_string);
+        mComponentContextFixture.putResource(R.string.dialer_default_class,
+                dialer_default_class_string);
+        when(mDefaultDialerCache.getSystemDialerApplication()).thenReturn(ui_package_string);
+        when(mDefaultDialerCache.getDialtactsSystemDialerComponent()).thenReturn(
+                new ComponentName(ui_package_string, dialer_default_class_string));
+
+        int result = processIntent(intent, false).disconnectCause;
+
+        assertEquals(DisconnectCause.OUTGOING_CANCELED, result);
+        verifyNoBroadcastSent();
+        verifyNoCallPlaced();
+
+        ArgumentCaptor<Intent> dialerIntentCaptor = ArgumentCaptor.forClass(Intent.class);
+        verify(mContext).startActivityAsUser(dialerIntentCaptor.capture(), any(UserHandle.class));
+        Intent dialerIntent = dialerIntentCaptor.getValue();
+        assertEquals(new ComponentName(ui_package_string, dialer_default_class_string),
+                dialerIntent.getComponent());
+        assertEquals(Intent.ACTION_DIAL, dialerIntent.getAction());
+        assertEquals(handle, dialerIntent.getData());
+        assertEquals(Intent.FLAG_ACTIVITY_NEW_TASK, dialerIntent.getFlags());
+    }
+
+    @Test
+    public void testNonDangerousMmiCodeWithNonDefaultDialer() {
+        Uri handle = Uri.parse("tel:*12*1234567#");
+        doReturn(false).when(mMmiUtils).isDangerousMmiOrVerticalCode(handle);
+        Intent intent = new Intent(Intent.ACTION_CALL, handle);
+
+        String ui_package_string = "sample_string_1";
+        String dialer_default_class_string = "sample_string_2";
+        mComponentContextFixture.putResource(com.android.internal.R.string.config_defaultDialer,
+                ui_package_string);
+        mComponentContextFixture.putResource(R.string.dialer_default_class,
+                dialer_default_class_string);
+        when(mDefaultDialerCache.getSystemDialerApplication()).thenReturn(ui_package_string);
+        when(mDefaultDialerCache.getDialtactsSystemDialerComponent()).thenReturn(
+                new ComponentName(ui_package_string, dialer_default_class_string));
+
+        int result = processIntent(intent, false).disconnectCause;
+        assertEquals(DisconnectCause.NOT_DISCONNECTED, result);
+    }
+
     @SmallTest
     @Test
     public void testActionCallEmergencyCall() {
@@ -488,7 +542,7 @@
             boolean isDefaultPhoneApp) {
         NewOutgoingCallIntentBroadcaster b = new NewOutgoingCallIntentBroadcaster(
                 mContext, mCallsManager, intent, mPhoneNumberUtilsAdapter,
-                isDefaultPhoneApp, mDefaultDialerCache);
+                isDefaultPhoneApp, mDefaultDialerCache, mMmiUtils);
         NewOutgoingCallIntentBroadcaster.CallDisposition cd = b.evaluateCall();
         if (cd.disconnectCause == DisconnectCause.NOT_DISCONNECTED) {
             b.processCall(mCall, cd);