Confirm managed call when there are ongoing self-managed calls.

When the user places a managed call while there are ongoing self-managed
calls, the system will now display a dialog giving the user the option of
NOT placing the managed call, or placing the managed call and disconnecting
the ongoing self-managed call(s).

This is done by stopping the outgoing call during startOutgoingCall and
bringing up a dialog to confirm whether the user wants to place the call.
If they chose to place the call, ongoing self-mgds calls are disconnected,
the call is added, and the NewutgoingCallBroadcast is sent as usual.

If the user chooses not to start the call, the call is cancelled.

Test: Manual
Bug: 37828805
Change-Id: I8539b0601cf5f324d2fb204485ee0d9bbf03426d
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 6579106..4323274 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -212,6 +212,8 @@
                 <action android:name="com.android.server.telecom.ACTION_SEND_SMS_FROM_NOTIFICATION" />
                 <action android:name="com.android.server.telecom.ACTION_ANSWER_FROM_NOTIFICATION" />
                 <action android:name="com.android.server.telecom.ACTION_REJECT_FROM_NOTIFICATION" />
+                <action android:name="com.android.server.telecom.PROCEED_WITH_CALL" />
+                <action android:name="com.android.server.telecom.CANCEL_CALL" />
             </intent-filter>
         </receiver>
 
@@ -254,6 +256,14 @@
                 android:process=":ui">
         </activity>
 
+        <activity android:name=".ui.ConfirmCallDialogActivity"
+                android:configChanges="orientation|screenSize|keyboardHidden"
+                android:excludeFromRecents="true"
+                android:launchMode="singleInstance"
+                android:theme="@style/Theme.Telecomm.Transparent"
+                android:process=":ui">
+        </activity>
+
         <activity android:name=".components.ChangeDefaultDialerDialog"
                   android:label="@string/change_default_dialer_dialog_title"
                   android:excludeFromRecents="true"
@@ -265,7 +275,6 @@
                 <category android:name="android.intent.category.DEFAULT" />
             </intent-filter>
         </activity>
-        <activity android:name=".testapps.IncomingSelfManagedCallActivity" />
 
         <receiver android:name=".components.PrimaryCallReceiver"
                 android:exported="true"
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 2363a1d..58e5284 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -252,4 +252,8 @@
     <string name="notification_channel_incoming_call">Incoming calls</string>
     <!-- Notification channel name for a channel containing missed call notifications. -->
     <string name="notification_channel_missed_call">Missed calls</string>
+
+    <!-- Alert dialog content used to inform the user that placing a new outgoing call will end the
+         ongoing call in the app "other_app". -->
+    <string name="alert_outgoing_call">Placing this call will end your <xliff:g id="other_app">%1$s</xliff:g> call.</string>
 </resources>
diff --git a/src/com/android/server/telecom/Call.java b/src/com/android/server/telecom/Call.java
index a71770f..6e4329d 100644
--- a/src/com/android/server/telecom/Call.java
+++ b/src/com/android/server/telecom/Call.java
@@ -17,6 +17,7 @@
 package com.android.server.telecom;
 
 import android.content.Context;
+import android.content.Intent;
 import android.graphics.Bitmap;
 import android.graphics.drawable.Drawable;
 import android.net.Uri;
@@ -317,6 +318,12 @@
 
     private Bundle mIntentExtras = new Bundle();
 
+    /**
+     * The {@link Intent} which originally created this call.  Only populated when we are putting a
+     * call into a pending state and need to pick up initiation of the call later.
+     */
+    private Intent mOriginalCallIntent = null;
+
     /** Set of listeners on this call.
      *
      * ConcurrentHashMap constructor params: 8 is initial table size, 0.9f is
@@ -1786,6 +1793,14 @@
         mIntentExtras = extras;
     }
 
+    public Intent getOriginalCallIntent() {
+        return mOriginalCallIntent;
+    }
+
+    public void setOriginalCallIntent(Intent intent) {
+        mOriginalCallIntent = intent;
+    }
+
     /**
      * @return the uri of the contact associated with this call.
      */
diff --git a/src/com/android/server/telecom/CallIntentProcessor.java b/src/com/android/server/telecom/CallIntentProcessor.java
index 2126c48..fdd4ebc 100644
--- a/src/com/android/server/telecom/CallIntentProcessor.java
+++ b/src/com/android/server/telecom/CallIntentProcessor.java
@@ -9,7 +9,6 @@
 import android.os.Trace;
 import android.os.UserHandle;
 import android.os.UserManager;
