Merge "Add color to PhoneAccount objects (2/3)" into lmp-sprout-dev
diff --git a/src/com/android/server/telecom/BluetoothPhoneService.java b/src/com/android/server/telecom/BluetoothPhoneService.java
index ad6d490..62f4f5a 100644
--- a/src/com/android/server/telecom/BluetoothPhoneService.java
+++ b/src/com/android/server/telecom/BluetoothPhoneService.java
@@ -299,7 +299,7 @@
 
                 case MSG_QUERY_PHONE_STATE:
                     try {
-                        updateHeadsetWithCallState();
+                        updateHeadsetWithCallState(true /* force */);
                     } finally {
                         if (request != null) {
                             request.setResult(true);
@@ -317,28 +317,28 @@
     private CallsManagerListener mCallsManagerListener = new CallsManagerListenerBase() {
         @Override
         public void onCallAdded(Call call) {
-            updateHeadsetWithCallState();
+            updateHeadsetWithCallState(false /* force */);
         }
 
         @Override
         public void onCallRemoved(Call call) {
             mClccIndexMap.remove(call);
-            updateHeadsetWithCallState();
+            updateHeadsetWithCallState(false /* force */);
         }
 
         @Override
         public void onCallStateChanged(Call call, int oldState, int newState) {
-            updateHeadsetWithCallState();
+            updateHeadsetWithCallState(false /* force */);
         }
 
         @Override
         public void onForegroundCallChanged(Call oldForegroundCall, Call newForegroundCall) {
-            updateHeadsetWithCallState();
+            updateHeadsetWithCallState(false /* force */);
         }
 
         @Override
         public void onIsConferencedChanged(Call call) {
-            updateHeadsetWithCallState();
+            updateHeadsetWithCallState(false /* force */);
         }
     };
 
@@ -412,7 +412,7 @@
         registerReceiver(mBluetoothAdapterReceiver, intentFilter);
 
         CallsManager.getInstance().addListener(mCallsManagerListener);
-        updateHeadsetWithCallState();
+        updateHeadsetWithCallState(false /* force */);
     }
 
     @Override
@@ -581,13 +581,18 @@
                     index, direction, state, isPartOfConference, Log.piiHandle(address),
                     addressType);
         }
