am 7881604a: (-s ours) am 7da7ea93: Fixed typo (READ_PHONE_STATE to READ_PRIVILEGED_PHONE_STATE)

* commit '7881604a22dc092af0ccf3ebbbefedb16a5cc390':
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index ac3799f..56a594b 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -20,6 +20,9 @@
         coreApp="true"
         android:sharedUserId="android.uid.system">
 
+
+    <protected-broadcast android:name="android.intent.action.SHOW_MISSED_CALLS_NOTIFICATION" />
+
     <!-- Prevents the activity manager from delaying any activity-start
          requests by this package, including requests immediately after
          the user presses "home". -->
@@ -88,6 +91,7 @@
              URL with the schemes "tel", "sip", and "voicemail". It also handles URLs linked to
              contacts provider entries. Any data not fitting the schema described is ignored. -->
         <activity android:name=".components.UserCallActivity"
+                android:label="@string/userCallActivityLabel"
                 android:theme="@style/Theme.Telecomm.Transparent"
                 android:permission="android.permission.CALL_PHONE"
                 android:excludeFromRecents="true"
diff --git a/res/values/config.xml b/res/values/config.xml
index 365e758..e474d7e 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -38,4 +38,8 @@
 
     <!-- Flag indicating if the tty is enabled -->
     <bool name="tty_enabled">false</bool>
+
+    <!-- Component name for the notification handler. The presence of this value will disable
+         MissedCallNotifierImpl's presentation of missed call/voice notifications [DO NOT TRANSLATE] -->
+    <string name="notification_component" translatable="false"></string>
 </resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 8f73377..26df574 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -18,7 +18,11 @@
     <!-- Official label of the Telecomm/Phone app, as seen in "Manage Applications"
          and other settings UIs. This is the "app name" used in notification, recents,
          and app info screens. -->
-    <string name="telecommAppLabel" product="default">Phone - Call Management</string>
+    <string name="telecommAppLabel" product="default">Phone Call Management</string>
+
+    <!-- Title used for the activity for placing a call. This name appears
+         in activity disambig dialogs -->
+    <string name="userCallActivityLabel" product="default">Phone</string>
 
     <!-- Name for an "unknown" caller. -->
     <string name="unknown">Unknown</string>
diff --git a/src/com/android/server/telecom/Call.java b/src/com/android/server/telecom/Call.java
index 603ea7e..91f7c4f 100644
--- a/src/com/android/server/telecom/Call.java
+++ b/src/com/android/server/telecom/Call.java
@@ -48,6 +48,7 @@
 import com.android.server.telecom.ContactsAsyncHelper.OnImageLoadCompleteListener;
 import com.android.internal.util.Preconditions;
 
+import java.lang.String;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.LinkedList;
@@ -365,7 +366,6 @@
         mContactsAsyncHelper = contactsAsyncHelper;
         mCallerInfoAsyncQueryFactory = callerInfoAsyncQueryFactory;
         setHandle(handle);
-        setHandle(handle, TelecomManager.PRESENTATION_ALLOWED);
         mGatewayInfo = gatewayInfo;
         setConnectionManagerPhoneAccount(connectionManagerPhoneAccountHandle);
         setTargetPhoneAccount(targetPhoneAccountHandle);
@@ -634,8 +634,12 @@
                 }
             }
 
-            mIsEmergencyCall = mHandle != null && PhoneNumberUtils.isLocalEmergencyNumber(mContext,
-                    mHandle.getSchemeSpecificPart());
+            // Let's not allow resetting of the emergency flag. Once a call becomes an emergency
+            // call, it will remain so for the rest of it's lifetime.
+            if (!mIsEmergencyCall) {
+                mIsEmergencyCall = mHandle != null && PhoneNumberUtils.isLocalEmergencyNumber(
+                        mContext, mHandle.getSchemeSpecificPart());
+            }
             startCallerInfoLookup();
             for (Listener l : mListeners) {
                 l.onHandleChanged(this);
@@ -666,6 +670,10 @@
         return mCallerInfo == null ? null : mCallerInfo.name;
     }
 