-import android.telecom.Connection;
 import android.telecom.DefaultDialerManager;
 import android.telecom.Log;
 import android.telecom.PhoneAccount;
@@ -128,8 +127,6 @@
                 VideoProfile.STATE_AUDIO_ONLY);
         clientExtras.putInt(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE, videoState);
 
-        final boolean isPrivilegedDialer = intent.getBooleanExtra(KEY_IS_PRIVILEGED_DIALER, false);
-
         boolean fixedInitiatingUser = fixInitiatingUserIfNecessary(context, intent);
         // Show the toast to warn user that it is a personal call though initiated in work profile.
         if (fixedInitiatingUser) {
@@ -140,23 +137,31 @@
 
         // Send to CallsManager to ensure the InCallUI gets kicked off before the broadcast returns
         Call call = callsManager
-                .startOutgoingCall(handle, phoneAccountHandle, clientExtras, initiatingUser);
+                .startOutgoingCall(handle, phoneAccountHandle, clientExtras, initiatingUser,
+                        intent);
 
         if (call != null) {
-            // Asynchronous calls should not usually be made inside a BroadcastReceiver because once
-            // onReceive is complete, the BroadcastReceiver's process runs the risk of getting
-            // killed if memory is scarce. However, this is OK here because the entire Telecom
-            // process will be running throughout the duration of the phone call and should never
-            // be killed.
-            NewOutgoingCallIntentBroadcaster broadcaster = new NewOutgoingCallIntentBroadcaster(
-                    context, callsManager, call, intent, callsManager.getPhoneNumberUtilsAdapter(),
-                    isPrivilegedDialer);
-            final int result = broadcaster.processIntent();
-            final boolean success = result == DisconnectCause.NOT_DISCONNECTED;
+            sendNewOutgoingCallIntent(context, call, callsManager, intent);
+        }
+    }
 
-            if (!success && call != null) {
-                disconnectCallAndShowErrorDialog(context, call, result);
-            }
+    static void sendNewOutgoingCallIntent(Context context, Call call, CallsManager callsManager,
+            Intent intent) {
+        // Asynchronous calls should not usually be made inside a BroadcastReceiver because once
+        // onReceive is complete, the BroadcastReceiver's process runs the risk of getting
+        // killed if memory is scarce. However, this is OK here because the entire Telecom
+        // process will be running throughout the duration of the phone call and should never
+        // be killed.
+        final boolean isPrivilegedDialer = intent.getBooleanExtra(KEY_IS_PRIVILEGED_DIALER, false);
+
+        NewOutgoingCallIntentBroadcaster broadcaster = new NewOutgoingCallIntentBroadcaster(
+                context, callsManager, call, intent, callsManager.getPhoneNumberUtilsAdapter(),
+                isPrivilegedDialer);
+        final int result = broadcaster.processIntent();
+        final boolean success = result == DisconnectCause.NOT_DISCONNECTED;
+
+        if (!success && call != null) {
+            disconnectCallAndShowErrorDialog(context, call, result);
         }
     }
 
diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java
index 8f97b1b..4f03177 100644
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -65,6 +65,7 @@
 import com.android.server.telecom.callfiltering.DirectToVoicemailCallFilter;
 import com.android.server.telecom.callfiltering.IncomingCallFilter;
 import com.android.server.telecom.components.ErrorDialogActivity;
+import com.android.server.telecom.ui.ConfirmCallDialogActivity;
 import com.android.server.telecom.ui.IncomingCallNotifier;
 
 import java.util.ArrayList;
@@ -208,6 +209,12 @@
             new ConcurrentHashMap<Call, Boolean>(8, 0.9f, 1));
 
     /**
+     * A pending call is one which requires user-intervention in order to be placed.
+     * Used by {@link #startCallConfirmation(Call)}.
+     */
+    private Call mPendingCall;
+
+    /**
      * The current telecom call ID.  Used when creating new instances of {@link Call}.  Should
      * only be accessed using the {@link #getNextCallId()} method which synchronizes on the
      * {@link #mLock} sync root.
@@ -994,15 +1001,15 @@
      * For managed connections, this is the first step to launching the Incall UI.
      * For self-managed connections, we don't expect the Incall UI to launch, but this is still a
      * first step in getting the self-managed ConnectionService to create the connection.
-     *
      * @param handle Handle to connect the call with.
      * @param phoneAccountHandle The phone account which contains the component name of the
      *        connection service to use for this call.
      * @param extras The optional extras Bundle passed with the intent used for the incoming call.
      * @param initiatingUser {@link UserHandle} of user that place the outgoing call.
+     * @param originalIntent
      */
     Call startOutgoingCall(Uri handle, PhoneAccountHandle phoneAccountHandle, Bundle extras,
-            UserHandle initiatingUser) {
+            UserHandle initiatingUser, Intent originalIntent) {
         boolean isReusedCall = true;
         Call call = reuseOutgoingCall(handle);
 
@@ -1042,7 +1049,6 @@
             }
 
             call.setInitiatingUser(initiatingUser);
