Merge "Add Telecom debug menu."
diff --git a/src/com/android/server/telecom/Call.java b/src/com/android/server/telecom/Call.java
index f905514..79b57b9 100644
--- a/src/com/android/server/telecom/Call.java
+++ b/src/com/android/server/telecom/Call.java
@@ -1608,6 +1608,7 @@
 
         setConnectionCapabilities(connection.getConnectionCapabilities());
         setConnectionProperties(connection.getConnectionProperties());
+        setIsVoipAudioMode(connection.getIsVoipAudioMode());
         setSupportedAudioRoutes(connection.getSupportedAudioRoutes());
         setVideoProvider(connection.getVideoProvider());
         setVideoState(connection.getVideoState());
diff --git a/src/com/android/server/telecom/CallAudioRouteStateMachine.java b/src/com/android/server/telecom/CallAudioRouteStateMachine.java
index ef65d8b..facbf39 100644
--- a/src/com/android/server/telecom/CallAudioRouteStateMachine.java
+++ b/src/com/android/server/telecom/CallAudioRouteStateMachine.java
@@ -19,7 +19,10 @@
 
 import android.app.ActivityManager;
 import android.bluetooth.BluetoothDevice;
+import android.content.BroadcastReceiver;
 import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.pm.UserInfo;
 import android.media.AudioDeviceInfo;
 import android.media.AudioManager;
@@ -122,6 +125,7 @@
     public static final int MUTE_ON = 3001;
     public static final int MUTE_OFF = 3002;
     public static final int TOGGLE_MUTE = 3003;
+    public static final int MUTE_EXTERNALLY_CHANGED = 3004;
 
     public static final int SWITCH_FOCUS = 4001;
 
@@ -1221,6 +1225,18 @@
         }
     }
 
+    private final BroadcastReceiver mMuteChangeReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            Log.startSession("CARSM.mCR");
+            if (AudioManager.ACTION_MICROPHONE_MUTE_CHANGED.equals(intent.getAction())) {
+                sendInternalMessage(MUTE_EXTERNALLY_CHANGED);
+            } else {
+                Log.w(this, "Received non-mute-change intent");
+            }
+        }
+    };
+
     private final ActiveEarpieceRoute mActiveEarpieceRoute = new ActiveEarpieceRoute();
     private final ActiveHeadsetRoute mActiveHeadsetRoute = new ActiveHeadsetRoute();
     private final ActiveBluetoothRoute mActiveBluetoothRoute = new ActiveBluetoothRoute();
@@ -1337,6 +1353,8 @@
         mAvailableRoutes = mDeviceSupportedRoutes & getCurrentCallSupportedRoutes();
         mIsMuted = initState.isMuted();
         mWasOnSpeaker = false;
+        mContext.registerReceiver(mMuteChangeReceiver,
+                new IntentFilter(AudioManager.ACTION_MICROPHONE_MUTE_CHANGED));
 
         mStatusBarNotifier.notifyMute(initState.isMuted());
         mStatusBarNotifier.notifySpeakerphone(initState.getRoute() == CallAudioState.ROUTE_SPEAKER);
