Merge "Add script for semi-automating Telecom testing"
diff --git a/src/com/android/server/telecom/Call.java b/src/com/android/server/telecom/Call.java
index 8555c20..c2d4aa5 100644
--- a/src/com/android/server/telecom/Call.java
+++ b/src/com/android/server/telecom/Call.java
@@ -1148,7 +1148,8 @@
     /**
      * Attempts to disconnect the call through the connection service.
      */
-    void disconnect(boolean wasViaNewOutgoingCallBroadcaster) {
+    @VisibleForTesting
+    public void disconnect(boolean wasViaNewOutgoingCallBroadcaster) {
         Log.event(this, Log.Events.REQUEST_DISCONNECT);
 
         // Track that the call is now locally disconnecting.
diff --git a/src/com/android/server/telecom/CallIntentProcessor.java b/src/com/android/server/telecom/CallIntentProcessor.java
index 831b708..9150084 100644
--- a/src/com/android/server/telecom/CallIntentProcessor.java
+++ b/src/com/android/server/telecom/CallIntentProcessor.java
@@ -139,7 +139,8 @@
             // process will be running throughout the duration of the phone call and should never
             // be killed.
             NewOutgoingCallIntentBroadcaster broadcaster = new NewOutgoingCallIntentBroadcaster(
-                    context, callsManager, call, intent, isPrivilegedDialer);
+                    context, callsManager, call, intent, new PhoneNumberUtilsAdapterImpl(),
+                    isPrivilegedDialer);
             final int result = broadcaster.processIntent();
             final boolean success = result == DisconnectCause.NOT_DISCONNECTED;
 
diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java
index f388977..c885181 100644
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -782,8 +782,9 @@
      * @param speakerphoneOn Whether or not to turn the speakerphone on once the call connects.
      * @param videoState The desired video state for the outgoing call.
      */
-    void placeOutgoingCall(Call call, Uri handle, GatewayInfo gatewayInfo, boolean speakerphoneOn,
-            int videoState) {
+    @VisibleForTesting
+    public void placeOutgoingCall(Call call, Uri handle, GatewayInfo gatewayInfo,
+            boolean speakerphoneOn, int videoState) {
         if (call == null) {
             // don't do anything if the call no longer exists
             Log.i(this, "Canceling unknown call.");
diff --git a/src/com/android/server/telecom/Log.java b/src/com/android/server/telecom/Log.java
index 075f4bb..06a6243 100644
--- a/src/com/android/server/telecom/Log.java
+++ b/src/com/android/server/telecom/Log.java
@@ -305,6 +305,8 @@
         }
     }
 
+    public static final long DEFAULT_SESSION_TIMEOUT_MS = 30000L; // 30 seconds
+
     @VisibleForTesting
     public static void setTag(String tag) {
         TAG = tag;
@@ -319,12 +321,16 @@
     public interface ISessionCleanupTimeoutMs {
         long get();
     }
-
     @VisibleForTesting
     public static ISessionCleanupTimeoutMs sSessionCleanupTimeoutMs =
             new ISessionCleanupTimeoutMs() {
                 @Override
                 public long get() {
+                    // mContext will be null if Log is called from another process
+                    // (UserCallActivity, for example). For these cases, use the default value.
+                    if(mContext == null) {
+                        return DEFAULT_SESSION_TIMEOUT_MS;
+                    }
                     return Timeouts.getStaleSessionCleanupTimeoutMillis(
                             mContext.getContentResolver());
                 }
diff --git a/src/com/android/server/telecom/NewOutgoingCallIntentBroadcaster.java b/src/com/android/server/telecom/NewOutgoingCallIntentBroadcaster.java
index d9c6c33..a842911 100644
--- a/src/com/android/server/telecom/NewOutgoingCallIntentBroadcaster.java
+++ b/src/com/android/server/telecom/NewOutgoingCallIntentBroadcaster.java
@@ -31,9 +31,10 @@
 import android.telecom.TelecomManager;
 import android.telecom.VideoProfile;
 import android.telephony.DisconnectCause;
-import android.telephony.PhoneNumberUtils;
 import android.text.TextUtils;
 
+import com.android.internal.annotations.VisibleForTesting;
+
 // TODO: Needed for move to system service: import com.android.internal.R;
 
 /**
@@ -52,7 +53,8 @@
  * Calls to emergency numbers are still broadcast for informative purposes. The call is placed
  * prior to sending ACTION_NEW_OUTGOING_CALL and cannot be redirected nor prevented.
  */
-class NewOutgoingCallIntentBroadcaster {
+@VisibleForTesting
+public class NewOutgoingCallIntentBroadcaster {
     private static final String EXTRA_ACTUAL_NUMBER_TO_DIAL =
             "android.telecom.extra.ACTUAL_NUMBER_TO_DIAL";
 
@@ -72,6 +74,7 @@
     private final Call mCall;
     private final Intent mIntent;
     private final Context mContext;
+    private final PhoneNumberUtilsAdapter mPhoneNumberUtilsAdapter;
 
     /*
      * Whether or not the outgoing call intent originated from the default phone application. If
@@ -79,12 +82,15 @@
      */
     private final boolean mIsDefaultOrSystemPhoneApp;
 
-    NewOutgoingCallIntentBroadcaster(Context context, CallsManager callsManager, Call call,
-            Intent intent, boolean isDefaultPhoneApp) {
+    @VisibleForTesting
+    public NewOutgoingCallIntentBroadcaster(Context context, CallsManager callsManager, Call call,
+            Intent intent, PhoneNumberUtilsAdapter phoneNumberUtilsAdapter,
+            boolean isDefaultPhoneApp) {
         mContext = context;
         mCallsManager = callsManager;
         mCall = call;
         mIntent = intent;
+        mPhoneNumberUtilsAdapter = phoneNumberUtilsAdapter;
         mIsDefaultOrSystemPhoneApp = isDefaultPhoneApp;
     }
 
@@ -92,7 +98,7 @@
      * Processes the result of the outgoing call broadcast intent, and performs callbacks to
      * the OutgoingCallIntentBroadcasterListener as necessary.
      */
-    private class NewOutgoingCallBroadcastIntentReceiver extends BroadcastReceiver {
+    public class NewOutgoingCallBroadcastIntentReceiver extends BroadcastReceiver {
 
         @Override
         public void onReceive(Context context, Intent intent) {
@@ -111,7 +117,7 @@
                 if (resultNumber == null) {
                     Log.v(this, "Call cancelled (null number), returning...");
                     endEarly = true;
-                } else if (PhoneNumberUtils.isPotentialLocalEmergencyNumber(
+                } else if (mPhoneNumberUtilsAdapter.isPotentialLocalEmergencyNumber(
                         mContext, resultNumber)) {
                     Log.w(this, "Cannot modify outgoing call to emergency number %s.",
                             resultNumber);
@@ -125,8 +131,10 @@
                     return;
                 }
 
-                Uri resultHandleUri = Uri.fromParts(PhoneNumberUtils.isUriNumber(resultNumber) ?
-                        PhoneAccount.SCHEME_SIP : PhoneAccount.SCHEME_TEL, resultNumber, null);
+                Uri resultHandleUri = Uri.fromParts(
+                        mPhoneNumberUtilsAdapter.isUriNumber(resultNumber) ?
+                                PhoneAccount.SCHEME_SIP : PhoneAccount.SCHEME_TEL,
+                        resultNumber, null);
 
                 Uri originalUri = mIntent.getData();
 
@@ -166,7 +174,8 @@
      * @return {@link DisconnectCause#NOT_DISCONNECTED} if the call succeeded, and an appropriate
      *         {@link DisconnectCause} if the call did not, describing why it failed.
      */
-    int processIntent() {
+    @VisibleForTesting
+    public int processIntent() {
         Log.v(this, "Processing call intent in OutgoingCallIntentBroadcaster.");
 
         Intent intent = mIntent;
@@ -198,16 +207,16 @@
             }
         }
 
-        String number = PhoneNumberUtils.getNumberFromIntent(intent, mContext);
+        String number = mPhoneNumberUtilsAdapter.getNumberFromIntent(intent, mContext);
         if (TextUtils.isEmpty(number)) {
             Log.w(this, "Empty number obtained from the call intent.");
             return DisconnectCause.NO_PHONE_NUMBER_SUPPLIED;
         }
 
-        boolean isUriNumber = PhoneNumberUtils.isUriNumber(number);
+        boolean isUriNumber = mPhoneNumberUtilsAdapter.isUriNumber(number);
         if (!isUriNumber) {
-            number = PhoneNumberUtils.convertKeypadLettersToDigits(number);
-            number = PhoneNumberUtils.stripSeparators(number);
+            number = mPhoneNumberUtilsAdapter.convertKeypadLettersToDigits(number);
+            number = mPhoneNumberUtilsAdapter.stripSeparators(number);
         }
 
         final boolean isPotentialEmergencyNumber = isPotentialEmergencyNumber(number);
@@ -394,8 +403,8 @@
      */
     private boolean isPotentialEmergencyNumber(String number) {
         Log.v(this, "Checking restrictions for number : %s", Log.pii(number));
-        return (number != null) && PhoneNumberUtils.isPotentialLocalEmergencyNumber(mContext,
-                number);
+        return (number != null)
+                && mPhoneNumberUtilsAdapter.isPotentialLocalEmergencyNumber(mContext, number);
     }
 
     /**
diff --git a/src/com/android/server/telecom/PhoneNumberUtilsAdapter.java b/src/com/android/server/telecom/PhoneNumberUtilsAdapter.java
new file mode 100644
index 0000000..41284cb
--- /dev/null
+++ b/src/com/android/server/telecom/PhoneNumberUtilsAdapter.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2015 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.content.Context;
+import android.content.Intent;
+
+/**
+ * Interface to avoid static calls to PhoneNumberUtils. Add methods to this interface as needed for
+ * refactoring.
+ */
+public interface PhoneNumberUtilsAdapter {
+    boolean isPotentialLocalEmergencyNumber(Context context, String number);
+    boolean isUriNumber(String number);
+    String getNumberFromIntent(Intent intent, Context context);
+    String convertKeypadLettersToDigits(String number);
+    String stripSeparators(String number);
+}
diff --git a/src/com/android/server/telecom/PhoneNumberUtilsAdapterImpl.java b/src/com/android/server/telecom/PhoneNumberUtilsAdapterImpl.java
new file mode 100644
index 0000000..640d814
--- /dev/null
+++ b/src/com/android/server/telecom/PhoneNumberUtilsAdapterImpl.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2015 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.content.Context;
+import android.content.Intent;
+import android.telephony.PhoneNumberUtils;
+
+public class PhoneNumberUtilsAdapterImpl implements PhoneNumberUtilsAdapter {
+    @Override
+    public boolean isPotentialLocalEmergencyNumber(Context context, String number) {
+        return PhoneNumberUtils.isPotentialLocalEmergencyNumber(context, number);
+    }
+
+    @Override
+    public boolean isUriNumber(String number) {
+        return PhoneNumberUtils.isUriNumber(number);
+    }
+
+    @Override
+    public String getNumberFromIntent(Intent intent, Context context) {
+        return PhoneNumberUtils.getNumberFromIntent(intent, context);
+    }
+
+    @Override
+    public String convertKeypadLettersToDigits(String number) {
+        return PhoneNumberUtils.convertKeypadLettersToDigits(number);
+    }
+
+    @Override
+    public String stripSeparators(String number) {
+        return PhoneNumberUtils.stripSeparators(number);
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/server/telecom/Timeouts.java b/src/com/android/server/telecom/Timeouts.java
index ba16990..cd1b9af 100644
--- a/src/com/android/server/telecom/Timeouts.java
+++ b/src/com/android/server/telecom/Timeouts.java
@@ -113,6 +113,7 @@
      * perform a sweep to check and make sure that the session is still not incomplete (stale).
      */
     public static long getStaleSessionCleanupTimeoutMillis(ContentResolver contentResolver) {
-        return get(contentResolver, "stale_session_cleanup_timeout_millis", 30000L);
+        return get(contentResolver, "stale_session_cleanup_timeout_millis",
+                Log.DEFAULT_SESSION_TIMEOUT_MS);
     }
 }
diff --git a/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java b/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java
index d16f86b..b6ce48a 100644
--- a/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java
+++ b/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java
@@ -283,10 +283,11 @@
             // Don't bother enforcing anything in mock.
         }
 
-        /**
-         * Used to work around a Mockito/ART bug. If you remove any of these, tests will fail.
-         */
-    };
+        @Override
+        public void startActivityAsUser(Intent intent, UserHandle userHandle) {
+            // For capturing
+        }
+    }
 
     public class FakeAudioManager extends AudioManager {
 
diff --git a/tests/src/com/android/server/telecom/tests/LogTest.java b/tests/src/com/android/server/telecom/tests/LogTest.java
index 673582a..98f4237 100644
--- a/tests/src/com/android/server/telecom/tests/LogTest.java
+++ b/tests/src/com/android/server/telecom/tests/LogTest.java
@@ -200,7 +200,7 @@
             // Set to the default value of Timeouts.getStaleSessionCleanupTimeoutMillis without
             // needing to query.
             public long get() {
-                return 30000;
+                return Log.DEFAULT_SESSION_TIMEOUT_MS;
             }
         };
     }
diff --git a/tests/src/com/android/server/telecom/tests/NewOutgoingCallIntentBroadcasterTest.java b/tests/src/com/android/server/telecom/tests/NewOutgoingCallIntentBroadcasterTest.java
new file mode 100644
index 0000000..462593b
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/NewOutgoingCallIntentBroadcasterTest.java
@@ -0,0 +1,401 @@
+/*
+ * Copyright (C) 2015 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 android.Manifest;
+import android.app.Activity;
+import android.app.AppOpsManager;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.telecom.GatewayInfo;
+import android.telecom.TelecomManager;
+import android.telecom.VideoProfile;
+import android.telephony.DisconnectCause;
+
+import com.android.server.telecom.Call;
+import com.android.server.telecom.CallsManager;
+import com.android.server.telecom.NewOutgoingCallIntentBroadcaster;
+import com.android.server.telecom.PhoneNumberUtilsAdapter;
+import com.android.server.telecom.PhoneNumberUtilsAdapterImpl;
+
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyBoolean;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Matchers.isNotNull;
+import static org.mockito.Matchers.isNull;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+public class NewOutgoingCallIntentBroadcasterTest extends TelecomTestCase {
+    private static class ReceiverIntentPair {
+        public BroadcastReceiver receiver;
+        public Intent intent;
+
+        public ReceiverIntentPair(BroadcastReceiver receiver, Intent intent) {
+            this.receiver = receiver;
+            this.intent = intent;
+        }
+    }
+
+    @Mock private CallsManager mCallsManager;
+    @Mock private Call mCall;
+
+    private PhoneNumberUtilsAdapter mPhoneNumberUtilsAdapterSpy;
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        mContext = mComponentContextFixture.getTestDouble().getApplicationContext();
+        mPhoneNumberUtilsAdapterSpy = spy(new PhoneNumberUtilsAdapterImpl());
+    }
+
+    public void testNullHandle() {
+        Intent intent = new Intent(Intent.ACTION_CALL, null);
+        int result = processIntent(intent, true);
+        assertEquals(DisconnectCause.INVALID_NUMBER, result);
+        verifyNoBroadcastSent();
+        verifyNoCallPlaced();
+    }
+
+    public void testVoicemailCall() {
+        String voicemailNumber = "voicemail:18005551234";
+        Intent intent = new Intent(Intent.ACTION_CALL, Uri.parse(voicemailNumber));
+        intent.putExtra(TelecomManager.EXTRA_START_CALL_WITH_SPEAKERPHONE, true);
+
+        int result = processIntent(intent, true);
+
+        assertEquals(DisconnectCause.NOT_DISCONNECTED, result);
+        verify(mCallsManager).placeOutgoingCall(eq(mCall), eq(Uri.parse(voicemailNumber)),
+                any(GatewayInfo.class), eq(true), eq(VideoProfile.STATE_AUDIO_ONLY));
+    }
+
+    public void testVoicemailCallWithBadAction() {
+        badCallActionHelper(Uri.parse("voicemail:18005551234"), DisconnectCause.OUTGOING_CANCELED);
+    }
+
+    public void testTelCallWithBadCallAction() {
+        badCallActionHelper(Uri.parse("tel:6505551234"), DisconnectCause.INVALID_NUMBER);
+    }
+
+    public void testSipCallWithBadCallAction() {
+        badCallActionHelper(Uri.parse("sip:testuser@testsite.com"), DisconnectCause.INVALID_NUMBER);
+    }
+
+    private void badCallActionHelper(Uri handle, int expectedCode) {
+        Intent intent = new Intent(Intent.ACTION_ALARM_CHANGED, handle);
+
+        int result = processIntent(intent, true);
+
+        assertEquals(expectedCode, result);
+        verifyNoBroadcastSent();
+        verifyNoCallPlaced();
+    }
+
+    public void testNoNumberSupplied() {
+        Uri handle = Uri.parse("tel:");
+        Intent intent = new Intent(Intent.ACTION_CALL, handle);
+
+        int result = processIntent(intent, true);
+
+        assertEquals(DisconnectCause.NO_PHONE_NUMBER_SUPPLIED, result);
+        verifyNoBroadcastSent();
+        verifyNoCallPlaced();
+    }
+
+
+    public void testEmergencyCallWithNonDefaultDialer() {
+        Uri handle = Uri.parse("tel:6505551911");
+        doReturn(true).when(mPhoneNumberUtilsAdapterSpy).isPotentialLocalEmergencyNumber(
+                any(Context.class), eq(handle.getSchemeSpecificPart()));
+        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(R.string.ui_default_package, ui_package_string);
+        mComponentContextFixture.putResource(R.string.dialer_default_class,
+                dialer_default_class_string);
+
+        int result = processIntent(intent, false);
+
+        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());
+    }
+
+    public void testActionCallEmergencyCall() {
+        Uri handle = Uri.parse("tel:6505551911");
+        Intent intent = buildIntent(handle, Intent.ACTION_CALL, null);
+        emergencyCallTestHelper(intent, null);
+    }
+
+    public void testActionEmergencyWithEmergencyNumber() {
+        Uri handle = Uri.parse("tel:6505551911");
+        Intent intent = buildIntent(handle, Intent.ACTION_CALL_EMERGENCY, null);
+        emergencyCallTestHelper(intent, null);
+    }
+
+    public void testActionPrivCallWithEmergencyNumber() {
+        Uri handle = Uri.parse("tel:6505551911");
+        Intent intent = buildIntent(handle, Intent.ACTION_CALL_PRIVILEGED, null);
+        emergencyCallTestHelper(intent, null);
+    }
+
+    public void testEmergencyCallWithGatewayExtras() {
+        Uri handle = Uri.parse("tel:6505551911");
+        Bundle gatewayExtras = new Bundle();
+        gatewayExtras.putString(NewOutgoingCallIntentBroadcaster.EXTRA_GATEWAY_PROVIDER_PACKAGE,
+                "sample1");
+        gatewayExtras.putString(NewOutgoingCallIntentBroadcaster.EXTRA_GATEWAY_URI, "sample2");
+
+        Intent intent = buildIntent(handle, Intent.ACTION_CALL, gatewayExtras);
+        emergencyCallTestHelper(intent, gatewayExtras);
+    }
+
+    public void testActionEmergencyWithNonEmergencyNumber() {
+        Uri handle = Uri.parse("tel:6505551911");
+        doReturn(false).when(mPhoneNumberUtilsAdapterSpy).isPotentialLocalEmergencyNumber(
+                any(Context.class), eq(handle.getSchemeSpecificPart()));
+        Intent intent = new Intent(Intent.ACTION_CALL_EMERGENCY, handle);
+        int result = processIntent(intent, true);
+
+        assertEquals(DisconnectCause.OUTGOING_CANCELED, result);
+        verifyNoCallPlaced();
+        verifyNoBroadcastSent();
+    }
+
+    private void emergencyCallTestHelper(Intent intent, Bundle expectedAdditionalExtras) {
+        Uri handle = intent.getData();
+        int videoState = VideoProfile.STATE_BIDIRECTIONAL;
+        boolean isSpeakerphoneOn = true;
+        doReturn(true).when(mPhoneNumberUtilsAdapterSpy).isPotentialLocalEmergencyNumber(
+                any(Context.class), eq(handle.getSchemeSpecificPart()));
+        intent.putExtra(TelecomManager.EXTRA_START_CALL_WITH_SPEAKERPHONE, isSpeakerphoneOn);
+        intent.putExtra(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE, videoState);
+        int result = processIntent(intent, true);
+
+        assertEquals(DisconnectCause.NOT_DISCONNECTED, result);
+        verify(mCallsManager).placeOutgoingCall(eq(mCall), eq(handle), isNull(GatewayInfo.class),
+                eq(isSpeakerphoneOn), eq(videoState));
+
+        Bundle expectedExtras = createNumberExtras(handle.getSchemeSpecificPart());
+        if (expectedAdditionalExtras != null) {
+            expectedExtras.putAll(expectedAdditionalExtras);
+        }
+        BroadcastReceiver receiver = verifyBroadcastSent(handle.getSchemeSpecificPart(),
+                expectedExtras).receiver;
+        assertNull(receiver);
+    }
+
+    public void testUnmodifiedRegularCall() {
+        Uri handle = Uri.parse("tel:6505551234");
+        Intent callIntent = buildIntent(handle, Intent.ACTION_CALL, null);
+        ReceiverIntentPair result = regularCallTestHelper(callIntent, null);
+
+        result.receiver.setResultData(
+                result.intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER));
+
+        result.receiver.onReceive(mContext, result.intent);
+
+        verify(mCallsManager).placeOutgoingCall(eq(mCall), eq(handle), isNull(GatewayInfo.class),
+                eq(true), eq(VideoProfile.STATE_BIDIRECTIONAL));
+    }
+
+    public void testUnmodifiedSipCall() {
+        Uri handle = Uri.parse("sip:test@test.com");
+        Intent callIntent = buildIntent(handle, Intent.ACTION_CALL, null);
+        ReceiverIntentPair result = regularCallTestHelper(callIntent, null);
+
+        result.receiver.setResultData(
+                result.intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER));
+
+        result.receiver.onReceive(mContext, result.intent);
+
+        Uri encHandle = Uri.fromParts(handle.getScheme(),
+                handle.getSchemeSpecificPart(), null);
+        verify(mCallsManager).placeOutgoingCall(eq(mCall), eq(encHandle), isNull(GatewayInfo.class),
+                eq(true), eq(VideoProfile.STATE_BIDIRECTIONAL));
+    }
+
+    public void testCallWithGatewayInfo() {
+        Uri handle = Uri.parse("tel:6505551234");
+        Intent callIntent = buildIntent(handle, Intent.ACTION_CALL, null);
+
+        callIntent.putExtra(NewOutgoingCallIntentBroadcaster
+                        .EXTRA_GATEWAY_PROVIDER_PACKAGE, "sample1");
+        callIntent.putExtra(NewOutgoingCallIntentBroadcaster.EXTRA_GATEWAY_URI, "sample2");
+        ReceiverIntentPair result = regularCallTestHelper(callIntent, callIntent.getExtras());
+
+        result.receiver.setResultData(
+                result.intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER));
+
+        result.receiver.onReceive(mContext, result.intent);
+
+        verify(mCallsManager).placeOutgoingCall(eq(mCall), eq(handle),
+                isNotNull(GatewayInfo.class), eq(true), eq(VideoProfile.STATE_BIDIRECTIONAL));
+    }
+
+    public void testCallNumberModifiedToNull() {
+        Uri handle = Uri.parse("tel:6505551234");
+        Intent callIntent = buildIntent(handle, Intent.ACTION_CALL, null);
+        ReceiverIntentPair result = regularCallTestHelper(callIntent, null);
+
+        result.receiver.setResultData(null);
+
+        result.receiver.onReceive(mContext, result.intent);
+        verifyNoCallPlaced();
+        verify(mCall).disconnect(true);
+    }
+
+    public void testCallModifiedToEmergency() {
+        Uri handle = Uri.parse("tel:6505551234");
+        Intent callIntent = buildIntent(handle, Intent.ACTION_CALL, null);
+        ReceiverIntentPair result = regularCallTestHelper(callIntent, null);
+
+        String newEmergencyNumber = "1234567890";
+        result.receiver.setResultData(newEmergencyNumber);
+
+        doReturn(true).when(mPhoneNumberUtilsAdapterSpy).isPotentialLocalEmergencyNumber(
+                any(Context.class), eq(newEmergencyNumber));
+        result.receiver.onReceive(mContext, result.intent);
+        verify(mCall).disconnect(true);
+    }
+
+    private ReceiverIntentPair regularCallTestHelper(Intent intent,
+            Bundle expectedAdditionalExtras) {
+        Uri handle = intent.getData();
+        int videoState = VideoProfile.STATE_BIDIRECTIONAL;
+        boolean isSpeakerphoneOn = true;
+        intent.putExtra(TelecomManager.EXTRA_START_CALL_WITH_SPEAKERPHONE, isSpeakerphoneOn);
+        intent.putExtra(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE, videoState);
+
+        int result = processIntent(intent, true);
+
+        assertEquals(DisconnectCause.NOT_DISCONNECTED, result);
+        Bundle expectedExtras = createNumberExtras(handle.getSchemeSpecificPart());
+        if (expectedAdditionalExtras != null) {
+            expectedExtras.putAll(expectedAdditionalExtras);
+        }
+        return verifyBroadcastSent(handle.getSchemeSpecificPart(), expectedExtras);
+    }
+
+    private Intent buildIntent(Uri handle, String action, Bundle extras) {
+        Intent i = new Intent(action, handle);
+        if (extras != null) {
+            i.putExtras(extras);
+        }
+        return i;
+    }
+
+    private int processIntent(Intent intent,
+            boolean isDefaultPhoneApp) {
+        NewOutgoingCallIntentBroadcaster b = new NewOutgoingCallIntentBroadcaster(
+                mContext, mCallsManager, mCall, intent, mPhoneNumberUtilsAdapterSpy,
+                isDefaultPhoneApp);
+        return b.processIntent();
+    }
+
+    private ReceiverIntentPair verifyBroadcastSent(String number, Bundle expectedExtras) {
+        ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+        ArgumentCaptor<BroadcastReceiver> receiverCaptor =
+                ArgumentCaptor.forClass(BroadcastReceiver.class);
+
+        verify(mContext).sendOrderedBroadcastAsUser(
+                intentCaptor.capture(),
+                eq(UserHandle.CURRENT),
+                eq(Manifest.permission.PROCESS_OUTGOING_CALLS),
+                eq(AppOpsManager.OP_PROCESS_OUTGOING_CALLS),
+                receiverCaptor.capture(),
+                isNull(Handler.class),
+                eq(Activity.RESULT_OK),
+                eq(number),
+                isNull(Bundle.class));
+
+        Intent capturedIntent = intentCaptor.getValue();
+        assertEquals(Intent.ACTION_NEW_OUTGOING_CALL, capturedIntent.getAction());
+        assertEquals(Intent.FLAG_RECEIVER_FOREGROUND, capturedIntent.getFlags());
+        assertTrue(areBundlesEqual(expectedExtras, capturedIntent.getExtras()));
+
+        BroadcastReceiver receiver = receiverCaptor.getValue();
+        if (receiver != null) {
+            receiver.setPendingResult(
+                    new BroadcastReceiver.PendingResult(0, "", null, 0, true, false, null, 0, 0));
+        }
+
+        return new ReceiverIntentPair(receiver, capturedIntent);
+    }
+
+    private Bundle createNumberExtras(String number) {
+        Bundle b = new Bundle();
+        b.putString(Intent.EXTRA_PHONE_NUMBER, number);
+        return b;
+    }
+
+    private void verifyNoCallPlaced() {
+        verify(mCallsManager, never()).placeOutgoingCall(any(Call.class), any(Uri.class),
+                any(GatewayInfo.class), anyBoolean(), anyInt());
+    }
+
+    private void verifyNoBroadcastSent() {
+        verify(mContext, never()).sendOrderedBroadcastAsUser(
+                any(Intent.class),
+                any(UserHandle.class),
+                anyString(),
+                anyInt(),
+                any(BroadcastReceiver.class),
+                any(Handler.class),
+                anyInt(),
+                anyString(),
+                any(Bundle.class));
+    }
+
+    private static boolean areBundlesEqual(Bundle b1, Bundle b2) {
+        for (String key1 : b1.keySet()) {
+            if (!b1.get(key1).equals(b2.get(key1))) {
+                return false;
+            }
+        }
+
+        for (String key2 : b2.keySet()) {
+            if (!b2.get(key2).equals(b1.get(key2))) {
+                return false;
+            }
+        }
+        return true;
+    }
+}