+    public String getPhoneNumber() {
+        return mCallerInfo == null ? null : mCallerInfo.phoneNumber;
+    }
+
     public Bitmap getPhotoIcon() {
         return mCallerInfo == null ? null : mCallerInfo.cachedPhotoIcon;
     }
diff --git a/src/com/android/server/telecom/CallAudioManager.java b/src/com/android/server/telecom/CallAudioManager.java
index b5c2eff..da3deb6 100644
--- a/src/com/android/server/telecom/CallAudioManager.java
+++ b/src/com/android/server/telecom/CallAudioManager.java
@@ -360,12 +360,17 @@
         }
     }
 
-    void setIsRinging(boolean isRinging) {
+    /**
+     * Sets the audio stream and mode based on whether a call is ringing.
+     *
+     * @param call The call which changed ringing state.
+     * @param isRinging {@code true} if the call is ringing, {@code false} otherwise.
+     */
+    void setIsRinging(Call call, boolean isRinging) {
         if (mIsRinging != isRinging) {
-            Log.i(this, "setIsRinging %b -> %b", mIsRinging, isRinging);
+            Log.i(this, "setIsRinging %b -> %b (call = %s)", mIsRinging, isRinging, call);
             mIsRinging = isRinging;
-
-            updateAudioStreamAndMode();
+            updateAudioStreamAndMode(call);
         }
     }
 
diff --git a/src/com/android/server/telecom/CallIntentProcessor.java b/src/com/android/server/telecom/CallIntentProcessor.java
index cfec90c..a6840b9 100644
--- a/src/com/android/server/telecom/CallIntentProcessor.java
+++ b/src/com/android/server/telecom/CallIntentProcessor.java
@@ -8,6 +8,7 @@
 import android.os.Bundle;
 import android.os.Trace;
 import android.os.UserHandle;
+import android.telecom.Connection;
 import android.telecom.PhoneAccount;
 import android.telecom.PhoneAccountHandle;
 import android.telecom.TelecomManager;
@@ -87,6 +88,12 @@
             clientExtras = new Bundle();
         }
 
+        // Ensure call subject is passed on to the connection service.
+        if (intent.hasExtra(TelecomManager.EXTRA_CALL_SUBJECT)) {
+            String callsubject = intent.getStringExtra(TelecomManager.EXTRA_CALL_SUBJECT);
+            clientExtras.putString(TelecomManager.EXTRA_CALL_SUBJECT, callsubject);
+        }
+
         final boolean isPrivilegedDialer = intent.getBooleanExtra(KEY_IS_PRIVILEGED_DIALER, false);
 
         // Send to CallsManager to ensure the InCallUI gets kicked off before the broadcast returns
