Merge "Import translations. DO NOT MERGE" into oc-dev
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 4caa4a3..6579106 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -43,6 +43,7 @@
     <uses-permission android:name="android.permission.WRITE_CALL_LOG" />
     <uses-permission android:name="android.permission.READ_BLOCKED_NUMBERS" />
     <uses-permission android:name="android.permission.WRITE_BLOCKED_NUMBERS" />
+    <uses-permission android:name="android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME" />
 
     <permission
             android:name="android.permission.BROADCAST_CALLLOG_INFO"
@@ -209,6 +210,8 @@
                 <action android:name="com.android.server.telecom.ACTION_CLEAR_MISSED_CALLS" />
                 <action android:name="com.android.server.telecom.ACTION_CALL_BACK_FROM_NOTIFICATION" />
                 <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" />
             </intent-filter>
         </receiver>
 
diff --git a/res/anim/on_going_call.xml b/res/anim/on_going_call.xml
new file mode 100644
index 0000000..ba3ed46
--- /dev/null
+++ b/res/anim/on_going_call.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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
+  -->
+
+<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
+  android:oneshot="false">
+  <item
+    android:drawable="@drawable/ic_ongoing_phone_24px_01"
+    android:duration="200"/>
+  <item
+    android:drawable="@drawable/ic_ongoing_phone_24px_02"
+    android:duration="200"/>
+  <item
+    android:drawable="@drawable/ic_ongoing_phone_24px_03"
+    android:duration="200"/>
+  <item
+    android:drawable="@drawable/ic_ongoing_phone_24px_04"
+    android:duration="200"/>
+  <item
+    android:drawable="@drawable/ic_ongoing_phone_24px_05"
+    android:duration="200"/>
+  <item
+    android:drawable="@drawable/ic_ongoing_phone_24px_06"
+    android:duration="200"/>
+  <item
+    android:drawable="@drawable/ic_ongoing_phone_24px_07"
+    android:duration="200"/>
+  <item
+    android:drawable="@drawable/ic_ongoing_phone_24px_08"
+    android:duration="200"/>
+  <item
+    android:drawable="@drawable/ic_ongoing_phone_24px_09"
+    android:duration="200"/>
+</animation-list>
\ No newline at end of file
diff --git a/res/drawable-hdpi/ic_call_white_24dp.png b/res/drawable-hdpi/ic_call_white_24dp.png
new file mode 100644
index 0000000..77f9de5
--- /dev/null
+++ b/res/drawable-hdpi/ic_call_white_24dp.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_close_dk.png b/res/drawable-hdpi/ic_close_dk.png
new file mode 100644
index 0000000..590a728
--- /dev/null
+++ b/res/drawable-hdpi/ic_close_dk.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_ongoing_phone_24px_01.png b/res/drawable-hdpi/ic_ongoing_phone_24px_01.png
new file mode 100644
index 0000000..ae31e04
--- /dev/null
+++ b/res/drawable-hdpi/ic_ongoing_phone_24px_01.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_ongoing_phone_24px_02.png b/res/drawable-hdpi/ic_ongoing_phone_24px_02.png
new file mode 100644
index 0000000..67b2b16
--- /dev/null
+++ b/res/drawable-hdpi/ic_ongoing_phone_24px_02.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_ongoing_phone_24px_03.png b/res/drawable-hdpi/ic_ongoing_phone_24px_03.png
new file mode 100644
index 0000000..fa936cb
--- /dev/null
+++ b/res/drawable-hdpi/ic_ongoing_phone_24px_03.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_ongoing_phone_24px_04.png b/res/drawable-hdpi/ic_ongoing_phone_24px_04.png
new file mode 100644
index 0000000..ef51379
--- /dev/null
+++ b/res/drawable-hdpi/ic_ongoing_phone_24px_04.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_ongoing_phone_24px_05.png b/res/drawable-hdpi/ic_ongoing_phone_24px_05.png
new file mode 100644
index 0000000..3712d16
--- /dev/null
+++ b/res/drawable-hdpi/ic_ongoing_phone_24px_05.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_ongoing_phone_24px_06.png b/res/drawable-hdpi/ic_ongoing_phone_24px_06.png
new file mode 100644
index 0000000..c6a4216
--- /dev/null
+++ b/res/drawable-hdpi/ic_ongoing_phone_24px_06.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_ongoing_phone_24px_07.png b/res/drawable-hdpi/ic_ongoing_phone_24px_07.png
new file mode 100644
index 0000000..80ad50b
--- /dev/null
+++ b/res/drawable-hdpi/ic_ongoing_phone_24px_07.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_ongoing_phone_24px_08.png b/res/drawable-hdpi/ic_ongoing_phone_24px_08.png
new file mode 100644
index 0000000..871a1ee
--- /dev/null
+++ b/res/drawable-hdpi/ic_ongoing_phone_24px_08.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_ongoing_phone_24px_09.png b/res/drawable-hdpi/ic_ongoing_phone_24px_09.png
new file mode 100644
index 0000000..871a1ee
--- /dev/null
+++ b/res/drawable-hdpi/ic_ongoing_phone_24px_09.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_call_white_24dp.png b/res/drawable-mdpi/ic_call_white_24dp.png
new file mode 100644
index 0000000..77f9de5
--- /dev/null
+++ b/res/drawable-mdpi/ic_call_white_24dp.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_close_dk.png b/res/drawable-mdpi/ic_close_dk.png
new file mode 100644
index 0000000..590a728
--- /dev/null
+++ b/res/drawable-mdpi/ic_close_dk.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_ongoing_phone_24px_01.png b/res/drawable-mdpi/ic_ongoing_phone_24px_01.png
new file mode 100644
index 0000000..ae31e04
--- /dev/null
+++ b/res/drawable-mdpi/ic_ongoing_phone_24px_01.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_ongoing_phone_24px_02.png b/res/drawable-mdpi/ic_ongoing_phone_24px_02.png
new file mode 100644
index 0000000..67b2b16
--- /dev/null
+++ b/res/drawable-mdpi/ic_ongoing_phone_24px_02.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_ongoing_phone_24px_03.png b/res/drawable-mdpi/ic_ongoing_phone_24px_03.png
new file mode 100644
index 0000000..fa936cb
--- /dev/null
+++ b/res/drawable-mdpi/ic_ongoing_phone_24px_03.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_ongoing_phone_24px_04.png b/res/drawable-mdpi/ic_ongoing_phone_24px_04.png
new file mode 100644
index 0000000..ef51379
--- /dev/null
+++ b/res/drawable-mdpi/ic_ongoing_phone_24px_04.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_ongoing_phone_24px_05.png b/res/drawable-mdpi/ic_ongoing_phone_24px_05.png
new file mode 100644
index 0000000..3712d16
--- /dev/null
+++ b/res/drawable-mdpi/ic_ongoing_phone_24px_05.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_ongoing_phone_24px_06.png b/res/drawable-mdpi/ic_ongoing_phone_24px_06.png
new file mode 100644
index 0000000..c6a4216
--- /dev/null
+++ b/res/drawable-mdpi/ic_ongoing_phone_24px_06.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_ongoing_phone_24px_07.png b/res/drawable-mdpi/ic_ongoing_phone_24px_07.png
new file mode 100644
index 0000000..80ad50b
--- /dev/null
+++ b/res/drawable-mdpi/ic_ongoing_phone_24px_07.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_ongoing_phone_24px_08.png b/res/drawable-mdpi/ic_ongoing_phone_24px_08.png
new file mode 100644
index 0000000..871a1ee
--- /dev/null
+++ b/res/drawable-mdpi/ic_ongoing_phone_24px_08.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_ongoing_phone_24px_09.png b/res/drawable-mdpi/ic_ongoing_phone_24px_09.png
new file mode 100644
index 0000000..871a1ee
--- /dev/null
+++ b/res/drawable-mdpi/ic_ongoing_phone_24px_09.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_call_white_24dp.png b/res/drawable-xhdpi/ic_call_white_24dp.png
new file mode 100644
index 0000000..77f9de5
--- /dev/null
+++ b/res/drawable-xhdpi/ic_call_white_24dp.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_close_dk.png b/res/drawable-xhdpi/ic_close_dk.png
new file mode 100644
index 0000000..590a728
--- /dev/null
+++ b/res/drawable-xhdpi/ic_close_dk.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_ongoing_phone_24px_01.png b/res/drawable-xhdpi/ic_ongoing_phone_24px_01.png
new file mode 100644
index 0000000..ae31e04
--- /dev/null
+++ b/res/drawable-xhdpi/ic_ongoing_phone_24px_01.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_ongoing_phone_24px_02.png b/res/drawable-xhdpi/ic_ongoing_phone_24px_02.png
new file mode 100644
index 0000000..67b2b16
--- /dev/null
+++ b/res/drawable-xhdpi/ic_ongoing_phone_24px_02.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_ongoing_phone_24px_03.png b/res/drawable-xhdpi/ic_ongoing_phone_24px_03.png
new file mode 100644
index 0000000..fa936cb
--- /dev/null
+++ b/res/drawable-xhdpi/ic_ongoing_phone_24px_03.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_ongoing_phone_24px_04.png b/res/drawable-xhdpi/ic_ongoing_phone_24px_04.png
new file mode 100644
index 0000000..ef51379
--- /dev/null
+++ b/res/drawable-xhdpi/ic_ongoing_phone_24px_04.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_ongoing_phone_24px_05.png b/res/drawable-xhdpi/ic_ongoing_phone_24px_05.png
new file mode 100644
index 0000000..3712d16
--- /dev/null
+++ b/res/drawable-xhdpi/ic_ongoing_phone_24px_05.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_ongoing_phone_24px_06.png b/res/drawable-xhdpi/ic_ongoing_phone_24px_06.png
new file mode 100644
index 0000000..c6a4216
--- /dev/null
+++ b/res/drawable-xhdpi/ic_ongoing_phone_24px_06.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_ongoing_phone_24px_07.png b/res/drawable-xhdpi/ic_ongoing_phone_24px_07.png
new file mode 100644
index 0000000..80ad50b
--- /dev/null
+++ b/res/drawable-xhdpi/ic_ongoing_phone_24px_07.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_ongoing_phone_24px_08.png b/res/drawable-xhdpi/ic_ongoing_phone_24px_08.png
new file mode 100644
index 0000000..871a1ee
--- /dev/null
+++ b/res/drawable-xhdpi/ic_ongoing_phone_24px_08.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_ongoing_phone_24px_09.png b/res/drawable-xhdpi/ic_ongoing_phone_24px_09.png
new file mode 100644
index 0000000..871a1ee
--- /dev/null
+++ b/res/drawable-xhdpi/ic_ongoing_phone_24px_09.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_call_white_24dp.png b/res/drawable-xxhdpi/ic_call_white_24dp.png
new file mode 100644
index 0000000..77f9de5
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_call_white_24dp.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_close_dk.png b/res/drawable-xxhdpi/ic_close_dk.png
new file mode 100644
index 0000000..590a728
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_close_dk.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_ongoing_phone_24px_01.png b/res/drawable-xxhdpi/ic_ongoing_phone_24px_01.png
new file mode 100644
index 0000000..ae31e04
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_ongoing_phone_24px_01.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_ongoing_phone_24px_02.png b/res/drawable-xxhdpi/ic_ongoing_phone_24px_02.png
new file mode 100644
index 0000000..67b2b16
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_ongoing_phone_24px_02.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_ongoing_phone_24px_03.png b/res/drawable-xxhdpi/ic_ongoing_phone_24px_03.png
new file mode 100644
index 0000000..fa936cb
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_ongoing_phone_24px_03.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_ongoing_phone_24px_04.png b/res/drawable-xxhdpi/ic_ongoing_phone_24px_04.png
new file mode 100644
index 0000000..ef51379
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_ongoing_phone_24px_04.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_ongoing_phone_24px_05.png b/res/drawable-xxhdpi/ic_ongoing_phone_24px_05.png
new file mode 100644
index 0000000..3712d16
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_ongoing_phone_24px_05.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_ongoing_phone_24px_06.png b/res/drawable-xxhdpi/ic_ongoing_phone_24px_06.png
new file mode 100644
index 0000000..c6a4216
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_ongoing_phone_24px_06.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_ongoing_phone_24px_07.png b/res/drawable-xxhdpi/ic_ongoing_phone_24px_07.png
new file mode 100644
index 0000000..80ad50b
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_ongoing_phone_24px_07.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_ongoing_phone_24px_08.png b/res/drawable-xxhdpi/ic_ongoing_phone_24px_08.png
new file mode 100644
index 0000000..871a1ee
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_ongoing_phone_24px_08.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_ongoing_phone_24px_09.png b/res/drawable-xxhdpi/ic_ongoing_phone_24px_09.png
new file mode 100644
index 0000000..871a1ee
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_ongoing_phone_24px_09.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_call_white_24dp.png b/res/drawable-xxxhdpi/ic_call_white_24dp.png
new file mode 100644
index 0000000..77f9de5
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_call_white_24dp.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_close_dk.png b/res/drawable-xxxhdpi/ic_close_dk.png
new file mode 100644
index 0000000..590a728
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_close_dk.png
Binary files differ
diff --git a/res/values/colors.xml b/res/values/colors.xml
index b0bcd3c..8ff0b8e 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -25,4 +25,7 @@
     <color name="blocked_numbers_butter_bar_color">#f5f5f5</color>
     <color name="blocked_numbers_title_text_color">#ba000000</color>
     <color name="blocked_numbers_secondary_text_color">#89000000</color>