@@ -1373,27 +1391,20 @@
      */
     @Override
     protected void unhandledMessage(Message msg) {
-        CallAudioState newCallAudioState;
         switch (msg.what) {
             case MUTE_ON:
                 setMuteOn(true);
-                newCallAudioState = new CallAudioState(mIsMuted,
-                        mCurrentCallAudioState.getRoute(),
-                        mAvailableRoutes,
-                        mCurrentCallAudioState.getActiveBluetoothDevice(),
-                        mBluetoothRouteManager.getConnectedDevices());
-                setSystemAudioState(newCallAudioState);
-                updateInternalCallAudioState();
+                updateSystemMuteState();
                 return;
             case MUTE_OFF:
                 setMuteOn(false);
-                newCallAudioState = new CallAudioState(mIsMuted,
-                        mCurrentCallAudioState.getRoute(),
-                        mAvailableRoutes,
-                        mCurrentCallAudioState.getActiveBluetoothDevice(),
-                        mBluetoothRouteManager.getConnectedDevices());
-                setSystemAudioState(newCallAudioState);
-                updateInternalCallAudioState();
+                updateSystemMuteState();
+                return;
+            case MUTE_EXTERNALLY_CHANGED:
+                mIsMuted = mAudioManager.isMicrophoneMute();
+                if (isInActiveState()) {
+                    updateSystemMuteState();
+                }
                 return;
             case TOGGLE_MUTE:
                 if (mIsMuted) {
@@ -1474,7 +1485,6 @@
                     // user and not the current foreground, which we want to avoid.
                     audio.setMicrophoneMute(
                             mute, mContext.getOpPackageName(), getCurrentUserId());
-                    mStatusBarNotifier.notifyMute(mute);
                 } catch (RemoteException e) {
                     Log.e(this, e, "Remote exception while toggling mute.");
                 }
@@ -1484,6 +1494,16 @@
         }
     }
 
+    private void updateSystemMuteState() {
+        CallAudioState newCallAudioState = new CallAudioState(mIsMuted,
+                mCurrentCallAudioState.getRoute(),
+                mAvailableRoutes,
+                mCurrentCallAudioState.getActiveBluetoothDevice(),
+                mBluetoothRouteManager.getConnectedDevices());
+        setSystemAudioState(newCallAudioState);
+        updateInternalCallAudioState();
+    }
+
     /**
      * Updates the CallAudioState object from current internal state. The result is used for
      * external communication only.
@@ -1518,7 +1538,7 @@
             Log.i(this, "setSystemAudioState: changing from %s to %s", mLastKnownCallAudioState,
                     newCallAudioState);
             if (force || !newCallAudioState.equals(mLastKnownCallAudioState)) {
-
+                mStatusBarNotifier.notifyMute(newCallAudioState.isMuted());
                 mCallsManager.onCallAudioStateChanged(mLastKnownCallAudioState, newCallAudioState);
                 updateAudioForForegroundCall(newCallAudioState);
                 mLastKnownCallAudioState = newCallAudioState;
diff --git a/src/com/android/server/telecom/CallLogManager.java b/src/com/android/server/telecom/CallLogManager.java
index ce062c0..496b6f4 100755
--- a/src/com/android/server/telecom/CallLogManager.java
+++ b/src/com/android/server/telecom/CallLogManager.java
@@ -69,12 +69,13 @@
          * @param creationDate Time when the call was created (milliseconds since epoch).
          * @param durationInMillis Duration of the call (milliseconds).
          * @param dataUsage Data usage in bytes, or null if not applicable.
+         * @param isRead Indicates if the entry has been read or not.
          * @param logCallCompletedListener optional callback called after the call is logged.
          */
         public AddCallArgs(Context context, CallerInfo callerInfo, String number,
                 String postDialDigits, String viaNumber, int presentation, int callType,
                 int features, PhoneAccountHandle accountHandle, long creationDate,
-                long durationInMillis, Long dataUsage, UserHandle initiatingUser,
+                long durationInMillis, Long dataUsage, UserHandle initiatingUser, boolean isRead,
                 @Nullable LogCallCompletedListener logCallCompletedListener) {
             this.context = context;
             this.callerInfo = callerInfo;
@@ -89,6 +90,7 @@
             this.durationInSec = (int)(durationInMillis / 1000);
             this.dataUsage = dataUsage;
             this.initiatingUser = initiatingUser;
+            this.isRead = isRead;
             this.logCallCompletedListener = logCallCompletedListener;
         }
         // Since the members are accessed directly, we don't use the
@@ -106,6 +108,7 @@
         public final int durationInSec;
         public final Long dataUsage;
         public final UserHandle initiatingUser;
+        public final boolean isRead;
 
         @Nullable
         public final LogCallCompletedListener logCallCompletedListener;
