Import translations. DO NOT MERGE
am: ebf1ae8b3e  -s ours

Change-Id: I53dbfbe21c978ddc30f6ca7a28451260135b4e4f
diff --git a/Android.mk b/Android.mk
index 4785b24..79ef194 100644
--- a/Android.mk
+++ b/Android.mk
@@ -5,9 +5,13 @@
 
 LOCAL_JAVA_LIBRARIES := telephony-common
 
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_SRC_FILES := $(call all-java-files-under, src) $(call all-proto-files-under, proto)
 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
 
+LOCAL_PROTOC_OPTIMIZE_TYPE := nano
+LOCAL_PROTOC_FLAGS := --proto_path=$(LOCAL_PATH)/proto/
+LOCAL_PROTO_JAVA_OUTPUT_PARAMS := optional_field_style=accessors
+
 LOCAL_PACKAGE_NAME := Telecom
 
 LOCAL_CERTIFICATE := platform
diff --git a/proto/telecom.proto b/proto/telecom.proto
new file mode 100644
index 0000000..06dd394
--- /dev/null
+++ b/proto/telecom.proto
@@ -0,0 +1,280 @@
+syntax = "proto2";
+
+package com.android.server.telecom;
+
+option java_package = "com.android.server.telecom";
+option java_outer_classname = "TelecomLogClass";
+
+// The information about the telecom events.
+message TelecomLog {
+
+  // Information about each call.
+  repeated CallLog call_logs = 1;
+
+  // Timing information for the logging sessions
+  repeated LogSessionTiming session_timings = 2;
+}
+
+message LogSessionTiming {
+  enum SessionEntryPoint {
+    ICA_ANSWER_CALL = 1;
+    ICA_REJECT_CALL = 2;
+    ICA_DISCONNECT_CALL = 3;
+    ICA_HOLD_CALL = 4;
+    ICA_UNHOLD_CALL = 5;
+    ICA_MUTE = 6;
+    ICA_SET_AUDIO_ROUTE = 7;
+    ICA_CONFERENCE = 8;
+
+    CSW_HANDLE_CREATE_CONNECTION_COMPLETE = 100;
+    CSW_SET_ACTIVE = 101;
+    CSW_SET_RINGING = 102;
+    CSW_SET_DIALING = 103;
+    CSW_SET_DISCONNECTED = 104;
+    CSW_SET_ON_HOLD = 105;
+    CSW_REMOVE_CALL = 106;
+    CSW_SET_IS_CONFERENCED = 107;
+    CSW_ADD_CONFERENCE_CALL = 108;
+  }
+
+  // The entry point into Telecom code that this session tracks.
+  optional SessionEntryPoint sessionEntryPoint = 1;
+  // The time it took for this session to finish.
+  optional int64 time_millis = 2;
+}
+
+message Event {
+  // From android.telecom.ParcelableAnalytics
+  enum EventName {
+    SET_SELECT_PHONE_ACCOUNT = 0;
+    SET_ACTIVE = 1;
+    SET_DISCONNECTED = 2;
+    START_CONNECTION = 3;
+    SET_DIALING = 4;
+    BIND_CS = 5;
+    CS_BOUND = 6;
+    REQUEST_ACCEPT = 7;
+    REQUEST_REJECT = 8;
+
+    SCREENING_SENT = 100;
+    SCREENING_COMPLETED = 101;
+    DIRECT_TO_VM_INITIATED = 102;
+    DIRECT_TO_VM_FINISHED = 103;
+    BLOCK_CHECK_INITIATED = 104;
+    BLOCK_CHECK_FINISHED = 105;
+    FILTERING_INITIATED = 106;
+    FILTERING_COMPLETED = 107;
+    FILTERING_TIMED_OUT = 108;
+
+    SKIP_RINGING = 200;
+    SILENCE = 201;
+    MUTE = 202;
+    UNMUTE = 203;
+    AUDIO_ROUTE_BT = 204;
+    AUDIO_ROUTE_EARPIECE = 205;
+    AUDIO_ROUTE_HEADSET = 206;
+    AUDIO_ROUTE_SPEAKER = 207;
+
+    CONFERENCE_WITH = 300;
+    SPLIT_CONFERENCE = 301;
+    SET_PARENT = 302;
+
+    REQUEST_HOLD = 400;
+    REQUEST_UNHOLD = 401;
+    REMOTELY_HELD = 402;
+    REMOTELY_UNHELD = 403;
+    SET_HOLD = 404;
+    SWAP = 405;
+
+    REQUEST_PULL = 500;
+  }
+
+  // The ID of the event.
+  optional EventName event_name = 1;
+
+  // The elapsed time since the last event, rounded to one significant digit.
+  // If the event is the first, this will be negative.
+  optional int64 time_since_last_event_millis = 2;
+}
+
+message VideoEvent {
+  // From android.telecom.ParcelableCallAnalytics
+  enum VideoEventName {
+    SEND_LOCAL_SESSION_MODIFY_REQUEST = 0;
+    SEND_LOCAL_SESSION_MODIFY_RESPONSE = 1;
+    RECEIVE_REMOTE_SESSION_MODIFY_REQUEST = 2;
+    RECEIVE_REMOTE_SESSION_MODIFY_RESPONSE = 3;
+  }
+
+  // From android.telecom.VideoProfile
+  enum VideoState {
+     STATE_AUDIO_ONLY = 0;
+     STATE_TX_ENABLED = 1;
+     STATE_RX_ENABLED = 2;
+     STATE_BIDIRECTIONAL = 3;
+     STATE_PAUSED = 4;
+  }
+
+  // The ID of the event.
+  optional VideoEventName event_name = 1;
+
+  // The elapsed time since the last event, rounded to one significant digit.
+  // If the event is the first, this will be negative.
+  optional int64 time_since_last_event_millis = 2;
+
+  // The video state
+  optional int32 video_state = 3;
+}
+
+message EventTimingEntry {
+  enum EventTimingName {
+    ACCEPT_TIMING = 0;
+    REJECT_TIMING = 1;
+    DISCONNECT_TIMING = 2;
+    HOLD_TIMING = 3;
+    UNHOLD_TIMING = 4;
+    OUTGOING_TIME_TO_DIALING_TIMING = 5;
+    BIND_CS_TIMING = 6;
+    SCREENING_COMPLETED_TIMING = 7;
+    DIRECT_TO_VM_FINISHED_TIMING = 8;
+    BLOCK_CHECK_FINISHED_TIMING = 9;
+    FILTERING_COMPLETED_TIMING = 10;
+    FILTERING_TIMED_OUT_TIMING = 11;
+  }
+
+  // The name of the event timing.
+  optional EventTimingName timing_name = 1;
+
+  // The number of milliseconds that this event pair took.
+  optional int64 time_millis = 2;
+}
+
+message InCallServiceInfo {
+  // Keep this up-to-date with com.android.server.telecom.InCallController.
+  enum InCallServiceType {
+    IN_CALL_SERVICE_TYPE_INVALID = 0;
+    IN_CALL_SERVICE_TYPE_DIALER_UI = 1;
+    IN_CALL_SERVICE_TYPE_SYSTEM_UI = 2;
+    IN_CALL_SERVICE_TYPE_CAR_MODE_UI = 3;
+    IN_CALL_SERVICE_TYPE_NON_UI = 4;
+  }
+
+  // The shortened component name of the in-call service.
+  optional string in_call_service_name = 1;
+
+  // The type of the in-call service
+  optional InCallServiceType in_call_service_type = 2;
+}
+
+// Information about each call.
+message CallLog {
+
+  // Information on call-types.
+  enum CallType {
+
+    // Call type is not known.
+    CALLTYPE_UNKNOWN = 0;
+
+    // Incoming call.
+    CALLTYPE_INCOMING = 1;
+
+    // Outgoing call.
+    CALLTYPE_OUTGOING = 2;
+  }
+
+  // Termination code.
+  enum CallTerminationCode {
+
+    // Disconnected because of an unknown or unspecified reason.
+    CALL_TERMINATION_CODE_UNKNOWN = 0;
+
+    // Disconnected because there was an error, such as a problem
+    // with the network.
+    CALL_TERMINATION_CODE_ERROR = 1;
+
+    // Disconnected because of a local user-initiated action,
+    // such as hanging up.
+    CALL_TERMINATION_CODE_LOCAL = 2;
+
+    // Disconnected because of a remote user-initiated action,
+    // such as the other party hanging up.
+    CALL_TERMINATION_CODE_REMOTE = 3;
+
+    // Disconnected because it has been canceled.
+    CALL_TERMINATION_CODE_CANCELED = 4;
+
+    // Disconnected because there was no response to an incoming call.
+    CALL_TERMINATION_CODE_MISSED = 5;
+
+    // Disconnected because the user rejected an incoming call.
+    CALL_TERMINATION_CODE_REJECTED = 6;
+
+    // Disconnected because the other party was busy.
+    CALL_TERMINATION_CODE_BUSY = 7;
+
+    // Disconnected because of a restriction on placing the call,
+    // such as dialing in airplane mode.
+    CALL_TERMINATION_CODE_RESTRICTED = 8;
+
+    // Disconnected for reason not described by other disconnect codes.
+    CALL_TERMINATION_CODE_OTHER = 9;
+
+    // Disconnected because the connection manager did not support the call.
+    // The call will be tried again without a connection manager.
+    CONNECTION_MANAGER_NOT_SUPPORTED = 10;
+  }
+
+  // Start time of the connection.
+  // Rounded to the nearest 5 minute interval.
+  optional int64 start_time_5min = 1;
+
+  // Duration in millis.
+  optional int64 call_duration_millis = 2;
+
+  // Call type.
+  optional CallType type  = 3;
+
+  // True if the call interrupted an in-progress call, whether it was the
+  // user dialing out during a call or an incoming call during another call.
+  optional bool is_additional_call = 4 [default = false];
+
+  // True if the call was interrupted by another call.
+  optional bool is_interrupted = 5 [default = false];
+
+  // A bitmask with bits corresponding to call technologies that were used
+  // during the call. The ones that we will record are CDMA, GSM, IMS, SIP,
+  // and third-party.
+  // See the com.android.server.telecom.Analytics.*_PHONE constants.
+  optional int32 call_technologies = 6;
+
+  // Indicates the call termination code.
+  optional CallTerminationCode call_termination_code = 7;
+
+  // A list of the package names of connection services used.
+  repeated string connection_service = 9;
+
+  // Set to true if the call was created from createCallForExistingConnection.
+  optional bool is_created_from_existing_connection = 10 [default = false];
+
+  // Set to true if its an emergency call.
+  optional bool is_emergency_call = 11 [default = false];
+
+  // A list of the events that occur during the call.
+  repeated Event call_events = 12;
+
+  // A map from the names of latency timings to the timings.
+  repeated EventTimingEntry call_timings = 13;
+
+  // Whether this call has ever been a video call
+  optional bool is_video_call = 14 [default = false];
+
+  // A list of the video events during the call.
+  repeated VideoEvent video_events = 15;
+
+  // A list of the in-call services bound during the call.
+  repeated InCallServiceInfo in_call_services = 16;
+
+  // A bitmask of the properties that were set at any point during the call.
+  // Bits are defined by android.telecom.Connection.PROPERTY_* constants.
+  optional int32 connection_properties = 17;
+}
diff --git a/res/values-kn-rIN/strings.xml b/res/values-kn-rIN/strings.xml
index b94eb59..7ae3a29 100644
--- a/res/values-kn-rIN/strings.xml
+++ b/res/values-kn-rIN/strings.xml
@@ -18,7 +18,7 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="telecommAppLabel" product="default" msgid="382363169988504520">"ಕರೆ ನಿರ್ವಹಣೆ"</string>
     <string name="userCallActivityLabel" product="default" msgid="5415173590855187131">"ಫೋನ್"</string>
-    <string name="unknown" msgid="6878797917991465859">"ಅಪರಿಚಿತ"</string>
+    <string name="unknown" msgid="6878797917991465859">"ಅಜ್ಞಾತ"</string>
     <string name="notification_missedCallTitle" msgid="7554385905572364535">"ಮಿಸ್ಡ್‌ ಕಾಲ್‌"</string>
     <string name="notification_missedWorkCallTitle" msgid="6242489980390803090">"ಮಿಸ್ಡ್‌ ಕೆಲಸದ ಕರೆ"</string>
     <string name="notification_missedCallsTitle" msgid="1361677948941502522">"ತಪ್ಪಿದ ಕರೆಗಳು"</string>
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 75b8352..b0bcd3c 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -15,11 +15,11 @@
   -->
 
 <resources>
-    <color name="theme_color">#0288d1</color>
+    <color name="theme_color">#2a56c6</color>
 
     <color name="dialer_settings_actionbar_text_color">#ffffff</color>
     <color name="dialer_settings_actionbar_background_color">@color/theme_color</color>
-    <color name="dialer_settings_color_dark">#0277bd</color>
+    <color name="dialer_settings_color_dark">#1c3aa9</color>
 
     <color name="blocked_numbers_divider_color">#e5e5e5</color>
     <color name="blocked_numbers_butter_bar_color">#f5f5f5</color>
diff --git a/src/com/android/server/telecom/Analytics.java b/src/com/android/server/telecom/Analytics.java
index 50ff14d..4456734 100644
--- a/src/com/android/server/telecom/Analytics.java
+++ b/src/com/android/server/telecom/Analytics.java
@@ -16,14 +16,18 @@
 
 package com.android.server.telecom;
 
+import android.telecom.Connection;
 import android.telecom.DisconnectCause;
 import android.telecom.ParcelableCallAnalytics;
 import android.telecom.TelecomAnalytics;
+import android.util.Base64;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.IndentingPrintWriter;
 