+
+    <color name="notification_action_answer">#097138</color>
+    <color name="notification_action_decline">#A52714</color>
 </resources>
diff --git a/src/com/android/server/telecom/Call.java b/src/com/android/server/telecom/Call.java
index 5056266..2eeb96c 100644
--- a/src/com/android/server/telecom/Call.java
+++ b/src/com/android/server/telecom/Call.java
@@ -596,12 +596,30 @@
 
     @Override
     public String getDescription() {
-        StringBuilder s = new StringBuilder("Call ");
+        StringBuilder s = new StringBuilder();
+        if (isSelfManaged()) {
+            s.append("SelfMgd Call");
+        } else if (isExternalCall()) {
+            s.append("External Call");
+        } else {
+            s.append("Call");
+        }
         s.append(getId());
         s.append(" [");
         s.append(SimpleDateFormat.getDateTimeInstance().format(new Date(getCreationTimeMillis())));
         s.append("]");
         s.append(isIncoming() ? "(MT - incoming)" : "(MO - outgoing)");
+        s.append("\n\tVia PhoneAccount: ");
+        PhoneAccountHandle targetPhoneAccountHandle = getTargetPhoneAccount();
+        if (targetPhoneAccountHandle != null) {
+            s.append(targetPhoneAccountHandle);
+            s.append(" (");
+            s.append(getTargetPhoneAccountLabel());
+            s.append(")");
+        } else {
+            s.append("not set");
+        }
+
         s.append("\n\tTo address: ");
         s.append(Log.piiHandle(getHandle()));
         s.append("\n");
@@ -858,11 +876,11 @@
         }
     }
 
-    String getCallerDisplayName() {
+    public String getCallerDisplayName() {
         return mCallerDisplayName;
     }
 
-    int getCallerDisplayNamePresentation() {
+    public int getCallerDisplayNamePresentation() {
         return mCallerDisplayNamePresentation;
     }
 
@@ -966,6 +984,20 @@
         checkIfVideoCapable();
     }
 
+    public CharSequence getTargetPhoneAccountLabel() {
+        if (getTargetPhoneAccount() == null) {
+            return null;
+        }
+        PhoneAccount phoneAccount = mCallsManager.getPhoneAccountRegistrar()
+                .getPhoneAccountUnchecked(getTargetPhoneAccount());
+
+        if (phoneAccount == null) {
+            return null;
+        }
+
+        return phoneAccount.getLabel();
+    }
+
     @VisibleForTesting
     public boolean isIncoming() {
         return mCallDirection == CALL_DIRECTION_INCOMING;
diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java
index 82703e2..45cd488 100644
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -66,6 +66,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.IncomingCallNotifier;
 
 import java.util.ArrayList;
 import java.util.Collection;
@@ -206,6 +207,7 @@
     private final CallerInfoAsyncQueryFactory mCallerInfoAsyncQueryFactory;
     private final PhoneAccountRegistrar mPhoneAccountRegistrar;
     private final MissedCallNotifier mMissedCallNotifier;
+    private IncomingCallNotifier mIncomingCallNotifier;
     private final CallerInfoLookupHelper mCallerInfoLookupHelper;
     private final DefaultDialerCache mDefaultDialerCache;
     private final Timeouts.Adapter mTimeoutsAdapter;
@@ -330,6 +332,14 @@
         }
     }
 
+    public void setIncomingCallNotifier(IncomingCallNotifier incomingCallNotifier) {
+        if (mIncomingCallNotifier != null) {
+            mListeners.remove(mIncomingCallNotifier);
+        }
+        mIncomingCallNotifier = incomingCallNotifier;
+        mListeners.add(mIncomingCallNotifier);
+    }
+
     public void setRespondViaSmsManager(RespondViaSmsManager respondViaSmsManager) {
         if (mRespondViaSmsManager != null) {
             mListeners.remove(mRespondViaSmsManager);
@@ -762,6 +772,27 @@
                 phoneAccountHandle);
         if (phoneAccount != null) {
             call.setIsSelfManaged(phoneAccount.isSelfManaged());
+            if (call.isSelfManaged()) {
+                // Self managed calls will always be voip audio mode.
+                call.setIsVoipAudioMode(true);
+            } else {
+                // Incoming call is not self-managed, so we need to set extras on it to indicate
+                // whether answering will cause a background self-managed call to drop.
+                if (hasSelfManagedCalls()) {
+                    Bundle dropCallExtras = new Bundle();
+                    dropCallExtras.putBoolean(Connection.EXTRA_ANSWERING_DROPS_FG_CALL, true);
+
+                    // Include the name of the app which will drop the call.
+                    Call foregroundCall = getForegroundCall();
+                    if (foregroundCall != null) {
+                        CharSequence droppedApp = foregroundCall.getTargetPhoneAccountLabel();
+                        dropCallExtras.putCharSequence(
+                                Connection.EXTRA_ANSWERING_DROPS_FG_CALL_APP_NAME, droppedApp);
+                        Log.i(this, "Incoming managed call will drop %s call.", droppedApp);
+                    }
+                    call.putExtras(Call.SOURCE_CONNECTION_SERVICE, dropCallExtras);
+                }
+            }
         }
         if (extras.getBoolean(TelecomManager.EXTRA_START_CALL_WITH_RTT, false)) {
             if (phoneAccount != null &&
@@ -769,6 +800,13 @@
                 call.setRttStreams(true);
             }
         }
+        // If the extras specifies a video state, set it on the call if the PhoneAccount supports
+        // video.
+        if (extras.containsKey(TelecomManager.EXTRA_INCOMING_VIDEO_STATE) &&
+                phoneAccount != null && phoneAccount.hasCapabilities(
+                        PhoneAccount.CAPABILITY_VIDEO_CALLING)) {
+            call.setVideoState(extras.getInt(TelecomManager.EXTRA_INCOMING_VIDEO_STATE));
+        }
 
         call.initAnalytics();
         if (getForegroundCall() != null) {
@@ -897,6 +935,10 @@
             // self-managed from the moment it is created.
             if (account != null) {
                 call.setIsSelfManaged(account.isSelfManaged());
+                if (call.isSelfManaged()) {
+                    // Self-managed calls will ALWAYS use voip audio mode.
+                    call.setIsVoipAudioMode(true);
+                }
             }
 
             call.setInitiatingUser(initiatingUser);
@@ -1085,15 +1127,33 @@
 
         final boolean requireCallCapableAccountByHandle = mContext.getResources().getBoolean(
                 com.android.internal.R.bool.config_requireCallCapableAccountForHandle);
-
+        final boolean isOutgoingCallPermitted = isOutgoingCallPermitted(call,
+                call.getTargetPhoneAccount());
         if (call.getTargetPhoneAccount() != null || call.isEmergencyCall()) {
             // If the account has been set, proceed to place the outgoing call.
             // Otherwise the connection will be initiated when the account is set by the user.
-            if (call.isSelfManaged() && !isOutgoingCallPermitted(call,
-                    call.getTargetPhoneAccount())) {
-
+            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);
             } 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());
+                }
+
                 call.startCreateConnection(mPhoneAccountRegistrar);
             }
         } else if (mPhoneAccountRegistrar.getCallCapablePhoneAccounts(
@@ -1137,7 +1197,21 @@
                     (foregroundCall.isActive() ||
                      foregroundCall.getState() == CallState.DIALING ||
                      foregroundCall.getState() == CallState.PULLING)) {
-                if (0 == (foregroundCall.getConnectionCapabilities()
+                if (!foregroundCall.getTargetPhoneAccount().equals(
+                                call.getTargetPhoneAccount()) &&
+                        ((call.isSelfManaged() != foregroundCall.isSelfManaged()) ||
+                         call.isSelfManaged())) {
+                    // The foreground call is from another connection service, and either:
+                    // 1. FG call's managed state doesn't match that of the incoming call.
+                    //    E.g. Incoming is self-managed and FG is managed, or incoming is managed
+                    //    and foreground is self-managed.
+                    // 2. The incoming call is self-managed.
+                    //    E.g. The incoming call is
+                    Log.i(this, "Answering call from %s CS; disconnecting calls from %s CS.",
+                            foregroundCall.isSelfManaged() ? "selfMg" : "mg",
+                            call.isSelfManaged() ? "selfMg" : "mg");
+                    disconnectOtherCalls(call.getTargetPhoneAccount());
+                } else if (0 == (foregroundCall.getConnectionCapabilities()
                         & Connection.CAPABILITY_HOLD)) {
                     // This call does not support hold.  If it is from a different connection
                     // service, then disconnect it, otherwise allow the connection service to
@@ -1148,13 +1222,11 @@
                 } else {
                     Call heldCall = getHeldCall();
                     if (heldCall != null) {
-                        Log.v(this, "Disconnecting held call %s before holding active call.",
+                        Log.i(this, "Disconnecting held call %s before holding active call.",
                                 heldCall);
                         heldCall.disconnect();
                     }
 
-                    Log.v(this, "Holding active/dialing call %s before answering incoming call %s.",
-                            foregroundCall, call);
                     foregroundCall.hold();
                 }
                 // TODO: Wait until we get confirmation of the active call being
@@ -1298,6 +1370,19 @@
         }
     }
 
+    /**
+     * Disconnects calls for any other {@link PhoneAccountHandle} but the one specified.
+     * Note: As a protective measure, will NEVER disconnect an emergency call.  Although that
+     * situation should never arise, its a good safeguard.
+     * @param phoneAccountHandle Calls owned by {@link PhoneAccountHandle}s other than this one will
+     *                          be disconnected.
+     */
+    private void disconnectOtherCalls(PhoneAccountHandle phoneAccountHandle) {
+        mCalls.stream()
+                .filter(c -> !c.isEmergencyCall() &&
+                        !c.getTargetPhoneAccount().equals(phoneAccountHandle))
+                .forEach(c -> disconnectCall(c));
+    }
 
     /**
      * Instructs Telecom to put the specified call on hold. Intended to be invoked by the
@@ -1692,7 +1777,6 @@
         return getFirstCallWithState(CallState.RINGING);
     }
 
-    @VisibleForTesting
     public Call getActiveCall() {
         return getFirstCallWithState(CallState.ACTIVE);
     }
@@ -1845,6 +1929,14 @@
     }
 
     /**
+     * Retrieves the {@link IncomingCallNotifier}.
+     * @return The {@link IncomingCallNotifier}.
+     */
+    IncomingCallNotifier getIncomingCallNotifier() {
+        return mIncomingCallNotifier;
+    }
+
+    /**
      * Reject an incoming call and manually add it to the Call Log.
      * @param incomingCall Incoming call that has been rejected
      */
@@ -2088,10 +2180,19 @@
      * @return {@code true} if there are other calls, {@code false} otherwise.
      */
     public boolean hasCallsForOtherPhoneAccount(PhoneAccountHandle phoneAccountHandle) {
-        return mCalls.stream().filter(call ->
+        return getNumCallsForOtherPhoneAccount(phoneAccountHandle) > 0;
+    }
+
+    /**
+     * Determines the number of calls present for PhoneAccounts other than the one specified.
+     * @param phoneAccountHandle The handle of the PhoneAccount.
+     * @return Number of calls owned by other PhoneAccounts.
+     */
+    public int getNumCallsForOtherPhoneAccount(PhoneAccountHandle phoneAccountHandle) {
+        return (int) mCalls.stream().filter(call ->
                 !phoneAccountHandle.equals(call.getTargetPhoneAccount()) &&
-                call.getParentCall() == null &&
-                !call.isExternalCall()).count() > 0;
+                        call.getParentCall() == null &&
+                        !call.isExternalCall()).count();
     }
 
     /**
@@ -2104,6 +2205,14 @@
     }
 
     /**
+     * Determines if there are any self-managed calls.
+     * @return {@code true} if there are self-managed calls, {@code false} otherwise.
+     */
+    public boolean hasSelfManagedCalls() {
+        return mCalls.stream().filter(call -> call.isSelfManaged()).count() > 0;
+    }
+
+    /**
      * Determines if there are any ongoing managed calls.
      * @return {@code true} if there are ongoing managed calls, {@code false} otherwise.
      */
@@ -2114,6 +2223,18 @@
                 LIVE_CALL_STATES) > 0;
     }
 
+    /**
+     * Deteremines if the system incoming call UI should be shown.
+     * The system incoming call UI will be shown if the new incoming call is self-managed, and there
+     * are ongoing calls for another PhoneAccount.
+     * @param incomingCall The incoming call.
+     * @return {@code true} if the system incoming call UI should be shown, {@code false} otherwise.
+     */
+    public boolean shouldShowSystemIncomingCallUi(Call incomingCall) {
+        return incomingCall.isIncoming() && incomingCall.isSelfManaged() &&
+                hasCallsForOtherPhoneAccount(incomingCall.getTargetPhoneAccount());
+    }
+
     private boolean makeRoomForOutgoingCall(Call call, boolean isEmergency) {
         if (hasMaximumManagedLiveCalls(call)) {
             // NOTE: If the amount of live calls changes beyond 1, this logic will probably
@@ -2390,8 +2511,7 @@
         } else {
             return !hasEmergencyCall() &&
                     !hasMaximumSelfManagedRingingCalls(excludeCall, phoneAccountHandle) &&
-                    !hasMaximumSelfManagedCalls(excludeCall, phoneAccountHandle) &&
-                    !hasManagedCalls();
+                    !hasMaximumSelfManagedCalls(excludeCall, phoneAccountHandle);
         }
     }
 
@@ -2420,7 +2540,8 @@
             // are associated with the current PhoneAccountHandle.
             return !hasEmergencyCall() &&
                     !hasMaximumSelfManagedCalls(excludeCall, phoneAccountHandle) &&
-                    !hasCallsForOtherPhoneAccount(phoneAccountHandle);
+                    !hasCallsForOtherPhoneAccount(phoneAccountHandle) &&
+                    !hasManagedCalls();
         }
     }
 