@@ -235,7 +238,7 @@
         logCall(call.getCallerInfo(), logNumber, call.getPostDialDigits(), formattedViaNumber,
                 call.getHandlePresentation(), callLogType, callFeatures, accountHandle,
                 creationTime, age, callDataUsage, call.isEmergencyCall(), call.getInitiatingUser(),
-                logCallCompletedListener);
+                call.isSelfManaged(), logCallCompletedListener);
     }
 
     /**
@@ -253,6 +256,8 @@
      * @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.
      * @param logCallCompletedListener optional callback called after the call is logged.
+     * @param initiatingUser The user the call was initiated under.
+     * @param isSelfManaged {@code true} if this is a self-managed call, {@code false} otherwise.
      */
     private void logCall(
             CallerInfo callerInfo,
@@ -268,6 +273,7 @@
             Long dataUsage,
             boolean isEmergency,
             UserHandle initiatingUser,
+            boolean isSelfManaged,
             @Nullable LogCallCompletedListener logCallCompletedListener) {
 
         // On some devices, to avoid accidental redialing of emergency numbers, we *never* log
@@ -289,12 +295,18 @@
         sendAddCallBroadcast(callType, duration);
 
         if (isOkToLogThisCall) {
-            Log.d(TAG, "Logging Calllog entry: " + callerInfo + ", "
+            Log.d(TAG, "Logging Call log entry: " + callerInfo + ", "
                     + Log.pii(number) + "," + presentation + ", " + callType
                     + ", " + start + ", " + duration);
+            boolean isRead = false;
+            if (isSelfManaged) {
+                // Mark self-managed calls are read since they're being handled by their own app.
+                // Their inclusion in the call log is informational only.
+                isRead = true;
+            }
             AddCallArgs args = new AddCallArgs(mContext, callerInfo, number, postDialDigits,
                     viaNumber, presentation, callType, features, accountHandle, start, duration,
-                    dataUsage, initiatingUser, logCallCompletedListener);
+                    dataUsage, initiatingUser, isRead, logCallCompletedListener);
             logCallAsync(args);
         } else {
           Log.d(TAG, "Not adding emergency call to call log.");
@@ -440,7 +452,7 @@
             return Calls.addCall(c.callerInfo, c.context, c.number, c.postDialDigits, c.viaNumber,
                     c.presentation, c.callType, c.features, c.accountHandle, c.timestamp,
                     c.durationInSec, c.dataUsage, userToBeInserted == null,
-                    userToBeInserted);
+                    userToBeInserted, c.isRead);
         }
 
 
diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java
index f030e22..14b662c 100644
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -947,9 +947,9 @@
                 call.setIsVoipAudioMode(true);
             }
         }
-        if (isRttSettingOn() &&
+        if (isRttSettingOn() ||
                 extras.getBoolean(TelecomManager.EXTRA_START_CALL_WITH_RTT, false)) {
-            Log.d(this, "Incoming call requesting RTT, rtt setting is %b", isRttSettingOn());
+            Log.i(this, "Incoming call requesting RTT, rtt setting is %b", isRttSettingOn());
             if (phoneAccount != null &&
                     phoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_RTT)) {
                 call.createRttStreams();
@@ -3436,7 +3436,7 @@
         // Send an error back if there are any ongoing emergency calls.
         if (hasEmergencyCall()) {
             handoverFromCall.onHandoverFailed(
-                    android.telecom.Call.Callback.HANDOVER_FAILURE_ONGOING_EMERG_CALL);
+                    android.telecom.Call.Callback.HANDOVER_FAILURE_ONGOING_EMERGENCY_CALL);
             return;
         }
 
diff --git a/src/com/android/server/telecom/ConnectionServiceFocusManager.java b/src/com/android/server/telecom/ConnectionServiceFocusManager.java
index e213968..06d892a 100644
--- a/src/com/android/server/telecom/ConnectionServiceFocusManager.java
+++ b/src/com/android/server/telecom/ConnectionServiceFocusManager.java
@@ -21,6 +21,9 @@
 import android.os.Looper;
 import android.os.Message;
 import android.telecom.Log;
+import android.telecom.Logging.Session;
+import android.text.TextUtils;
+
 import com.android.internal.annotations.VisibleForTesting;
 
 import java.util.ArrayList;
@@ -157,7 +160,13 @@
                         return;
                     }
 