+import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.LinkedList;
@@ -39,6 +43,9 @@
  * aggregate these into useful statistics.
  */
 public class Analytics {
+    public static final String ANALYTICS_DUMPSYS_ARG = "analytics";
+    private static final String CLEAR_ANALYTICS_ARG = "clear";
+
     public static final Map<String, Integer> sLogEventToAnalyticsEvent =
             new HashMap<String, Integer>() {{
                 put(Log.Events.SET_SELECT_PHONE_ACCOUNT, AnalyticsEvent.SET_SELECT_PHONE_ACCOUNT);
@@ -170,6 +177,12 @@
 
         public void addVideoEvent(int eventId, int videoState) {
         }
+
+        public void addInCallService(String serviceName, int type) {
+        }
+
+        public void addCallProperties(int properties) {
+        }
     }
 
     /**
@@ -200,7 +213,10 @@
         public Log.CallEventRecord callEvents;
 
         public boolean isVideo = false;
-        public List<ParcelableCallAnalytics.VideoEvent> videoEvents;
+        public List<TelecomLogClass.VideoEvent> videoEvents;
+        public List<TelecomLogClass.InCallServiceInfo> inCallServiceInfos;
+        public int callProperties = 0;
+
         private long mTimeOfLastVideoEvent = -1;
 
         CallInfoImpl(String callId, int callDirection) {
@@ -211,6 +227,7 @@
             callTechnologies = 0;
             connectionService = "";
             videoEvents = new LinkedList<>();
+            inCallServiceInfos = new LinkedList<>();
         }
 
         CallInfoImpl(CallInfoImpl other) {
@@ -227,6 +244,7 @@
             this.callEvents = other.callEvents;
             this.isVideo = other.isVideo;
             this.videoEvents = other.videoEvents;
+            this.callProperties = other.callProperties;
 
             if (other.callTerminationReason != null) {
                 this.callTerminationReason = new DisconnectCause(
@@ -311,8 +329,22 @@
             }
             mTimeOfLastVideoEvent = currentTime;
 
-            videoEvents.add(new ParcelableCallAnalytics.VideoEvent(
-                    eventId, timeSinceLastEvent, videoState));
+            videoEvents.add(new TelecomLogClass.VideoEvent()
+                    .setEventName(eventId)
+                    .setTimeSinceLastEventMillis(timeSinceLastEvent)
+                    .setVideoState(videoState));
+        }
+
+        @Override
+        public void addInCallService(String serviceName, int type) {
+            inCallServiceInfos.add(new TelecomLogClass.InCallServiceInfo()
+                    .setInCallServiceName(serviceName)
+                    .setInCallServiceType(type));
+        }
+
+        @Override
+        public void addCallProperties(int properties) {
+            this.callProperties |= properties;
         }
 
         @Override
@@ -327,44 +359,91 @@
                     + "    callTerminationReason: " + getCallDisconnectReasonString() + '\n'
                     + "    connectionService: " + connectionService + '\n'
                     + "    isVideoCall: " + isVideo + '\n'
+                    + "    inCallServices: " + getInCallServicesString() + '\n'
+                    + "    callProperties: " + Connection.propertiesToStringShort(callProperties)
+                    + '\n'
                     + "}\n";
         }
 
         public ParcelableCallAnalytics toParcelableAnalytics() {
+            TelecomLogClass.CallLog analyticsProto = toProto();
+            List<ParcelableCallAnalytics.AnalyticsEvent> events =
+                    Arrays.stream(analyticsProto.callEvents)
+                    .map(callEventProto -> new ParcelableCallAnalytics.AnalyticsEvent(
+                                callEventProto.getEventName(),
+                                callEventProto.getTimeSinceLastEventMillis())
+                    ).collect(Collectors.toList());
+
+            List<ParcelableCallAnalytics.EventTiming> timings =
+                    Arrays.stream(analyticsProto.callTimings)
+                    .map(callTimingProto -> new ParcelableCallAnalytics.EventTiming(
+                            callTimingProto.getTimingName(),
+                            callTimingProto.getTimeMillis())
+                    ).collect(Collectors.toList());
+
+            ParcelableCallAnalytics result = new ParcelableCallAnalytics(
+                    // rounds down to nearest 5 minute mark
+                    analyticsProto.getStartTime5Min(),
+                    analyticsProto.getCallDurationMillis(),
+                    analyticsProto.getType(),
+                    analyticsProto.getIsAdditionalCall(),
+                    analyticsProto.getIsInterrupted(),
+                    analyticsProto.getCallTechnologies(),
+                    analyticsProto.getCallTerminationCode(),
+                    analyticsProto.getIsEmergencyCall(),
+                    analyticsProto.connectionService[0],
+                    analyticsProto.getIsCreatedFromExistingConnection(),
+                    events,
+                    timings);
+
+            result.setIsVideoCall(analyticsProto.getIsVideoCall());
+            result.setVideoEvents(Arrays.stream(analyticsProto.videoEvents)
+                    .map(videoEventProto -> new ParcelableCallAnalytics.VideoEvent(
+                            videoEventProto.getEventName(),
+                            videoEventProto.getTimeSinceLastEventMillis(),
+                            videoEventProto.getVideoState())
+                    ).collect(Collectors.toList()));
+
+            return result;
+        }
+
+        public TelecomLogClass.CallLog toProto() {
+            TelecomLogClass.CallLog result = new TelecomLogClass.CallLog();
+            result.setStartTime5Min(
+                    startTime - startTime % ParcelableCallAnalytics.MILLIS_IN_5_MINUTES);
+
             // Rounds up to the nearest second.
             long callDuration = (endTime == 0 || startTime == 0) ? 0 : endTime - startTime;
             callDuration += (callDuration % MILLIS_IN_1_SECOND == 0) ?
                     0 : (MILLIS_IN_1_SECOND - callDuration % MILLIS_IN_1_SECOND);
+            result.setCallDurationMillis(callDuration);
 
-            List<AnalyticsEvent> events;
-            List<ParcelableCallAnalytics.EventTiming> timings;
+            result.setType(callDirection)
+                    .setIsAdditionalCall(isAdditionalCall)
+                    .setIsInterrupted(isInterrupted)
+                    .setCallTechnologies(callTechnologies)
+                    .setCallTerminationCode(
+                            callTerminationReason == null ?
+                                    ParcelableCallAnalytics.STILL_CONNECTED :
+                                    callTerminationReason.getCode())
+                    .setIsEmergencyCall(isEmergency)
+                    .setIsCreatedFromExistingConnection(createdFromExistingConnection)
+                    .setIsEmergencyCall(isEmergency)
+                    .setIsVideoCall(isVideo)
+                    .setConnectionProperties(callProperties);
+
+            result.connectionService = new String[] {connectionService};
             if (callEvents != null) {
-                events = convertLogEventsToAnalyticsEvents(callEvents.getEvents());
-                timings = callEvents.extractEventTimings().stream()
-                        .map(Analytics::logEventTimingToAnalyticsEventTiming)
-                        .collect(Collectors.toList());
-            } else {
-                events = Collections.emptyList();
-                timings = Collections.emptyList();
+                result.callEvents = convertLogEventsToProtoEvents(callEvents.getEvents());
+                result.callTimings = callEvents.extractEventTimings().stream()
+                        .map(Analytics::logEventTimingToProtoEventTiming)
+                        .toArray(TelecomLogClass.EventTimingEntry[]::new);
             }
-            ParcelableCallAnalytics result = new ParcelableCallAnalytics(
-                    // rounds down to nearest 5 minute mark
-                    startTime - startTime % ParcelableCallAnalytics.MILLIS_IN_5_MINUTES,
-                    callDuration,
-                    callDirection,
-                    isAdditionalCall,
-                    isInterrupted,
-                    callTechnologies,
-                    callTerminationReason == null ?
-                            ParcelableCallAnalytics.STILL_CONNECTED :
-                            callTerminationReason.getCode(),
-                    isEmergency,
-                    connectionService,
-                    createdFromExistingConnection,
-                    events,
-                    timings);
-            result.setIsVideoCall(isVideo);
-            result.setVideoEvents(videoEvents);
+            result.videoEvents =
+                    videoEvents.toArray(new TelecomLogClass.VideoEvent[videoEvents.size()]);
+            result.inCallServices = inCallServiceInfos.toArray(
+                    new TelecomLogClass.InCallServiceInfo[inCallServiceInfos.size()]);
+
             return result;
         }
 
@@ -400,6 +479,21 @@
                 return "NOT SET";
             }
         }
+
+        private String getInCallServicesString() {
+            StringBuilder s = new StringBuilder();
+            s.append("[\n");
+            for (TelecomLogClass.InCallServiceInfo service : inCallServiceInfos) {
+                s.append("    ");
+                s.append("name: ");
+                s.append(service.getInCallServiceName());
+                s.append(" type: ");
+                s.append(service.getInCallServiceType());
+                s.append("\n");
+            }
+            s.append("]");
+            return s.toString();
+        }
     }
     public static final String TAG = "TelecomAnalytics";
 
@@ -463,6 +557,28 @@
         return new TelecomAnalytics(sessionTimings, calls);
     }
 
+    public static void dumpToEncodedProto(PrintWriter pw, String[] args) {
+        TelecomLogClass.TelecomLog result = new TelecomLogClass.TelecomLog();
+
+        synchronized (sLock) {
+            result.callLogs = sCallIdToInfo.values().stream()
+                    .map(CallInfoImpl::toProto)
+                    .toArray(TelecomLogClass.CallLog[]::new);
+            result.sessionTimings = sSessionTimings.stream()
+                    .map(timing -> new TelecomLogClass.LogSessionTiming()
+                            .setSessionEntryPoint(timing.getKey())
+                            .setTimeMillis(timing.getTime()))
+                    .toArray(TelecomLogClass.LogSessionTiming[]::new);
+            if (args.length > 1 && CLEAR_ANALYTICS_ARG.equals(args[1])) {
+                sCallIdToInfo.clear();
+                sSessionTimings.clear();
+            }
+        }
+        String encodedProto = Base64.encodeToString(
+                TelecomLogClass.TelecomLog.toByteArray(result), Base64.DEFAULT);
+        pw.write(encodedProto);
+    }
+
     public static void dump(IndentingPrintWriter writer) {
         synchronized (sLock) {
             int prefixLength = CallsManager.TELECOM_CALL_ID_PREFIX.length();
@@ -521,33 +637,32 @@
         }
     }
 
-    private static List<AnalyticsEvent> convertLogEventsToAnalyticsEvents(
+    private static TelecomLogClass.Event[] convertLogEventsToProtoEvents(
             List<Log.CallEvent> logEvents) {
         long timeOfLastEvent = -1;
-        ArrayList<AnalyticsEvent> events = new ArrayList<>(logEvents.size());
+        ArrayList<TelecomLogClass.Event> events = new ArrayList<>(logEvents.size());
         for (Log.CallEvent logEvent : logEvents) {
             if (sLogEventToAnalyticsEvent.containsKey(logEvent.eventId)) {
-                int analyticsEventId = sLogEventToAnalyticsEvent.get(logEvent.eventId);
-                long timeSinceLastEvent =
-                        timeOfLastEvent < 0 ? -1 : logEvent.time - timeOfLastEvent;
-                events.add(new AnalyticsEvent(
-                        analyticsEventId,
-                        roundToOneSigFig(timeSinceLastEvent)
-                ));
+                TelecomLogClass.Event event = new TelecomLogClass.Event();
+                event.setEventName(sLogEventToAnalyticsEvent.get(logEvent.eventId));
+                event.setTimeSinceLastEventMillis(roundToOneSigFig(
+                        timeOfLastEvent < 0 ? -1 : logEvent.time - timeOfLastEvent));
+                events.add(event);
                 timeOfLastEvent = logEvent.time;
             }
         }
-        return events;
+        return events.toArray(new TelecomLogClass.Event[events.size()]);
     }
 
-    private static ParcelableCallAnalytics.EventTiming logEventTimingToAnalyticsEventTiming(
+    private static TelecomLogClass.EventTimingEntry logEventTimingToProtoEventTiming(
             Log.CallEventRecord.EventTiming logEventTiming) {
         int analyticsEventTimingName =
                 sLogEventTimingToAnalyticsEventTiming.containsKey(logEventTiming.name) ?
                         sLogEventTimingToAnalyticsEventTiming.get(logEventTiming.name) :
                         ParcelableCallAnalytics.EventTiming.INVALID;
-        return new ParcelableCallAnalytics.EventTiming(analyticsEventTimingName,
-                (long) logEventTiming.time);
+        return new TelecomLogClass.EventTimingEntry()
+                .setTimingName(analyticsEventTimingName)
+                .setTimeMillis(logEventTiming.time);
     }
 
     @VisibleForTesting
diff --git a/src/com/android/server/telecom/Call.java b/src/com/android/server/telecom/Call.java
index f536561..8d98179 100644
--- a/src/com/android/server/telecom/Call.java
+++ b/src/com/android/server/telecom/Call.java
@@ -1052,6 +1052,8 @@
 
             }
 
+            mAnalytics.addCallProperties(mConnectionProperties);
+
             int xorProps = previousProperties ^ mConnectionProperties;
             Log.event(this, Log.Events.PROPERTY_CHANGE,
                     "Current: [%s], Removed [%s], Added [%s]",
@@ -1328,8 +1330,6 @@
      */
     @VisibleForTesting
     public void answer(int videoState) {
-        Preconditions.checkNotNull(mConnectionService);
-
         // Check to verify that the call is still in the ringing state. A call can change states
         // between the time the user hits 'answer' and Telecom receives the command.
         if (isRinging("answer")) {
@@ -1342,7 +1342,12 @@
             // that it will work. Instead, we wait until confirmation from the connectino service
             // that the call is in a non-STATE_RINGING state before changing the UI. See
             // {@link ConnectionServiceAdapter#setActive} and other set* methods.
-            mConnectionService.answer(this, videoState);
+            if (mConnectionService != null) {
+                mConnectionService.answer(this, videoState);
+            } else {
+                Log.e(this, new NullPointerException(),
+                        "answer call failed due to null CS callId=%s", getId());
+            }
             Log.event(this, Log.Events.REQUEST_ACCEPT);
         }
     }
@@ -1355,16 +1360,20 @@
      */
     @VisibleForTesting
     public void reject(boolean rejectWithMessage, String textMessage) {
-        Preconditions.checkNotNull(mConnectionService);
-
         // Check to verify that the call is still in the ringing state. A call can change states
         // between the time the user hits 'reject' and Telecomm receives the command.
         if (isRinging("reject")) {
             // Ensure video state history tracks video state at time of rejection.
             mVideoStateHistory |= mVideoState;
 
-            mConnectionService.reject(this, rejectWithMessage, textMessage);
+            if (mConnectionService != null) {
+                mConnectionService.reject(this, rejectWithMessage, textMessage);
+            } else {
+                Log.e(this, new NullPointerException(),
+                        "reject call failed due to null CS callId=%s", getId());
+            }
             Log.event(this, Log.Events.REQUEST_REJECT);
+
         }
     }
 
@@ -1372,10 +1381,13 @@
      * Puts the call on hold if it is currently active.
      */
     void hold() {
-        Preconditions.checkNotNull(mConnectionService);
-
         if (mState == CallState.ACTIVE) {
-            mConnectionService.hold(this);
+            if (mConnectionService != null) {
+                mConnectionService.hold(this);
+            } else {
+                Log.e(this, new NullPointerException(),
+                        "hold call failed due to null CS callId=%s", getId());
+            }
             Log.event(this, Log.Events.REQUEST_HOLD);
         }
     }
@@ -1384,10 +1396,13 @@
      * Releases the call from hold if it is currently active.
      */
     void unhold() {
-        Preconditions.checkNotNull(mConnectionService);
-
         if (mState == CallState.ON_HOLD) {
-            mConnectionService.unhold(this);
+            if (mConnectionService != null) {
+                mConnectionService.unhold(this);
+            } else {
+                Log.e(this, new NullPointerException(),
+                        "unhold call failed due to null CS callId=%s", getId());
+            }
             Log.event(this, Log.Events.REQUEST_UNHOLD);
         }
     }
@@ -1441,7 +1456,12 @@
 
         // If the change originated from an InCallService, notify the connection service.
         if (source == SOURCE_INCALL_SERVICE) {
-            mConnectionService.onExtrasChanged(this, mExtras);
+            if (mConnectionService != null) {
+                mConnectionService.onExtrasChanged(this, mExtras);
+            } else {
+                Log.e(this, new NullPointerException(),
+                        "putExtras failed due to null CS callId=%s", getId());
+            }
         }
     }
 
@@ -1471,7 +1491,12 @@
 
         // If the change originated from an InCallService, notify the connection service.
         if (source == SOURCE_INCALL_SERVICE) {
-            mConnectionService.onExtrasChanged(this, mExtras);
+            if (mConnectionService != null) {
+                mConnectionService.onExtrasChanged(this, mExtras);
+            } else {
+                Log.e(this, new NullPointerException(),
+                        "removeExtras failed due to null CS callId=%s", getId());
+            }
         }
     }
 