diff --git a/src/com/android/server/telecom/ConnectionServiceWrapper.java b/src/com/android/server/telecom/ConnectionServiceWrapper.java
index 37257af..60ce2eb 100644
--- a/src/com/android/server/telecom/ConnectionServiceWrapper.java
+++ b/src/com/android/server/telecom/ConnectionServiceWrapper.java
@@ -922,20 +922,17 @@
                 Log.addEvent(call, LogUtils.Events.START_CONNECTION,
                         Log.piiHandle(call.getHandle()));
 
-                // For self-managed incoming calls, if there is another ongoing call Telecom is
-                // responsible for showing a UI to ask the user if they'd like to answer this
-                // new incoming call.
-                boolean shouldShowIncomingCallUI = call.isSelfManaged() &&
-                        !mCallsManager.hasCallsForOtherPhoneAccount(
-                                call.getTargetPhoneAccount());
-
                 ConnectionRequest connectionRequest = new ConnectionRequest.Builder()
                         .setAccountHandle(call.getTargetPhoneAccount())
                         .setAddress(call.getHandle())
                         .setExtras(extras)
                         .setVideoState(call.getVideoState())
                         .setTelecomCallId(callId)
-                        .setShouldShowIncomingCallUi(shouldShowIncomingCallUI)
+                        // For self-managed incoming calls, if there is another ongoing call Telecom
+                        // is responsible for showing a UI to ask the user if they'd like to answer
+                        // this new incoming call.
+                        .setShouldShowIncomingCallUi(
+                                !mCallsManager.shouldShowSystemIncomingCallUi(call))
                         .setRttPipeFromInCall(call.getInCallToCsRttPipeForCs())
                         .setRttPipeToInCall(call.getCsToInCallRttPipeForCs())
                         .build();
diff --git a/src/com/android/server/telecom/InCallController.java b/src/com/android/server/telecom/InCallController.java
index 2dafece..f84e157 100644
--- a/src/com/android/server/telecom/InCallController.java
+++ b/src/com/android/server/telecom/InCallController.java
@@ -64,14 +64,30 @@
 public final class InCallController extends CallsManagerListenerBase {
 
     public class InCallServiceConnection {
+        /**
+         * Indicates that a call to {@link #connect(Call)} has succeeded and resulted in a
+         * connection to an InCallService.
+         */
+        public static final int CONNECTION_SUCCEEDED = 1;
+        /**
+         * Indicates that a call to {@link #connect(Call)} has failed because of a binding issue.
+         */
+        public static final int CONNECTION_FAILED = 2;
+        /**
+         * Indicates that a call to {@link #connect(Call)} has been skipped because the
+         * IncallService does not support the type of call..
+         */
+        public static final int CONNECTION_NOT_SUPPORTED = 3;
+
         public class Listener {
             public void onDisconnect(InCallServiceConnection conn) {}
         }
 
         protected Listener mListener;
 
-        public boolean connect(Call call) { return false; }
+        public int connect(Call call) { return CONNECTION_FAILED; }
         public void disconnect() {}
+        public boolean isConnected() { return false; }
         public void setHasEmergency(boolean hasEmergency) {}
         public void setListener(Listener l) {
             mListener = l;
@@ -190,10 +206,17 @@
         }
 
         @Override
-        public boolean connect(Call call) {
+        public int connect(Call call) {
             if (mIsConnected) {
                 Log.addEvent(call, LogUtils.Events.INFO, "Already connected, ignoring request.");
-                return true;
+                return CONNECTION_SUCCEEDED;
+            }
+
+            if (call.isSelfManaged() && !mInCallServiceInfo.isSelfManagedCallsSupported()) {
+                Log.i(this, "Skipping binding to %s - doesn't support self-mgd calls",
+                        mInCallServiceInfo);
+                mIsConnected = false;
+                return CONNECTION_NOT_SUPPORTED;
             }
 
             Intent intent = new Intent(InCallService.SERVICE_INTERFACE);
@@ -220,7 +243,7 @@
                         mInCallServiceInfo.getType());
             }
 
-            return mIsConnected;
+            return mIsConnected ? CONNECTION_SUCCEEDED : CONNECTION_FAILED;
         }
 
         @Override
@@ -239,6 +262,11 @@
         }
 
         @Override
+        public boolean isConnected() {
+            return mIsConnected;
+        }
+
+        @Override
         public void dump(IndentingPrintWriter pw) {
             pw.append("BindingConnection [");
             pw.append(mIsConnected ? "" : "not ").append("connected, ");
@@ -304,11 +332,13 @@
         }
 
         @Override