-                    mEventHandler.obtainMessage(MSG_ADD_CALL, call).sendToTarget();
+                    mEventHandler
+                            .obtainMessage(MSG_ADD_CALL,
+                                    new MessageArgs(
+                                            Log.createSubsession(),
+                                            "CSFM.oCA",
+                                            call))
+                            .sendToTarget();
                 }
 
                 @Override
@@ -166,7 +175,13 @@
                         return;
                     }
 
-                    mEventHandler.obtainMessage(MSG_REMOVE_CALL, call).sendToTarget();
+                    mEventHandler
+                            .obtainMessage(MSG_REMOVE_CALL,
+                                    new MessageArgs(
+                                            Log.createSubsession(),
+                                            "CSFM.oCR",
+                                            call))
+                            .sendToTarget();
                 }
 
                 @Override
@@ -175,16 +190,33 @@
                         return;
                     }
 
-                    mEventHandler.obtainMessage(MSG_CALL_STATE_CHANGED, oldState, newState, call)
+                    mEventHandler
+                            .obtainMessage(MSG_CALL_STATE_CHANGED, oldState, newState,
+                                    new MessageArgs(
+                                            Log.createSubsession(),
+                                            "CSFM.oCSS",
+                                            call))
                             .sendToTarget();
                 }
 
                 @Override
                 public void onExternalCallChanged(Call call, boolean isExternalCall) {
                     if (isExternalCall) {
-                        mEventHandler.obtainMessage(MSG_REMOVE_CALL, call).sendToTarget();
+                        mEventHandler
+                                .obtainMessage(MSG_REMOVE_CALL,
+                                        new MessageArgs(
+                                                Log.createSubsession(),
+                                                "CSFM.oECC",
+                                                call))
+                                .sendToTarget();
                     } else {
-                        mEventHandler.obtainMessage(MSG_ADD_CALL, call).sendToTarget();
+                        mEventHandler
+                                .obtainMessage(MSG_ADD_CALL,
+                                        new MessageArgs(
+                                                Log.createSubsession(),
+                                                "CSFM.oECC",
+                                                call))
+                                .sendToTarget();
                     }
                 }
 
@@ -199,7 +231,11 @@
                 public void onConnectionServiceReleased(
                         ConnectionServiceFocus connectionServiceFocus) {
                     mEventHandler
-                            .obtainMessage(MSG_RELEASE_CONNECTION_FOCUS, connectionServiceFocus)
+                            .obtainMessage(MSG_RELEASE_CONNECTION_FOCUS,
+                                    new MessageArgs(
+                                            Log.createSubsession(),
+                                            "CSFM.oCSR",
+                                            connectionServiceFocus))
                             .sendToTarget();
                 }
 
@@ -207,7 +243,11 @@
                 public void onConnectionServiceDeath(
                         ConnectionServiceFocus connectionServiceFocus) {
                     mEventHandler
-                            .obtainMessage(MSG_CONNECTION_SERVICE_DEATH, connectionServiceFocus)
+                            .obtainMessage(MSG_CONNECTION_SERVICE_DEATH,
+                                    new MessageArgs(
+                                            Log.createSubsession(),
+                                            "CSFM.oCSD",
+                                            connectionServiceFocus))
                             .sendToTarget();
                 }
             };