@@ -1512,7 +1537,12 @@
     }
 
     void postDialContinue(boolean proceed) {
-        mConnectionService.onPostDialContinue(this, proceed);
+        if (mConnectionService != null) {
+            mConnectionService.onPostDialContinue(this, proceed);
+        } else {
+            Log.e(this, new NullPointerException(),
+                    "postDialContinue failed due to null CS callId=%s", getId());
+        }
     }
 
     void conferenceWith(Call otherCall) {
@@ -1614,7 +1644,12 @@
      * @param extras Associated extras.
      */
     public void sendCallEvent(String event, Bundle extras) {
-        mConnectionService.sendCallEvent(this, event, extras);
+        if (mConnectionService != null) {
+            mConnectionService.sendCallEvent(this, event, extras);
+        } else {
+            Log.e(this, new NullPointerException(),
+                    "sendCallEvent failed due to null CS callId=%s", getId());
+        }
     }
 
     void setParentCall(Call parentCall) {
@@ -2113,6 +2148,7 @@
      * @param extras The extras.
      */
     public void onConnectionEvent(String event, Bundle extras) {
+        Log.event(this, Log.Events.CONNECTION_EVENT, event);
         if (Connection.EVENT_ON_HOLD_TONE_START.equals(event)) {
             mIsRemotelyHeld = true;
             Log.event(this, Log.Events.REMOTELY_HELD);
diff --git a/src/com/android/server/telecom/CallAudioManager.java b/src/com/android/server/telecom/CallAudioManager.java
index 53cd349..236df5f 100644
--- a/src/com/android/server/telecom/CallAudioManager.java
+++ b/src/com/android/server/telecom/CallAudioManager.java
@@ -106,7 +106,7 @@
         }
 
         updateForegroundCall();
-        if (newState == CallState.DISCONNECTED) {
+        if (shouldPlayDisconnectTone(oldState, newState)) {
             playToneForDisconnectedCall(call);
         }
 
@@ -422,8 +422,8 @@
     }
 
     @VisibleForTesting
-    public void startRinging() {
-        mRinger.startRinging(mForegroundCall);
+    public boolean startRinging() {
+        return mRinger.startRinging(mForegroundCall);
     }
 
     @VisibleForTesting
@@ -452,6 +452,11 @@
         return mCallAudioRouteStateMachine;
     }
 
+    @VisibleForTesting
+    public CallAudioModeStateMachine getCallAudioModeStateMachine() {
+        return mCallAudioModeStateMachine;
+    }
+
     void dump(IndentingPrintWriter pw) {
         pw.println("All calls:");
         pw.increaseIndent();
@@ -732,6 +737,15 @@
         }
     }
 
+    private boolean shouldPlayDisconnectTone(int oldState, int newState) {
+        if (newState != CallState.DISCONNECTED) {
+            return false;
+        }
+        return oldState == CallState.ACTIVE ||
+                oldState == CallState.DIALING ||
+                oldState == CallState.ON_HOLD;
+    }
+
     @VisibleForTesting
     public Set<Call> getTrackedCalls() {
         return mCalls;
diff --git a/src/com/android/server/telecom/CallAudioModeStateMachine.java b/src/com/android/server/telecom/CallAudioModeStateMachine.java
index 92adca6..57043bc 100644
--- a/src/com/android/server/telecom/CallAudioModeStateMachine.java
+++ b/src/com/android/server/telecom/CallAudioModeStateMachine.java
@@ -201,19 +201,22 @@
         @Override
         public void enter() {
             Log.i(LOG_TAG, "Audio focus entering RINGING state");
-            mAudioManager.requestAudioFocusForCall(AudioManager.STREAM_RING,
-                    AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
-            if (mMostRecentMode == AudioManager.MODE_IN_CALL) {
-                // Preserving behavior from the old CallAudioManager.
-                Log.i(LOG_TAG, "Transition from IN_CALL -> RINGTONE."
-                        + "  Resetting to NORMAL first.");
-                mAudioManager.setMode(AudioManager.MODE_NORMAL);
+            if (mCallAudioManager.startRinging()) {
+                mAudioManager.requestAudioFocusForCall(AudioManager.STREAM_RING,
+                        AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
+                if (mMostRecentMode == AudioManager.MODE_IN_CALL) {
+                    // Preserving behavior from the old CallAudioManager.
+                    Log.i(LOG_TAG, "Transition from IN_CALL -> RINGTONE."
+                            + "  Resetting to NORMAL first.");
+                    mAudioManager.setMode(AudioManager.MODE_NORMAL);
+                }
+                mAudioManager.setMode(AudioManager.MODE_RINGTONE);
+                mCallAudioManager.setCallAudioRouteFocusState(CallAudioRouteStateMachine.RINGING_FOCUS);
+            } else {
+                Log.i(LOG_TAG, "Entering RINGING but not acquiring focus -- silent ringtone");
             }
-            mAudioManager.setMode(AudioManager.MODE_RINGTONE);
 
             mCallAudioManager.stopCallWaiting();
-            mCallAudioManager.startRinging();
-            mCallAudioManager.setCallAudioRouteFocusState(CallAudioRouteStateMachine.HAS_FOCUS);
         }
 
         @Override
@@ -288,7 +291,7 @@
                     AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
             mAudioManager.setMode(AudioManager.MODE_IN_CALL);
             mMostRecentMode = AudioManager.MODE_IN_CALL;
-            mCallAudioManager.setCallAudioRouteFocusState(CallAudioRouteStateMachine.HAS_FOCUS);
+            mCallAudioManager.setCallAudioRouteFocusState(CallAudioRouteStateMachine.ACTIVE_FOCUS);
         }
 
         @Override
@@ -350,7 +353,7 @@
                     AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
             mAudioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
             mMostRecentMode = AudioManager.MODE_IN_COMMUNICATION;
-            mCallAudioManager.setCallAudioRouteFocusState(CallAudioRouteStateMachine.HAS_FOCUS);
+            mCallAudioManager.setCallAudioRouteFocusState(CallAudioRouteStateMachine.ACTIVE_FOCUS);
         }
 
         @Override
@@ -407,7 +410,7 @@
             mAudioManager.requestAudioFocusForCall(AudioManager.STREAM_VOICE_CALL,
                     AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
             mAudioManager.setMode(mMostRecentMode);
-            mCallAudioManager.setCallAudioRouteFocusState(CallAudioRouteStateMachine.HAS_FOCUS);
+            mCallAudioManager.setCallAudioRouteFocusState(CallAudioRouteStateMachine.ACTIVE_FOCUS);
         }
 
         @Override
diff --git a/src/com/android/server/telecom/CallAudioRouteStateMachine.java b/src/com/android/server/telecom/CallAudioRouteStateMachine.java
index f4bdc4d..3cc2dad 100644
--- a/src/com/android/server/telecom/CallAudioRouteStateMachine.java
+++ b/src/com/android/server/telecom/CallAudioRouteStateMachine.java
@@ -105,7 +105,8 @@
 
     /** Valid values for mAudioFocusType */
     public static final int NO_FOCUS = 1;
-    public static final int HAS_FOCUS = 2;
+    public static final int ACTIVE_FOCUS = 2;
+    public static final int RINGING_FOCUS = 3;
 
     private static final SparseArray<String> AUDIO_ROUTE_TO_LOG_EVENT = new SparseArray<String>() {{
         put(CallAudioState.ROUTE_BLUETOOTH, Log.Events.AUDIO_ROUTE_BT);
@@ -135,6 +136,8 @@
         put(USER_SWITCH_SPEAKER, "USER_SWITCH_SPEAKER");
         put(USER_SWITCH_BASELINE_ROUTE, "USER_SWITCH_BASELINE_ROUTE");
 
+        put(UPDATE_SYSTEM_AUDIO_ROUTE, "UPDATE_SYSTEM_AUDIO_ROUTE");
+
         put(MUTE_ON, "MUTE_ON");
         put(MUTE_OFF, "MUTE_OFF");
         put(TOGGLE_MUTE, "TOGGLE_MUTE");
@@ -148,6 +151,7 @@
     private static final String ACTIVE_BLUETOOTH_ROUTE_NAME = "ActiveBluetoothRoute";
     private static final String ACTIVE_SPEAKER_ROUTE_NAME = "ActiveSpeakerRoute";
     private static final String ACTIVE_HEADSET_ROUTE_NAME = "ActiveHeadsetRoute";
+    private static final String RINGING_BLUETOOTH_ROUTE_NAME = "RingingBluetoothRoute";
     private static final String QUIESCENT_EARPIECE_ROUTE_NAME = "QuiescentEarpieceRoute";
     private static final String QUIESCENT_BLUETOOTH_ROUTE_NAME = "QuiescentBluetoothRoute";
     private static final String QUIESCENT_SPEAKER_ROUTE_NAME = "QuiescentSpeakerRoute";
@@ -160,7 +164,7 @@
         if (msg.obj != null && msg.obj instanceof Session) {
             String messageCodeName = MESSAGE_CODE_TO_NAME.get(msg.what, "unknown");
             Log.continueSession((Session) msg.obj, "CARSM.pM_" + messageCodeName);
-            Log.i(this, "Message received: %s=%d", messageCodeName, msg.what);
+            Log.i(this, "Message received: %s=%d, arg1=%d", messageCodeName, msg.what, msg.arg1);
         }
     }
 
@@ -217,6 +221,9 @@
                 case USER_SWITCH_BASELINE_ROUTE:
                     sendInternalMessage(calculateBaselineRouteMessage(true));
                     return HANDLED;
+                case SWITCH_FOCUS:
+                    mAudioFocusType = msg.arg1;
+                    return NOT_HANDLED;
                 default:
                     return NOT_HANDLED;
             }
@@ -268,7 +275,8 @@
                 case SWITCH_BLUETOOTH:
                 case USER_SWITCH_BLUETOOTH:
                     if ((mAvailableRoutes & ROUTE_BLUETOOTH) != 0) {
-                        transitionTo(mActiveBluetoothRoute);
+                        transitionTo(mAudioFocusType == ACTIVE_FOCUS ?
+                                mActiveBluetoothRoute : mRingingBluetoothRoute);
                     } else {
                         Log.w(this, "Ignoring switch to bluetooth command. Not available.");
                     }
@@ -350,7 +358,7 @@
                     transitionTo(mQuiescentSpeakerRoute);
                     return HANDLED;
                 case SWITCH_FOCUS:
-                    if (msg.arg1 == HAS_FOCUS) {
+                    if (msg.arg1 == ACTIVE_FOCUS || msg.arg1 == RINGING_FOCUS) {
                         transitionTo(mActiveEarpieceRoute);
                     }
                     return HANDLED;
@@ -449,7 +457,8 @@
                 case SWITCH_BLUETOOTH:
                 case USER_SWITCH_BLUETOOTH:
                     if ((mAvailableRoutes & ROUTE_BLUETOOTH) != 0) {
-                        transitionTo(mActiveBluetoothRoute);
+                        transitionTo(mAudioFocusType == ACTIVE_FOCUS ?
+                                mActiveBluetoothRoute : mRingingBluetoothRoute);
                     } else {
                         Log.w(this, "Ignoring switch to bluetooth command. Not available.");
                     }
@@ -527,7 +536,7 @@
                     transitionTo(mQuiescentSpeakerRoute);
                     return HANDLED;
                 case SWITCH_FOCUS:
-                    if (msg.arg1 == HAS_FOCUS) {
+                    if (msg.arg1 == ACTIVE_FOCUS || msg.arg1 == RINGING_FOCUS) {
                         transitionTo(mActiveHeadsetRoute);
                     }
                     return HANDLED;
@@ -652,6 +661,8 @@
                 case SWITCH_FOCUS:
                     if (msg.arg1 == NO_FOCUS) {
                         reinitialize();
+                    } else if (msg.arg1 == RINGING_FOCUS) {
+                        transitionTo(mRingingBluetoothRoute);
                     }
                     return HANDLED;
                 case BT_AUDIO_DISCONNECT:
@@ -663,6 +674,87 @@
         }
     }
 
+    class RingingBluetoothRoute extends BluetoothRoute {
+        @Override
+        public String getName() {
+            return RINGING_BLUETOOTH_ROUTE_NAME;
+        }
+
+        @Override
+        public boolean isActive() {
+            return false;
+        }
+
+        @Override
+        public void enter() {
+            super.enter();
+            setSpeakerphoneOn(false);
+            // Do not enable SCO audio here, since RING is being sent to the headset.
+            CallAudioState newState = new CallAudioState(mIsMuted, ROUTE_BLUETOOTH,
+                    mAvailableRoutes);
+            setSystemAudioState(newState);
+            updateInternalCallAudioState();
+        }
+
+        @Override
+        public void updateSystemAudioState() {
+            updateInternalCallAudioState();
+            setSystemAudioState(mCurrentCallAudioState);
+        }
+
+        @Override
+        public boolean processMessage(Message msg) {
+            if (super.processMessage(msg) == HANDLED) {
+                return HANDLED;
+            }
+            switch (msg.what) {
+                case USER_SWITCH_EARPIECE:
+                    mHasUserExplicitlyLeftBluetooth = true;
+                    // fall through
+                case SWITCH_EARPIECE:
+                    if ((mAvailableRoutes & ROUTE_EARPIECE) != 0) {
+                        transitionTo(mActiveEarpieceRoute);
+                    } else {
+                        Log.w(this, "Ignoring switch to earpiece command. Not available.");
+                    }
+                    return HANDLED;
+                case SWITCH_BLUETOOTH:
+                case USER_SWITCH_BLUETOOTH:
+                    // Nothing to do
+                    return HANDLED;
+                case USER_SWITCH_HEADSET:
+                    mHasUserExplicitlyLeftBluetooth = true;
+                    // fall through
+                case SWITCH_HEADSET:
+                    if ((mAvailableRoutes & ROUTE_WIRED_HEADSET) != 0) {
+                        transitionTo(mActiveHeadsetRoute);
+                    } else {
+                        Log.w(this, "Ignoring switch to headset command. Not available.");
+                    }
+                    return HANDLED;
+                case USER_SWITCH_SPEAKER:
+                    mHasUserExplicitlyLeftBluetooth = true;
+                    // fall through
+                case SWITCH_SPEAKER:
+                    transitionTo(mActiveSpeakerRoute);
+                    return HANDLED;
+                case SWITCH_FOCUS:
+                    if (msg.arg1 == NO_FOCUS) {
+                        reinitialize();
+                    } else if (msg.arg1 == ACTIVE_FOCUS) {
+                        transitionTo(mActiveBluetoothRoute);
+                    }
+                    return HANDLED;
+                case BT_AUDIO_DISCONNECT:
+                    // Ignore BT_AUDIO_DISCONNECT when ringing, since SCO audio should not be
+                    // connected.
+                    return HANDLED;
+                default:
+                    return NOT_HANDLED;
+            }
+        }
+    }
+
     class QuiescentBluetoothRoute extends BluetoothRoute {
         @Override
         public String getName() {
@@ -717,8 +809,10 @@
                     transitionTo(mQuiescentSpeakerRoute);
                     return HANDLED;
                 case SWITCH_FOCUS:
-                    if (msg.arg1 == HAS_FOCUS) {
+                    if (msg.arg1 == ACTIVE_FOCUS) {
                         transitionTo(mActiveBluetoothRoute);
+                    } else if (msg.arg1 == RINGING_FOCUS) {
+                        transitionTo(mRingingBluetoothRoute);
                     }
                     return HANDLED;
                 case BT_AUDIO_DISCONNECT:
@@ -816,7 +910,8 @@
                     // fall through
                 case SWITCH_BLUETOOTH:
                     if ((mAvailableRoutes & ROUTE_BLUETOOTH) != 0) {
-                        transitionTo(mActiveBluetoothRoute);
+                        transitionTo(mAudioFocusType == ACTIVE_FOCUS ?
+                                mActiveBluetoothRoute : mRingingBluetoothRoute);
                     } else {
                         Log.w(this, "Ignoring switch to bluetooth command. Not available.");
                     }
@@ -906,7 +1001,7 @@
                     // Nothing to do
                     return HANDLED;
                 case SWITCH_FOCUS:
-                    if (msg.arg1 == HAS_FOCUS) {
+                    if (msg.arg1 == ACTIVE_FOCUS || msg.arg1 == RINGING_FOCUS) {
                         transitionTo(mActiveSpeakerRoute);
                     }
                     return HANDLED;
@@ -962,6 +1057,7 @@
     private final ActiveHeadsetRoute mActiveHeadsetRoute = new ActiveHeadsetRoute();
     private final ActiveBluetoothRoute mActiveBluetoothRoute = new ActiveBluetoothRoute();
     private final ActiveSpeakerRoute mActiveSpeakerRoute = new ActiveSpeakerRoute();
+    private final RingingBluetoothRoute mRingingBluetoothRoute = new RingingBluetoothRoute();
     private final QuiescentEarpieceRoute mQuiescentEarpieceRoute = new QuiescentEarpieceRoute();
     private final QuiescentHeadsetRoute mQuiescentHeadsetRoute = new QuiescentHeadsetRoute();
     private final QuiescentBluetoothRoute mQuiescentBluetoothRoute = new QuiescentBluetoothRoute();
@@ -972,6 +1068,7 @@
      * states
      */
     private int mAvailableRoutes;
+    private int mAudioFocusType;
     private boolean mWasOnSpeaker;
     private boolean mIsMuted;
 
@@ -983,6 +1080,7 @@
     private final StatusBarNotifier mStatusBarNotifier;
     private final CallAudioManager.AudioServiceFactory mAudioServiceFactory;
     private final boolean mDoesDeviceSupportEarpieceRoute;
+    private final TelecomSystem.SyncRoot mLock;
     private boolean mHasUserExplicitlyLeftBluetooth = false;
 
     private HashMap<String, Integer> mStateNameToRouteCode;
@@ -1006,6 +1104,7 @@
         addState(mActiveHeadsetRoute);
         addState(mActiveBluetoothRoute);
         addState(mActiveSpeakerRoute);
+        addState(mRingingBluetoothRoute);
         addState(mQuiescentEarpieceRoute);
         addState(mQuiescentHeadsetRoute);
         addState(mQuiescentBluetoothRoute);
@@ -1019,12 +1118,14 @@
         mStatusBarNotifier = statusBarNotifier;
         mAudioServiceFactory = audioServiceFactory;
         mDoesDeviceSupportEarpieceRoute = doesDeviceSupportEarpieceRoute;
+        mLock = callsManager.getLock();
 
         mStateNameToRouteCode = new HashMap<>(8);
         mStateNameToRouteCode.put(mQuiescentEarpieceRoute.getName(), ROUTE_EARPIECE);
         mStateNameToRouteCode.put(mQuiescentBluetoothRoute.getName(), ROUTE_BLUETOOTH);
         mStateNameToRouteCode.put(mQuiescentHeadsetRoute.getName(), ROUTE_WIRED_HEADSET);
         mStateNameToRouteCode.put(mQuiescentSpeakerRoute.getName(), ROUTE_SPEAKER);
+        mStateNameToRouteCode.put(mRingingBluetoothRoute.getName(), ROUTE_BLUETOOTH);
         mStateNameToRouteCode.put(mActiveEarpieceRoute.getName(), ROUTE_EARPIECE);
         mStateNameToRouteCode.put(mActiveBluetoothRoute.getName(), ROUTE_BLUETOOTH);
         mStateNameToRouteCode.put(mActiveHeadsetRoute.getName(), ROUTE_WIRED_HEADSET);
@@ -1199,18 +1300,20 @@
     }
 
     private void setSystemAudioState(CallAudioState newCallAudioState, boolean force) {
-        Log.i(this, "setSystemAudioState: changing from %s to %s", mLastKnownCallAudioState,
-                newCallAudioState);
-        if (force || !newCallAudioState.equals(mLastKnownCallAudioState)) {
-            if (newCallAudioState.getRoute() != mLastKnownCallAudioState.getRoute()) {
-                Log.event(mCallsManager.getForegroundCall(),
-                        AUDIO_ROUTE_TO_LOG_EVENT.get(newCallAudioState.getRoute(),
-                                Log.Events.AUDIO_ROUTE));
-            }
+        synchronized (mLock) {
+            Log.i(this, "setSystemAudioState: changing from %s to %s", mLastKnownCallAudioState,
+                    newCallAudioState);
+            if (force || !newCallAudioState.equals(mLastKnownCallAudioState)) {
+                if (newCallAudioState.getRoute() != mLastKnownCallAudioState.getRoute()) {
+                    Log.event(mCallsManager.getForegroundCall(),
+                            AUDIO_ROUTE_TO_LOG_EVENT.get(newCallAudioState.getRoute(),
+                                    Log.Events.AUDIO_ROUTE));
+                }
 
-            mCallsManager.onCallAudioStateChanged(mLastKnownCallAudioState, newCallAudioState);
-            updateAudioForForegroundCall(newCallAudioState);
-            mLastKnownCallAudioState = newCallAudioState;
+                mCallsManager.onCallAudioStateChanged(mLastKnownCallAudioState, newCallAudioState);
+                updateAudioForForegroundCall(newCallAudioState);
+                mLastKnownCallAudioState = newCallAudioState;
+            }
         }
     }
 
diff --git a/src/com/android/server/telecom/CallerInfoLookupHelper.java b/src/com/android/server/telecom/CallerInfoLookupHelper.java
index 0dec317..e557c8a 100644
--- a/src/com/android/server/telecom/CallerInfoLookupHelper.java
+++ b/src/com/android/server/telecom/CallerInfoLookupHelper.java
@@ -150,7 +150,7 @@
                         }
                     } else {
                         Log.i(CallerInfoLookupHelper.this, "CI query for handle %s has completed," +
-                                " but there are no listeners left.", handle);
+                                " but there are no listeners left.", Log.piiHandle(handle));
                     }
                 } finally {
                     Log.endSession();
@@ -197,7 +197,8 @@
                         mQueryEntries.remove(handle);
                     } else {
                         Log.i(CallerInfoLookupHelper.this, "Photo query for handle %s has" +
-                                " completed, but there are no listeners left.", handle);
+                                " completed, but there are no listeners left.",
+                                Log.piiHandle(handle));
                     }
                 } finally {
                     Log.endSession();
diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java
index 8df77cf..d70daec 100644
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -267,7 +267,7 @@
         RingtoneFactory ringtoneFactory = new RingtoneFactory(this, context);
         SystemVibrator systemVibrator = new SystemVibrator(context);
         mInCallController = new InCallController(
-                context, mLock, this, systemStateProvider, defaultDialerAdapter);
+                context, mLock, this, systemStateProvider, defaultDialerAdapter, mTimeoutsAdapter);
         mRinger = new Ringer(playerFactory, context, systemSettingsUtil, asyncRingtonePlayer,
                 ringtoneFactory, systemVibrator, mInCallController);
 
@@ -348,6 +348,12 @@
     @Override
     public void onSuccessfulIncomingCall(Call incomingCall) {
         Log.d(this, "onSuccessfulIncomingCall");
+        if (incomingCall.hasProperty(Connection.PROPERTY_EMERGENCY_CALLBACK_MODE)) {
+            Log.i(this, "Skipping call filtering due to ECBM");
+            onCallFilteringComplete(incomingCall, new CallFilteringResult(true, false, true, true));
+            return;
+        }
+
         List<IncomingCallFilter.CallFilter> filters = new ArrayList<>();
         filters.add(new DirectToVoicemailCallFilter(mCallerInfoLookupHelper));
         filters.add(new AsyncBlockCheckFilter(mContext, new BlockCheckerAdapter()));
@@ -625,7 +631,8 @@
         return false;
     }
 
-    CallAudioState getAudioState() {
+    @VisibleForTesting
+    public CallAudioState getAudioState() {
         return mCallAudioManager.getCallAudioState();
     }
 
@@ -1486,7 +1493,8 @@
     /**
      * Returns true if telecom supports adding another top-level call.
      */
-    boolean canAddCall() {
+    @VisibleForTesting
+    public boolean canAddCall() {
         boolean isDeviceProvisioned = Settings.Global.getInt(mContext.getContentResolver(),
                 Settings.Global.DEVICE_PROVISIONED, 0) != 0;
         if (!isDeviceProvisioned) {
@@ -1685,7 +1693,16 @@
      * @param incomingCall Incoming call that has been rejected
      */
     private void rejectCallAndLog(Call incomingCall) {
-        incomingCall.reject(false, null);
+        if (incomingCall.getConnectionService() != null) {
+            // Only reject the call if it has not already been destroyed.  If a call ends while
+            // incoming call filtering is taking place, it is possible that the call has already
+            // been destroyed, and as such it will be impossible to send the reject to the
+            // associated ConnectionService.
+            incomingCall.reject(false, null);
+        } else {
+            Log.i(this, "rejectCallAndLog - call already destroyed.");
+        }
+
         // Since the call was not added to the list of calls, we have to call the missed
         // call notifier and the call logger manually.
         // Do we need missed call notification for direct to Voicemail calls?
diff --git a/src/com/android/server/telecom/InCallController.java b/src/com/android/server/telecom/InCallController.java
index 8b69245..2157bf3 100644
--- a/src/com/android/server/telecom/InCallController.java
+++ b/src/com/android/server/telecom/InCallController.java
@@ -79,12 +79,16 @@
     }
 
     private class InCallServiceInfo {
-        private ComponentName mComponentName;
+        private final ComponentName mComponentName;
         private boolean mIsExternalCallsSupported;
+        private final int mType;
 
-        public InCallServiceInfo(ComponentName componentName, boolean isExternalCallsSupported) {
+        public InCallServiceInfo(ComponentName componentName,
+                boolean isExternalCallsSupported,
+                int type) {
             mComponentName = componentName;
             mIsExternalCallsSupported = isExternalCallsSupported;
+            mType = type;
         }
 
         public ComponentName getComponentName() {
@@ -95,6 +99,10 @@
             return mIsExternalCallsSupported;
         }
 
+        public int getType() {
+            return mType;
+        }
+
         @Override
         public boolean equals(Object o) {
             if (this == o) {
@@ -192,6 +200,12 @@
                 mIsConnected = false;
             }
 
+            if (call != null && mIsConnected) {
+                call.getAnalytics().addInCallService(
+                        mInCallServiceInfo.getComponentName().flattenToShortString(),
+                        mInCallServiceInfo.getType());
+            }
+
             return mIsConnected;
         }
 
@@ -217,7 +231,7 @@
                     InCallController.this.onConnected(mInCallServiceInfo, service);
             if (!shouldRemainConnected) {
                 // Sometimes we can opt to disconnect for certain reasons, like if the
-                // InCallService rejected our intialization step, or the calls went away
+                // InCallService rejected our initialization step, or the calls went away
                 // in the time it took us to bind to the InCallService. In such cases, we go
                 // ahead and disconnect ourselves.
                 disconnect();
@@ -599,17 +613,19 @@
     private final CallsManager mCallsManager;
     private final SystemStateProvider mSystemStateProvider;
     private final DefaultDialerManagerAdapter mDefaultDialerAdapter;
+    private final Timeouts.Adapter mTimeoutsAdapter;
     private CarSwappingInCallServiceConnection mInCallServiceConnection;
     private NonUIInCallServiceConnectionCollection mNonUIInCallServiceConnections;
 
     public InCallController(Context context, TelecomSystem.SyncRoot lock, CallsManager callsManager,
             SystemStateProvider systemStateProvider,
-            DefaultDialerManagerAdapter defaultDialerAdapter) {
+            DefaultDialerManagerAdapter defaultDialerAdapter, Timeouts.Adapter timeoutsAdapter) {
         mContext = context;
         mLock = lock;
         mCallsManager = callsManager;
         mSystemStateProvider = systemStateProvider;
         mDefaultDialerAdapter = defaultDialerAdapter;
+        mTimeoutsAdapter = timeoutsAdapter;
 
         Resources resources = mContext.getResources();
         mSystemInCallComponentName = new ComponentName(
@@ -669,7 +685,7 @@
                         unbindFromServices();
                     }
                 }
-            }.prepare(), Timeouts.getCallRemoveUnbindInCallServicesDelay(
+            }.prepare(), mTimeoutsAdapter.getCallRemoveUnbindInCallServicesDelay(
                             mContext.getContentResolver()));
         }
         call.removeListener(mCallListener);
@@ -840,10 +856,14 @@
      */
     private void unbindFromServices() {
         if (isBoundToServices()) {
-            mInCallServiceConnection.disconnect();
-            mInCallServiceConnection = null;
-            mNonUIInCallServiceConnections.disconnect();
-            mNonUIInCallServiceConnections = null;
+            if (mInCallServiceConnection != null) {
+                mInCallServiceConnection.disconnect();
+                mInCallServiceConnection = null;
+            }
+            if (mNonUIInCallServiceConnections != null) {
+                mNonUIInCallServiceConnections.disconnect();
+                mNonUIInCallServiceConnections = null;
+            }
         }
     }
 
@@ -914,7 +934,7 @@
             // Last Resort: Try to bind to the ComponentName given directly.
             Log.e(this, new Exception(), "Package Manager could not find ComponentName: "
                     + componentName +". Trying to bind anyway.");
-            return new InCallServiceInfo(componentName, false);
+            return new InCallServiceInfo(componentName, false, type);
         }
     }
 
@@ -968,7 +988,7 @@
 
                     retval.add(new InCallServiceInfo(
                             new ComponentName(serviceInfo.packageName, serviceInfo.name),
-                            isExternalCallsSupported));
+                            isExternalCallsSupported, requestedType));
                 }
             }
         }