-        public boolean connect(Call call) {
+        public int connect(Call call) {
             mIsConnected = true;
             if (mIsProxying) {
-                if (mSubConnection.connect(call)) {
-                    return true;
+                int result = mSubConnection.connect(call);
+                mIsConnected = result == CONNECTION_SUCCEEDED;
+                if (result != CONNECTION_FAILED) {
+                    return result;
                 }
                 // Could not connect to child, stop proxying.
                 mIsProxying = false;
@@ -371,7 +401,9 @@
 
         @Override
         public void dump(IndentingPrintWriter pw) {
-            pw.println("Emergency ICS Connection");
+            pw.print("Emergency ICS Connection [");
+            pw.append(mIsProxying ? "" : "not ").append("proxying, ");
+            pw.append(mIsConnected ? "" : "not ").append("connected]\n");
             pw.increaseIndent();
             pw.print("Emergency: ");
             super.dump(pw);
@@ -423,7 +455,8 @@
                 if (newConnection != mCurrentConnection) {
                     if (mIsConnected) {
                         mCurrentConnection.disconnect();
-                        newConnection.connect(null);
+                        int result = newConnection.connect(null);
+                        mIsConnected = result == CONNECTION_SUCCEEDED;
                     }
                     mCurrentConnection = newConnection;
                 }
@@ -431,18 +464,19 @@
         }
 
         @Override
-        public boolean connect(Call call) {
+        public int connect(Call call) {
             if (mIsConnected) {
                 Log.i(this, "already connected");
-                return true;
+                return CONNECTION_SUCCEEDED;
             } else {
-                if (mCurrentConnection.connect(call)) {
-                    mIsConnected = true;
-                    return true;
+                int result = mCurrentConnection.connect(call);
+                if (result != CONNECTION_FAILED) {
+                    mIsConnected = result == CONNECTION_SUCCEEDED;
+                    return result;
                 }
             }
 
-            return false;
+            return CONNECTION_FAILED;
         }
 
         @Override
@@ -456,6 +490,11 @@
         }
 
         @Override
+        public boolean isConnected() {
+            return mIsConnected;
+        }
+
+        @Override
         public void setHasEmergency(boolean hasEmergency) {
             if (mDialerConnection != null) {
                 mDialerConnection.setHasEmergency(hasEmergency);
@@ -472,7 +511,8 @@
 
         @Override
         public void dump(IndentingPrintWriter pw) {
-            pw.println("Car Swapping ICS");
+            pw.print("Car Swapping ICS [");
+            pw.append(mIsConnected ? "" : "not ").append("connected]\n");
             pw.increaseIndent();
             if (mDialerConnection != null) {
                 pw.print("Dialer: ");
@@ -502,21 +542,32 @@
         }
 
         @Override
-        public boolean connect(Call call) {
+        public int connect(Call call) {
             for (InCallServiceBindingConnection subConnection : mSubConnections) {
                 subConnection.connect(call);
             }
-            return true;
+            return CONNECTION_SUCCEEDED;
         }
 
         @Override
         public void disconnect() {
             for (InCallServiceBindingConnection subConnection : mSubConnections) {
-                subConnection.disconnect();
+                if (subConnection.isConnected()) {
+                    subConnection.disconnect();
+                }
             }
         }
 
         @Override
+        public boolean isConnected() {
+            boolean connected = false;
+            for (InCallServiceBindingConnection subConnection : mSubConnections) {
+                connected = connected || subConnection.isConnected();
+            }
+            return connected;
+        }
+
+        @Override
         public void dump(IndentingPrintWriter pw) {
             pw.println("Non-UI Connections:");
             pw.increaseIndent();
@@ -696,15 +747,21 @@
 
     @Override
     public void onCallAdded(Call call) {
-        if (!isBoundToServices()) {
+        if (!isBoundAndConnectedToServices()) {
+            Log.i(this, "onCallAdded: %s; not bound or connected.", call);
+            // We are not bound, or we're not connected.
             bindToServices(call);
         } else {
+            // We are bound, and we are connected.
             adjustServiceBindingsForEmergency();
 
             Log.i(this, "onCallAdded: %s", call);
             // Track the call if we don't already know about it.
             addCall(call);
 
+            Log.i(this, "mInCallServiceConnection isConnected=%b",
+                    mInCallServiceConnection.isConnected());
+
             List<ComponentName> componentsUpdated = new ArrayList<>();
             for (Map.Entry<InCallServiceInfo, IInCallService> entry : mInCallServices.entrySet()) {
                 InCallServiceInfo info = entry.getKey();
@@ -960,53 +1017,66 @@
      * Unbinds an existing bound connection to the in-call app.
      */
     private void unbindFromServices() {
-        if (isBoundToServices()) {
-            if (mInCallServiceConnection != null) {
-                mInCallServiceConnection.disconnect();
-                mInCallServiceConnection = null;
-            }
-            if (mNonUIInCallServiceConnections != null) {
-                mNonUIInCallServiceConnections.disconnect();
-                mNonUIInCallServiceConnections = null;
-            }
+        if (mInCallServiceConnection != null) {
+            mInCallServiceConnection.disconnect();
+            mInCallServiceConnection = null;
+        }
+        if (mNonUIInCallServiceConnections != null) {
+            mNonUIInCallServiceConnections.disconnect();
+            mNonUIInCallServiceConnections = null;
         }
     }
 
     /**
      * Binds to all the UI-providing InCallService as well as system-implemented non-UI
-     * InCallServices. Method-invoker must check {@link #isBoundToServices()} before invoking.
+     * InCallServices. Method-invoker must check {@link #isBoundAndConnectedToServices()} before invoking.
      *
      * @param call The newly added call that triggered the binding to the in-call services.
      */
     @VisibleForTesting
     public void bindToServices(Call call) {
-        InCallServiceConnection dialerInCall = null;
-        InCallServiceInfo defaultDialerComponentInfo = getDefaultDialerComponent();
-        Log.i(this, "defaultDialer: " + defaultDialerComponentInfo);
-        if (defaultDialerComponentInfo != null &&
-                !defaultDialerComponentInfo.getComponentName().equals(mSystemInCallComponentName)) {
-            dialerInCall = new InCallServiceBindingConnection(defaultDialerComponentInfo);
-        }
-        Log.i(this, "defaultDialer: " + dialerInCall);
+        if (mInCallServiceConnection == null) {
+            InCallServiceConnection dialerInCall = null;
+            InCallServiceInfo defaultDialerComponentInfo = getDefaultDialerComponent();
+            Log.i(this, "defaultDialer: " + defaultDialerComponentInfo);
+            if (defaultDialerComponentInfo != null &&
+                    !defaultDialerComponentInfo.getComponentName().equals(
+                            mSystemInCallComponentName)) {
+                dialerInCall = new InCallServiceBindingConnection(defaultDialerComponentInfo);
+            }
+            Log.i(this, "defaultDialer: " + dialerInCall);
 
-        InCallServiceInfo systemInCallInfo =  getInCallServiceComponent(mSystemInCallComponentName,
-                IN_CALL_SERVICE_TYPE_SYSTEM_UI);
-        EmergencyInCallServiceConnection systemInCall =
-                new EmergencyInCallServiceConnection(systemInCallInfo, dialerInCall);
-        systemInCall.setHasEmergency(mCallsManager.hasEmergencyCall());
+            InCallServiceInfo systemInCallInfo = getInCallServiceComponent(
+                    mSystemInCallComponentName, IN_CALL_SERVICE_TYPE_SYSTEM_UI);
+            EmergencyInCallServiceConnection systemInCall =
+                    new EmergencyInCallServiceConnection(systemInCallInfo, dialerInCall);
+            systemInCall.setHasEmergency(mCallsManager.hasEmergencyCall());
 
-        InCallServiceConnection carModeInCall = null;
-        InCallServiceInfo carModeComponentInfo = getCarModeComponent();
-        if (carModeComponentInfo != null &&
-                !carModeComponentInfo.getComponentName().equals(mSystemInCallComponentName)) {
-            carModeInCall = new InCallServiceBindingConnection(carModeComponentInfo);
+            InCallServiceConnection carModeInCall = null;
+            InCallServiceInfo carModeComponentInfo = getCarModeComponent();
+            if (carModeComponentInfo != null &&
+                    !carModeComponentInfo.getComponentName().equals(mSystemInCallComponentName)) {
+                carModeInCall = new InCallServiceBindingConnection(carModeComponentInfo);
+            }
+
+            mInCallServiceConnection =
+                    new CarSwappingInCallServiceConnection(systemInCall, carModeInCall);
         }
 
-        mInCallServiceConnection =
-            new CarSwappingInCallServiceConnection(systemInCall, carModeInCall);
         mInCallServiceConnection.setCarMode(shouldUseCarModeUI());
-        mInCallServiceConnection.connect(call);
 
+        // Actually try binding to the UI InCallService.  If the response
+        if (mInCallServiceConnection.connect(call) ==
+                InCallServiceConnection.CONNECTION_SUCCEEDED) {
+            // Only connect to the non-ui InCallServices if we actually connected to the main UI
+            // one.
+            connectToNonUiInCallServices(call);
+        } else {
+            Log.i(this, "bindToServices: current UI doesn't support call; not binding.");
+        }
+    }
+
+    private void connectToNonUiInCallServices(Call call) {
         List<InCallServiceInfo> nonUIInCallComponents =
                 getInCallServiceComponents(IN_CALL_SERVICE_TYPE_NON_UI);
         List<InCallServiceBindingConnection> nonUIInCalls = new LinkedList<>();
@@ -1091,9 +1161,14 @@
                 boolean isSelfManageCallsSupported = serviceInfo.metaData != null &&
                         serviceInfo.metaData.getBoolean(
                                 TelecomManager.METADATA_INCLUDE_SELF_MANAGED_CALLS, false);
-                if (requestedType == 0 || requestedType == getInCallServiceType(entry.serviceInfo,
-                        packageManager)) {
 
+                int currentType = getInCallServiceType(entry.serviceInfo, packageManager);
+                if (requestedType == 0 || requestedType == currentType) {
+                    if (requestedType == IN_CALL_SERVICE_TYPE_NON_UI) {
+                        // We enforce the rule that self-managed calls are not supported by non-ui
+                        // InCallServices.
+                        isSelfManageCallsSupported = false;
+                    }
                     retval.add(new InCallServiceInfo(
                             new ComponentName(serviceInfo.packageName, serviceInfo.name),
                             isExternalCallsSupported, isSelfManageCallsSupported, requestedType));
@@ -1313,8 +1388,11 @@
         }
     }
 
-    private boolean isBoundToServices() {
-        return mInCallServiceConnection != null;
+    /**
+     * @return true if we are bound to the UI InCallService and it is connected.
+     */
+    private boolean isBoundAndConnectedToServices() {
+        return mInCallServiceConnection != null && mInCallServiceConnection.isConnected();
     }
 
     /**
diff --git a/src/com/android/server/telecom/PhoneAccountRegistrar.java b/src/com/android/server/telecom/PhoneAccountRegistrar.java
index 4fe6ed6..ea82d52 100644
--- a/src/com/android/server/telecom/PhoneAccountRegistrar.java
+++ b/src/com/android/server/telecom/PhoneAccountRegistrar.java
@@ -122,6 +122,14 @@
         public void onSimCallManagerChanged(PhoneAccountRegistrar registrar) {}
     }
 
+    /**
+     * Abstracts away dependency on the {@link PackageManager} required to fetch the label for an
+     * app.
+     */
+    public interface AppLabelProxy {
+        CharSequence getAppLabel(String packageName);
+    }
+
     private static final String FILE_NAME = "phone-account-registrar-state.xml";
     @VisibleForTesting
     public static final int EXPECTED_STATE_VERSION = 9;
@@ -135,6 +143,7 @@
     private final UserManager mUserManager;
     private final SubscriptionManager mSubscriptionManager;
     private final DefaultDialerCache mDefaultDialerCache;
+    private final AppLabelProxy mAppLabelProxy;
     private State mState;
     private UserHandle mCurrentUserHandle;
     private interface PhoneAccountRegistrarWriteLock {}
@@ -142,21 +151,15 @@
             new PhoneAccountRegistrarWriteLock() {};
 
     @VisibleForTesting
-    public PhoneAccountRegistrar(Context context, DefaultDialerCache defaultDialerCache) {
-        this(context, FILE_NAME, defaultDialerCache);
+    public PhoneAccountRegistrar(Context context, DefaultDialerCache defaultDialerCache,
+                                 AppLabelProxy appLabelProxy) {
+        this(context, FILE_NAME, defaultDialerCache, appLabelProxy);
     }
 
     @VisibleForTesting
     public PhoneAccountRegistrar(Context context, String fileName,
-            DefaultDialerCache defaultDialerCache) {
-        // TODO: This file path is subject to change -- it is storing the phone account registry
-        // state file in the path /data/system/users/0/, which is likely not correct in a
-        // multi-user setting.
-        /** UNCOMMENT_FOR_MOVE_TO_SYSTEM_SERVICE
-        String filePath = Environment.getUserSystemDirectory(UserHandle.myUserId()).
-                getAbsolutePath();
-        mAtomicFile = new AtomicFile(new File(filePath, fileName));
-         UNCOMMENT_FOR_MOVE_TO_SYSTEM_SERVICE */
+            DefaultDialerCache defaultDialerCache, AppLabelProxy appLabelProxy) {
+
         mAtomicFile = new AtomicFile(new File(context.getFilesDir(), fileName));
 
         mState = new State();
@@ -164,6 +167,7 @@
         mUserManager = UserManager.get(context);
         mDefaultDialerCache = defaultDialerCache;
         mSubscriptionManager = SubscriptionManager.from(mContext);
+        mAppLabelProxy = appLabelProxy;
         mCurrentUserHandle = Process.myUserHandle();
         read();
     }
@@ -640,6 +644,27 @@
             Log.i(this, "New phone account registered: " + account);
         }
 
+        // When registering a self-managed PhoneAccount we enforce the rule that the label that the
+        // app uses is also its phone account label.  Also ensure it does not attempt to declare
+        // itself as a sim acct, call manager or call provider.
+        if (account.hasCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED)) {
+            // Turn off bits we don't want to be able to set (TelecomServiceImpl protects against
+            // this but we'll also prevent it from happening here, just to be safe).
+            int newCapabilities = account.getCapabilities() &
+                    ~(PhoneAccount.CAPABILITY_CALL_PROVIDER |
+                        PhoneAccount.CAPABILITY_CONNECTION_MANAGER |
+                        PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION);
+
+            // Ensure name is correct.
+            CharSequence newLabel = mAppLabelProxy.getAppLabel(
+                    account.getAccountHandle().getComponentName().getPackageName());
+
+            account = account.toBuilder()
+                    .setLabel(newLabel)
+                    .setCapabilities(newCapabilities)
+                    .build();
+        }
+
         mState.accounts.add(account);
         // Set defaults and replace based on the group Id.
         maybeReplaceOldAccount(account);
diff --git a/src/com/android/server/telecom/TelecomBroadcastIntentProcessor.java b/src/com/android/server/telecom/TelecomBroadcastIntentProcessor.java
index 60bfe16..5df4451 100644
--- a/src/com/android/server/telecom/TelecomBroadcastIntentProcessor.java
+++ b/src/com/android/server/telecom/TelecomBroadcastIntentProcessor.java
@@ -34,6 +34,20 @@
     public static final String ACTION_CLEAR_MISSED_CALLS =
             "com.android.server.telecom.ACTION_CLEAR_MISSED_CALLS";
 
+    /**
+     * The action used to answer the current incoming call displayed by
+     * {@link com.android.server.telecom.ui.IncomingCallNotifier}.
+     */
+    public static final String ACTION_ANSWER_FROM_NOTIFICATION =
+            "com.android.server.telecom.ACTION_ANSWER_FROM_NOTIFICATION";
+
+    /**
+     * The action used to reject the current incoming call displayed by
+     * {@link com.android.server.telecom.ui.IncomingCallNotifier}.
+     */
+    public static final String ACTION_REJECT_FROM_NOTIFICATION =
+            "com.android.server.telecom.ACTION_REJECT_FROM_NOTIFICATION";
+
     public static final String EXTRA_USERHANDLE = "userhandle";
 
     private final Context mContext;
@@ -47,39 +61,65 @@
     public void processIntent(Intent intent) {
         String action = intent.getAction();
 
-        Log.v(this, "Action received: %s.", action);
-        UserHandle userHandle = intent.getParcelableExtra(EXTRA_USERHANDLE);
-        if (userHandle == null) {
-            Log.d(this, "user handle can't be null, not processing the broadcast");
-            return;
-        }
+        if (ACTION_SEND_SMS_FROM_NOTIFICATION.equals(action) ||
+                ACTION_CALL_BACK_FROM_NOTIFICATION.equals(action) ||
+                ACTION_CLEAR_MISSED_CALLS.equals(action)) {
+            Log.v(this, "Action received: %s.", action);
+            UserHandle userHandle = intent.getParcelableExtra(EXTRA_USERHANDLE);
+            if (userHandle == null) {
+                Log.d(this, "user handle can't be null, not processing the broadcast");
+                return;
+            }
 
-        MissedCallNotifier missedCallNotifier = mCallsManager.getMissedCallNotifier();
+            MissedCallNotifier missedCallNotifier = mCallsManager.getMissedCallNotifier();
 
-        // Send an SMS from the missed call notification.
-        if (ACTION_SEND_SMS_FROM_NOTIFICATION.equals(action)) {
-            // Close the notification shade and the notification itself.
-            closeSystemDialogs(mContext);
-            missedCallNotifier.clearMissedCalls(userHandle);
+            // Send an SMS from the missed call notification.
+            if (ACTION_SEND_SMS_FROM_NOTIFICATION.equals(action)) {
+                // Close the notification shade and the notification itself.
+                closeSystemDialogs(mContext);
+                missedCallNotifier.clearMissedCalls(userHandle);
 
-            Intent callIntent = new Intent(Intent.ACTION_SENDTO, intent.getData());
-            callIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-            mContext.startActivityAsUser(callIntent, userHandle);
+                Intent callIntent = new Intent(Intent.ACTION_SENDTO, intent.getData());
+                callIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                mContext.startActivityAsUser(callIntent, userHandle);
 
-        // Call back recent caller from the missed call notification.
-        } else if (ACTION_CALL_BACK_FROM_NOTIFICATION.equals(action)) {
-            // Close the notification shade and the notification itself.
-            closeSystemDialogs(mContext);
-            missedCallNotifier.clearMissedCalls(userHandle);
+                // Call back recent caller from the missed call notification.
+            } else if (ACTION_CALL_BACK_FROM_NOTIFICATION.equals(action)) {
+                // Close the notification shade and the notification itself.
+                closeSystemDialogs(mContext);
+                missedCallNotifier.clearMissedCalls(userHandle);
 
-            Intent callIntent = new Intent(Intent.ACTION_CALL, intent.getData());
-            callIntent.setFlags(
-                    Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
-            mContext.startActivityAsUser(callIntent, userHandle);
+                Intent callIntent = new Intent(Intent.ACTION_CALL, intent.getData());
+                callIntent.setFlags(
+                        Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+                mContext.startActivityAsUser(callIntent, userHandle);
 
-        // Clear the missed call notification and call log entries.
-        } else if (ACTION_CLEAR_MISSED_CALLS.equals(action)) {
-            missedCallNotifier.clearMissedCalls(userHandle);
+                // Clear the missed call notification and call log entries.
+            } else if (ACTION_CLEAR_MISSED_CALLS.equals(action)) {
+                missedCallNotifier.clearMissedCalls(userHandle);
+            }
+        } else if (ACTION_ANSWER_FROM_NOTIFICATION.equals(action)) {
+            Log.startSession("TBIP.aAFM");
+            try {
+                // Answer the current ringing call.
+                Call incomingCall = mCallsManager.getIncomingCallNotifier().getIncomingCall();
+                if (incomingCall != null) {
+                    mCallsManager.answerCall(incomingCall, incomingCall.getVideoState());
+                }
+            } finally {
+                Log.endSession();
+            }
+        } 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) {
+                    mCallsManager.rejectCall(incomingCall, false /* isRejectWithMessage */, null);
+                }
+            } finally {
+                Log.endSession();
+            }
         }
     }
 
diff --git a/src/com/android/server/telecom/TelecomServiceImpl.java b/src/com/android/server/telecom/TelecomServiceImpl.java
index 9cded98..94da28b 100644
--- a/src/com/android/server/telecom/TelecomServiceImpl.java
+++ b/src/com/android/server/telecom/TelecomServiceImpl.java
@@ -361,6 +361,10 @@
                                         "cannot also be call capable, connection managers, or " +
                                         "SIM accounts.");
                             }
+
+                            // For self-managed CS, the phone account registrar will override the
+                            // label the user has set for the phone account.  This ensures the
+                            // self-managed cs implementation can't spoof their app name.
                         }
                         if (account.hasCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)) {
                             enforceRegisterSimSubscriptionPermission();
diff --git a/src/com/android/server/telecom/TelecomSystem.java b/src/com/android/server/telecom/TelecomSystem.java
index f91581a..9c34ef5 100644
--- a/src/com/android/server/telecom/TelecomSystem.java
+++ b/src/com/android/server/telecom/TelecomSystem.java
@@ -21,6 +21,8 @@
 import com.android.server.telecom.bluetooth.BluetoothRouteManager;
 import com.android.server.telecom.components.UserCallIntentProcessor;
 import com.android.server.telecom.components.UserCallIntentProcessorFactory;
+import com.android.server.telecom.ui.IncomingCallNotifier;
+import com.android.server.telecom.ui.IncomingCallNotifier.IncomingCallNotifierFactory;
 import com.android.server.telecom.ui.MissedCallNotifierImpl.MissedCallNotifierImplFactory;
 import com.android.server.telecom.BluetoothPhoneServiceImpl.BluetoothPhoneServiceImplFactory;
 import com.android.server.telecom.CallAudioManager.AudioServiceFactory;
@@ -31,9 +33,12 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
 import android.net.Uri;
 import android.os.UserHandle;
 import android.telecom.Log;
+import android.telecom.PhoneAccountHandle;
 
 import java.io.FileNotFoundException;
 import java.io.InputStream;
@@ -97,6 +102,7 @@
 
     private final SyncRoot mLock = new SyncRoot() { };
     private final MissedCallNotifier mMissedCallNotifier;
+    private final IncomingCallNotifier mIncomingCallNotifier;
     private final PhoneAccountRegistrar mPhoneAccountRegistrar;
     private final CallsManager mCallsManager;
     private final RespondViaSmsManager mRespondViaSmsManager;
@@ -183,7 +189,8 @@
             Timeouts.Adapter timeoutsAdapter,
             AsyncRingtonePlayer asyncRingtonePlayer,
             PhoneNumberUtilsAdapter phoneNumberUtilsAdapter,
-            InterruptionFilterProxy interruptionFilterProxy) {
+            InterruptionFilterProxy interruptionFilterProxy,
+            IncomingCallNotifier incomingCallNotifier) {
         mContext = context.getApplicationContext();
         LogUtils.initLogging(mContext);
         DefaultDialerManagerAdapter defaultDialerAdapter =
@@ -193,7 +200,21 @@
                 defaultDialerAdapter, mLock);
 
         Log.startSession("TS.init");
-        mPhoneAccountRegistrar = new PhoneAccountRegistrar(mContext, defaultDialerCache);
+        mPhoneAccountRegistrar = new PhoneAccountRegistrar(mContext, defaultDialerCache,
+                new PhoneAccountRegistrar.AppLabelProxy() {
+                    @Override
+                    public CharSequence getAppLabel(String packageName) {
+                        PackageManager pm = mContext.getPackageManager();
+                        try {
+                            ApplicationInfo info = pm.getApplicationInfo(packageName, 0);
+                            return pm.getApplicationLabel(info);
+                        } catch (PackageManager.NameNotFoundException nnfe) {
+                            Log.w(this, "Could not determine package name.");
+                        }
+
+                        return null;
+                    }
+                });
         mContactsAsyncHelper = new ContactsAsyncHelper(
                 new ContactsAsyncHelper.ContentResolverAdapter() {
                     @Override
@@ -236,6 +257,25 @@
                 interruptionFilterProxy,
                 emergencyCallHelper);
 
+        mIncomingCallNotifier = incomingCallNotifier;
+        incomingCallNotifier.setCallsManagerProxy(new IncomingCallNotifier.CallsManagerProxy() {
+            @Override
+            public boolean hasCallsForOtherPhoneAccount(PhoneAccountHandle phoneAccountHandle) {
+                return mCallsManager.hasCallsForOtherPhoneAccount(phoneAccountHandle);
+            }
+
+            @Override
+            public int getNumCallsForOtherPhoneAccount(PhoneAccountHandle phoneAccountHandle) {
+                return mCallsManager.getNumCallsForOtherPhoneAccount(phoneAccountHandle);
+            }
+
+            @Override
+            public Call getActiveCall() {
+                return mCallsManager.getActiveCall();
+            }
+        });
+        mCallsManager.setIncomingCallNotifier(mIncomingCallNotifier);
+
         mRespondViaSmsManager = new RespondViaSmsManager(mCallsManager, mLock);
         mCallsManager.setRespondViaSmsManager(mRespondViaSmsManager);
 
diff --git a/src/com/android/server/telecom/components/TelecomService.java b/src/com/android/server/telecom/components/TelecomService.java
index a066b6c..3d28eae 100644
--- a/src/com/android/server/telecom/components/TelecomService.java
+++ b/src/com/android/server/telecom/components/TelecomService.java
@@ -16,7 +16,6 @@
 
 package com.android.server.telecom.components;
 
-import android.app.Notification;
 import android.app.NotificationManager;
 import android.app.Service;
 import android.bluetooth.BluetoothAdapter;
@@ -28,6 +27,7 @@
 import android.os.ServiceManager;
 import android.service.notification.ZenModeConfig;
 import android.telecom.Log;
+import android.telecom.PhoneAccountHandle;
 
 import com.android.internal.telephony.CallerInfoAsyncQuery;
 import com.android.server.telecom.AsyncRingtonePlayer;
@@ -42,7 +42,6 @@
 import com.android.server.telecom.CallAudioManager;
 import com.android.server.telecom.InterruptionFilterProxy;
 import com.android.server.telecom.PhoneAccountRegistrar;
-import com.android.server.telecom.PhoneNumberUtilsAdapter;
 import com.android.server.telecom.PhoneNumberUtilsAdapterImpl;
 import com.android.server.telecom.ProximitySensorManagerFactory;
 import com.android.server.telecom.InCallWakeLockController;
@@ -50,6 +49,7 @@
 import com.android.server.telecom.TelecomSystem;
 import com.android.server.telecom.TelecomWakeLock;
 import com.android.server.telecom.Timeouts;
+import com.android.server.telecom.ui.IncomingCallNotifier;
 import com.android.server.telecom.ui.MissedCallNotifierImpl;
 
 /**
@@ -132,7 +132,7 @@
                             new InCallWakeLockControllerFactory() {
                                 @Override
                                 public InCallWakeLockController create(Context context,
-                                        CallsManager callsManager) {
+                                                                       CallsManager callsManager) {
                                     return new InCallWakeLockController(
                                             new TelecomWakeLock(context,
                                                     PowerManager.FULL_WAKE_LOCK,
@@ -180,7 +180,8 @@
                                     }
                                     return null;
                                 }
-                            }
+                            },
+                            new IncomingCallNotifier(context)
                     ));
         }
         if (BluetoothAdapter.getDefaultAdapter() != null) {
diff --git a/src/com/android/server/telecom/ui/IncomingCallNotifier.java b/src/com/android/server/telecom/ui/IncomingCallNotifier.java
new file mode 100644
index 0000000..cd34e71
--- /dev/null
+++ b/src/com/android/server/telecom/ui/IncomingCallNotifier.java
@@ -0,0 +1,293 @@
+/*
+ * 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 android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.telecom.Log;
+import android.telecom.PhoneAccount;
+import android.telecom.PhoneAccountHandle;
+import android.telecom.TelecomManager;
+import android.telecom.VideoProfile;
+import android.text.Spannable;
+import android.text.SpannableString;
+import android.text.TextUtils;
+import android.text.style.ForegroundColorSpan;
+import android.util.ArraySet;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.CallerInfo;
+import com.android.server.telecom.Call;
+import com.android.server.telecom.CallState;
+import com.android.server.telecom.CallsManagerListenerBase;
+import com.android.server.telecom.R;
+import com.android.server.telecom.TelecomBroadcastIntentProcessor;
+import com.android.server.telecom.components.TelecomBroadcastReceiver;
+
+import java.util.Optional;
+import java.util.Set;
+
+/**
+ * Manages the display of an incoming call UX when a new ringing self-managed call is added, and
+ * there is an ongoing call in another {@link android.telecom.PhoneAccount}.
+ */
+public class IncomingCallNotifier extends CallsManagerListenerBase {
+
+    public interface IncomingCallNotifierFactory {
+        IncomingCallNotifier make(Context context, CallsManagerProxy mCallsManagerProxy);
+    }
+
+    /**
+     * Eliminates strict dependency between this class and CallsManager.
+     */
+    public interface CallsManagerProxy {
+        boolean hasCallsForOtherPhoneAccount(PhoneAccountHandle phoneAccountHandle);
+        int getNumCallsForOtherPhoneAccount(PhoneAccountHandle phoneAccountHandle);
+        Call getActiveCall();
+    }
+
+    // Notification for incoming calls. This is interruptive and will show up as a HUN.
+    @VisibleForTesting
+    public static final int NOTIFICATION_INCOMING_CALL = 1;
+    private static final String NOTIFICATION_TAG = IncomingCallNotifier.class.getSimpleName();
+
+
+    public final Call.ListenerBase mCallListener = new Call.ListenerBase() {
+        @Override
+        public void onCallerInfoChanged(Call call) {
+            if (mIncomingCall != call) {
+                return;
+            }
+            showIncomingCallNotification(mIncomingCall);
+        }
+    };
+
+    private final Context mContext;
+    private final NotificationManager mNotificationManager;
+    private final Set<Call> mCalls = new ArraySet<>();
+    private CallsManagerProxy mCallsManagerProxy;
+
+    // The current incoming call we are displaying UX for.
+    private Call mIncomingCall;
+
+    public IncomingCallNotifier(Context context) {
+        mContext = context;
+        mNotificationManager =
+                (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+    }
+
+    public void setCallsManagerProxy(CallsManagerProxy callsManagerProxy) {
+        mCallsManagerProxy = callsManagerProxy;
+    }
+
+    public Call getIncomingCall() {
+        return mIncomingCall;
+    }
+
+    @Override
+    public void onCallAdded(Call call) {
+        if (!mCalls.contains(call)) {
+            mCalls.add(call);
+        }
+
+        updateIncomingCall();
+    }
+
+    @Override
+    public void onCallRemoved(Call call) {
+        if (mCalls.contains(call)) {
+            mCalls.remove(call);
+        }
+
+        updateIncomingCall();
+    }
+
+    @Override
+    public void onCallStateChanged(Call call, int oldState, int newState) {
+        updateIncomingCall();
+    }
+
+    /**
+     * Determines which call is the active ringing call at this time and triggers the display of the
+     * UI.
+     */
+    private void updateIncomingCall() {
+        Optional<Call> incomingCallOp = mCalls.stream()
+                .filter(call -> call.isSelfManaged() && call.isIncoming() &&
+                        call.getState() == CallState.RINGING)
+                .findFirst();
+        Call incomingCall = incomingCallOp.orElse(null);
+        if (incomingCall != null && mCallsManagerProxy != null &&
+                !mCallsManagerProxy.hasCallsForOtherPhoneAccount(
+                        incomingCallOp.get().getTargetPhoneAccount())) {
+            // If there is no calls in any other ConnectionService, we can rely on the
+            // third-party app to display its own incoming call UI.
+            incomingCall = null;
+        }
+
+        Log.i(this, "updateIncomingCall: foundIncomingcall = %s", incomingCall);
+
+        boolean hadIncomingCall = mIncomingCall != null;
+        boolean hasIncomingCall = incomingCall != null;
+        if (incomingCall != mIncomingCall) {
+            Call previousIncomingCall = mIncomingCall;
+            mIncomingCall = incomingCall;
+
+            if (hasIncomingCall && !hadIncomingCall) {
+                mIncomingCall.addListener(mCallListener);
+                showIncomingCallNotification(mIncomingCall);
+            } else if (hadIncomingCall && !hasIncomingCall) {
+                previousIncomingCall.removeListener(mCallListener);
+                hideIncomingCallNotification();
+            }
+        }
+    }
+
+    private void showIncomingCallNotification(Call call) {
+        Log.i(this, "showIncomingCallNotification showCall = %s", call);
+
+        Notification.Builder builder = getNotificationBuilder(call,
+                mCallsManagerProxy.getActiveCall());
+        mNotificationManager.notify(NOTIFICATION_TAG, NOTIFICATION_INCOMING_CALL, builder.build());
+    }
+
+    private void hideIncomingCallNotification() {
+        Log.i(this, "hideIncomingCallNotification");
+        mNotificationManager.cancel(NOTIFICATION_TAG, NOTIFICATION_INCOMING_CALL);
+    }
+
+    private String getNotificationName(Call call) {
+        String name = "";
+        if (call.getCallerDisplayNamePresentation() == TelecomManager.PRESENTATION_ALLOWED) {
+            name = call.getCallerDisplayName();
+        }
+        if (TextUtils.isEmpty(name)) {
+            name = call.getName();
+        }
+
+        if (TextUtils.isEmpty(name)) {
+            name = call.getPhoneNumber();
+        }
+        return name;
+    }
+
+    private Notification.Builder getNotificationBuilder(Call incomingCall, Call ongoingCall) {
+        // Change the notification app name to "Android System" to sufficiently distinguish this
+        // from the phone app's name.
+        Bundle extras = new Bundle();
+        extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME, mContext.getString(
+                com.android.internal.R.string.android_system_label));
+
+        Intent answerIntent = new Intent(
+                TelecomBroadcastIntentProcessor.ACTION_ANSWER_FROM_NOTIFICATION, null, mContext,
+                TelecomBroadcastReceiver.class);
+        Intent rejectIntent = new Intent(
+                TelecomBroadcastIntentProcessor.ACTION_REJECT_FROM_NOTIFICATION, null, mContext,
+                TelecomBroadcastReceiver.class);
+
+        String nameOrNumber = getNotificationName(incomingCall);
+        CharSequence viaApp = incomingCall.getTargetPhoneAccountLabel();
+        boolean isIncomingVideo = VideoProfile.isVideo(incomingCall.getVideoState());
+        boolean isOngoingVideo = ongoingCall != null ?
+                VideoProfile.isVideo(ongoingCall.getVideoState()) : false;
+        int numOtherCalls = ongoingCall != null ?
+                mCallsManagerProxy.getNumCallsForOtherPhoneAccount(
+                        incomingCall.getTargetPhoneAccount()) : 1;
+
+        // Build the "IncomingApp call from John Smith" message.
+        CharSequence incomingCallText;
+        if (isIncomingVideo) {
+            incomingCallText = mContext.getString(R.string.notification_incoming_video_call, viaApp,
+                    nameOrNumber);
+        } else {
+            incomingCallText = mContext.getString(R.string.notification_incoming_call, viaApp,
+                    nameOrNumber);
+        }
+
+        // Build the "Answering will end your OtherApp call" line.
+        CharSequence disconnectText;
+        if (ongoingCall != null && ongoingCall.isSelfManaged()) {
+            CharSequence ongoingApp = ongoingCall.getTargetPhoneAccountLabel();
+            // For an ongoing self-managed call, we use a message like:
+            // "Answering will end your OtherApp call".
+            if (numOtherCalls > 1) {
+                // Multiple ongoing calls in the other app, so don't bother specifing whether it is
+                // a video call or audio call.
+                disconnectText = mContext.getString(R.string.answering_ends_other_calls,
+                        ongoingApp);
+            } else if (isOngoingVideo) {
+                disconnectText = mContext.getString(R.string.answering_ends_other_video_call,
+                        ongoingApp);
+            } else {
+                disconnectText = mContext.getString(R.string.answering_ends_other_call, ongoingApp);
+            }
+        } else {
+            // For an ongoing managed call, we use a message like:
+            // "Answering will end your ongoing call".
+            if (numOtherCalls > 1) {
+                // Multiple ongoing manage calls, so don't bother specifing whether it is a video
+                // call or audio call.
+                disconnectText = mContext.getString(R.string.answering_ends_other_managed_calls);
+            } else if (isOngoingVideo) {
+                disconnectText = mContext.getString(
+                        R.string.answering_ends_other_managed_video_call);
+            } else {
+                disconnectText = mContext.getString(R.string.answering_ends_other_managed_call);
+            }
+        }
+
+        final Notification.Builder builder = new Notification.Builder(mContext);
+        builder.setOngoing(true);
+        builder.setExtras(extras);
+        builder.setPriority(Notification.PRIORITY_HIGH);
+        builder.setCategory(Notification.CATEGORY_CALL);
+        builder.setContentTitle(incomingCallText);
+        builder.setContentText(disconnectText);
+        builder.setSmallIcon(R.drawable.ic_phone);
+        // Ensures this is a heads up notification.  A heads-up notification is typically only shown
+        // if there is a fullscreen intent.  However since this notification doesn't have that we
+        // will use this trick to get it to show as one anyways.
+        builder.setVibrate(new long[0]);
+        builder.setColor(mContext.getResources().getColor(R.color.theme_color));
+        builder.addAction(
+                R.anim.on_going_call,
+                getActionText(R.string.answer_incoming_call, R.color.notification_action_answer),
+                PendingIntent.getBroadcast(mContext, 0, answerIntent,
+                        PendingIntent.FLAG_CANCEL_CURRENT));
+        builder.addAction(
+                R.drawable.ic_close_dk,
+                getActionText(R.string.decline_incoming_call, R.color.notification_action_decline),
+                PendingIntent.getBroadcast(mContext, 0, rejectIntent,
+                        PendingIntent.FLAG_CANCEL_CURRENT));
+        return builder;
+    }
+
+    private CharSequence getActionText(int stringRes, int colorRes) {
+        CharSequence string = mContext.getText(stringRes);
+        if (string == null) {
+            return "";
+        }
+        Spannable spannable = new SpannableString(string);
+        spannable.setSpan(
+                    new ForegroundColorSpan(mContext.getColor(colorRes)), 0, spannable.length(), 0);
+        return spannable;
+    }
+}
diff --git a/src/com/android/server/telecom/ui/MissedCallNotifierImpl.java b/src/com/android/server/telecom/ui/MissedCallNotifierImpl.java
index edb3d72..534b7f2 100644
--- a/src/com/android/server/telecom/ui/MissedCallNotifierImpl.java
+++ b/src/com/android/server/telecom/ui/MissedCallNotifierImpl.java
@@ -122,6 +122,7 @@
     public static final int CALL_LOG_COLUMN_TYPE = 5;
 
     private static final int MISSED_CALL_NOTIFICATION_ID = 1;
+    private static final String NOTIFICATION_TAG = MissedCallNotifierImpl.class.getSimpleName();
 
     private final Context mContext;
     private final PhoneAccountRegistrar mPhoneAccountRegistrar;
@@ -375,7 +376,7 @@
         long token = Binder.clearCallingIdentity();
         try {
             mNotificationManager.notifyAsUser(
-                    null /* tag */, MISSED_CALL_NOTIFICATION_ID, notification, userHandle);
+                    NOTIFICATION_TAG, MISSED_CALL_NOTIFICATION_ID, notification, userHandle);
         } finally {
             Binder.restoreCallingIdentity(token);
         }
@@ -395,7 +396,8 @@
 
         long token = Binder.clearCallingIdentity();
         try {
-            mNotificationManager.cancelAsUser(null, MISSED_CALL_NOTIFICATION_ID, userHandle);
+            mNotificationManager.cancelAsUser(NOTIFICATION_TAG, MISSED_CALL_NOTIFICATION_ID,
+                    userHandle);
         } finally {
             Binder.restoreCallingIdentity(token);
         }
diff --git a/testapps/AndroidManifest.xml b/testapps/AndroidManifest.xml
index 994abbe..a57b158 100644
--- a/testapps/AndroidManifest.xml
+++ b/testapps/AndroidManifest.xml
@@ -179,7 +179,8 @@
 
         <activity android:name="com.android.server.telecom.testapps.SelfManagedCallingActivity"
                   android:label="@string/selfManagedCallingActivityLabel"
-                  android:process="com.android.server.telecom.testapps.SelfMangingCallingApp">
+                  android:process="com.android.server.telecom.testapps.SelfMangingCallingApp"
+                  android:theme="@android:style/Theme.Material.Light">
           <intent-filter>
               <action android:name="android.intent.action.MAIN" />
               <category android:name="android.intent.category.DEFAULT" />
diff --git a/testapps/res/layout/self_managed_sample_main.xml b/testapps/res/layout/self_managed_sample_main.xml
index 144d6ec..e30ef42 100644
--- a/testapps/res/layout/self_managed_sample_main.xml
+++ b/testapps/res/layout/self_managed_sample_main.xml
@@ -24,63 +24,80 @@
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:text="@string/checkIfPermittedBeforeCallingButton" />
-    <TableLayout
+
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="This app provides two sample implementations of the self-managed ConnectionService API.  Use this UI to add simulated self-managed calls:" />
+
+    <RadioGroup
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal">
+        <RadioButton
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Sample Source 1"
+            android:id="@+id/useAcct1Button"
+            android:background="@color/test_call_a_color"/>
+        <RadioButton
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Sample Source 2"
+            android:id="@+id/useAcct2Button"
+            android:background="@color/test_call_b_color"/>
+    </RadioGroup>
+
+    <RadioGroup
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal">
+        <RadioButton
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Video Call"
+            android:id="@+id/videoCallButton"/>
+        <RadioButton
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Audio Call"
+            android:id="@+id/audioCallButton"/>
+    </RadioGroup>
+
+    <LinearLayout android:orientation="horizontal"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content">
-        <TableRow android:layout_span="2">
-            <RadioGroup>
-                <RadioButton
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:text="Acct 1"
-                    android:id="@+id/useAcct1Button"/>
-                <RadioButton
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:text="Acct 2"
-                    android:id="@+id/useAcct2Button"/>
-            </RadioGroup>
-        </TableRow>
-        <TableRow android:layout_span="2">
-            <TextView
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:text="Place call:" />
-        </TableRow>
-        <TableRow>
-            <TextView
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:text="Number:" />
-            <EditText
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:id="@+id/phoneNumber" />
-        </TableRow>
-        <TableRow>
-            <Button
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:text="Outgoing Call"
-                android:id="@+id/placeOutgoingCallButton" />
-            <Button
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:text="Incoming Call"
-                android:id="@+id/placeIncomingCallButton" />
-            <Button
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:text="Incoming Call (Delay)"
-                android:id="@+id/placeIncomingCallDelayButton" />
-        </TableRow>
-        <TableRow android:layout_span="2">
-            <ListView
-                android:id="@+id/callList"
-                android:layout_width="match_parent"
-                android:layout_height="match_parent"
-                android:listSelector="@null"
-                android:divider="@null" />
-        </TableRow>
-    </TableLayout>
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Number:" />
+        <EditText
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:id="@+id/phoneNumber"
+            android:text="tel:555-1212"/>
+    </LinearLayout>
+
+    <LinearLayout android:orientation="horizontal"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content">
+
+        <Button
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Outgoing Call"
+            android:id="@+id/placeOutgoingCallButton" />
+        <Button
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Incoming Call"
+            android:id="@+id/placeIncomingCallButton" />
+    </LinearLayout>
+
+    <ListView
+        android:id="@+id/callList"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:listSelector="@null"
+        android:divider="@null" />
 </LinearLayout>
\ No newline at end of file
diff --git a/testapps/res/values/colors.xml b/testapps/res/values/colors.xml
new file mode 100644
index 0000000..3939e78
--- /dev/null
+++ b/testapps/res/values/colors.xml
@@ -0,0 +1,20 @@
+<!--
+  ~ 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
+  -->
+
+<resources>
+    <color name="test_call_a_color">#f2eebf</color>
+    <color name="test_call_b_color">#afc5e6</color>
+</resources>
diff --git a/testapps/src/com/android/server/telecom/testapps/SelfManagedCallList.java b/testapps/src/com/android/server/telecom/testapps/SelfManagedCallList.java
index 1d6367e..4275079 100644
--- a/testapps/src/com/android/server/telecom/testapps/SelfManagedCallList.java
+++ b/testapps/src/com/android/server/telecom/testapps/SelfManagedCallList.java
@@ -43,6 +43,8 @@
 
     public static String SELF_MANAGED_ACCOUNT_1 = "1";
     public static String SELF_MANAGED_ACCOUNT_2 = "2";
+    public static String SELF_MANAGED_NAME_1 = "SuperCall";
+    public static String SELF_MANAGED_NAME_2 = "Mega Call";
 
     private static SelfManagedCallList sInstance;
     private static ComponentName COMPONENT_NAME = new ComponentName(
@@ -89,20 +91,23 @@
     }
 
     public void registerPhoneAccounts(Context context) {
-        registerPhoneAccount(context, SELF_MANAGED_ACCOUNT_1, SELF_MANAGED_ADDRESS_1);
-        registerPhoneAccount(context, SELF_MANAGED_ACCOUNT_2, SELF_MANAGED_ADDRESS_2);
+        registerPhoneAccount(context, SELF_MANAGED_ACCOUNT_1, SELF_MANAGED_ADDRESS_1,
+                SELF_MANAGED_NAME_1);
+        registerPhoneAccount(context, SELF_MANAGED_ACCOUNT_2, SELF_MANAGED_ADDRESS_2,
+                SELF_MANAGED_NAME_2);
     }
 
-    public void registerPhoneAccount(Context context, String id, Uri address) {
+    public void registerPhoneAccount(Context context, String id, Uri address, String name) {
         PhoneAccountHandle handle = new PhoneAccountHandle(COMPONENT_NAME, id);
         mPhoneAccounts.put(id, handle);
-        PhoneAccount.Builder builder = PhoneAccount.builder(handle, id)
+        PhoneAccount.Builder builder = PhoneAccount.builder(handle, name)
                 .addSupportedUriScheme(PhoneAccount.SCHEME_TEL)
                 .addSupportedUriScheme(PhoneAccount.SCHEME_SIP)
                 .setAddress(address)
                 .setCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED |
                         PhoneAccount.CAPABILITY_VIDEO_CALLING |
-                        PhoneAccount.CAPABILITY_SUPPORTS_VIDEO_CALLING);
+                        PhoneAccount.CAPABILITY_SUPPORTS_VIDEO_CALLING)
+                .setShortDescription(name);
 
         TelecomManager.from(context).registerPhoneAccount(builder.build());
     }
diff --git a/testapps/src/com/android/server/telecom/testapps/SelfManagedCallListAdapter.java b/testapps/src/com/android/server/telecom/testapps/SelfManagedCallListAdapter.java
index 29d3c5f..b46d5e1 100644
--- a/testapps/src/com/android/server/telecom/testapps/SelfManagedCallListAdapter.java
+++ b/testapps/src/com/android/server/telecom/testapps/SelfManagedCallListAdapter.java
@@ -26,6 +26,8 @@
 import android.widget.BaseAdapter;
 import android.widget.TextView;
 
+import com.android.server.telecom.testapps.R;
+
 import java.util.List;
 
 public class SelfManagedCallListAdapter extends BaseAdapter {
@@ -124,6 +126,12 @@
         SelfManagedConnection connection = mConnections.get(position);
         PhoneAccountHandle phoneAccountHandle = connection.getExtras().getParcelable(
                 SelfManagedConnection.EXTRA_PHONE_ACCOUNT_HANDLE);
+        if (phoneAccountHandle.getId().equals(SelfManagedCallList.SELF_MANAGED_ACCOUNT_1)) {
+            result.setBackgroundColor(result.getContext().getColor(R.color.test_call_a_color));
+        } else {
+            result.setBackgroundColor(result.getContext().getColor(R.color.test_call_b_color));
+        }
+
         CallAudioState audioState = connection.getCallAudioState();
         String audioRoute = "?";
         if (audioState != null) {
@@ -148,9 +156,9 @@
         String callType;
         if (connection.isIncomingCall()) {
             if (connection.isIncomingCallUiShowing()) {
-                callType = "Incoming - Show Incoming UX";
+                callType = "Incoming(our ux) ";
             } else {
-                callType = "Incoming - DO NOT SHOW Incoming UX";
+                callType = "Incoming(sys ux) ";
             }
         } else {
             callType = "Outgoing";
diff --git a/testapps/src/com/android/server/telecom/testapps/SelfManagedCallingActivity.java b/testapps/src/com/android/server/telecom/testapps/SelfManagedCallingActivity.java
index c8f6157..4dfa012 100644
--- a/testapps/src/com/android/server/telecom/testapps/SelfManagedCallingActivity.java
+++ b/testapps/src/com/android/server/telecom/testapps/SelfManagedCallingActivity.java
@@ -22,6 +22,7 @@
 import android.telecom.ConnectionRequest;
 import android.telecom.PhoneAccountHandle;
 import android.telecom.TelecomManager;
+import android.telecom.VideoProfile;
 import android.util.Log;
 import android.view.View;
 import android.view.WindowManager;
@@ -46,9 +47,10 @@
     private CheckBox mCheckIfPermittedBeforeCalling;
     private Button mPlaceOutgoingCallButton;
     private Button mPlaceIncomingCallButton;
-    private Button mPlaceIncomingCallDelayButton;
     private RadioButton mUseAcct1Button;
     private RadioButton mUseAcct2Button;
+    private RadioButton mVideoCallButton;
+    private RadioButton mAudioCallButton;
     private EditText mNumber;
     private ListView mListView;
     private SelfManagedCallListAdapter mListAdapter;
@@ -102,21 +104,10 @@
             }
         });
 
-        mPlaceIncomingCallDelayButton = (Button) findViewById(R.id.placeIncomingCallDelayButton);
-        mPlaceIncomingCallDelayButton.setOnClickListener(new View.OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                // Delay the incoming call so that we can turn off the screen and
-                try {
-                    Thread.sleep(5000);
-                } catch (InterruptedException e) {
-                    e.printStackTrace();
-                }
-                placeIncomingCall();
-            }
-        });
         mUseAcct1Button = (RadioButton) findViewById(R.id.useAcct1Button);
         mUseAcct2Button = (RadioButton) findViewById(R.id.useAcct2Button);
+        mVideoCallButton = (RadioButton) findViewById(R.id.videoCallButton);
+        mAudioCallButton = (RadioButton) findViewById(R.id.audioCallButton);
         mNumber = (EditText) findViewById(R.id.phoneNumber);
         mListView = (ListView) findViewById(R.id.callList);
         mCallList.setListener(mCallListListener);
@@ -150,6 +141,10 @@
         Bundle extras = new Bundle();
         extras.putParcelable(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE,
                 getSelectedPhoneAccountHandle());
+        if (mVideoCallButton.isChecked()) {
+            extras.putInt(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE,
+                    VideoProfile.STATE_BIDIRECTIONAL);
+        }
         tm.placeCall(Uri.parse(mNumber.getText().toString()), extras);
     }
 
@@ -167,6 +162,10 @@
         Bundle extras = new Bundle();
         extras.putParcelable(TelecomManager.EXTRA_INCOMING_CALL_ADDRESS,
                 Uri.parse(mNumber.getText().toString()));
+        if (mVideoCallButton.isChecked()) {
+            extras.putInt(TelecomManager.EXTRA_INCOMING_VIDEO_STATE,
+                    VideoProfile.STATE_BIDIRECTIONAL);
+        }
         tm.addNewIncomingCall(getSelectedPhoneAccountHandle(), extras);
     }
 }
\ No newline at end of file
diff --git a/testapps/src/com/android/server/telecom/testapps/SelfManagedConnection.java b/testapps/src/com/android/server/telecom/testapps/SelfManagedConnection.java
index 051948b..766efa5 100644
--- a/testapps/src/com/android/server/telecom/testapps/SelfManagedConnection.java
+++ b/testapps/src/com/android/server/telecom/testapps/SelfManagedConnection.java
@@ -27,6 +27,7 @@
 import android.telecom.Connection;
 import android.telecom.ConnectionService;
 import android.telecom.DisconnectCause;
+import android.telecom.VideoProfile;
 
 import com.android.server.telecom.testapps.R;
 
@@ -129,6 +130,36 @@
         notificationManager.notify(CALL_NOTIFICATION, mCallId, builder.build());
     }
 
+    @Override
+    public void onHold() {
+        setOnHold();
+    }
+
+    @Override
+    public void onUnhold() {
+        setActive();
+    }
+
+    @Override
+    public void onAnswer(int videoState) {
+        setConnectionActive();
+    }
+
+    @Override
+    public void onAnswer() {
+        onAnswer(VideoProfile.STATE_AUDIO_ONLY);
+    }
+
+    @Override
+    public void onReject() {
+        setConnectionDisconnected(DisconnectCause.REJECTED);
+    }
+
+    @Override
+    public void onDisconnect() {
+        setConnectionDisconnected(DisconnectCause.LOCAL);
+    }
+
     public void setConnectionActive() {
         mMediaPlayer.start();
         setActive();
diff --git a/testapps/src/com/android/server/telecom/testapps/SelfManagedConnectionService.java b/testapps/src/com/android/server/telecom/testapps/SelfManagedConnectionService.java
index e1a01ba..1d52a3b 100644
--- a/testapps/src/com/android/server/telecom/testapps/SelfManagedConnectionService.java
+++ b/testapps/src/com/android/server/telecom/testapps/SelfManagedConnectionService.java
@@ -23,8 +23,10 @@
 import android.telecom.Log;
 import android.telecom.PhoneAccountHandle;
 import android.telecom.TelecomManager;
+import android.telecom.VideoProfile;
 
 import java.util.Objects;
+import java.util.Random;
 
 /**
  * Sample implementation of the self-managed {@link ConnectionService} API.
@@ -32,6 +34,8 @@
  * See {@link android.telecom} for more information on self-managed {@link ConnectionService}s.
  */
 public class SelfManagedConnectionService extends ConnectionService {
+    private static final String[] TEST_NAMES = {"Tom Smith", "Jane Appleseed", "Joseph Engleton",
+            "Claudia McPherson", "Chris P. Bacon", "Seymour Butz", "Hugh Mungus", "Anita Bath"};
     private final SelfManagedCallList mCallList = SelfManagedCallList.getInstance();
 
     @Override
@@ -60,13 +64,17 @@
         mCallList.notifyCreateOutgoingConnectionFailed(request);
     }
 
-
     private Connection createSelfManagedConnection(ConnectionRequest request, boolean isIncoming) {
         SelfManagedConnection connection = new SelfManagedConnection(mCallList,
                 getApplicationContext(), isIncoming);
         connection.setListener(mCallList.getConnectionListener());
         connection.setConnectionProperties(Connection.PROPERTY_SELF_MANAGED);
         connection.setAddress(request.getAddress(), TelecomManager.PRESENTATION_ALLOWED);
+        connection.setAudioModeIsVoip(true);
+        connection.setVideoState(request.getVideoState());
+        Random random = new Random();
+        connection.setCallerDisplayName(TEST_NAMES[random.nextInt(TEST_NAMES.length)],
+                TelecomManager.PRESENTATION_ALLOWED);
         connection.setExtras(request.getExtras());
         if (isIncoming) {
             connection.setIsIncomingCallUiShowing(request.shouldShowIncomingCallUi());
diff --git a/tests/src/com/android/server/telecom/tests/IncomingCallNotifierTest.java b/tests/src/com/android/server/telecom/tests/IncomingCallNotifierTest.java
new file mode 100644
index 0000000..c842379
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/IncomingCallNotifierTest.java
@@ -0,0 +1,130 @@
+/*
+ * 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.tests;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.res.Resources;
+import android.os.Build;
+import android.telecom.VideoProfile;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.server.telecom.Call;
+import com.android.server.telecom.CallState;
+import com.android.server.telecom.ui.IncomingCallNotifier;
+
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Matchers.isNull;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+/**
+ * Tests for the {@link com.android.server.telecom.ui.IncomingCallNotifier} class.
+ */
+public class IncomingCallNotifierTest extends TelecomTestCase {
+
+    @Mock private IncomingCallNotifier.CallsManagerProxy mCallsManagerProxy;
+    @Mock private Call mAudioCall;
+    @Mock private Call mVideoCall;
+    @Mock private Call mRingingCall;
+    private IncomingCallNotifier mIncomingCallNotifier;
+    private NotificationManager mNotificationManager;
+
+    public void setUp() throws Exception {
+        super.setUp();
+        mContext = mComponentContextFixture.getTestDouble().getApplicationContext();
+        ApplicationInfo info = new ApplicationInfo();
+        info.targetSdkVersion = Build.VERSION_CODES.N_MR1;
+        doReturn(info).when(mContext).getApplicationInfo();
+        doReturn(null).when(mContext).getTheme();
+        mNotificationManager = (NotificationManager) mContext.getSystemService(
+                Context.NOTIFICATION_SERVICE);
+        mIncomingCallNotifier = new IncomingCallNotifier(mContext);
+        mIncomingCallNotifier.setCallsManagerProxy(mCallsManagerProxy);
+
+        when(mAudioCall.getVideoState()).thenReturn(VideoProfile.STATE_AUDIO_ONLY);
+        when(mAudioCall.getTargetPhoneAccountLabel()).thenReturn("Bar");
+        when(mVideoCall.getVideoState()).thenReturn(VideoProfile.STATE_BIDIRECTIONAL);
+        when(mVideoCall.getTargetPhoneAccountLabel()).thenReturn("Bar");
+        when(mRingingCall.isSelfManaged()).thenReturn(true);
+        when(mRingingCall.isIncoming()).thenReturn(true);
+        when(mRingingCall.getState()).thenReturn(CallState.RINGING);
+        when(mRingingCall.getVideoState()).thenReturn(VideoProfile.STATE_AUDIO_ONLY);
+        when(mRingingCall.getTargetPhoneAccountLabel()).thenReturn("Foo");
+    }
+
+    /**
+     * Add a call that isn't ringing.
+     */
+    @SmallTest
+    public void testSingleCall() {
+        mIncomingCallNotifier.onCallAdded(mAudioCall);
+        verify(mNotificationManager, never()).notify(
+                eq(IncomingCallNotifier.NOTIFICATION_INCOMING_CALL), any());
+    }
+
+    /**
+     * Add a ringing call when there is no other ongoing call.
+     */
+    @SmallTest
+    public void testIncomingDuringOngoingCall() {
+        when(mCallsManagerProxy.hasCallsForOtherPhoneAccount(any())).thenReturn(false);
+        mIncomingCallNotifier.onCallAdded(mRingingCall);
+        verify(mNotificationManager, never()).notify(
+                eq(IncomingCallNotifier.NOTIFICATION_INCOMING_CALL), any());
+    }
+
+    /**
+     * Add a ringing call with another call ongoing, not from a different phone account.
+     */
+    @SmallTest
+    public void testIncomingDuringOngoingCall2() {
+        when(mCallsManagerProxy.hasCallsForOtherPhoneAccount(any())).thenReturn(false);
+        when(mCallsManagerProxy.getNumCallsForOtherPhoneAccount(any())).thenReturn(0);
+        when(mCallsManagerProxy.getActiveCall()).thenReturn(mAudioCall);
+
+        mIncomingCallNotifier.onCallAdded(mAudioCall);
+        mIncomingCallNotifier.onCallAdded(mRingingCall);
+        verify(mNotificationManager, never()).notify(
+                eq(IncomingCallNotifier.NOTIFICATION_INCOMING_CALL), any());;
+    }
+
+    /**
+     * Remove ringing call with another call ongoing.
+     */
+    @SmallTest
+    public void testCallRemoved() {
+        when(mCallsManagerProxy.hasCallsForOtherPhoneAccount(any())).thenReturn(true);
+        when(mCallsManagerProxy.getNumCallsForOtherPhoneAccount(any())).thenReturn(1);
+        when(mCallsManagerProxy.getActiveCall()).thenReturn(mAudioCall);
+
+        mIncomingCallNotifier.onCallAdded(mAudioCall);
+        mIncomingCallNotifier.onCallAdded(mRingingCall);
+        verify(mNotificationManager).notify(
+                eq(IncomingCallNotifier.NOTIFICATION_INCOMING_CALL), any());
+        mIncomingCallNotifier.onCallRemoved(mRingingCall);
+        verify(mNotificationManager).cancel(eq(IncomingCallNotifier.NOTIFICATION_INCOMING_CALL));
+    }
+}
diff --git a/tests/src/com/android/server/telecom/tests/PhoneAccountRegistrarTest.java b/tests/src/com/android/server/telecom/tests/PhoneAccountRegistrarTest.java
index f2faf90..2898457 100644
--- a/tests/src/com/android/server/telecom/tests/PhoneAccountRegistrarTest.java
+++ b/tests/src/com/android/server/telecom/tests/PhoneAccountRegistrarTest.java
@@ -54,15 +54,18 @@
 import java.util.Set;
 
 import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyString;
 import static org.mockito.Mockito.when;
 
 public class PhoneAccountRegistrarTest extends TelecomTestCase {
 
     private static final int MAX_VERSION = Integer.MAX_VALUE;
     private static final String FILE_NAME = "phone-account-registrar-test-1223.xml";
+    private static final String TEST_LABEL = "right";
     private PhoneAccountRegistrar mRegistrar;
     @Mock private TelecomManager mTelecomManager;
     @Mock private DefaultDialerCache mDefaultDialerCache;
+    @Mock private PhoneAccountRegistrar.AppLabelProxy mAppLabelProxy;
 
     @Override
     public void setUp() throws Exception {
@@ -75,9 +78,11 @@
                 .delete();
         when(mDefaultDialerCache.getDefaultDialerApplication(anyInt()))
                 .thenReturn("com.android.dialer");
+        when(mAppLabelProxy.getAppLabel(anyString()))
+                .thenReturn(TEST_LABEL);
         mRegistrar = new PhoneAccountRegistrar(
                 mComponentContextFixture.getTestDouble().getApplicationContext(),
-                FILE_NAME, mDefaultDialerCache);
+                FILE_NAME, mDefaultDialerCache, mAppLabelProxy);
     }
 
     @Override
@@ -523,6 +528,55 @@
                         .build());
     }
 
+    /**
+     * Tests ability to register a self-managed PhoneAccount; verifies that the user defined label
+     * is overridden.
+     * @throws Exception
+     */
+    @MediumTest
+    public void testSelfManagedPhoneAccount() throws Exception {
+        mComponentContextFixture.addConnectionService(makeQuickConnectionServiceComponentName(),
+                Mockito.mock(IConnectionService.class));
+
+        PhoneAccountHandle selfManagedHandle =  makeQuickAccountHandle(
+                new ComponentName("self", "managed"), "selfie1");
+
+        PhoneAccount selfManagedAccount = new PhoneAccount.Builder(selfManagedHandle, "Wrong")
+                .setCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED)
+                .build();
+
+        mRegistrar.registerPhoneAccount(selfManagedAccount);
+
+        PhoneAccount registeredAccount = mRegistrar.getPhoneAccountUnchecked(selfManagedHandle);
+        assertEquals(TEST_LABEL, registeredAccount.getLabel());
+    }
+
+    /**
+     * Tests to ensure that when registering a self-managed PhoneAccount, it cannot also be defined
+     * as a call provider, connection manager, or sim subscription.
+     * @throws Exception
+     */
+    @MediumTest
+    public void testSelfManagedCapabilityOverride() throws Exception {
+        mComponentContextFixture.addConnectionService(makeQuickConnectionServiceComponentName(),
+                Mockito.mock(IConnectionService.class));
+
+        PhoneAccountHandle selfManagedHandle =  makeQuickAccountHandle(
+                new ComponentName("self", "managed"), "selfie1");
+
+        PhoneAccount selfManagedAccount = new PhoneAccount.Builder(selfManagedHandle, TEST_LABEL)
+                .setCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED |
+                        PhoneAccount.CAPABILITY_CALL_PROVIDER |
+                        PhoneAccount.CAPABILITY_CONNECTION_MANAGER |
+                        PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)
+                .build();
+
+        mRegistrar.registerPhoneAccount(selfManagedAccount);
+
+        PhoneAccount registeredAccount = mRegistrar.getPhoneAccountUnchecked(selfManagedHandle);
+        assertEquals(PhoneAccount.CAPABILITY_SELF_MANAGED, registeredAccount.getCapabilities());
+    }
+
     private static ComponentName makeQuickConnectionServiceComponentName() {
         return new ComponentName(
                 "com.android.server.telecom.tests",
diff --git a/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java b/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
index 1f3554d..b90ca97 100644
--- a/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
+++ b/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
@@ -84,6 +84,7 @@
 import com.android.server.telecom.Timeouts;
 import com.android.server.telecom.callfiltering.AsyncBlockCheckFilter;
 import com.android.server.telecom.components.UserCallIntentProcessor;
+import com.android.server.telecom.ui.IncomingCallNotifier;
 import com.android.server.telecom.ui.MissedCallNotifierImpl.MissedCallNotifierImplFactory;
 
 import com.google.common.base.Predicate;
@@ -194,6 +195,7 @@
     @Mock BluetoothPhoneServiceImpl mBluetoothPhoneServiceImpl;
     @Mock AsyncRingtonePlayer mAsyncRingtonePlayer;
     @Mock InterruptionFilterProxy mInterruptionFilterProxy;
+    @Mock IncomingCallNotifier mIncomingCallNotifier;
 
     final ComponentName mInCallServiceComponentNameX =
             new ComponentName(
@@ -389,6 +391,7 @@
         mTimeoutsAdapter = mock(Timeouts.Adapter.class);
         when(mTimeoutsAdapter.getCallScreeningTimeoutMillis(any(ContentResolver.class)))
                 .thenReturn(TEST_TIMEOUT / 5L);
+        mIncomingCallNotifier = mock(IncomingCallNotifier.class);
 
         mTelecomSystem = new TelecomSystem(
                 mComponentContextFixture.getTestDouble(),
@@ -421,7 +424,8 @@
                 mTimeoutsAdapter,
                 mAsyncRingtonePlayer,
                 mPhoneNumberUtilsAdapter,
-                mInterruptionFilterProxy);
+                mInterruptionFilterProxy,
+                mIncomingCallNotifier);
 
         mComponentContextFixture.setTelecomManager(new TelecomManager(
                 mComponentContextFixture.getTestDouble(),