diff --git a/src/com/android/server/telecom/CallLogManager.java b/src/com/android/server/telecom/CallLogManager.java
index 2007f6e..1fe491e 100755
--- a/src/com/android/server/telecom/CallLogManager.java
+++ b/src/com/android/server/telecom/CallLogManager.java
@@ -148,7 +148,8 @@
         // TODO(vt): Once data usage is available, wire it up here.
         int callFeatures = getCallFeatures(call.getVideoStateHistory());
         logCall(call.getCallerInfo(), logNumber, call.getHandlePresentation(),
-                callLogType, callFeatures, accountHandle, creationTime, age, null);
+                callLogType, callFeatures, accountHandle, creationTime, age, null,
+                call.isEmergencyCall());
     }
 
     /**
@@ -162,6 +163,7 @@
      * @param start The start time of the call, in milliseconds.
      * @param duration The duration of the call, in milliseconds.
      * @param dataUsage The data usage for the call, null if not applicable.
+     * @param isEmergency {@code true} if this is an emergency call, {@code false} otherwise.
      */
     private void logCall(
             CallerInfo callerInfo,
@@ -172,8 +174,8 @@
             PhoneAccountHandle accountHandle,
             long start,
             long duration,
-            Long dataUsage) {
-        boolean isEmergencyNumber = PhoneNumberUtils.isLocalEmergencyNumber(mContext, number);
+            Long dataUsage,
+            boolean isEmergency) {
 
         // On some devices, to avoid accidental redialing of emergency numbers, we *never* log
         // emergency calls to the Call Log.  (This behavior is set on a per-product basis, based
@@ -182,7 +184,7 @@
                 mContext.getResources().getBoolean(R.bool.allow_emergency_numbers_in_call_log);
 
         // Don't log emergency numbers if the device doesn't allow it.
-        final boolean isOkToLogThisCall = !isEmergencyNumber || okToLogEmergencyNumber;
+        final boolean isOkToLogThisCall = !isEmergency || okToLogEmergencyNumber;
 
         sendAddCallBroadcast(callType, duration);
 
diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java
index 297afa8..5d00924 100644
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -590,9 +590,19 @@
 
         Log.v(this, "startOutgoingCall found accounts = " + accounts);
 
-        if (mForegroundCall != null && mForegroundCall.getTargetPhoneAccount() != null) {
+        if (mForegroundCall != null) {
+            Call ongoingCall = mForegroundCall;
             // If there is an ongoing call, use the same phone account to place this new call.
-            phoneAccountHandle = mForegroundCall.getTargetPhoneAccount();
+            // If the ongoing call is a conference call, we fetch the phone account from the
+            // child calls because we don't have targetPhoneAccount set on Conference calls.
+            // TODO: Set targetPhoneAccount for all conference calls (b/23035408).
+            if (ongoingCall.getTargetPhoneAccount() == null &&
+                    !ongoingCall.getChildCalls().isEmpty()) {
+                ongoingCall = ongoingCall.getChildCalls().get(0);
+            }
+            if (ongoingCall.getTargetPhoneAccount() != null) {
+                phoneAccountHandle = ongoingCall.getTargetPhoneAccount();
+            }
         }
 
         // Only dial with the requested phoneAccount if it is still valid. Otherwise treat this call
@@ -613,13 +623,11 @@
 
         call.setTargetPhoneAccount(phoneAccountHandle);
 
-        boolean isEmergencyCall = TelephonyUtil.shouldProcessAsEmergency(mContext,
-                call.getHandle());
         boolean isPotentialInCallMMICode = isPotentialInCallMMICode(handle);
 
         // Do not support any more live calls.  Our options are to move a call to hold, disconnect
         // a call, or cancel this call altogether.
-        if (!isPotentialInCallMMICode && !makeRoomForOutgoingCall(call, isEmergencyCall)) {
+        if (!isPotentialInCallMMICode && !makeRoomForOutgoingCall(call, call.isEmergencyCall())) {
             // just cancel at this point.
             Log.i(this, "No remaining room for outgoing call: %s", call);
             if (mCalls.contains(call)) {
@@ -631,7 +639,7 @@
         }
 
         boolean needsAccountSelection = phoneAccountHandle == null && accounts.size() > 1 &&
-                !isEmergencyCall;
+                !call.isEmergencyCall();
 
         if (needsAccountSelection) {
             // This is the state where the user is expected to select an account
@@ -696,14 +704,12 @@
         }
         call.setStartWithSpeakerphoneOn(speakerphoneOn || mDockManager.isDocked());
 
-        boolean isEmergencyCall = TelephonyUtil.shouldProcessAsEmergency(mContext,
-                call.getHandle());
-        if (isEmergencyCall) {
+        if (call.isEmergencyCall()) {
             // Emergency -- CreateConnectionProcessor will choose accounts automatically
             call.setTargetPhoneAccount(null);
         }
 
-        if (call.getTargetPhoneAccount() != null || isEmergencyCall) {
+        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.
             call.startCreateConnection(mPhoneAccountRegistrar);
@@ -1070,6 +1076,14 @@
             if (call.isEmergencyCall()) {
                 // We never support add call if one of the calls is an emergency call.
                 return false;
+            } else  if (!call.getChildCalls().isEmpty() && !call.can(Connection.CAPABILITY_HOLD)) {
+                // This is to deal with CDMA conference calls. CDMA conference calls do not
+                // allow the addition of another call when it is already in a 3 way conference.
+                // So, we detect that it is a CDMA conference call by checking if the call has
+                // some children and it does not support the CAPABILILTY_HOLD
+                // TODO: This maybe cleaner if the lower layers can explicitly signal to telecom
+                // about this limitation (b/22880180).
+                return false;
             } else if (call.getParentCall() == null) {
                 count++;
             }
@@ -1486,10 +1500,27 @@
 
             // We have room for at least one more holding call at this point.
 
+            // TODO: Remove once b/23035408 has been corrected.
+            // If the live call is a conference, it will not have a target phone account set.  This
+            // means the check to see if the live call has the same target phone account as the new
+            // call will not cause us to bail early.  As a result, we'll end up holding the
+            // ongoing conference call.  However, the ConnectionService is already doing that.  This
+            // has caused problems with some carriers.  As a workaround until b/23035408 is
+            // corrected, we will try and get the target phone account for one of the conference's
+            // children and use that instead.
+            PhoneAccountHandle liveCallPhoneAccount = liveCall.getTargetPhoneAccount();
+            if (liveCallPhoneAccount == null && liveCall.isConference() &&
+                    !liveCall.getChildCalls().isEmpty()) {
+                liveCallPhoneAccount = getFirstChildPhoneAccount(liveCall);
+                Log.i(this, "makeRoomForOutgoingCall: using child call PhoneAccount = " +
+                        liveCallPhoneAccount);
+            }
+
             // First thing, if we are trying to make a call with the same phone account as the live
             // call, then allow it so that the connection service can make its own decision about
             // how to handle the new call relative to the current one.
-            if (Objects.equals(liveCall.getTargetPhoneAccount(), call.getTargetPhoneAccount())) {
+            if (Objects.equals(liveCallPhoneAccount, call.getTargetPhoneAccount())) {
+                Log.i(this, "makeRoomForOutgoingCall: phoneAccount matches.");
                 return true;
             } else if (call.getTargetPhoneAccount() == null) {
                 // Without a phone account, we can't say reliably that the call will fail.
@@ -1503,6 +1534,7 @@
 
             // Try to hold the live call before attempting the new outgoing call.
             if (liveCall.can(Connection.CAPABILITY_HOLD)) {
+                Log.i(this, "makeRoomForOutgoingCall: holding live call.");
                 liveCall.hold();
                 return true;
             }
@@ -1514,6 +1546,22 @@
     }
 
     /**
+     * Given a call, find the first non-null phone account handle of its children.
+     *
+     * @param parentCall The parent call.
+     * @return The first non-null phone account handle of the children, or {@code null} if none.
+     */
+    private PhoneAccountHandle getFirstChildPhoneAccount(Call parentCall) {
+        for (Call childCall : parentCall.getChildCalls()) {
+            PhoneAccountHandle childPhoneAccount = childCall.getTargetPhoneAccount();
+            if (childPhoneAccount != null) {
+                return childPhoneAccount;
+            }
+        }
+        return null;
+    }
+
+    /**
      * Checks to see if the call should be on speakerphone and if so, set it.
      */
     private void maybeMoveToSpeakerPhone(Call call) {
@@ -1543,7 +1591,8 @@
                 null /* connectionManagerPhoneAccount */,
                 connection.getPhoneAccount(), /* targetPhoneAccountHandle */
                 false /* isIncoming */,
-                false /* isConference */);
+                false /* isConference */,
+                connection.getConnectTimeMillis() /* connectTimeMillis */);
 
         setCallState(call, Call.getStateFromConnectionState(connection.getState()),
                 "existing connection");
diff --git a/src/com/android/server/telecom/CreateConnectionProcessor.java b/src/com/android/server/telecom/CreateConnectionProcessor.java
index 6126e66..b846470 100644
--- a/src/com/android/server/telecom/CreateConnectionProcessor.java
+++ b/src/com/android/server/telecom/CreateConnectionProcessor.java
@@ -297,7 +297,7 @@
     // If we are possibly attempting to call a local emergency number, ensure that the
     // plain PSTN connection services are listed, and nothing else.
     private void adjustAttemptsForEmergency()  {
-        if (TelephonyUtil.shouldProcessAsEmergency(mContext, mCall.getHandle())) {
+        if (mCall.isEmergencyCall()) {
             Log.i(this, "Emergency number detected");
             mAttemptRecords.clear();
             List<PhoneAccount> allAccounts = mPhoneAccountRegistrar.getAllPhoneAccounts();
diff --git a/src/com/android/server/telecom/CreateConnectionTimeout.java b/src/com/android/server/telecom/CreateConnectionTimeout.java
index 45305d5..8acc2b4 100644
--- a/src/com/android/server/telecom/CreateConnectionTimeout.java
+++ b/src/com/android/server/telecom/CreateConnectionTimeout.java
@@ -53,7 +53,7 @@
     boolean isTimeoutNeededForCall(Collection<PhoneAccountHandle> accounts,
             PhoneAccountHandle currentAccount) {
         // Non-emergency calls timeout automatically at the radio layer. No need for a timeout here.
-        if (!TelephonyUtil.shouldProcessAsEmergency(mContext, mCall.getHandle())) {
+        if (!mCall.isEmergencyCall()) {
             return false;
         }
 
diff --git a/src/com/android/server/telecom/PhoneAccountRegistrar.java b/src/com/android/server/telecom/PhoneAccountRegistrar.java
index e9fa52a..65847b8 100644
--- a/src/com/android/server/telecom/PhoneAccountRegistrar.java
+++ b/src/com/android/server/telecom/PhoneAccountRegistrar.java
@@ -16,6 +16,7 @@
 
 package com.android.server.telecom;
 
+import android.app.ActivityManager;
 import android.Manifest;
 import android.content.ComponentName;
 import android.content.Context;
@@ -120,7 +121,7 @@
 
     private static final String FILE_NAME = "phone-account-registrar-state.xml";
     @VisibleForTesting
-    public static final int EXPECTED_STATE_VERSION = 7;
+    public static final int EXPECTED_STATE_VERSION = 8;
 
     /** Keep in sync with the same in SipSettings.java */
     private static final String SIP_SHARED_PREFERENCES = "SIP_PREFERENCES";
@@ -275,8 +276,28 @@
      * 3. Otherwise, we return null.
      */
     public PhoneAccountHandle getSimCallManager() {
+        long token = Binder.clearCallingIdentity();
+        int user;
+        try {
+            user = ActivityManager.getCurrentUser();
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+        return getSimCallManager(user);
+    }
+
+    /**
+     * Returns the {@link PhoneAccountHandle} corresponding to the currently active SIM Call
+     * Manager. SIM Call Manager returned corresponds to the following priority order:
+     * 1. If a SIM Call Manager {@link PhoneAccount} is registered for the same package as the
+     * default dialer, then that one is returned.
+     * 2. If there is a SIM Call Manager {@link PhoneAccount} registered which matches the
+     * carrier configuration's default, then that one is returned.
+     * 3. Otherwise, we return null.
+     */
+    public PhoneAccountHandle getSimCallManager(int user) {
         // Get the default dialer in case it has a connection manager associated with it.
-        String dialerPackage = DefaultDialerManager.getDefaultDialerApplication(mContext);
+        String dialerPackage = DefaultDialerManager.getDefaultDialerApplication(mContext, user);
 
         // Check carrier config.
         String defaultSimCallManager = null;
@@ -602,14 +623,15 @@
 
         StringBuffer sb = new StringBuffer();
         sb.append("[").append(account1.getAccountHandle());
-        appendDiff(sb, "addr", account1.getAddress(), account2.getAddress());
+        appendDiff(sb, "addr", Log.piiHandle(account1.getAddress()),
+                Log.piiHandle(account2.getAddress()));
         appendDiff(sb, "cap", account1.getCapabilities(), account2.getCapabilities());
         appendDiff(sb, "hl", account1.getHighlightColor(), account2.getHighlightColor());
         appendDiff(sb, "icon", account1.getIcon(), account2.getIcon());
         appendDiff(sb, "lbl", account1.getLabel(), account2.getLabel());
         appendDiff(sb, "desc", account1.getShortDescription(), account2.getShortDescription());
-        appendDiff(sb, "subAddr", account1.getSubscriptionAddress(),
-                account2.getSubscriptionAddress());
+        appendDiff(sb, "subAddr", Log.piiHandle(account1.getSubscriptionAddress()),
+                Log.piiHandle(account2.getSubscriptionAddress()));
         appendDiff(sb, "uris", account1.getSupportedUriSchemes(),
                 account2.getSupportedUriSchemes());
         sb.append("]");
@@ -1238,6 +1260,18 @@
                         enabled = true;
                     }
                 }
+                if (version < 8) {
+                    // Migrate the SIP account handle ids to use SIP username instead of SIP URI.
+                    if (accountHandle.getComponentName().equals(sipComponentName)) {
+                        Uri accountUri = Uri.parse(accountHandle.getId());
+                        if (accountUri.getScheme() != null &&
+                            accountUri.getScheme().equals(PhoneAccount.SCHEME_SIP)) {
+                            accountHandle = new PhoneAccountHandle(accountHandle.getComponentName(),
+                                    accountUri.getSchemeSpecificPart(),
+                                    accountHandle.getUserHandle());
+                        }
+                    }
+                }
 
                 PhoneAccount.Builder builder = PhoneAccount.builder(accountHandle, label)
                         .setAddress(address)
diff --git a/src/com/android/server/telecom/Ringer.java b/src/com/android/server/telecom/Ringer.java
index b2ed8e7..99fb842 100644
--- a/src/com/android/server/telecom/Ringer.java
+++ b/src/com/android/server/telecom/Ringer.java
@@ -196,7 +196,7 @@
                     Log.event(call, Log.Events.START_RINGER);
                     mState = STATE_RINGING;
                 }
-                mCallAudioManager.setIsRinging(true);
+                mCallAudioManager.setIsRinging(call, true);
 
                 // Because we wait until a contact info query to complete before processing a
                 // call (for the purposes of direct-to-voicemail), the information about custom
@@ -261,7 +261,7 @@
 
         // Even though stop is asynchronous it's ok to update the audio manager. Things like audio
         // focus are voluntary so releasing focus too early is not detrimental.
-        mCallAudioManager.setIsRinging(false);
+        mCallAudioManager.setIsRinging(call, false);
     }
 
     private void stopCallWaiting(Call call) {
diff --git a/src/com/android/server/telecom/TelecomServiceImpl.java b/src/com/android/server/telecom/TelecomServiceImpl.java
index e209097..5c498c0 100644
--- a/src/com/android/server/telecom/TelecomServiceImpl.java
+++ b/src/com/android/server/telecom/TelecomServiceImpl.java
@@ -256,13 +256,25 @@
 
         @Override
         public PhoneAccountHandle getSimCallManager() {
+            long token  = Binder.clearCallingIdentity();
+            int user;
+            try {
+                user = ActivityManager.getCurrentUser();
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+            return getSimCallManagerForUser(user);
+        }
+
+        @Override
+        public PhoneAccountHandle getSimCallManagerForUser(int user) {
             synchronized (mLock) {
                 try {
                     PhoneAccountHandle accountHandle = null;
 
                     long token = Binder.clearCallingIdentity();
                     try {
-                        accountHandle = mPhoneAccountRegistrar.getSimCallManager();
+                        accountHandle = mPhoneAccountRegistrar.getSimCallManager(user);
                     } finally {
                         // We restore early so that isVisibleToCaller invocation below uses the
                         // right user context.
@@ -1112,8 +1124,9 @@
         }
 
         try {
-            mContext.enforceCallingPermission(READ_PRIVILEGED_PHONE_STATE, message);
-            // SKIP checking run-time OP_READ_PHONE_STATE since using PRIVILEGED
+            mContext.enforceCallingOrSelfPermission(READ_PRIVILEGED_PHONE_STATE, message);
+            // SKIP checking run-time OP_READ_PHONE_STATE since caller or self has PRIVILEGED
+            // permission
             return true;
         } catch (SecurityException e) {
             // Accessing phone state is gated by a special permission.
diff --git a/src/com/android/server/telecom/ui/MissedCallNotifierImpl.java b/src/com/android/server/telecom/ui/MissedCallNotifierImpl.java
index ad6fc16..ef16e59 100644
--- a/src/com/android/server/telecom/ui/MissedCallNotifierImpl.java
+++ b/src/com/android/server/telecom/ui/MissedCallNotifierImpl.java
@@ -16,6 +16,8 @@
 
 package com.android.server.telecom.ui;
 
+import android.content.ComponentName;
+import android.telecom.TelecomManager;
 import com.android.server.telecom.Call;
 import com.android.server.telecom.CallState;
 import com.android.server.telecom.CallerInfoAsyncQueryFactory;
@@ -54,6 +56,9 @@
 import android.text.TextDirectionHeuristics;
 import android.text.TextUtils;
 
+import java.lang.Override;
+import java.lang.String;
+
 // TODO: Needed for move to system service: import com.android.internal.R;
 
 /**
@@ -89,6 +94,8 @@
     private final Context mContext;
     private final NotificationManager mNotificationManager;
 
+    private final ComponentName mNotificationComponent;
+
     // Used to track the number of missed calls.
     private int mMissedCallCount = 0;
 
@@ -96,6 +103,10 @@
         mContext = context;
         mNotificationManager =
                 (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+        final String notificationComponent = context.getString(R.string.notification_component);
+
+        mNotificationComponent = notificationComponent != null
+                ? ComponentName.unflattenFromString(notificationComponent) : null;
     }
 
     /** {@inheritDoc} */
@@ -135,6 +146,44 @@
     }
 
     /**
+     * Broadcasts missed call notification to custom component if set.
+     * @param number The phone number associated with the notification. null if
+     *               no call.
+     * @param count The number of calls associated with the notification.
+     * @return {@code true} if the broadcast was sent. {@code false} otherwise.
+     */
+    private boolean sendNotificationCustomComponent(Call call, int count) {
+        if (mNotificationComponent != null) {
+            Intent intent = new Intent();
+            intent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+            intent.setComponent(mNotificationComponent);
+            intent.setAction(TelecomManager.ACTION_SHOW_MISSED_CALLS_NOTIFICATION);
+            intent.putExtra(TelecomManager.EXTRA_NOTIFICATION_COUNT, count);
+            intent.putExtra(TelecomManager.EXTRA_NOTIFICATION_PHONE_NUMBER,
+                    call != null ? call.getPhoneNumber() : null);
+            intent.putExtra(TelecomManager.EXTRA_CLEAR_MISSED_CALLS_INTENT,
+                    createClearMissedCallsPendingIntent());
+
+
+            if (count == 1 && call != null) {
+                final Uri handleUri = call.getHandle();
+                String handle = handleUri == null ? null : handleUri.getSchemeSpecificPart();
+
+                if (!TextUtils.isEmpty(handle) && !TextUtils.equals(handle,
+                        mContext.getString(R.string.handle_restricted))) {
+                    intent.putExtra(TelecomManager.EXTRA_CALL_BACK_INTENT,
+                            createCallBackPendingIntent(handleUri));
+                }
+            }
+
+            mContext.sendBroadcast(intent);
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
      * Create a system notification for the missed call.
      *
      * @param call The missed call.
@@ -143,6 +192,10 @@
     public void showMissedCallNotification(Call call) {
         mMissedCallCount++;
 
+        if (sendNotificationCustomComponent(call, mMissedCallCount)) {
+            return;
+        }
+
         final int titleResId;
         final String expandedText;  // The text in the notification's line 1 and 2.
 
@@ -218,6 +271,12 @@
     private void cancelMissedCallNotification() {
         // Reset the number of missed calls to 0.
         mMissedCallCount = 0;
+
+
+        if (sendNotificationCustomComponent(null, mMissedCallCount)) {
+            return;
+        }
+
         long token = Binder.clearCallingIdentity();
         try {
             mNotificationManager.cancelAsUser(null, MISSED_CALL_NOTIFICATION_ID,
@@ -386,6 +445,7 @@
         StringBuilder where = new StringBuilder("type=");
         where.append(Calls.MISSED_TYPE);
         where.append(" AND new=1");
+        where.append(" AND is_read=0");
 
         // start the query
         queryHandler.startQuery(0, null, Calls.CONTENT_URI, CALL_LOG_PROJECTION,
diff --git a/testapps/src/com/android/server/telecom/testapps/CallServiceNotifier.java b/testapps/src/com/android/server/telecom/testapps/CallServiceNotifier.java
index cd0800e..c1ced80 100644
--- a/testapps/src/com/android/server/telecom/testapps/CallServiceNotifier.java
+++ b/testapps/src/com/android/server/telecom/testapps/CallServiceNotifier.java
@@ -111,7 +111,8 @@
                 .setAddress(Uri.parse("tel:555-TEST"))
                 .setSubscriptionAddress(Uri.parse("tel:555-TEST"))
                 .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER |
-                        PhoneAccount.CAPABILITY_VIDEO_CALLING)
+                        PhoneAccount.CAPABILITY_VIDEO_CALLING |
+                        PhoneAccount.CAPABILITY_CALL_SUBJECT)
                 .setIcon(Icon.createWithResource(
                         context.getResources(), R.drawable.stat_sys_phone_call))
                 // TODO: Add icon tint (Color.RED)
@@ -129,7 +130,8 @@
                 .setSubscriptionAddress(Uri.parse("tel:555-TSIM"))
                 .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER |
                         PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION |
-                        PhoneAccount.CAPABILITY_VIDEO_CALLING)
+                        PhoneAccount.CAPABILITY_VIDEO_CALLING |
+                        PhoneAccount.CAPABILITY_CALL_SUBJECT)
                 .setIcon(Icon.createWithResource(
                         context.getResources(), R.drawable.stat_sys_phone_call))
                 // TODO: Add icon tint (Color.GREEN)
diff --git a/testapps/src/com/android/server/telecom/testapps/TestConnectionService.java b/testapps/src/com/android/server/telecom/testapps/TestConnectionService.java
index 45896f4..7964355 100644
--- a/testapps/src/com/android/server/telecom/testapps/TestConnectionService.java
+++ b/testapps/src/com/android/server/telecom/testapps/TestConnectionService.java
@@ -36,6 +36,7 @@
 import android.telecom.TelecomManager;
 import android.telecom.VideoProfile;
 import android.util.Log;
+import android.widget.Toast;
 
 import com.android.server.telecom.testapps.R;
 
@@ -299,6 +300,13 @@
         String gatewayPackage = extras.getString(TelecomManager.GATEWAY_PROVIDER_PACKAGE);
         Uri originalHandle = extras.getParcelable(TelecomManager.GATEWAY_ORIGINAL_ADDRESS);
 
+        if (extras.containsKey(TelecomManager.EXTRA_CALL_SUBJECT)) {
+            String callSubject = extras.getString(TelecomManager.EXTRA_CALL_SUBJECT);
+            log("Got subject: " + callSubject);
+            Toast.makeText(getApplicationContext(), "Got subject :" + callSubject,
+                    Toast.LENGTH_SHORT).show();
+        }
+
         log("gateway package [" + gatewayPackage + "], original handle [" +
                 originalHandle + "]");
 
@@ -354,6 +362,24 @@
                     VideoProfile.STATE_BIDIRECTIONAL :
                     VideoProfile.STATE_AUDIO_ONLY;
             connection.setVideoState(videoState);
+
+            Bundle connectionExtras = connection.getExtras();
+            if (connectionExtras == null) {
+                connectionExtras = new Bundle();
+            }
+
+            // Randomly choose a varying length call subject.
+            int subjectFormat = mRandom.nextInt(3);
+            if (subjectFormat == 0) {
+                connectionExtras.putString(Connection.EXTRA_CALL_SUBJECT,
+                        "This is a test of call subject lines. Subjects for a call can be long " +
+                                " and can go even longer.");
+            } else if (subjectFormat == 1) {
+                connectionExtras.putString(Connection.EXTRA_CALL_SUBJECT,
+                        "This is a test of call subject lines.");
+            }
+            connection.setExtras(connectionExtras);
+
             setAddress(connection, address);
 
             addVideoProvider(connection);
diff --git a/tests/src/com/android/server/telecom/tests/ConnectionServiceFixture.java b/tests/src/com/android/server/telecom/tests/ConnectionServiceFixture.java
index b7b4ba7..964f014 100644
--- a/tests/src/com/android/server/telecom/tests/ConnectionServiceFixture.java
+++ b/tests/src/com/android/server/telecom/tests/ConnectionServiceFixture.java
@@ -396,6 +396,7 @@
                 c.videoState,
                 false, /* ringback requested */
                 false, /* voip audio mode */
+                0, /* Connect Time for conf call on this connection */
                 c.statusHints,
                 c.disconnectCause,
                 c.conferenceableConnectionIds,