@@ -233,8 +273,12 @@
      * @param callback the callback associated with this request.
      */
     public void requestFocus(CallFocus focus, RequestFocusCallback callback) {
-        mEventHandler.obtainMessage(
-                MSG_REQUEST_FOCUS, new FocusRequest(focus, callback)).sendToTarget();
+        mEventHandler.obtainMessage(MSG_REQUEST_FOCUS,
+                new MessageArgs(
+                        Log.createSubsession(),
+                        "CSFM.rF",
+                        new FocusRequest(focus, callback)))
+                .sendToTarget();
     }
 
     /**
@@ -311,8 +355,12 @@
         } else {
             mCurrentFocus.connectionServiceFocusLost();
             mCurrentFocusRequest = focusRequest;
-            Message msg = mEventHandler.obtainMessage(MSG_RELEASE_FOCUS_TIMEOUT);
-            msg.obj = focusRequest;
+            Message msg = mEventHandler.obtainMessage(
+                    MSG_RELEASE_FOCUS_TIMEOUT,
+                    new MessageArgs(
+                            Log.createSubsession(),
+                            "CSFM.hRF",
+                            focusRequest));
             mEventHandler.sendMessageDelayed(msg, RELEASE_FOCUS_TIMEOUT_MS);
         }
     }
@@ -390,28 +438,40 @@
 
         @Override
         public void handleMessage(Message msg) {
-            switch (msg.what) {
-                case MSG_REQUEST_FOCUS:
-                    handleRequestFocus((FocusRequest) msg.obj);
-                    break;
-                case MSG_RELEASE_CONNECTION_FOCUS:
-                    handleReleasedFocus((ConnectionServiceFocus) msg.obj);
-                    break;
-                case MSG_RELEASE_FOCUS_TIMEOUT:
-                    handleReleasedFocusTimeout((FocusRequest) msg.obj);
-                    break;
-                case MSG_CONNECTION_SERVICE_DEATH:
-                    handleConnectionServiceDeath((ConnectionServiceFocus) msg.obj);
-                    break;
-                case MSG_ADD_CALL:
-                    handleAddedCall((CallFocus) msg.obj);
-                    break;
-                case MSG_REMOVE_CALL:
-                    handleRemovedCall((CallFocus) msg.obj);
-                    break;
-                case MSG_CALL_STATE_CHANGED:
-                    handleCallStateChanged((CallFocus) msg.obj, msg.arg1, msg.arg2);
-                    break;
+            Session session = ((MessageArgs) msg.obj).logSession;
+            String shortName = ((MessageArgs) msg.obj).shortName;
+            if (TextUtils.isEmpty(shortName)) {
+                shortName = "hM";
+            }
+            Log.continueSession(session, shortName);
+            Object msgObj = ((MessageArgs) msg.obj).obj;
+
+            try {
+                switch (msg.what) {
+                    case MSG_REQUEST_FOCUS:
+                        handleRequestFocus((FocusRequest) msgObj);
+                        break;
+                    case MSG_RELEASE_CONNECTION_FOCUS:
+                        handleReleasedFocus((ConnectionServiceFocus) msgObj);
+                        break;
+                    case MSG_RELEASE_FOCUS_TIMEOUT:
+                        handleReleasedFocusTimeout((FocusRequest) msgObj);
+                        break;
+                    case MSG_CONNECTION_SERVICE_DEATH:
+                        handleConnectionServiceDeath((ConnectionServiceFocus) msgObj);
+                        break;
+                    case MSG_ADD_CALL:
+                        handleAddedCall((CallFocus) msgObj);
+                        break;
+                    case MSG_REMOVE_CALL:
+                        handleRemovedCall((CallFocus) msgObj);
+                        break;
+                    case MSG_CALL_STATE_CHANGED:
+                        handleCallStateChanged((CallFocus) msgObj, msg.arg1, msg.arg2);
+                        break;
+                }
+            } finally {
+                Log.endSession();
             }
         }
     }
@@ -425,4 +485,16 @@
             this.callback = callback;
         }
     }
+
+    private static final class MessageArgs {
+        Session logSession;
+        String shortName;
+        Object obj;
+
+        MessageArgs(Session logSession, String shortName, Object obj) {
+            this.logSession = logSession;
+            this.shortName = shortName;
+            this.obj = obj;
+        }
+    }
 }
diff --git a/src/com/android/server/telecom/ui/MissedCallNotifierImpl.java b/src/com/android/server/telecom/ui/MissedCallNotifierImpl.java
index e02fd56..ec80911 100644
--- a/src/com/android/server/telecom/ui/MissedCallNotifierImpl.java
+++ b/src/com/android/server/telecom/ui/MissedCallNotifierImpl.java
@@ -66,6 +66,7 @@
 
 import java.lang.Override;
 import java.lang.String;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Locale;
 import java.util.Objects;
@@ -134,7 +135,7 @@
     // Used to track the number of missed calls.
     private ConcurrentMap<UserHandle, AtomicInteger> mMissedCallCounts;
 
-    private UserHandle userToLoadAfterBootComplete;
+    private List<UserHandle> mUsersToLoadAfterBootComplete = new ArrayList<>();
 
     public MissedCallNotifierImpl(Context context, PhoneAccountRegistrar phoneAccountRegistrar,
             DefaultDialerCache defaultDialerCache) {
@@ -271,7 +272,7 @@
     }
 
     private void showMissedCallNotification(@NonNull CallInfo callInfo, UserHandle userHandle) {
-        Log.i(this, "showMissedCallNotification()");
+        Log.i(this, "showMissedCallNotification: userHandle=%d", userHandle.getIdentifier());
         mMissedCallCounts.putIfAbsent(userHandle, new AtomicInteger(0));
         int missCallCounts = mMissedCallCounts.get(userHandle).incrementAndGet();
 
@@ -537,10 +538,14 @@
     @Override
     public void reloadAfterBootComplete(final CallerInfoLookupHelper callerInfoLookupHelper,
             CallInfoFactory callInfoFactory) {
-        if (userToLoadAfterBootComplete != null) {
-            reloadFromDatabase(callerInfoLookupHelper,
-                    callInfoFactory, userToLoadAfterBootComplete);
-            userToLoadAfterBootComplete = null;
+        if (!mUsersToLoadAfterBootComplete.isEmpty()) {
+            for (UserHandle handle : mUsersToLoadAfterBootComplete) {
+                Log.i(this, "reloadAfterBootComplete: user=%d", handle.getIdentifier());
+                reloadFromDatabase(callerInfoLookupHelper, callInfoFactory, handle);
+            }
+            mUsersToLoadAfterBootComplete.clear();
+        } else {
+            Log.i(this, "reloadAfterBootComplete: no user(s) to check; skipping reload.");
         }
     }
     /**
@@ -549,11 +554,12 @@
     @Override
     public void reloadFromDatabase(final CallerInfoLookupHelper callerInfoLookupHelper,
             CallInfoFactory callInfoFactory, final UserHandle userHandle) {
-        Log.d(this, "reloadFromDatabase()...");
+        Log.d(this, "reloadFromDatabase: user=%d", userHandle.getIdentifier());
         if (TelecomSystem.getInstance() == null || !TelecomSystem.getInstance().isBootComplete()) {
-            Log.i(this, "Boot not yet complete -- call log db may not be available. Deferring " +
-                    "loading until boot complete.");
-            userToLoadAfterBootComplete = userHandle;
+            Log.i(this, "reloadFromDatabase: Boot not yet complete -- call log db may not be "
+                    + "available. Deferring loading until boot complete for user %d",
+                    userHandle.getIdentifier());
+            mUsersToLoadAfterBootComplete.add(userHandle);
             return;
         }
 
diff --git a/testapps/res/layout/self_managed_call_list_item.xml b/testapps/res/layout/self_managed_call_list_item.xml
index f3be4ad..7e149a8 100644
--- a/testapps/res/layout/self_managed_call_list_item.xml
+++ b/testapps/res/layout/self_managed_call_list_item.xml
@@ -50,6 +50,11 @@
         <Button
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
+            android:text="Missed"
+            android:id="@+id/missedButton" />
+        <Button
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
             android:text="Disconnect"
             android:id="@+id/disconnectButton" />
     </LinearLayout>
diff --git a/testapps/src/com/android/server/telecom/testapps/SelfManagedCallListAdapter.java b/testapps/src/com/android/server/telecom/testapps/SelfManagedCallListAdapter.java
index b46d5e1..71e8922 100644
--- a/testapps/src/com/android/server/telecom/testapps/SelfManagedCallListAdapter.java
+++ b/testapps/src/com/android/server/telecom/testapps/SelfManagedCallListAdapter.java
@@ -60,8 +60,18 @@
     };
 
     /**
-     * Listener used to handle tap of the "held" button for a connection.
+     * Listener used to handle tap of the "missed" button for a connection.
      */