@@ -1077,34 +1097,32 @@
 
         // Upon successful connection, send the state of the world to the service.
         List<Call> calls = orderCallsWithChildrenFirst(mCallsManager.getCalls());
-        if (!calls.isEmpty()) {
-            Log.i(this, "Adding %s calls to InCallService after onConnected: %s", calls.size(),
-                    info.getComponentName());
-            for (Call call : calls) {
-                try {
-                    if (call.isExternalCall() && !info.isExternalCallsSupported()) {
-                        continue;
-                    }
-
-                    // Track the call if we don't already know about it.
-                    addCall(call);
-
-                    inCallService.addCall(ParcelableCallUtils.toParcelableCall(
-                            call,
-                            true /* includeVideoProvider */,
-                            mCallsManager.getPhoneAccountRegistrar(),
-                            info.isExternalCallsSupported()));
-                } catch (RemoteException ignored) {
-                }
-            }
+        Log.i(this, "Adding %s calls to InCallService after onConnected: %s, including external " +
+                "calls", calls.size(), info.getComponentName());
+        int numCallsSent = 0;
+        for (Call call : calls) {
             try {
-                inCallService.onCallAudioStateChanged(mCallsManager.getAudioState());
-                inCallService.onCanAddCallChanged(mCallsManager.canAddCall());
+                if (call.isExternalCall() && !info.isExternalCallsSupported()) {
+                    continue;
+                }
+
+                // Track the call if we don't already know about it.
+                addCall(call);
+                numCallsSent += 1;
+                inCallService.addCall(ParcelableCallUtils.toParcelableCall(
+                        call,
+                        true /* includeVideoProvider */,
+                        mCallsManager.getPhoneAccountRegistrar(),
+                        info.isExternalCallsSupported()));
             } catch (RemoteException ignored) {
             }
-        } else {
-            return false;
         }
+        try {
+            inCallService.onCallAudioStateChanged(mCallsManager.getAudioState());
+            inCallService.onCanAddCallChanged(mCallsManager.canAddCall());
+        } catch (RemoteException ignored) {
+        }
+        Log.i(this, "%s calls sent to InCallService.", numCallsSent);
         Trace.endSection();
         return true;
     }