-        mBluetoothHeadset.clccResponse(
-                index, direction, state, 0, isPartOfConference, address, addressType);
+
+        if (mBluetoothHeadset != null) {
+            mBluetoothHeadset.clccResponse(
+                    index, direction, state, 0, isPartOfConference, address, addressType);
+        }
     }
 
     private void sendClccEndMarker() {
         // End marker is recognized with an index value of 0. All other parameters are ignored.
-        mBluetoothHeadset.clccResponse(0 /* index */, 0, 0, 0, false, null, 0);
+        if (mBluetoothHeadset != null) {
+            mBluetoothHeadset.clccResponse(0 /* index */, 0, 0, 0, false, null, 0);
+        }
     }
 
     /**
@@ -609,7 +614,14 @@
         return i;
     }
 
-    private void updateHeadsetWithCallState() {
+    /**
+     * Sends an update of the current call state to the current Headset.
+     *
+     * @param force {@code true} if the headset state should be sent regardless if no changes to the
+     *      state have occurred, {@code false} if the state should only be sent if the state has
+     *      changed.
+     */
+    private void updateHeadsetWithCallState(boolean force) {
         CallsManager callsManager = getCallsManager();
         Call activeCall = callsManager.getActiveCall();
         Call ringingCall = callsManager.getRingingCall();
@@ -650,7 +662,8 @@
                  numHeldCalls != mNumHeldCalls ||
                  bluetoothCallState != mBluetoothCallState ||
                  !TextUtils.equals(ringingAddress, mRingingAddress) ||
-                 ringingAddressType != mRingingAddressType)) {
+                 ringingAddressType != mRingingAddressType ||
+                 force)) {
 
             // If the call is transitioning into the alerting state, send DIALING first.
             // Some devices expect to see a DIALING state prior to seeing an ALERTING state
diff --git a/src/com/android/server/telecom/Call.java b/src/com/android/server/telecom/Call.java
index ca72ec8..85463eb 100644
--- a/src/com/android/server/telecom/Call.java
+++ b/src/com/android/server/telecom/Call.java
@@ -70,6 +70,8 @@
         void onFailedOutgoingCall(Call call, DisconnectCause disconnectCause);
         void onSuccessfulIncomingCall(Call call);
         void onFailedIncomingCall(Call call);
+        void onSuccessfulUnknownCall(Call call, int callState);
+        void onFailedUnknownCall(Call call);
         void onRingbackRequested(Call call, boolean ringbackRequested);
         void onPostDialWait(Call call, String remaining);
         void onCallCapabilitiesChanged(Call call);
@@ -99,6 +101,10 @@
         @Override
         public void onFailedIncomingCall(Call call) {}
         @Override
+        public void onSuccessfulUnknownCall(Call call, int callState) {}
+        @Override
+        public void onFailedUnknownCall(Call call) {}
+        @Override
         public void onRingbackRequested(Call call, boolean ringbackRequested) {}
         @Override
         public void onPostDialWait(Call call, String remaining) {}
@@ -167,6 +173,11 @@
     /** True if this is an incoming call. */
     private final boolean mIsIncoming;
 
+    /** True if this is a currently unknown call that was not previously tracked by CallsManager,
+     *  and did not originate via the regular incoming/outgoing call code paths.
+     */
+    private boolean mIsUnknown;
+
     /**
      * The time this call was created. Beyond logging and such, may also be used for bookkeeping
      * and specifically for marking certain call attempts as failed attempts.
@@ -286,6 +297,8 @@
     // switches every time the user hits "swap".
     private Call mConferenceLevelActiveCall = null;
 
+    private boolean mIsLocallyDisconnecting = false;
+
     /**
      * Persists the specified parameters and initializes the new instance.
      *
@@ -361,6 +374,7 @@
             maybeLoadCannedSmsResponses();
 
             if (mState == CallState.DISCONNECTED) {
+                setLocallyDisconnecting(false);
                 fixParentAfterDisconnect();
             }
         }
@@ -660,7 +674,11 @@
             mConferenceableCalls.add(idMapper.getCall(id));
         }
 
-        if (mIsIncoming) {
+        if (mIsUnknown) {
+            for (Listener l : mListeners) {
+                l.onSuccessfulUnknownCall(this, getStateFromConnectionState(connection.getState()));
+            }
+        } else if (mIsIncoming) {
             // We do not handle incoming calls immediately when they are verified by the connection
             // service. We allow the caller-info-query code to execute first so that we can read the
             // direct-to-voicemail property before deciding if we want to show the incoming call to
@@ -686,7 +704,11 @@
         setDisconnectCause(disconnectCause);
         CallsManager.getInstance().markCallAsDisconnected(this, disconnectCause);
 
-        if (mIsIncoming) {
+        if (mIsUnknown) {
+            for (Listener listener : mListeners) {
+                listener.onFailedUnknownCall(this);
+            }
+        } else if (mIsIncoming) {
             for (Listener listener : mListeners) {
                 listener.onFailedIncomingCall(this);
             }
@@ -725,6 +747,9 @@
      * Attempts to disconnect the call through the connection service.
      */
     void disconnect() {
+        // Track that the call is now locally disconnecting.
+        setLocallyDisconnecting(true);
+
         if (mState == CallState.NEW || mState == CallState.PRE_DIAL_WAIT ||
                 mState == CallState.CONNECTING) {
             Log.v(this, "Aborting call %s", this);
@@ -1255,6 +1280,32 @@
         }
     }
 
+    public boolean isUnknown() {
+        return mIsUnknown;
+    }
+
+    public void setIsUnknown(boolean isUnknown) {
+        mIsUnknown = isUnknown;
+    }
+
+    /**
+     * Determines if this call is in a disconnecting state.
+     *
+     * @return {@code true} if this call is locally disconnecting.
+     */
+    public boolean isLocallyDisconnecting() {
+        return mIsLocallyDisconnecting;
+    }
+
+    /**
+     * Sets whether this call is in a disconnecting state.
+     *
+     * @param isLocallyDisconnecting {@code true} if this call is locally disconnecting.
+     */
+    private void setLocallyDisconnecting(boolean isLocallyDisconnecting) {
+        mIsLocallyDisconnecting = isLocallyDisconnecting;
+    }
+
     static int getStateFromConnectionState(int state) {
         switch (state) {
             case Connection.STATE_INITIALIZING:
diff --git a/src/com/android/server/telecom/CallActivity.java b/src/com/android/server/telecom/CallActivity.java
index b249788..7022327 100644
--- a/src/com/android/server/telecom/CallActivity.java
+++ b/src/com/android/server/telecom/CallActivity.java
@@ -78,6 +78,7 @@
             return;
         }
 
+        verifyCallAction(intent);
         String action = intent.getAction();
 
         if (Intent.ACTION_CALL.equals(action) ||
@@ -89,6 +90,17 @@
         }
     }
 
+    private void verifyCallAction(Intent intent) {
+        if (CallActivity.class.getName().equals(intent.getComponent().getClassName())) {
+            // If we were launched directly from the CallActivity, not one of its more privileged
+            // aliases, then make sure that only the non-privileged actions are allowed.
+            if (!Intent.ACTION_CALL.equals(intent.getAction())) {
+                Log.w(this, "Attempt to deliver non-CALL action; forcing to CALL");
+                intent.setAction(Intent.ACTION_CALL);
+            }
+        }
+    }
+
     private void processOutgoingCallIntent(Intent intent) {
         Uri handle = intent.getData();
         String scheme = handle.getScheme();
@@ -112,11 +124,20 @@
         }
 
         intent.putExtra(CallReceiver.KEY_IS_DEFAULT_DIALER, isDefaultDialer());
-        sendBroadcastToReceiver(intent, false /* isIncoming */);
+
+        if (UserHandle.myUserId() == UserHandle.USER_OWNER) {
+            CallReceiver.processOutgoingCallIntent(getApplicationContext(), intent);
+        } else {
+            sendBroadcastToReceiver(intent, false /* isIncoming */);
+        }
     }
 
     private void processIncomingCallIntent(Intent intent) {
-        sendBroadcastToReceiver(intent, true /* isIncoming */);
+        if (UserHandle.myUserId() == UserHandle.USER_OWNER) {
+            CallReceiver.processIncomingCallIntent(intent);
+        } else {
+            sendBroadcastToReceiver(intent, true /* isIncoming */);
+        }
     }
 
     private boolean isDefaultDialer() {
@@ -147,8 +168,7 @@
      */
     private boolean sendBroadcastToReceiver(Intent intent, boolean incoming) {
         intent.putExtra(CallReceiver.KEY_IS_INCOMING_CALL, incoming);
-        // Clear out any flags set previously since we don't need it for the broadcast.
-        intent.setFlags(0);
+        intent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
         intent.setClass(this, CallReceiver.class);
         Log.d(this, "Sending broadcast as user to CallReceiver- isIncoming: %s", incoming);
         sendBroadcastAsUser(intent, UserHandle.OWNER);
diff --git a/src/com/android/server/telecom/CallReceiver.java b/src/com/android/server/telecom/CallReceiver.java
index fe82b67..17ca3e5 100644
--- a/src/com/android/server/telecom/CallReceiver.java
+++ b/src/com/android/server/telecom/CallReceiver.java
@@ -19,17 +19,23 @@
  * the primary user.
  */
 public class CallReceiver extends BroadcastReceiver {
+    private static final String TAG = CallReceiver.class.getName();
 
+    static final String KEY_IS_UNKNOWN_CALL = "is_unknown_call";
     static final String KEY_IS_INCOMING_CALL = "is_incoming_call";
     static final String KEY_IS_DEFAULT_DIALER =
             "is_default_dialer";
 
     @Override
     public void onReceive(Context context, Intent intent) {
+        final boolean isUnknownCall = intent.getBooleanExtra(KEY_IS_UNKNOWN_CALL, false);
         final boolean isIncomingCall = intent.getBooleanExtra(KEY_IS_INCOMING_CALL, false);
-        Log.d(this, "onReceive - isIncomingCall: %s", isIncomingCall);
+        Log.i(this, "onReceive - isIncomingCall: %s isUnknownCall: %s", isIncomingCall,
+                isUnknownCall);
 
-        if (isIncomingCall) {
+        if (isUnknownCall) {
+            processUnknownCallIntent(intent);
+        } else if (isIncomingCall) {
             processIncomingCallIntent(intent);
         } else {
             processOutgoingCallIntent(context, intent);
@@ -41,7 +47,7 @@
      *
      * @param intent Call intent containing data about the handle to call.
      */
-    private void processOutgoingCallIntent(Context context, Intent intent) {
+    static void processOutgoingCallIntent(Context context, Intent intent) {
         Uri handle = intent.getData();
         String scheme = handle.getScheme();
         String uriString = handle.getSchemeSpecificPart();
@@ -84,16 +90,16 @@
         }
     }
 
-    private void processIncomingCallIntent(Intent intent) {
+    static void processIncomingCallIntent(Intent intent) {
         PhoneAccountHandle phoneAccountHandle = intent.getParcelableExtra(
                 TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE);
 
         if (phoneAccountHandle == null) {
-            Log.w(this, "Rejecting incoming call due to null phone account");
+            Log.w(TAG, "Rejecting incoming call due to null phone account");
             return;
         }
         if (phoneAccountHandle.getComponentName() == null) {
-            Log.w(this, "Rejecting incoming call due to null component name");
+            Log.w(TAG, "Rejecting incoming call due to null component name");
             return;
         }
 
@@ -105,16 +111,33 @@
             clientExtras = Bundle.EMPTY;
         }
 
-        Log.d(this, "Processing incoming call from connection service [%s]",
+        Log.d(TAG, "Processing incoming call from connection service [%s]",
                 phoneAccountHandle.getComponentName());
         getCallsManager().processIncomingCallIntent(phoneAccountHandle, clientExtras);
     }
 
-    CallsManager getCallsManager() {
+    private void processUnknownCallIntent(Intent intent) {
+        PhoneAccountHandle phoneAccountHandle = intent.getParcelableExtra(
+                TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE);
+
+        if (phoneAccountHandle == null) {
+            Log.w(this, "Rejecting unknown call due to null phone account");
+            return;
+        }
+        if (phoneAccountHandle.getComponentName() == null) {
+            Log.w(this, "Rejecting unknown call due to null component name");
+            return;
+        }
+
+        getCallsManager().addNewUnknownCall(phoneAccountHandle, intent.getExtras());
+    }
+
+    static CallsManager getCallsManager() {
         return CallsManager.getInstance();
     }
 
-    private void disconnectCallAndShowErrorDialog(Context context, Call call, int errorCode) {
+    private static void disconnectCallAndShowErrorDialog(
+            Context context, Call call, int errorCode) {
         call.disconnect();
         final Intent errorIntent = new Intent(context, ErrorDialogActivity.class);
         int errorMessageId = -1;
diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java
index 1b0a9e3..52c0b44 100644
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -32,6 +32,7 @@
 import android.telephony.TelephonyManager;
 
 import com.android.internal.util.IndentingPrintWriter;
+
 import com.google.common.collect.ImmutableCollection;
 import com.google.common.collect.ImmutableList;
 
@@ -226,6 +227,20 @@
     }
 
     @Override
+    public void onSuccessfulUnknownCall(Call call, int callState) {
+        setCallState(call, callState);
+        Log.i(this, "onSuccessfulUnknownCall for call %s", call);
+        addCall(call);
+    }
+
+    @Override
+    public void onFailedUnknownCall(Call call) {
+        Log.i(this, "onFailedUnknownCall for call %s", call);
+        setCallState(call, CallState.DISCONNECTED);
+        call.removeListener(this);
+    }
+
+    @Override
     public void onRingbackRequested(Call call, boolean ringback) {
         for (CallsManagerListener listener : mListeners) {
             listener.onRingbackRequested(call, ringback);
@@ -336,6 +351,27 @@
         call.startCreateConnection(mPhoneAccountRegistrar);
     }
 
+    void addNewUnknownCall(PhoneAccountHandle phoneAccountHandle, Bundle extras) {
+        Uri handle = extras.getParcelable(TelecomManager.EXTRA_UNKNOWN_CALL_HANDLE);
+        Log.i(this, "addNewUnknownCall with handle: %s", Log.pii(handle));
+        Call call = new Call(
+                mContext,
+                mConnectionServiceRepository,
+                handle,
+                null /* gatewayInfo */,
+                null /* connectionManagerPhoneAccount */,
+                phoneAccountHandle,
+                // Use onCreateIncomingConnection in TelephonyConnectionService, so that we attach
+                // to the existing connection instead of trying to create a new one.
+                true /* isIncoming */,
+                false /* isConference */);
+        call.setConnectTimeMillis(System.currentTimeMillis());
+        call.setIsUnknown(true);
+        call.setExtras(extras);
+        call.addListener(this);
+        call.startCreateConnection(mPhoneAccountRegistrar);
+    }
+
     /**
      * Kicks off the first steps to creating an outgoing call so that InCallUI can launch.
      *
diff --git a/src/com/android/server/telecom/ConnectionServiceWrapper.java b/src/com/android/server/telecom/ConnectionServiceWrapper.java
index cacdc81..32c6107 100644
--- a/src/com/android/server/telecom/ConnectionServiceWrapper.java
+++ b/src/com/android/server/telecom/ConnectionServiceWrapper.java
@@ -643,7 +643,8 @@
                                     call.getHandle(),
                                     extras,
                                     call.getVideoState()),
-                            call.isIncoming());
+                            call.isIncoming(),
+                            call.isUnknown());
                 } catch (RemoteException e) {
                     Log.e(this, e, "Failure to createConnection -- %s", getComponentName());
                     mPendingResponses.remove(callId).handleCreateConnectionFailure(
diff --git a/src/com/android/server/telecom/CreateConnectionProcessor.java b/src/com/android/server/telecom/CreateConnectionProcessor.java
index 6aae49b..0c6e25d 100644
--- a/src/com/android/server/telecom/CreateConnectionProcessor.java
+++ b/src/com/android/server/telecom/CreateConnectionProcessor.java
@@ -31,7 +31,7 @@
 import java.util.Objects;
 
 /**
- * This class creates connections to place new outgoing calls to attached to an existing incoming
+ * This class creates connections to place new outgoing calls or to attach to an existing incoming
  * call. In either case, this class cycles through a set of connection services until:
  *   - a connection service returns a newly created connection in which case the call is displayed
  *     to the user
diff --git a/src/com/android/server/telecom/InCallController.java b/src/com/android/server/telecom/InCallController.java
index 2dc30da..78553ad 100644
--- a/src/com/android/server/telecom/InCallController.java
+++ b/src/com/android/server/telecom/InCallController.java
@@ -261,8 +261,6 @@
     private void bind() {
         ThreadUtil.checkOnMainThread();
         if (mInCallServices.isEmpty()) {
-            mServiceConnections.clear();
-
             PackageManager packageManager = mContext.getPackageManager();
             Intent serviceIntent = new Intent(InCallService.SERVICE_INTERFACE);
 
@@ -289,11 +287,14 @@
                         continue;
                     }
 
-                    Log.i(this, "Attempting to bind to InCall " + serviceInfo.packageName);
                     InCallServiceConnection inCallServiceConnection = new InCallServiceConnection();
                     ComponentName componentName = new ComponentName(serviceInfo.packageName,
                             serviceInfo.name);
 
+                    Log.i(this, "Attempting to bind to InCall %s, is dupe? %b ",
+                            serviceInfo.packageName,
+                            mServiceConnections.containsKey(componentName));
+
                     if (!mServiceConnections.containsKey(componentName)) {
                         Intent intent = new Intent(InCallService.SERVICE_INTERFACE);
                         intent.setComponent(componentName);
@@ -444,6 +445,10 @@
             state = CallState.DISCONNECTED;
         }
 
+        if (call.isLocallyDisconnecting() && state != CallState.DISCONNECTED) {
+            state = CallState.DISCONNECTING;
+        }
+
         String parentCallId = null;
         Call parentCall = call.getParentCall();
         if (parentCall != null) {
diff --git a/src/com/android/server/telecom/NewOutgoingCallIntentBroadcaster.java b/src/com/android/server/telecom/NewOutgoingCallIntentBroadcaster.java
index 4e866a1..d645009 100644
--- a/src/com/android/server/telecom/NewOutgoingCallIntentBroadcaster.java
+++ b/src/com/android/server/telecom/NewOutgoingCallIntentBroadcaster.java
@@ -361,7 +361,7 @@
         systemDialerIntent.setData(handle);
         systemDialerIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
         Log.v(this, "calling startActivity for default dialer: %s", systemDialerIntent);
-        mContext.startActivity(systemDialerIntent);
+        mContext.startActivityAsUser(systemDialerIntent, UserHandle.CURRENT);
     }
 
     /**
@@ -393,15 +393,6 @@
      * number.
      */
     private void rewriteCallIntentAction(Intent intent, boolean isPotentialEmergencyNumber) {
-        if (CallActivity.class.getName().equals(intent.getComponent().getClassName())) {
-            // If we were launched directly from the CallActivity, not one of its more privileged
-            // aliases, then make sure that only the non-privileged actions are allowed.
-            if (!Intent.ACTION_CALL.equals(intent.getAction())) {
-                Log.w(this, "Attempt to deliver non-CALL action; forcing to CALL");
-                intent.setAction(Intent.ACTION_CALL);
-            }
-        }
-
         String action = intent.getAction();
 
         /* Change CALL_PRIVILEGED into CALL or CALL_EMERGENCY as needed. */
diff --git a/src/com/android/server/telecom/TelecomServiceImpl.java b/src/com/android/server/telecom/TelecomServiceImpl.java
index 299ca24..fbbe1c9 100644
--- a/src/com/android/server/telecom/TelecomServiceImpl.java
+++ b/src/com/android/server/telecom/TelecomServiceImpl.java
@@ -37,6 +37,7 @@
 import android.telecom.TelecomManager;
 import android.telephony.TelephonyManager;
 
+
 // TODO: Needed for move to system service: import com.android.internal.R;
 import com.android.internal.telecom.ITelecomService;
 import com.android.internal.util.IndentingPrintWriter;
@@ -472,6 +473,29 @@
         }
     }
 
+    /**
+     * @see android.telecom.TelecomManager#addNewUnknownCall
+     */
+    @Override
+    public void addNewUnknownCall(PhoneAccountHandle phoneAccountHandle, Bundle extras) {
+        if (phoneAccountHandle != null && phoneAccountHandle.getComponentName() != null &&
+                TelephonyUtil.isPstnComponentName(phoneAccountHandle.getComponentName())) {
+            mAppOpsManager.checkPackage(
+                    Binder.getCallingUid(), phoneAccountHandle.getComponentName().getPackageName());
+
+            Intent intent = new Intent(TelecomManager.ACTION_NEW_UNKNOWN_CALL);
+            intent.setClass(mContext, CallReceiver.class);
+            intent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+            intent.putExtras(extras);
+            intent.putExtra(CallReceiver.KEY_IS_UNKNOWN_CALL, true);
+            intent.putExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, phoneAccountHandle);
+            mContext.sendBroadcastAsUser(intent, UserHandle.OWNER);
+        } else {
+            Log.i(this, "Null phoneAccountHandle or not initiated by Telephony. Ignoring request"
+                    + " to add new unknown call.");
+        }
+    }
+
     //
     // Supporting methods for the ITelecomService interface implementation.
     //
diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml
index cd9b77f..9c09a40 100644
--- a/tests/AndroidManifest.xml
+++ b/tests/AndroidManifest.xml
@@ -69,6 +69,7 @@
             </intent-filter>
             <intent-filter>
                 <action android:name="android.telecom.testapps.ACTION_START_INCOMING_CALL" />
+                <action android:name="android.telecom.testapps.ACTION_NEW_UNKNOWN_CALL" />
                 <category android:name="android.intent.category.DEFAULT" />
                 <data android:scheme="tel" />
             </intent-filter>
diff --git a/tests/src/com/android/server/telecom/testapps/CallNotificationReceiver.java b/tests/src/com/android/server/telecom/testapps/CallNotificationReceiver.java
index 6105f3e..a835bf1 100644
--- a/tests/src/com/android/server/telecom/testapps/CallNotificationReceiver.java
+++ b/tests/src/com/android/server/telecom/testapps/CallNotificationReceiver.java
@@ -22,14 +22,18 @@
 import android.content.Intent;
 import android.net.Uri;
 import android.os.Bundle;
+import android.telecom.CallState;
 import android.telecom.PhoneAccountHandle;
 import android.telecom.TelecomManager;
+import android.util.Log;
 
 /**
  * This class receives the notification callback intents used to update call states for
  * {@link TestConnectionService}.
  */
 public class CallNotificationReceiver extends BroadcastReceiver {
+
+    static final String TAG = CallNotificationReceiver.class.getSimpleName();
     /**
      * Exit intent action is sent when the user clicks the "exit" action of the
      * TestConnectionService notification. Used to cancel (remove) the notification.
@@ -83,4 +87,22 @@
 
         TelecomManager.from(context).addNewIncomingCall(phoneAccount, extras);
     }
+
+    public static void addNewUnknownCall(Context context, Uri handle, Bundle extras) {
+        Log.i(TAG, "Adding new unknown call with handle " + handle);
+        PhoneAccountHandle phoneAccount = new PhoneAccountHandle(
+                new ComponentName(context, TestConnectionService.class),
+                CallServiceNotifier.SIM_SUBSCRIPTION_ID);
+
+        if (extras == null) {
+            extras = new Bundle();
+        }
+
+        if (handle != null) {
+            extras.putParcelable(TelecomManager.EXTRA_UNKNOWN_CALL_HANDLE, handle);
+            extras.putParcelable(TestConnectionService.EXTRA_HANDLE, handle);
+        }
+
+        TelecomManager.from(context).addNewUnknownCall(phoneAccount, extras);
+    }
 }
diff --git a/tests/src/com/android/server/telecom/testapps/TestCallActivity.java b/tests/src/com/android/server/telecom/testapps/TestCallActivity.java
index f5e24ec..9c9a40b 100644
--- a/tests/src/com/android/server/telecom/testapps/TestCallActivity.java
+++ b/tests/src/com/android/server/telecom/testapps/TestCallActivity.java
@@ -36,21 +36,25 @@
     public static final String ACTION_NEW_INCOMING_CALL =
             "android.telecom.testapps.ACTION_START_INCOMING_CALL";
 
+    /*
+     * Action to exercise TelecomManager.addNewUnknownCall().
+     */
+    public static final String ACTION_NEW_UNKNOWN_CALL =
+            "android.telecom.testapps.ACTION_NEW_UNKNOWN_CALL";
+
     @Override
     protected void onCreate(Bundle icicle) {
         super.onCreate(icicle);
         final Intent intent = getIntent();
-        if (intent != null && intent.getData() != null) {
-            startIncomingCallBroadcast(intent.getData());
+        final String action = intent != null ? intent.getAction() : null;
+        final Uri data = intent != null ? intent.getData() : null;
+        if (ACTION_NEW_INCOMING_CALL.equals(action) && data != null) {
+            CallNotificationReceiver.sendIncomingCallIntent(this, data, false);
+        } if (ACTION_NEW_UNKNOWN_CALL.equals(action) && data != null) {
+            CallNotificationReceiver.addNewUnknownCall(this, data, intent.getExtras());
+        } else {
+            CallServiceNotifier.getInstance().updateNotification(this);
         }
-        CallServiceNotifier.getInstance().updateNotification(this);
         finish();
     }
-
-    /**
-     * Bypass the notification and start the test incoming call directly.
-     */
-    private void startIncomingCallBroadcast(Uri handle) {
-        CallNotificationReceiver.sendIncomingCallIntent(this, handle, false);
-    }
 }
diff --git a/tests/src/com/android/server/telecom/testapps/TestConnectionService.java b/tests/src/com/android/server/telecom/testapps/TestConnectionService.java
index b327462..389373f 100644
--- a/tests/src/com/android/server/telecom/testapps/TestConnectionService.java
+++ b/tests/src/com/android/server/telecom/testapps/TestConnectionService.java
@@ -267,6 +267,7 @@
                 originalHandle + "]");
 
         final TestConnection connection = new TestConnection(false /* isIncoming */);
+        connection.setAddress(handle, TelecomManager.PRESENTATION_ALLOWED);
 
         // If the number starts with 555, then we handle it ourselves. If not, then we
         // use a remote connection service.
@@ -341,6 +342,31 @@
         }
     }
 
+    @Override
+    public Connection onCreateUnknownConnection(PhoneAccountHandle connectionManagerPhoneAccount,
+            final ConnectionRequest request) {
+        PhoneAccountHandle accountHandle = request.getAccountHandle();
+        ComponentName componentName = new ComponentName(this, TestConnectionService.class);
+        if (accountHandle != null && componentName.equals(accountHandle.getComponentName())) {
+            final TestConnection connection = new TestConnection(false);
+            final Bundle extras = request.getExtras();
+            final Uri providedHandle = extras.getParcelable(EXTRA_HANDLE);
+
+            Uri handle = providedHandle == null ?
+                    Uri.fromParts(PhoneAccount.SCHEME_TEL, getDummyNumber(false), null)
+                    : providedHandle;
+
+            connection.setAddress(handle,  TelecomManager.PRESENTATION_ALLOWED);
+            connection.setDialing();
+
+            addCall(connection);
+            return connection;
+        } else {
+            return Connection.createFailedConnection(new DisconnectCause(DisconnectCause.ERROR,
+                    "Invalid inputs: " + accountHandle + " " + componentName));
+        }
+    }
+
     private void activateCall(TestConnection connection) {
         if (mMediaPlayer == null) {
             mMediaPlayer = createMediaPlayer();