+    private View.OnClickListener mMissedListener = new View.OnClickListener() {
+        @Override
+        public void onClick(View v) {
+            View parent = (View) v.getParent().getParent();
+            SelfManagedConnection connection = (SelfManagedConnection) parent.getTag();
+            connection.setConnectionDisconnected(DisconnectCause.MISSED);
+            SelfManagedCallList.getInstance().removeConnection(connection);
+        }
+    };
+
     private View.OnClickListener mHeldListener = new View.OnClickListener() {
         @Override
         public void onClick(View v) {
@@ -165,7 +175,7 @@
         }
         setInfoForRow(result, phoneAccountHandle.getId(), connection.getAddress().toString(),
                 android.telecom.Connection.stateToString(connection.getState()), audioRoute,
-                callType);
+                callType, connection.getState() == android.telecom.Connection.STATE_RINGING);
         result.setTag(connection);
         return result;
     }
@@ -177,7 +187,8 @@
     }
 
     private void setInfoForRow(View view, String accountName, String number,
-                               String status, String audioRoute, String callType) {
+                               String status, String audioRoute, String callType,
+            boolean isRinging) {
 
         TextView numberTextView = (TextView) view.findViewById(R.id.phoneNumber);
         TextView statusTextView = (TextView) view.findViewById(R.id.callState);
@@ -191,6 +202,11 @@
         speakerButton.setOnClickListener(mSpeakerListener);
         View earpieceButton = view.findViewById(R.id.earpieceButton);
         earpieceButton.setOnClickListener(mEarpieceListener);
+        View missedButton = view.findViewById(R.id.missedButton);
+        missedButton.setOnClickListener(mMissedListener);
+        missedButton.setVisibility(isRinging ? View.VISIBLE : View.GONE);
+        setHeldButton.setVisibility(!isRinging ? View.VISIBLE : View.GONE);
+        disconnectButton.setVisibility(!isRinging ? View.VISIBLE : View.GONE);
         numberTextView.setText(accountName + " - " + number + " (" + audioRoute + ")");
         statusTextView.setText(callType + " - Status: " + status);
     }