diff --git a/src/com/android/server/telecom/Log.java b/src/com/android/server/telecom/Log.java
index 29d9576..2282ff0 100644
--- a/src/com/android/server/telecom/Log.java
+++ b/src/com/android/server/telecom/Log.java
@@ -170,6 +170,7 @@
         public static final String IS_EXTERNAL = "IS_EXTERNAL";
         public static final String PROPERTY_CHANGE = "PROPERTY_CHANGE";
         public static final String CAPABILITY_CHANGE = "CAPABILITY_CHANGE";
+        public static final String CONNECTION_EVENT = "CONNECTION_EVENT";
 
         public static class Timings {
             public static final String ACCEPT_TIMING = "accept";
diff --git a/src/com/android/server/telecom/ParcelableCallUtils.java b/src/com/android/server/telecom/ParcelableCallUtils.java
index b64ce9c..9cc61b3 100644
--- a/src/com/android/server/telecom/ParcelableCallUtils.java
+++ b/src/com/android/server/telecom/ParcelableCallUtils.java
@@ -97,7 +97,7 @@
         }
 
         // If this is a single-SIM device, the "default SIM" will always be the only SIM.
-        boolean isDefaultSmsAccount =
+        boolean isDefaultSmsAccount = phoneAccountRegistrar != null &&
                 phoneAccountRegistrar.isUserSelectedSmsPhoneAccount(call.getTargetPhoneAccount());
         if (call.isRespondViaSmsCapable() && isDefaultSmsAccount) {
             capabilities |= android.telecom.Call.Details.CAPABILITY_RESPOND_VIA_TEXT;
@@ -314,7 +314,7 @@
         Connection.PROPERTY_GENERIC_CONFERENCE,
         android.telecom.Call.Details.PROPERTY_GENERIC_CONFERENCE,
 
-        Connection.PROPERTY_SHOW_CALLBACK_NUMBER,
+        Connection.PROPERTY_EMERGENCY_CALLBACK_MODE,
         android.telecom.Call.Details.PROPERTY_EMERGENCY_CALLBACK_MODE,
 
         Connection.PROPERTY_IS_EXTERNAL_CALL,
diff --git a/src/com/android/server/telecom/Ringer.java b/src/com/android/server/telecom/Ringer.java
index 77b1590..262f437 100644
--- a/src/com/android/server/telecom/Ringer.java
+++ b/src/com/android/server/telecom/Ringer.java
@@ -94,30 +94,32 @@
         mInCallController = inCallController;
     }
 
-    public void startRinging(Call foregroundCall) {
+    public boolean startRinging(Call foregroundCall) {
+        AudioManager audioManager =
+                (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
+        boolean isRingerAudible = audioManager.getStreamVolume(AudioManager.STREAM_RING) > 0;
+
         if (mSystemSettingsUtil.isTheaterModeOn(mContext)) {
-            return;
+            return false;
         }
 
         if (foregroundCall == null) {
             Log.wtf(this, "startRinging called with null foreground call.");
-            return;
+            return false;
         }
 
         if (mInCallController.doesConnectedDialerSupportRinging()) {
             Log.event(foregroundCall, Log.Events.SKIP_RINGING);
-            return;
+            return isRingerAudible;
         }
 
         stopCallWaiting();
 
         if (!shouldRingForContact(foregroundCall.getContactUri())) {
-            return;
+            return false;
         }
 
-        AudioManager audioManager =
-                (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
-        if (audioManager.getStreamVolume(AudioManager.STREAM_RING) > 0) {
+        if (isRingerAudible) {
             mRingingCall = foregroundCall;
             Log.event(foregroundCall, Log.Events.START_RINGER);
             // Because we wait until a contact info query to complete before processing a
@@ -126,7 +128,7 @@
             // request the custom ringtone from the call and expect it to be current.
             mRingtonePlayer.play(mRingtoneFactory, foregroundCall);
         } else {
-            Log.v(this, "startRingingOrCallWaiting, skipping because volume is 0");
+            Log.i(this, "startRingingOrCallWaiting, skipping because volume is 0");
         }
 
         if (shouldVibrate(mContext) && !mIsVibrating) {
@@ -134,6 +136,8 @@
                     VIBRATION_ATTRIBUTES);
             mIsVibrating = true;
         }
+
+        return isRingerAudible;
     }
 
     public void startCallWaiting(Call call) {
diff --git a/src/com/android/server/telecom/TelecomServiceImpl.java b/src/com/android/server/telecom/TelecomServiceImpl.java
index 7dc0a79..c93a752 100644
--- a/src/com/android/server/telecom/TelecomServiceImpl.java
+++ b/src/com/android/server/telecom/TelecomServiceImpl.java
@@ -39,7 +39,6 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.telecom.DefaultDialerManager;
-import android.telecom.ParcelableCallAnalytics;
 import android.telecom.PhoneAccount;
 import android.telecom.PhoneAccountHandle;
 import android.telecom.TelecomAnalytics;
@@ -57,8 +56,6 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 
@@ -1174,6 +1171,11 @@
                 return;
             }
 
+            if (args.length > 0 && Analytics.ANALYTICS_DUMPSYS_ARG.equals(args[0])) {
+                Analytics.dumpToEncodedProto(writer, args);
+                return;
+            }
+
             final IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ");
             if (mCallsManager != null) {
                 pw.println("CallsManager: ");
diff --git a/src/com/android/server/telecom/Timeouts.java b/src/com/android/server/telecom/Timeouts.java
index 08d7410..7be59c3 100644
--- a/src/com/android/server/telecom/Timeouts.java
+++ b/src/com/android/server/telecom/Timeouts.java
@@ -32,6 +32,10 @@
         public long getCallScreeningTimeoutMillis(ContentResolver cr) {
             return Timeouts.getCallScreeningTimeoutMillis(cr);
         }
+
+        public long getCallRemoveUnbindInCallServicesDelay(ContentResolver cr) {
+            return Timeouts.getCallRemoveUnbindInCallServicesDelay(cr);
+        }
     }
 
     /** A prefix to use for all keys so to not clobber the global namespace. */
@@ -53,22 +57,13 @@
     }
 
     /**
-     * Returns the longest period, in milliseconds, to wait for the query for direct-to-voicemail
-     * to complete. If the query goes beyond this timeout, the incoming call screen is shown to the
-     * user.
-     */
-    public static long getDirectToVoicemailMillis(ContentResolver contentResolver) {
-        return get(contentResolver, "direct_to_voicemail_ms", 500L);
-    }
-
-    /**
      * Returns the amount of time to wait before disconnecting a call that was canceled via
      * NEW_OUTGOING_CALL broadcast. This timeout allows apps which repost the call using a gateway
      * to reuse the existing call, preventing the call from causing a start->end->start jank in the
      * in-call UI.
      */
     public static long getNewOutgoingCallCancelMillis(ContentResolver contentResolver) {
-        return get(contentResolver, "new_outgoing_call_cancel_ms", 400L);
+        return get(contentResolver, "new_outgoing_call_cancel_ms", 100000L);
     }
 
     /**
@@ -141,11 +136,4 @@
     public static long getCallScreeningTimeoutMillis(ContentResolver contentResolver) {
         return get(contentResolver, "call_screening_timeout", 5000L /* 5 seconds */);
     }
-
-    /**
-     * Returns the amount of time to wait for the block checker to allow or disallow a call.
-     */
-    public static long getBlockCheckTimeoutMillis(ContentResolver contentResolver) {
-        return get(contentResolver, "block_check_timeout_millis", 500L);
-    }
 }
diff --git a/src/com/android/server/telecom/VideoProviderProxy.java b/src/com/android/server/telecom/VideoProviderProxy.java
index a4dfce2..3722b59 100644
--- a/src/com/android/server/telecom/VideoProviderProxy.java
+++ b/src/com/android/server/telecom/VideoProviderProxy.java
@@ -187,7 +187,9 @@
                 if (status == Connection.VideoProvider.SESSION_MODIFY_REQUEST_SUCCESS) {
                     mCall.getAnalytics().addVideoEvent(
                             Analytics.RECEIVE_REMOTE_SESSION_MODIFY_RESPONSE,
-                            requestProfile.getVideoState());
+                            responseProfile == null ?
+                                    VideoProfile.STATE_AUDIO_ONLY :
+                                    responseProfile.getVideoState());
                 }
                 VideoProviderProxy.this.receiveSessionModifyResponse(status, requestProfile,
                         responseProfile);
@@ -203,7 +205,8 @@
         @Override
         public void handleCallSessionEvent(int event) {
             synchronized (mLock) {
-                logFromVideoProvider("handleCallSessionEvent: " + event);
+                logFromVideoProvider("handleCallSessionEvent: " +
+                        Connection.VideoProvider.sessionEventToString(event));
                 VideoProviderProxy.this.handleCallSessionEvent(event);
             }
         }
@@ -475,7 +478,7 @@
      * @param toLog The message to log.
      */
     private void logFromInCall(String toLog) {
-        Log.v(this, "IC->VP: " + toLog);
+        Log.i(this, "IC->VP (callId=" + (mCall == null ? "?" : mCall.getId()) + "): " + toLog);
     }
 
     /**
@@ -485,6 +488,6 @@
      * @param toLog The message to log.
      */
     private void logFromVideoProvider(String toLog) {
-        Log.v(this, "VP->IC: " + toLog);
+        Log.i(this, "VP->IC (callId=" + (mCall == null ? "?" : mCall.getId()) + "): " + toLog);
     }
 }
diff --git a/tests/Android.mk b/tests/Android.mk
index e639b55..8a8113b 100644
--- a/tests/Android.mk
+++ b/tests/Android.mk
@@ -21,11 +21,17 @@
         android-ex-camera2 \
         android-support-v4 \
         guava \
-        mockito-target
+        mockito-target \
+        platform-test-annotations
 
 LOCAL_SRC_FILES := \
         $(call all-java-files-under, src) \