-
             isReusedCall = false;
         }
 
@@ -1160,9 +1166,15 @@
         }
         setIntentExtrasAndStartTime(call, extras);
 
-        // Do not add the call if it is a potential MMI code.
-        if ((isPotentialMMICode(handle) || isPotentialInCallMMICode) && !needsAccountSelection) {
+        if ((isPotentialMMICode(handle) || isPotentialInCallMMICode)
+                && !needsAccountSelection) {
+            // Do not add the call if it is a potential MMI code.
             call.addListener(this);
+        } else if (!isSelfManaged && hasSelfManagedCalls() && !call.isEmergencyCall()) {
+            // Adding a managed call and there are ongoing self-managed call(s).
+            call.setOriginalCallIntent(originalIntent);
+            startCallConfirmation(call);
+            return null;
         } else if (!mCalls.contains(call)) {
             // We check if mCalls already contains the call because we could potentially be reusing
             // a call which was previously added (See {@link #reuseOutgoingCall}).
@@ -1235,23 +1247,11 @@
             if (call.isSelfManaged() && !isOutgoingCallPermitted) {
                 notifyCreateConnectionFailed(call.getTargetPhoneAccount(), call);
             } else if (!call.isSelfManaged() && hasSelfManagedCalls() && !call.isEmergencyCall()) {
-                Call activeCall = getActiveCall();
-                CharSequence errorMessage;
-                if (activeCall == null) {
-                    // Realistically this shouldn't happen, but best to handle gracefully
-                    errorMessage = mContext.getText(R.string.cant_call_due_to_ongoing_unknown_call);
-                } else {
-                    errorMessage = mContext.getString(R.string.cant_call_due_to_ongoing_call,
-                            activeCall.getTargetPhoneAccountLabel());
-                }
-                // Call is managed and there are ongoing self-managed calls.
-                markCallAsDisconnected(call, new DisconnectCause(DisconnectCause.ERROR,
-                        errorMessage, errorMessage, "Ongoing call in another app."));
-                markCallAsRemoved(call);
+                markCallDisconnectedDueToSelfManagedCall(call);
             } else {
                 if (call.isEmergencyCall()) {
                     // Disconnect all self-managed calls to make priority for emergency call.
-                    mCalls.stream().filter(c -> c.isSelfManaged()).forEach(c -> c.disconnect());
+                    disconnectSelfManagedCalls();
                 }
 
                 call.startCreateConnection(mPhoneAccountRegistrar);
@@ -1750,6 +1750,33 @@
     }
 
     /**
+     * Given a call, marks the call as disconnected and removes it.  Set the error message to
+     * indicate to the user that the call cannot me placed due to an ongoing call in another app.
+     *
+     * Used when there are ongoing self-managed calls and the user tries to make an outgoing managed
+     * call.  Called by {@link #startCallConfirmation(Call)} when the user is already confirming an
+     * outgoing call.  Realistically this should almost never be called since in practice the user
+     * won't make multiple outgoing calls at the same time.
+     *
+     * @param call The call to mark as disconnected.
+     */
+    void markCallDisconnectedDueToSelfManagedCall(Call call) {
+        Call activeCall = getActiveCall();
+        CharSequence errorMessage;
+        if (activeCall == null) {
+            // Realistically this shouldn't happen, but best to handle gracefully
+            errorMessage = mContext.getText(R.string.cant_call_due_to_ongoing_unknown_call);
+        } else {
+            errorMessage = mContext.getString(R.string.cant_call_due_to_ongoing_call,
+                    activeCall.getTargetPhoneAccountLabel());
+        }
+        // Call is managed and there are ongoing self-managed calls.
+        markCallAsDisconnected(call, new DisconnectCause(DisconnectCause.ERROR,
+                errorMessage, errorMessage, "Ongoing call in another app."));
+        markCallAsRemoved(call);
+    }
+
+    /**
      * Cleans up any calls currently associated with the specified connection service when the
      * service binder disconnects unexpectedly.
      *
@@ -2761,6 +2788,97 @@
     }
 
     /**
+     * Used to confirm creation of an outgoing call which was marked as pending confirmation in
+     * {@link #startOutgoingCall(Uri, PhoneAccountHandle, Bundle, UserHandle, Intent)}.
+     * Called via {@link TelecomBroadcastIntentProcessor} for a call which was confirmed via
+     * {@link ConfirmCallDialogActivity}.
+     * @param callId The call ID of the call to confirm.
+     */
+    public void confirmPendingCall(String callId) {
+        Log.i(this, "confirmPendingCall: callId=%s", callId);
+        if (mPendingCall != null && mPendingCall.getId().equals(callId)) {
+            Log.addEvent(mPendingCall, LogUtils.Events.USER_CONFIRMED);
+            addCall(mPendingCall);
+
+            // We are going to place the new outgoing call, so disconnect any ongoing self-managed
+            // calls which are ongoing at this time.
+            disconnectSelfManagedCalls();
+
+            // Kick of the new outgoing call intent from where it left off prior to confirming the
+            // call.
+            CallIntentProcessor.sendNewOutgoingCallIntent(mContext, mPendingCall, this,
+                    mPendingCall.getOriginalCallIntent());
+            mPendingCall = null;
+        }
+    }
+
+    /**
+     * Used to cancel an outgoing call which was marked as pending confirmation in
+     * {@link #startOutgoingCall(Uri, PhoneAccountHandle, Bundle, UserHandle, Intent)}.
+     * Called via {@link TelecomBroadcastIntentProcessor} for a call which was confirmed via
+     * {@link ConfirmCallDialogActivity}.
+     * @param callId The call ID of the call to cancel.
+     */
+    public void cancelPendingCall(String callId) {
+        Log.i(this, "cancelPendingCall: callId=%s", callId);
+        if (mPendingCall != null && mPendingCall.getId().equals(callId)) {
+            Log.addEvent(mPendingCall, LogUtils.Events.USER_CANCELLED);
+            markCallAsDisconnected(mPendingCall, new DisconnectCause(DisconnectCause.CANCELED));
+            markCallAsRemoved(mPendingCall);
+            mPendingCall = null;
+        }
+    }
+
+    /**
+     * Called from {@link #startOutgoingCall(Uri, PhoneAccountHandle, Bundle, UserHandle, Intent)} when
+     * a managed call is added while there are ongoing self-managed calls.  Starts
+     * {@link ConfirmCallDialogActivity} to prompt the user to see if they wish to place the
+     * outgoing call or not.
+     * @param call The call to confirm.
+     */
+    private void startCallConfirmation(Call call) {
+        if (mPendingCall != null) {
+            Log.i(this, "startCallConfirmation: call %s is already pending; disconnecting %s",
+                    mPendingCall.getId(), call.getId());
+            markCallDisconnectedDueToSelfManagedCall(call);
+            return;
+        }
+        Log.addEvent(call, LogUtils.Events.USER_CONFIRMATION);
+        mPendingCall = call;
+
+        // Figure out the name of the app in charge of the self-managed call(s).
+        Call selfManagedCall = mCalls.stream()
+                .filter(c -> c.isSelfManaged())
+                .findFirst()
+                .orElse(null);
+        CharSequence ongoingAppName = "";
+        if (selfManagedCall != null) {
+            ongoingAppName = selfManagedCall.getTargetPhoneAccountLabel();
+        }
+        Log.i(this, "startCallConfirmation: callId=%s, ongoingApp=%s", call.getId(),
+                ongoingAppName);
+
+        Intent confirmIntent = new Intent(mContext, ConfirmCallDialogActivity.class);
+        confirmIntent.putExtra(ConfirmCallDialogActivity.EXTRA_OUTGOING_CALL_ID, call.getId());
+        confirmIntent.putExtra(ConfirmCallDialogActivity.EXTRA_ONGOING_APP_NAME, ongoingAppName);
+        confirmIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        mContext.startActivityAsUser(confirmIntent, UserHandle.CURRENT);
+    }
+
+    /**
+     * Disconnects all self-managed calls.
+     */
+    private void disconnectSelfManagedCalls() {
+        // Disconnect all self-managed calls to make priority for emergency call.
+        // Use Call.disconnect() to command the ConnectionService to disconnect the calls.
+        // CallsManager.markCallAsDisconnected doesn't actually tell the ConnectionService to
+        // disconnect.
+        mCalls.stream()
+                .filter(c -> c.isSelfManaged())
+                .forEach(c -> c.disconnect());
+    }
+
+    /**
      * Dumps the state of the {@link CallsManager}.
      *
      * @param pw The {@code IndentingPrintWriter} to write the state to.
@@ -2776,6 +2894,11 @@
             pw.decreaseIndent();
         }
 
+        if (mPendingCall != null) {
+            pw.print("mPendingCall:");
+            pw.println(mPendingCall.getId());
+        }
+
         if (mCallAudioManager != null) {
             pw.println("mCallAudioManager:");
             pw.increaseIndent();
@@ -2903,7 +3026,7 @@
         extras.putParcelable(TelecomManager.EXTRA_CALL_AUDIO_STATE,
                 mCallAudioManager.getCallAudioState());
         Call handoverToCall = startOutgoingCall(handoverFromCall.getHandle(), handoverToHandle,
-                extras, getCurrentUserHandle());
+                extras, getCurrentUserHandle(), null /* originalIntent */);
         Log.addEvent(handoverFromCall, LogUtils.Events.START_HANDOVER,
                 "handOverFrom=%s, handOverTo=%s", handoverFromCall.getId(), handoverToCall.getId());
         handoverFromCall.setHandoverToCall(handoverToCall);
diff --git a/src/com/android/server/telecom/LogUtils.java b/src/com/android/server/telecom/LogUtils.java
index c61ff65..34d875e 100644
--- a/src/com/android/server/telecom/LogUtils.java
+++ b/src/com/android/server/telecom/LogUtils.java
@@ -54,6 +54,9 @@
 
     public final static class Events {
         public static final String CREATED = "CREATED";
+        public static final String USER_CONFIRMATION = "USER_CONFIRMATION";
+        public static final String USER_CONFIRMED = "USER_CONFIRMED";
+        public static final String USER_CANCELLED = "USER_CANCELLED";
         public static final String DESTROYED = "DESTROYED";
         public static final String SET_CONNECTING = "SET_CONNECTING";
         public static final String SET_DIALING = "SET_DIALING";
diff --git a/src/com/android/server/telecom/TelecomBroadcastIntentProcessor.java b/src/com/android/server/telecom/TelecomBroadcastIntentProcessor.java
index 5df4451..a51ef73 100644
--- a/src/com/android/server/telecom/TelecomBroadcastIntentProcessor.java
+++ b/src/com/android/server/telecom/TelecomBroadcastIntentProcessor.java
@@ -21,6 +21,8 @@
 import android.os.UserHandle;
 import android.telecom.Log;
 
+import com.android.server.telecom.ui.ConfirmCallDialogActivity;
+
 public final class TelecomBroadcastIntentProcessor {
     /** The action used to send SMS response for the missed call notification. */
     public static final String ACTION_SEND_SMS_FROM_NOTIFICATION =
@@ -48,6 +50,20 @@
     public static final String ACTION_REJECT_FROM_NOTIFICATION =
             "com.android.server.telecom.ACTION_REJECT_FROM_NOTIFICATION";
 
+    /**
+     * The action used to proceed with a call being confirmed via
+     * {@link com.android.server.telecom.ui.ConfirmCallDialogActivity}.
+     */
+    public static final String ACTION_PROCEED_WITH_CALL =
+            "com.android.server.telecom.PROCEED_WITH_CALL";
+
+    /**
+     * The action used to cancel a call being confirmed via
+     * {@link com.android.server.telecom.ui.ConfirmCallDialogActivity}.
+     */
+    public static final String ACTION_CANCEL_CALL =
+            "com.android.server.telecom.CANCEL_CALL";
+
     public static final String EXTRA_USERHANDLE = "userhandle";
 
     private final Context mContext;
@@ -112,6 +128,7 @@
         } else if (ACTION_REJECT_FROM_NOTIFICATION.equals(action)) {
             Log.startSession("TBIP.aRFM");
             try {
+
                 // Reject the current ringing call.
                 Call incomingCall = mCallsManager.getIncomingCallNotifier().getIncomingCall();
                 if (incomingCall != null) {
@@ -120,6 +137,24 @@
             } finally {
                 Log.endSession();
             }
+        } else if (ACTION_PROCEED_WITH_CALL.equals(action)) {
+            Log.startSession("TBIP.aPWC");
+            try {
+                String callId = intent.getStringExtra(
+                        ConfirmCallDialogActivity.EXTRA_OUTGOING_CALL_ID);
+                mCallsManager.confirmPendingCall(callId);
+            } finally {
+                Log.endSession();
+            }
+        } else if (ACTION_CANCEL_CALL.equals(action)) {
+            Log.startSession("TBIP.aCC");
+            try {
+                String callId = intent.getStringExtra(
+                        ConfirmCallDialogActivity.EXTRA_OUTGOING_CALL_ID);
+                mCallsManager.cancelPendingCall(callId);
+            } finally {
+                Log.endSession();
+            }
         }
     }
 
diff --git a/src/com/android/server/telecom/ui/ConfirmCallDialogActivity.java b/src/com/android/server/telecom/ui/ConfirmCallDialogActivity.java
new file mode 100644
index 0000000..4735e3c
--- /dev/null
+++ b/src/com/android/server/telecom/ui/ConfirmCallDialogActivity.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2017 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.ui;
+
+import com.android.server.telecom.R;
+import com.android.server.telecom.TelecomBroadcastIntentProcessor;
+import com.android.server.telecom.components.TelecomBroadcastReceiver;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.os.Bundle;
+import android.telecom.Log;
+
+/**
+ * Dialog activity used when there is an ongoing self-managed call and the user initiates a new
+ * outgoing managed call.  The dialog prompts the user to see if they want to disconnect the ongoing
+ * self-managed call in order to place the new managed call.
+ */
+public class ConfirmCallDialogActivity extends Activity {
+    public static final String EXTRA_OUTGOING_CALL_ID = "android.telecom.extra.OUTGOING_CALL_ID";
+    public static final String EXTRA_ONGOING_APP_NAME = "android.telecom.extra.ONGOING_APP_NAME";
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        final String callId = getIntent().getStringExtra(EXTRA_OUTGOING_CALL_ID);
+        final CharSequence ongoingAppName = getIntent().getCharSequenceExtra(
+                EXTRA_ONGOING_APP_NAME);
+        showDialog(callId, ongoingAppName);
+    }
+
+    private void showDialog(final String callId, CharSequence ongoingAppName) {
+        Log.i(this, "showDialog: confirming callId=%s, ongoing=%s", callId, ongoingAppName);
+        CharSequence message = getString(R.string.alert_outgoing_call, ongoingAppName);
+        final AlertDialog errorDialog = new AlertDialog.Builder(this)
+                .setMessage(message)
+                .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
+                    @Override
+                    public void onClick(DialogInterface dialog, int which) {
+                        Intent proceedWithCall = new Intent(
+                                TelecomBroadcastIntentProcessor.ACTION_PROCEED_WITH_CALL, null,
+                                ConfirmCallDialogActivity.this,
+                                TelecomBroadcastReceiver.class);
+                        proceedWithCall.putExtra(EXTRA_OUTGOING_CALL_ID, callId);
+                        sendBroadcast(proceedWithCall);
+                        dialog.dismiss();
+                        finish();
+                    }
+                })
+                .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
+                    @Override
+                    public void onClick(DialogInterface dialog, int which) {
+                        Intent cancelCall = new Intent(
+                                TelecomBroadcastIntentProcessor.ACTION_CANCEL_CALL, null,
+                                ConfirmCallDialogActivity.this,
+                                TelecomBroadcastReceiver.class);
+                        cancelCall.putExtra(EXTRA_OUTGOING_CALL_ID, callId);
+                        sendBroadcast(cancelCall);
+                        dialog.dismiss();
+                        finish();
+                    }
+                })
+                .setOnCancelListener(new DialogInterface.OnCancelListener() {
+                    @Override
+                    public void onCancel(DialogInterface dialog) {
+                        Intent cancelCall = new Intent(
+                                TelecomBroadcastIntentProcessor.ACTION_CANCEL_CALL, null,
+                                ConfirmCallDialogActivity.this,
+                                TelecomBroadcastReceiver.class);
+                        cancelCall.putExtra(EXTRA_OUTGOING_CALL_ID, callId);
+                        sendBroadcast(cancelCall);
+                        dialog.dismiss();
+                        finish();
+                    }
+                })
+                .create();
+
+        errorDialog.show();
+    }
+}