diff --git a/testapps/src/com/android/server/telecom/testapps/SelfManagedConnection.java b/testapps/src/com/android/server/telecom/testapps/SelfManagedConnection.java
index 82967c4..a84dd90 100644
--- a/testapps/src/com/android/server/telecom/testapps/SelfManagedConnection.java
+++ b/testapps/src/com/android/server/telecom/testapps/SelfManagedConnection.java
@@ -183,6 +183,9 @@
     }
 
     public void setConnectionDisconnected(int cause) {
+        NotificationManager notificationManager = mContext.getSystemService(
+                NotificationManager.class);
+        notificationManager.cancel(CALL_NOTIFICATION, mCallId);
         mMediaPlayer.stop();
         setDisconnected(new DisconnectCause(cause));
         destroy();
diff --git a/testapps/src/com/android/server/telecom/testapps/SelfManagedConnectionService.java b/testapps/src/com/android/server/telecom/testapps/SelfManagedConnectionService.java
index bb34530..12d1552 100644
--- a/testapps/src/com/android/server/telecom/testapps/SelfManagedConnectionService.java
+++ b/testapps/src/com/android/server/telecom/testapps/SelfManagedConnectionService.java
@@ -79,6 +79,7 @@
         connection.setExtras(request.getExtras());
         if (isIncoming) {
             connection.setIsIncomingCallUiShowing(request.shouldShowIncomingCallUi());
+            connection.setRinging();
         }
         Bundle requestExtras = request.getExtras();
         if (requestExtras != null) {
diff --git a/tests/src/com/android/server/telecom/tests/CallLogManagerTest.java b/tests/src/com/android/server/telecom/tests/CallLogManagerTest.java
index 52f1b0f..5840b07 100644
--- a/tests/src/com/android/server/telecom/tests/CallLogManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallLogManagerTest.java
@@ -46,6 +46,7 @@
 import com.android.server.telecom.Call;
 import com.android.server.telecom.CallLogManager;
 import com.android.server.telecom.CallState;
+import com.android.server.telecom.HandoverState;
 import com.android.server.telecom.MissedCallNotifier;
 import com.android.server.telecom.PhoneAccountRegistrar;
 import com.android.server.telecom.TelephonyUtil;
@@ -83,6 +84,7 @@
     private PhoneAccountHandle mDefaultAccountHandle;
     private PhoneAccountHandle mOtherUserAccountHandle;
     private PhoneAccountHandle mManagedProfileAccountHandle;
+    private PhoneAccountHandle mSelfManagedAccountHandle;
 
     private static final Uri TEL_PHONEHANDLE = Uri.parse("tel:5555551234");
 
@@ -95,6 +97,7 @@
     private static final String POST_DIAL_STRING = ";12345";
     private static final String VIA_NUMBER_STRING = "5555555678";
     private static final String TEST_PHONE_ACCOUNT_ID= "testPhoneAccountId";
+    private static final String TEST_SELF_MGD_PHONE_ACCOUNT_ID= "testPhoneAccountId";
 
     private static final int TEST_TIMEOUT_MILLIS = 200;
     private static final int CURRENT_USER_ID = 0;
@@ -136,6 +139,12 @@
                 UserHandle.of(MANAGED_USER_ID)
         );
 
+        mSelfManagedAccountHandle = new PhoneAccountHandle(
+                new ComponentName("com.android.server.telecom.tests", "CallLogManagerSelfMgdTest"),
+                TEST_SELF_MGD_PHONE_ACCOUNT_ID,
+                UserHandle.of(CURRENT_USER_ID)
+        );
+
         UserManager userManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
         UserInfo userInfo = new UserInfo(CURRENT_USER_ID, "test", 0);
         UserInfo otherUserInfo = new UserInfo(OTHER_USER_ID, "test2", 0);
@@ -654,6 +663,37 @@
         assertNull(insertedValues.getAsLong(CallLog.Calls.DATA_USAGE));
     }
 