-        $(call all-java-files-under, ../src)
+        $(call all-java-files-under, ../src) \
+        $(call all-proto-files-under, ../proto)
+
+LOCAL_PROTOC_OPTIMIZE_TYPE := nano
+LOCAL_PROTOC_FLAGS := --proto_path=$(LOCAL_PATH)/../proto/
+LOCAL_PROTO_JAVA_OUTPUT_PARAMS := optional_field_style=accessors
 
 LOCAL_RESOURCE_DIR := \
     $(LOCAL_PATH)/res \
@@ -46,6 +52,9 @@
 
 LOCAL_MODULE_TAGS := tests
 
+LOCAL_JACK_COVERAGE_INCLUDE_FILTER := com.android.server.telecom.*
+LOCAL_JACK_COVERAGE_EXCLUDE_FILTER := com.android.server.telecom.tests.*
+
 include frameworks/base/packages/SettingsLib/common.mk
 
 include $(BUILD_PACKAGE)
diff --git a/tests/src/com/android/server/telecom/tests/AnalyticsTests.java b/tests/src/com/android/server/telecom/tests/AnalyticsTests.java
index 241f66d..81d4aa5 100644
--- a/tests/src/com/android/server/telecom/tests/AnalyticsTests.java
+++ b/tests/src/com/android/server/telecom/tests/AnalyticsTests.java
@@ -17,22 +17,36 @@
 package com.android.server.telecom.tests;
 
 import android.content.Context;
+import android.telecom.Connection;
 import android.telecom.DisconnectCause;
+import android.telecom.InCallService;
 import android.telecom.ParcelableCallAnalytics;
 import android.telecom.TelecomAnalytics;
 import android.telecom.TelecomManager;
+import android.telecom.VideoCallImpl;
+import android.telecom.VideoProfile;
 import android.test.suitebuilder.annotation.MediumTest;
 import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Base64;
 
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.telecom.Analytics;
 import com.android.server.telecom.Log;
+import com.android.server.telecom.TelecomLogClass;
 
+import java.io.PrintWriter;
 import java.io.StringWriter;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
 
 public class AnalyticsTests extends TelecomSystemTest {
     @MediumTest
@@ -41,6 +55,7 @@
                 "650-555-1212",
                 mPhoneAccountA0.getAccountHandle(),
                 mConnectionServiceFixtureA);
+
         Map<String, Analytics.CallInfoImpl> analyticsMap = Analytics.cloneData();
 
         assertTrue(analyticsMap.containsKey(testCall.mCallId));
@@ -110,6 +125,7 @@
         Set<Integer> capturedEvents = new HashSet<>();
         for (ParcelableCallAnalytics.AnalyticsEvent e : analyticsEvents) {
             capturedEvents.add(e.getEventName());
+            assertIsRoundedToOneSigFig(e.getTimeSinceLastEvent());
         }
         assertTrue(capturedEvents.contains(ParcelableCallAnalytics.AnalyticsEvent.SET_ACTIVE));
         assertTrue(capturedEvents.contains(
@@ -168,6 +184,54 @@
         assertEquals(DisconnectCause.REMOTE, callAnalytics2.callTerminationReason.getCode());
     }
 
+    @MediumTest
+    public void testAnalyticsVideo() throws Exception {
+        Analytics.reset();
+        IdPair callIds = startAndMakeActiveOutgoingCall(
+                "650-555-1212",
+                mPhoneAccountA0.getAccountHandle(),
+                mConnectionServiceFixtureA);
+
+        CountDownLatch counter = new CountDownLatch(1);
+        InCallService.VideoCall.Callback callback = mock(InCallService.VideoCall.Callback.class);
+
+        doAnswer(invocation -> {
+            counter.countDown();
+            return null;
+        }).when(callback)
+                .onSessionModifyResponseReceived(anyInt(), any(VideoProfile.class),
+                        any(VideoProfile.class));
+
+        mConnectionServiceFixtureA.sendSetVideoProvider(
+                mConnectionServiceFixtureA.mLatestConnectionId);
+        InCallService.VideoCall videoCall =
+                mInCallServiceFixtureX.getCall(callIds.mCallId).getVideoCallImpl();
+        videoCall.registerCallback(callback);
+        ((VideoCallImpl) videoCall).setVideoState(VideoProfile.STATE_BIDIRECTIONAL);
+
+        videoCall.sendSessionModifyRequest(new VideoProfile(VideoProfile.STATE_RX_ENABLED));
+        counter.await(10000, TimeUnit.MILLISECONDS);
+
+        StringWriter sw = new StringWriter();
+        PrintWriter pw = new PrintWriter(sw);
+        Analytics.dumpToEncodedProto(pw, new String[]{});
+        TelecomLogClass.TelecomLog analyticsProto =
+                TelecomLogClass.TelecomLog.parseFrom(Base64.decode(sw.toString(), Base64.DEFAULT));
+
+        assertEquals(1, analyticsProto.callLogs.length);
+        TelecomLogClass.VideoEvent[] videoEvents = analyticsProto.callLogs[0].videoEvents;
+        assertEquals(2, videoEvents.length);
+
+        assertEquals(Analytics.SEND_LOCAL_SESSION_MODIFY_REQUEST, videoEvents[0].getEventName());
+        assertEquals(VideoProfile.STATE_RX_ENABLED, videoEvents[0].getVideoState());
+        assertEquals(-1, videoEvents[0].getTimeSinceLastEventMillis());
+
+        assertEquals(Analytics.RECEIVE_REMOTE_SESSION_MODIFY_RESPONSE,
+                videoEvents[1].getEventName());
+        assertEquals(VideoProfile.STATE_RX_ENABLED, videoEvents[1].getVideoState());
+        assertIsRoundedToOneSigFig(videoEvents[1].getTimeSinceLastEventMillis());
+    }
+
     @SmallTest
     public void testAnalyticsRounding() {
         long[] testVals = {0, -1, -10, -100, -57836, 1, 10, 100, 1000, 458457};
@@ -188,6 +252,92 @@
         sessions.stream()
                 .filter(s -> Log.Sessions.CSW_ADD_CONFERENCE_CALL.equals(
                         Analytics.sSessionIdToLogSession.get(s.getKey())))
-                .forEach(s -> assertTrue(s.getTime() > minTime));
+                .forEach(s -> assertTrue(s.getTime() >= minTime));
+    }
+
+    @MediumTest
+    public void testAnalyticsDumpToProto() throws Exception {
+        Analytics.reset();
+        IdPair testCall = startAndMakeActiveIncomingCall(
+                "650-555-1212",
+                mPhoneAccountA0.getAccountHandle(),
+                mConnectionServiceFixtureA);
+
+        mConnectionServiceFixtureA.
+                sendSetDisconnected(testCall.mConnectionId, DisconnectCause.ERROR);
+        Analytics.CallInfoImpl expectedAnalytics = Analytics.cloneData().get(testCall.mCallId);
+
+        StringWriter sw = new StringWriter();
+        PrintWriter pw = new PrintWriter(sw);
+        Analytics.dumpToEncodedProto(pw, new String[]{});
+        TelecomLogClass.TelecomLog analyticsProto =
+                TelecomLogClass.TelecomLog.parseFrom(Base64.decode(sw.toString(), Base64.DEFAULT));
+
+        assertEquals(1, analyticsProto.callLogs.length);
+        TelecomLogClass.CallLog callLog = analyticsProto.callLogs[0];
+
+        assertTrue(Math.abs(expectedAnalytics.startTime - callLog.getStartTime5Min()) <
+                ParcelableCallAnalytics.MILLIS_IN_5_MINUTES);
+        assertEquals(0, callLog.getStartTime5Min() % ParcelableCallAnalytics.MILLIS_IN_5_MINUTES);
+        assertTrue(Math.abs((expectedAnalytics.endTime - expectedAnalytics.startTime) -
+                callLog.getCallDurationMillis()) < ParcelableCallAnalytics.MILLIS_IN_1_SECOND);
+        assertEquals(0,
+                callLog.getCallDurationMillis() % ParcelableCallAnalytics.MILLIS_IN_1_SECOND);
+
+        assertEquals(expectedAnalytics.callDirection, callLog.getType());
+        assertEquals(expectedAnalytics.isAdditionalCall, callLog.getIsAdditionalCall());
+        assertEquals(expectedAnalytics.isInterrupted, callLog.getIsInterrupted());
+        assertEquals(expectedAnalytics.callTechnologies, callLog.getCallTechnologies());
+        assertEquals(expectedAnalytics.callTerminationReason.getCode(),
+                callLog.getCallTerminationCode());
+        assertEquals(expectedAnalytics.connectionService, callLog.connectionService[0]);
+        TelecomLogClass.Event[] analyticsEvents = callLog.callEvents;
+        Set<Integer> capturedEvents = new HashSet<>();
+        for (TelecomLogClass.Event e : analyticsEvents) {
+            capturedEvents.add(e.getEventName());
+            assertIsRoundedToOneSigFig(e.getTimeSinceLastEventMillis());
+        }
+        assertTrue(capturedEvents.contains(ParcelableCallAnalytics.AnalyticsEvent.SET_ACTIVE));
+        assertTrue(capturedEvents.contains(
+                ParcelableCallAnalytics.AnalyticsEvent.FILTERING_INITIATED));
+    }
+
+    @MediumTest
+    public void testAnalyticsConnectionProperties() throws Exception {
+        Analytics.reset();
+        IdPair testCall = startAndMakeActiveIncomingCall(
+                "650-555-1212",
+                mPhoneAccountA0.getAccountHandle(),
+                mConnectionServiceFixtureA);
+
+        int properties1 = Connection.PROPERTY_IS_DOWNGRADED_CONFERENCE
+                | Connection.PROPERTY_WIFI
+                | Connection.PROPERTY_EMERGENCY_CALLBACK_MODE;
+        int properties2 = Connection.PROPERTY_HIGH_DEF_AUDIO
+                | Connection.PROPERTY_WIFI;
+        int expectedProperties = properties1 | properties2;
+
+        mConnectionServiceFixtureA.mConnectionById.get(testCall.mConnectionId).properties =
+                properties1;
+        mConnectionServiceFixtureA.sendSetConnectionProperties(testCall.mConnectionId);
+        mConnectionServiceFixtureA.mConnectionById.get(testCall.mConnectionId).properties =
+                properties2;
+        mConnectionServiceFixtureA.sendSetConnectionProperties(testCall.mConnectionId);
+
+        mConnectionServiceFixtureA.
+                sendSetDisconnected(testCall.mConnectionId, DisconnectCause.ERROR);
+
+        StringWriter sw = new StringWriter();
+        PrintWriter pw = new PrintWriter(sw);
+        Analytics.dumpToEncodedProto(pw, new String[]{});
+        TelecomLogClass.TelecomLog analyticsProto =
+                TelecomLogClass.TelecomLog.parseFrom(Base64.decode(sw.toString(), Base64.DEFAULT));
+
+        assertEquals(expectedProperties,
+                analyticsProto.callLogs[0].getConnectionProperties() & expectedProperties);
+    }
+
+    private void assertIsRoundedToOneSigFig(long x) {
+        assertEquals(x, Analytics.roundToOneSigFig(x));
     }
 }
diff --git a/tests/src/com/android/server/telecom/tests/BasicCallTests.java b/tests/src/com/android/server/telecom/tests/BasicCallTests.java
index ceec91b..6688ca0 100644
--- a/tests/src/com/android/server/telecom/tests/BasicCallTests.java
+++ b/tests/src/com/android/server/telecom/tests/BasicCallTests.java
@@ -278,6 +278,7 @@
                 .createConnection(any(PhoneAccountHandle.class), anyString(),
                         any(ConnectionRequest.class), eq(true), eq(false));
 
+        waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
         assertEquals(1, mCallerInfoAsyncQueryFactoryFixture.mRequests.size());
         for (CallerInfoAsyncQueryFactoryFixture.Request request :
                 mCallerInfoAsyncQueryFactoryFixture.mRequests) {
@@ -314,10 +315,12 @@
         mTelecomSystem.getTelecomServiceImpl().getBinder()
                 .addNewIncomingCall(mPhoneAccountA0.getAccountHandle(), extras);
 
+        waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
         verify(mConnectionServiceFixtureA.getTestDouble())
                 .createConnection(any(PhoneAccountHandle.class), anyString(),
                         any(ConnectionRequest.class), eq(true), eq(false));
 
+        waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
         // Never reply to the caller info lookup.
         assertEquals(1, mCallerInfoAsyncQueryFactoryFixture.mRequests.size());
 
@@ -357,10 +360,12 @@
         mTelecomSystem.getTelecomServiceImpl().getBinder()
                 .addNewIncomingCall(mPhoneAccountA0.getAccountHandle(), extras);
 
+        waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
         verify(mConnectionServiceFixtureA.getTestDouble())
                 .createConnection(any(PhoneAccountHandle.class), anyString(),
                         any(ConnectionRequest.class), eq(true), eq(false));
 
+        waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
         assertEquals(1, mCallerInfoAsyncQueryFactoryFixture.mRequests.size());
         for (CallerInfoAsyncQueryFactoryFixture.Request request :
                 mCallerInfoAsyncQueryFactoryFixture.mRequests) {
diff --git a/tests/src/com/android/server/telecom/tests/CallAudioModeStateMachineTest.java b/tests/src/com/android/server/telecom/tests/CallAudioModeStateMachineTest.java
index f9502dd..38ea10e 100644
--- a/tests/src/com/android/server/telecom/tests/CallAudioModeStateMachineTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallAudioModeStateMachineTest.java
@@ -18,6 +18,7 @@
 
 import android.media.AudioManager;
 import android.test.suitebuilder.annotation.LargeTest;
+import android.test.suitebuilder.annotation.SmallTest;
 
 import com.android.server.telecom.CallAudioManager;
 import com.android.server.telecom.CallAudioModeStateMachine;
@@ -32,6 +33,7 @@
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 public class CallAudioModeStateMachineTest extends StateMachineTestBase<CallAudioModeStateMachine> {
     private static class ModeTestParameters extends TestParameters {
@@ -100,6 +102,37 @@
         parametrizedTestStateMachine(testCases);
     }
 
+    @SmallTest
+    public void testNoFocusWhenRingerSilenced() throws Throwable {
+        CallAudioModeStateMachine sm = new CallAudioModeStateMachine(mAudioManager);
+        sm.setCallAudioManager(mCallAudioManager);
+        sm.sendMessage(CallAudioModeStateMachine.ABANDON_FOCUS_FOR_TESTING);
+        waitForStateMachineActionCompletion(sm, CallAudioModeStateMachine.RUN_RUNNABLE);
+
+        resetMocks();
+        when(mCallAudioManager.startRinging()).thenReturn(false);
+
+        sm.sendMessage(CallAudioModeStateMachine.NEW_RINGING_CALL,
+                new CallAudioModeStateMachine.MessageArgs(
+                        false, // hasActiveOrDialingCalls
+                        true, // hasRingingCalls
+                        false, // hasHoldingCalls
+                        false, // isTonePlaying
+                        false, // foregroundCallIsVoip
+                        null // session
+                ));
+        waitForStateMachineActionCompletion(sm, CallAudioModeStateMachine.RUN_RUNNABLE);
+
+        assertEquals(CallAudioModeStateMachine.RING_STATE_NAME, sm.getCurrentStateName());
+
+        verify(mAudioManager, never()).requestAudioFocusForCall(anyInt(), anyInt());
+        verify(mAudioManager, never()).setMode(anyInt());
+
+        verify(mCallAudioManager, never()).stopRinging();
+
+        verify(mCallAudioManager).stopCallWaiting();
+    }
+
     private List<ModeTestParameters> generateTestCases() {
         List<ModeTestParameters> result = new ArrayList<>();
         result.add(new ModeTestParameters(
@@ -516,6 +549,7 @@
         waitForStateMachineActionCompletion(sm, CallAudioModeStateMachine.RUN_RUNNABLE);
 
         resetMocks();
+        when(mCallAudioManager.startRinging()).thenReturn(true);
 
         sm.sendMessage(params.messageType, params.externalState);
         waitForStateMachineActionCompletion(sm, CallAudioModeStateMachine.RUN_RUNNABLE);
diff --git a/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java b/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java
index c4526e4..4e5fe69 100644
--- a/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java
@@ -32,6 +32,7 @@
 import com.android.server.telecom.ConnectionServiceWrapper;
 import com.android.server.telecom.CallAudioManager;
 import com.android.server.telecom.StatusBarNotifier;
+import com.android.server.telecom.TelecomSystem;
 import com.android.server.telecom.WiredHeadsetManager;
 
 import org.mockito.ArgumentCaptor;
@@ -50,6 +51,7 @@
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -116,6 +118,7 @@
     private CallAudioManager.AudioServiceFactory mAudioServiceFactory;
     private static final int TEST_TIMEOUT = 500;
     private AudioManager mockAudioManager;
+    private final TelecomSystem.SyncRoot mLock = new TelecomSystem.SyncRoot() { };
 
     @Override
     public void setUp() throws Exception {
@@ -132,6 +135,7 @@
         };
 
         when(mockCallsManager.getForegroundCall()).thenReturn(fakeCall);
+        when(mockCallsManager.getLock()).thenReturn(mLock);
         when(fakeCall.getConnectionService()).thenReturn(mockConnectionServiceWrapper);
         when(fakeCall.isAlive()).thenReturn(true);
         doNothing().when(mockConnectionServiceWrapper).onCallAudioStateChanged(any(Call.class),
@@ -177,7 +181,7 @@
         stateMachine.initialize(initState);
 
         stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.SWITCH_FOCUS,
-                CallAudioRouteStateMachine.HAS_FOCUS);
+                CallAudioRouteStateMachine.ACTIVE_FOCUS);
         stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.CONNECT_WIRED_HEADSET);
         CallAudioState expectedMiddleState = new CallAudioState(false,
                 CallAudioState.ROUTE_WIRED_HEADSET,
@@ -209,7 +213,7 @@
         stateMachine.initialize(initState);
 
         stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.SWITCH_FOCUS,
-                CallAudioRouteStateMachine.HAS_FOCUS);
+                CallAudioRouteStateMachine.ACTIVE_FOCUS);
         stateMachine.sendMessageWithSessionInfo(
                 CallAudioRouteStateMachine.USER_SWITCH_BASELINE_ROUTE);
         CallAudioState expectedEndState = new CallAudioState(false,
@@ -230,6 +234,72 @@
         assertEquals(expectedEndState, stateMachine.getCurrentCallAudioState());
     }
 
+    @MediumTest
+    public void testBluetoothRinging() {
+        CallAudioRouteStateMachine stateMachine = new CallAudioRouteStateMachine(
+                mContext,
+                mockCallsManager,
+                mockBluetoothManager,
+                mockWiredHeadsetManager,
+                mockStatusBarNotifier,
+                mAudioServiceFactory,
+                true);
+
+        when(mockBluetoothManager.isBluetoothAudioConnectedOrPending()).thenReturn(false);
+        when(mockBluetoothManager.isBluetoothAvailable()).thenReturn(true);
+        when(mockAudioManager.isSpeakerphoneOn()).thenReturn(false);
+        CallAudioState initState = new CallAudioState(false, CallAudioState.ROUTE_BLUETOOTH,
+                CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH);
+        stateMachine.initialize(initState);
+
+        stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.SWITCH_FOCUS,
+                CallAudioRouteStateMachine.RINGING_FOCUS);
+        waitForStateMachineActionCompletion(stateMachine, CallAudioRouteStateMachine.RUN_RUNNABLE);
+
+        verify(mockBluetoothManager, never()).connectBluetoothAudio();
+
+        stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.SWITCH_FOCUS,
+                CallAudioRouteStateMachine.ACTIVE_FOCUS);
+        waitForStateMachineActionCompletion(stateMachine, CallAudioRouteStateMachine.RUN_RUNNABLE);
+        verify(mockBluetoothManager, times(1)).connectBluetoothAudio();
+    }
+
+    @MediumTest
+    public void testConnectBluetoothDuringRinging() {
+        CallAudioRouteStateMachine stateMachine = new CallAudioRouteStateMachine(
+                mContext,
+                mockCallsManager,
+                mockBluetoothManager,
+                mockWiredHeadsetManager,
+                mockStatusBarNotifier,
+                mAudioServiceFactory,
+                true);
+
+        when(mockBluetoothManager.isBluetoothAudioConnectedOrPending()).thenReturn(false);
+        when(mockBluetoothManager.isBluetoothAvailable()).thenReturn(false);
+        when(mockAudioManager.isSpeakerphoneOn()).thenReturn(false);
+        CallAudioState initState = new CallAudioState(false, CallAudioState.ROUTE_EARPIECE,
+                CallAudioState.ROUTE_EARPIECE);
+        stateMachine.initialize(initState);
+
+        stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.SWITCH_FOCUS,
+                CallAudioRouteStateMachine.RINGING_FOCUS);
+        when(mockBluetoothManager.isBluetoothAvailable()).thenReturn(true);
+        stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.CONNECT_BLUETOOTH);
+        waitForStateMachineActionCompletion(stateMachine, CallAudioRouteStateMachine.RUN_RUNNABLE);
+
+        verify(mockBluetoothManager, never()).connectBluetoothAudio();
+        CallAudioState expectedEndState = new CallAudioState(false,
+                CallAudioState.ROUTE_BLUETOOTH,
+                CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH);
+        verifyNewSystemCallAudioState(initState, expectedEndState);
+
+        stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.SWITCH_FOCUS,
+                CallAudioRouteStateMachine.ACTIVE_FOCUS);
+        waitForStateMachineActionCompletion(stateMachine, CallAudioRouteStateMachine.RUN_RUNNABLE);
+        verify(mockBluetoothManager, times(1)).connectBluetoothAudio();
+    }
+
     @SmallTest
     public void testInitializationWithEarpieceNoHeadsetNoBluetooth() {
         CallAudioState expectedState = new CallAudioState(false, CallAudioState.ROUTE_EARPIECE,
@@ -677,7 +747,7 @@
         stateMachine.initialize(initState);
         // Make the state machine have focus so that we actually do something
         stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.SWITCH_FOCUS,
-                CallAudioRouteStateMachine.HAS_FOCUS);
+                CallAudioRouteStateMachine.ACTIVE_FOCUS);
         stateMachine.sendMessageWithSessionInfo(params.action);
 
         waitForStateMachineActionCompletion(stateMachine, CallAudioRouteStateMachine.RUN_RUNNABLE);
@@ -789,6 +859,7 @@
         reset(mockAudioManager, mockBluetoothManager, mockCallsManager,
                 mockConnectionServiceWrapper);
         when(mockCallsManager.getForegroundCall()).thenReturn(fakeCall);
+        when(mockCallsManager.getLock()).thenReturn(mLock);
         doNothing().when(mockConnectionServiceWrapper).onCallAudioStateChanged(any(Call.class),
                 any(CallAudioState.class));
     }
diff --git a/tests/src/com/android/server/telecom/tests/CallLogManagerTest.java b/tests/src/com/android/server/telecom/tests/CallLogManagerTest.java
index b8dcb68..e9db543 100644
--- a/tests/src/com/android/server/telecom/tests/CallLogManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallLogManagerTest.java
@@ -28,14 +28,17 @@
 import android.location.CountryListener;
 import android.net.Uri;
 import android.os.Looper;
+import android.os.PersistableBundle;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.platform.test.annotations.Postsubmit;
 import android.provider.CallLog;
 import android.provider.CallLog.Calls;
 import android.telecom.DisconnectCause;
 import android.telecom.PhoneAccount;
 import android.telecom.PhoneAccountHandle;
 import android.telecom.VideoProfile;
+import android.telephony.CarrierConfigManager;
 import android.telephony.PhoneNumberUtils;
 import android.test.suitebuilder.annotation.MediumTest;
 import android.test.suitebuilder.annotation.SmallTest;
@@ -53,6 +56,7 @@
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.timeout;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -195,8 +199,13 @@
     public void testDontLogCallsFromEmergencyAccount() {
         when(mMockPhoneAccountRegistrar.getPhoneAccountUnchecked(any(PhoneAccountHandle.class)))
                 .thenReturn(makeFakePhoneAccount(EMERGENCY_ACCT_HANDLE, 0));
-        mComponentContextFixture.putBooleanResource(R.bool.allow_emergency_numbers_in_call_log,
-                false);
+        CarrierConfigManager mockCarrierConfigManager =
+                (CarrierConfigManager) mComponentContextFixture.getTestDouble()
+                        .getApplicationContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
+        PersistableBundle bundle = new PersistableBundle();
+        bundle.putBoolean(CarrierConfigManager.KEY_ALLOW_EMERGENCY_NUMBERS_IN_CALL_LOG_BOOL, false);
+        when(mockCarrierConfigManager.getConfig()).thenReturn(bundle);
+
         Call fakeCall = makeFakeCall(
                 DisconnectCause.OTHER, // disconnectCauseCode
                 false, // isConference
@@ -285,7 +294,9 @@
         ContentValues insertedValues = verifyInsertionWithCapture(CURRENT_USER_ID);
         assertEquals(insertedValues.getAsInteger(CallLog.Calls.TYPE),
                 Integer.valueOf(CallLog.Calls.MISSED_TYPE));
-        verify(mMissedCallNotifier).showMissedCallNotification(fakeMissedCall);
+        // Timeout needed because showMissedCallNotification is called from onPostExecute.
+        verify(mMissedCallNotifier, timeout(TEST_TIMEOUT_MILLIS))
+                .showMissedCallNotification(fakeMissedCall);
     }
 
     @MediumTest
@@ -413,6 +424,7 @@
     }
 
     @MediumTest