+    /**
+     * Ensures missed self-managed calls are marked as read..
+     */
+    @MediumTest
+    @Test
+    public void testLogMissedSelfManaged() {
+        when(mMockPhoneAccountRegistrar.getPhoneAccountUnchecked(any(PhoneAccountHandle.class)))
+                .thenReturn(makeFakePhoneAccount(mDefaultAccountHandle,
+                        PhoneAccount.CAPABILITY_SELF_MANAGED));
+        Call fakeMissedCall = makeFakeCall(
+                DisconnectCause.MISSED, // disconnectCauseCode
+                false, // isConference
+                true, // isIncoming
+                1L, // creationTimeMillis
+                1000L, // ageMillis
+                TEL_PHONEHANDLE, // callHandle
+                mSelfManagedAccountHandle, // phoneAccountHandle
+                NO_VIDEO_STATE, // callVideoState
+                POST_DIAL_STRING, // postDialDigits
+                VIA_NUMBER_STRING, // viaNumber
+                UserHandle.of(CURRENT_USER_ID)
+        );
+        when(fakeMissedCall.isSelfManaged()).thenReturn(true);
+        when(fakeMissedCall.isLoggedSelfManaged()).thenReturn(true);
+        when(fakeMissedCall.getHandoverState()).thenReturn(HandoverState.HANDOVER_NONE);
+        mCallLogManager.onCallStateChanged(fakeMissedCall, CallState.ACTIVE,
+                CallState.DISCONNECTED);
+        ContentValues insertedValues = verifyInsertionWithCapture(CURRENT_USER_ID);
+        assertEquals(1, insertedValues.getAsInteger(Calls.IS_READ).intValue());
+    }
+
     @SmallTest
     @Test
     public void testCountryIso_setCache() {