+    @Postsubmit
     public void testLogCallDirectionOutgoingWithMultiUserCapability() {
         when(mMockPhoneAccountRegistrar.getPhoneAccountUnchecked(any(PhoneAccountHandle.class)))
                 .thenReturn(makeFakePhoneAccount(mOtherUserAccountHandle,
@@ -506,6 +518,7 @@
     }
 
     @MediumTest
+    @Postsubmit
     public void testLogCallDirectionOutgoingFromManagedProfile() {
         when(mMockPhoneAccountRegistrar.getPhoneAccountUnchecked(any(PhoneAccountHandle.class)))
                 .thenReturn(makeFakePhoneAccount(mManagedProfileAccountHandle, 0));
@@ -656,10 +669,13 @@
 
     private void verifyNoInsertion() {
         try {
-            verify(mContentProvider, timeout(TEST_TIMEOUT_MILLIS).never()).insert(any(String.class),
+            Thread.sleep(TEST_TIMEOUT_MILLIS);
+            verify(mContentProvider, never()).insert(any(String.class),
                     any(Uri.class), any(ContentValues.class));
         } catch (android.os.RemoteException e) {
             fail("Remote exception occurred during test execution");
+        } catch (InterruptedException e) {
+            e.printStackTrace();
         }
     }
 
@@ -667,10 +683,13 @@
     private void verifyNoInsertionInUser(int userId) {
         try {
             Uri uri = ContentProvider.maybeAddUserId(CallLog.Calls.CONTENT_URI, userId);
-            verify(getContentProviderForUser(userId), timeout(TEST_TIMEOUT_MILLIS).never())
+            Thread.sleep(TEST_TIMEOUT_MILLIS);
+            verify(getContentProviderForUser(userId), never())
                     .insert(any(String.class), eq(uri), any(ContentValues.class));
         } catch (android.os.RemoteException e) {
             fail("Remote exception occurred during test execution");
+        } catch (InterruptedException e) {
+            e.printStackTrace();
         }
     }
 
@@ -719,6 +738,8 @@
         when(fakeCall.getViaNumber()).thenReturn(viaNumber);
         when(fakeCall.getInitiatingUser()).thenReturn(initiatingUser);
         when(fakeCall.getCallDataUsage()).thenReturn(callDataUsage);
+        when(fakeCall.isEmergencyCall()).thenReturn(
+                phoneAccountHandle.equals(EMERGENCY_ACCT_HANDLE));
         return fakeCall;
     }
 
diff --git a/tests/src/com/android/server/telecom/tests/ConnectionServiceFixture.java b/tests/src/com/android/server/telecom/tests/ConnectionServiceFixture.java
index 962ab10..255f3ea 100644
--- a/tests/src/com/android/server/telecom/tests/ConnectionServiceFixture.java
+++ b/tests/src/com/android/server/telecom/tests/ConnectionServiceFixture.java
@@ -434,6 +434,11 @@
         }
     }
 
+    public void sendSetConnectionProperties(String id) throws Exception {
+        for (IConnectionServiceAdapter a : mConnectionServiceAdapters) {
+            a.setConnectionProperties(id, mConnectionById.get(id).properties);
+        }
+    }
     public void sendSetIsConferenced(String id) throws Exception {
         for (IConnectionServiceAdapter a : mConnectionServiceAdapters) {
             a.setIsConferenced(id, mConnectionById.get(id).conferenceId);
diff --git a/tests/src/com/android/server/telecom/tests/InCallControllerTests.java b/tests/src/com/android/server/telecom/tests/InCallControllerTests.java
index 0173ed8..f5c0439 100644
--- a/tests/src/com/android/server/telecom/tests/InCallControllerTests.java
+++ b/tests/src/com/android/server/telecom/tests/InCallControllerTests.java
@@ -18,6 +18,7 @@
 
 import android.Manifest;
 import android.content.ComponentName;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.ServiceConnection;
@@ -29,6 +30,7 @@
 import android.os.IBinder;
 import android.os.UserHandle;
 import android.telecom.InCallService;
+import android.telecom.ParcelableCall;
 import android.telecom.PhoneAccountHandle;
 import android.telecom.TelecomManager;
 import android.test.mock.MockContext;
@@ -37,6 +39,7 @@
 
 import com.android.internal.telecom.IInCallAdapter;
 import com.android.internal.telecom.IInCallService;
+import com.android.server.telecom.Analytics;
 import com.android.server.telecom.BluetoothHeadsetProxy;
 import com.android.server.telecom.Call;
 import com.android.server.telecom.CallsManager;
@@ -46,6 +49,7 @@
 import com.android.server.telecom.SystemStateProvider;
 import com.android.server.telecom.TelecomServiceImpl.DefaultDialerManagerAdapter;
 import com.android.server.telecom.TelecomSystem;
+import com.android.server.telecom.Timeouts;
 
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
@@ -53,6 +57,7 @@
 import org.mockito.invocation.InvocationOnMock;
 import org.mockito.stubbing.Answer;
 
+import java.util.Collections;
 import java.util.LinkedList;
 
 import static org.mockito.Matchers.any;
@@ -79,6 +84,7 @@
     @Mock Resources mMockResources;
     @Mock MockContext mMockContext;
     @Mock DefaultDialerManagerAdapter mMockDefaultDialerAdapter;
+    @Mock Timeouts.Adapter mTimeoutsAdapter;
 
     private static final int CURRENT_USER_ID = 900973;
     private static final String DEF_PKG = "defpkg";
@@ -96,11 +102,12 @@
     public void setUp() throws Exception {
         super.setUp();
         MockitoAnnotations.initMocks(this);
+        when(mMockCall.getAnalytics()).thenReturn(new Analytics.CallInfo());
         doReturn(mMockResources).when(mMockContext).getResources();
         doReturn(SYS_PKG).when(mMockResources).getString(R.string.ui_default_package);
         doReturn(SYS_CLASS).when(mMockResources).getString(R.string.incall_default_class);
         mInCallController = new InCallController(mMockContext, mLock, mMockCallsManager,
-                mMockSystemStateProvider, mMockDefaultDialerAdapter);
+                mMockSystemStateProvider, mMockDefaultDialerAdapter, mTimeoutsAdapter);
     }
 
     @Override
@@ -116,7 +123,6 @@
         when(mMockCall.isIncoming()).thenReturn(true);
         when(mMockCall.isExternalCall()).thenReturn(false);
 
-        Intent queryIntent = new Intent(InCallService.SERVICE_INTERFACE);
         setupMockPackageManager(false /* default */, true /* system */, false /* external calls */);
         mInCallController.bindToServices(mMockCall);
 
@@ -278,10 +284,14 @@
         when(mMockCallsManager.getCurrentUserHandle()).thenReturn(mUserHandle);
         when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
         when(mMockCallsManager.hasEmergencyCall()).thenReturn(false);
+        when(mMockCallsManager.getCalls()).thenReturn(Collections.singletonList(mMockCall));
+        when(mMockCallsManager.getAudioState()).thenReturn(null);
+        when(mMockCallsManager.canAddCall()).thenReturn(false);
         when(mMockCall.isIncoming()).thenReturn(false);
         when(mMockCall.getTargetPhoneAccount()).thenReturn(PA_HANDLE);
         when(mMockCall.getIntentExtras()).thenReturn(callExtras);
         when(mMockCall.isExternalCall()).thenReturn(false);
+        when(mMockCall.getConferenceableCalls()).thenReturn(Collections.emptyList());
         when(mMockDefaultDialerAdapter.getDefaultDialerApplication(mMockContext, CURRENT_USER_ID))
                 .thenReturn(DEF_PKG);
         when(mMockContext.bindServiceAsUser(
@@ -386,6 +396,61 @@
         assertEquals(DEF_CLASS, bindIntent.getComponent().getClassName());
     }
 
+    /**
+     * Make sure that if a call goes away before the in-call service finishes binding and another
+     * call gets connected soon after, the new call will still be sent to the in-call service.
+     */
+    @MediumTest
+    public void testUnbindDueToCallDisconnect() throws Exception {
+        when(mMockCallsManager.getCurrentUserHandle()).thenReturn(mUserHandle);
+        when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+        when(mMockCallsManager.hasEmergencyCall()).thenReturn(false);
+        when(mMockCall.isIncoming()).thenReturn(true);
+        when(mMockCall.isExternalCall()).thenReturn(false);
+        when(mMockDefaultDialerAdapter.getDefaultDialerApplication(mMockContext, CURRENT_USER_ID))
+                .thenReturn(DEF_PKG);
+        when(mMockContext.bindServiceAsUser(
+                any(Intent.class), any(ServiceConnection.class), anyInt(), any(UserHandle.class)))
+                .thenReturn(true);
+        when(mTimeoutsAdapter.getCallRemoveUnbindInCallServicesDelay(any(ContentResolver.class)))
+                .thenReturn(500L);
+
+        when(mMockCallsManager.getCalls()).thenReturn(Collections.singletonList(mMockCall));
+        setupMockPackageManager(true /* default */, true /* system */, false /* external calls */);
+        mInCallController.bindToServices(mMockCall);
+
+        ArgumentCaptor<Intent> bindIntentCaptor = ArgumentCaptor.forClass(Intent.class);
+        ArgumentCaptor<ServiceConnection> serviceConnectionCaptor =
+                ArgumentCaptor.forClass(ServiceConnection.class);
+        verify(mMockContext, times(1)).bindServiceAsUser(
+                bindIntentCaptor.capture(),
+                serviceConnectionCaptor.capture(),
+                eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE),
+                eq(UserHandle.CURRENT));
+
+        // Pretend that the call has gone away.
+        when(mMockCallsManager.getCalls()).thenReturn(Collections.emptyList());
+        mInCallController.onCallRemoved(mMockCall);
+
+        // Start the connection, make sure we don't unbind, and make sure that we don't send
+        // anything to the in-call service yet.
+        ServiceConnection serviceConnection = serviceConnectionCaptor.getValue();
+        ComponentName defDialerComponentName = new ComponentName(DEF_PKG, DEF_CLASS);
+        IBinder mockBinder = mock(IBinder.class);
+        IInCallService mockInCallService = mock(IInCallService.class);
+        when(mockBinder.queryLocalInterface(anyString())).thenReturn(mockInCallService);
+
+        serviceConnection.onServiceConnected(defDialerComponentName, mockBinder);
+        verify(mockInCallService).setInCallAdapter(any(IInCallAdapter.class));
+        verify(mMockContext, never()).unbindService(serviceConnection);
+        verify(mockInCallService, never()).addCall(any(ParcelableCall.class));
+
+        // Now, we add in the call again and make sure that it's sent to the InCallService.
+        when(mMockCallsManager.getCalls()).thenReturn(Collections.singletonList(mMockCall));
+        mInCallController.onCallAdded(mMockCall);
+        verify(mockInCallService).addCall(any(ParcelableCall.class));
+    }
+
     private void setupMocks(boolean isExternalCall) {
         when(mMockCallsManager.getCurrentUserHandle()).thenReturn(mUserHandle);
         when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
diff --git a/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java b/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java
index 98231ce..8de54bf 100644
--- a/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java
+++ b/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java
@@ -53,7 +53,9 @@
 
 import org.mockito.ArgumentCaptor;
 import org.mockito.ArgumentMatcher;
+import org.mockito.Matchers;
 import org.mockito.Mock;
+import org.mockito.internal.matchers.VarargMatcher;
 
 import java.util.ArrayList;
 import java.util.Collection;
@@ -139,6 +141,13 @@
         }
     }
 
+    private static class IntVarArgMatcher extends ArgumentMatcher<int[]> implements VarargMatcher {
+        @Override
+        public boolean matches(Object argument) {
+            return true;
+        }
+    }
+
     private ITelecomService.Stub mTSIBinder;
     private AppOpsManager mAppOpsManager;
     private UserManager mUserManager;
@@ -859,7 +868,7 @@
     public void testEndCallWithNoForegroundCall() throws Exception {
         Call call = mock(Call.class);
         when(call.getState()).thenReturn(CallState.ACTIVE);
-        when(mFakeCallsManager.getFirstCallWithState(anyInt(), anyInt(), anyInt(), anyInt()))
+        when(mFakeCallsManager.getFirstCallWithState(argThat(new IntVarArgMatcher())))
                 .thenReturn(call);
         assertTrue(mTSIBinder.endCall());
         verify(call).disconnect();
diff --git a/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java b/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
index c8a1285..d275747 100644
--- a/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
+++ b/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
@@ -61,6 +61,7 @@
 import com.android.server.telecom.AsyncRingtonePlayer;
 import com.android.server.telecom.BluetoothPhoneServiceImpl;
 import com.android.server.telecom.CallAudioManager;
+import com.android.server.telecom.CallAudioRouteStateMachine;
 import com.android.server.telecom.CallerInfoAsyncQueryFactory;
 import com.android.server.telecom.CallsManager;
 import com.android.server.telecom.CallsManagerListenerBase;
@@ -75,8 +76,10 @@
 import com.android.server.telecom.PhoneNumberUtilsAdapterImpl;
 import com.android.server.telecom.ProximitySensorManager;
 import com.android.server.telecom.ProximitySensorManagerFactory;
+import com.android.server.telecom.Runnable;
 import com.android.server.telecom.TelecomSystem;
 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.MissedCallNotifierImpl.MissedCallNotifierImplFactory;
 
@@ -89,6 +92,8 @@
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 
 /**
  * Implements mocks and functionality required to implement telecom system tests.
@@ -197,7 +202,8 @@
                     .addSupportedUriScheme("tel")
                     .setCapabilities(
                             PhoneAccount.CAPABILITY_CALL_PROVIDER |
-                            PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)
+                                    PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION |
+                                    PhoneAccount.CAPABILITY_VIDEO_CALLING)
                     .build();
     final PhoneAccount mPhoneAccountA1 =
             PhoneAccount.builder(
@@ -342,7 +348,7 @@
 
         mTimeoutsAdapter = mock(Timeouts.Adapter.class);
         when(mTimeoutsAdapter.getCallScreeningTimeoutMillis(any(ContentResolver.class)))
-                .thenReturn(TEST_TIMEOUT / 10L);
+                .thenReturn(TEST_TIMEOUT / 5L);
 
         mTelecomSystem = new TelecomSystem(
                 mComponentContextFixture.getTestDouble(),
diff --git a/tests/src/com/android/server/telecom/tests/VideoCallTests.java b/tests/src/com/android/server/telecom/tests/VideoCallTests.java
index ca56247..0319e81 100644
--- a/tests/src/com/android/server/telecom/tests/VideoCallTests.java
+++ b/tests/src/com/android/server/telecom/tests/VideoCallTests.java
@@ -18,11 +18,14 @@
 
 import org.mockito.ArgumentCaptor;
 
+import android.os.RemoteException;
 import android.telecom.CallAudioState;
 import android.telecom.VideoProfile;
 import android.test.suitebuilder.annotation.MediumTest;
 
+import com.android.server.telecom.CallAudioModeStateMachine;
 import com.android.server.telecom.CallAudioRouteStateMachine;
+import com.android.server.telecom.Log;
 
 import java.util.List;
 
@@ -134,13 +137,25 @@
     private void verifyAudioRoute(int expectedRoute) throws Exception {
         // Capture all onCallAudioStateChanged callbacks to InCall.
         CallAudioRouteStateMachine carsm = mTelecomSystem.getCallsManager()
-                        .getCallAudioManager().getCallAudioRouteStateMachine();
+                .getCallAudioManager().getCallAudioRouteStateMachine();
+        CallAudioModeStateMachine camsm = mTelecomSystem.getCallsManager()
+                .getCallAudioManager().getCallAudioModeStateMachine();
+        waitForHandlerAction(camsm.getHandler(), TEST_TIMEOUT);
+        final boolean[] success = {true};
+        carsm.sendMessage(CallAudioRouteStateMachine.RUN_RUNNABLE, (Runnable) () -> {
+            ArgumentCaptor<CallAudioState> callAudioStateArgumentCaptor = ArgumentCaptor.forClass(
+                    CallAudioState.class);
+            try {
+                verify(mInCallServiceFixtureX.getTestDouble(), atLeastOnce())
+                        .onCallAudioStateChanged(callAudioStateArgumentCaptor.capture());
+            } catch (RemoteException e) {
+                fail("Remote exception in InCallServiceFixture");
+            }
+            List<CallAudioState> changes = callAudioStateArgumentCaptor.getAllValues();
+            assertEquals(expectedRoute, changes.get(changes.size() - 1).getRoute());
+            success[0] = true;
+        });
         waitForHandlerAction(carsm.getHandler(), TEST_TIMEOUT);
-        ArgumentCaptor<CallAudioState> callAudioStateArgumentCaptor = ArgumentCaptor.forClass(
-                CallAudioState.class);
-        verify(mInCallServiceFixtureX.getTestDouble(), atLeastOnce()).onCallAudioStateChanged(
-                callAudioStateArgumentCaptor.capture());
-        List<CallAudioState> changes = callAudioStateArgumentCaptor.getAllValues();
-        assertEquals(expectedRoute, changes.get(changes.size() - 1).getRoute());
+        assertTrue(success[0]);
     }
 }
diff --git a/tests/src/com/android/server/telecom/tests/VideoProviderTest.java b/tests/src/com/android/server/telecom/tests/VideoProviderTest.java
index 92f876b..993679e 100644
--- a/tests/src/com/android/server/telecom/tests/VideoProviderTest.java
+++ b/tests/src/com/android/server/telecom/tests/VideoProviderTest.java
@@ -113,6 +113,7 @@
 
         mConnectionInfo = mConnectionServiceFixtureA.mConnectionById.get(mCallIds.mConnectionId);
         mVerificationLock = new CountDownLatch(1);
+        waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
     }
 
     @Override