Merge "Skip call filtering when in ECB mode" into nyc-mr1-dev
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..f0b3d02
--- /dev/null
+++ b/proto/telecom.proto
@@ -0,0 +1,256 @@
+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;
+}
+
+// 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.
+ // https://googleplex-android-review.git.corp.google.com/#/c/816516/6/src/com/android/server/telecom/Analytics.java
+ 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;
+}
diff --git a/src/com/android/server/telecom/Analytics.java b/src/com/android/server/telecom/Analytics.java
index 50ff14d..0ad130b 100644
--- a/src/com/android/server/telecom/Analytics.java
+++ b/src/com/android/server/telecom/Analytics.java
@@ -19,11 +19,14 @@
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 +42,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);
@@ -200,7 +206,7 @@
public Log.CallEventRecord callEvents;
public boolean isVideo = false;
- public List<ParcelableCallAnalytics.VideoEvent> videoEvents;
+ public List<TelecomLogClass.VideoEvent> videoEvents;
private long mTimeOfLastVideoEvent = -1;
CallInfoImpl(String callId, int callDirection) {
@@ -311,8 +317,10 @@
}
mTimeOfLastVideoEvent = currentTime;
- videoEvents.add(new ParcelableCallAnalytics.VideoEvent(
- eventId, timeSinceLastEvent, videoState));
+ videoEvents.add(new TelecomLogClass.VideoEvent()
+ .setEventName(eventId)
+ .setTimeSinceLastEventMillis(timeSinceLastEvent)
+ .setVideoState(videoState));
}
@Override
@@ -331,40 +339,80 @@
}
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);
+
+ 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()]);
return result;
}
@@ -463,6 +511,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 +591,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/BluetoothManager.java b/src/com/android/server/telecom/BluetoothManager.java
index 3819879..d31c69d 100644
--- a/src/com/android/server/telecom/BluetoothManager.java
+++ b/src/com/android/server/telecom/BluetoothManager.java
@@ -121,7 +121,7 @@
private BluetoothHeadsetProxy mBluetoothHeadset;
private long mBluetoothConnectionRequestTime;
- private final Runnable mBluetoothConnectionTimeout = new Runnable("BM.cBA") {
+ private final Runnable mBluetoothConnectionTimeout = new Runnable("BM.cBA", null /*lock*/) {
@Override
public void loggedRun() {
if (!isBluetoothAudioConnected()) {
@@ -132,7 +132,7 @@
}
};
- private final Runnable mRetryConnectAudio = new Runnable("BM.rCA") {
+ private final Runnable mRetryConnectAudio = new Runnable("BM.rCA", null /*lock*/) {
@Override
public void loggedRun() {
Log.i(this, "Retrying connecting to bluetooth audio.");
diff --git a/src/com/android/server/telecom/CallAudioManager.java b/src/com/android/server/telecom/CallAudioManager.java
index 53cd349..b11a3e9 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);
}
@@ -732,6 +732,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..c4dbab1 100644
--- a/src/com/android/server/telecom/CallAudioModeStateMachine.java
+++ b/src/com/android/server/telecom/CallAudioModeStateMachine.java
@@ -213,7 +213,7 @@
mCallAudioManager.stopCallWaiting();
mCallAudioManager.startRinging();
- mCallAudioManager.setCallAudioRouteFocusState(CallAudioRouteStateMachine.HAS_FOCUS);
+ mCallAudioManager.setCallAudioRouteFocusState(CallAudioRouteStateMachine.RINGING_FOCUS);
}
@Override
@@ -288,7 +288,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 +350,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 +407,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..a501dca 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;
@@ -1006,6 +1103,7 @@
addState(mActiveHeadsetRoute);
addState(mActiveBluetoothRoute);
addState(mActiveSpeakerRoute);
+ addState(mRingingBluetoothRoute);
addState(mQuiescentEarpieceRoute);
addState(mQuiescentHeadsetRoute);
addState(mQuiescentBluetoothRoute);
@@ -1025,6 +1123,7 @@
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);
diff --git a/src/com/android/server/telecom/CallerInfoLookupHelper.java b/src/com/android/server/telecom/CallerInfoLookupHelper.java
index 4561c1c..0dec317 100644
--- a/src/com/android/server/telecom/CallerInfoLookupHelper.java
+++ b/src/com/android/server/telecom/CallerInfoLookupHelper.java
@@ -110,7 +110,7 @@
}
}
- mHandler.post(new Runnable("CILH.sL") {
+ mHandler.post(new Runnable("CILH.sL", mLock) {
@Override
public void loggedRun() {
Session continuedSession = Log.createSubsession();
@@ -160,7 +160,7 @@
}
private void startPhotoLookup(final Uri handle, final Uri contactPhotoUri) {
- mHandler.post(new Runnable("CILH.sPL") {
+ mHandler.post(new Runnable("CILH.sPL", mLock) {
@Override
public void loggedRun() {
Session continuedSession = Log.createSubsession();
diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java
index d6cb19b..b60f70b 100644
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -69,6 +69,7 @@
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -266,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);
@@ -451,15 +452,12 @@
mDtmfLocalTonePlayer.playTone(call, nextChar);
- // TODO: Create a LockedRunnable class that does the synchronization automatically.
- mStopTone = new Runnable("CM.oPDC") {
+ mStopTone = new Runnable("CM.oPDC", mLock) {
@Override
public void loggedRun() {
- synchronized (mLock) {
- // Set a timeout to stop the tone in case there isn't another tone to
- // follow.
- mDtmfLocalTonePlayer.stopTone(call);
- }
+ // Set a timeout to stop the tone in case there isn't another tone to
+ // follow.
+ mDtmfLocalTonePlayer.stopTone(call);
}
};
mHandler.postDelayed(mStopTone.prepare(),
@@ -513,14 +511,12 @@
@Override
public boolean onCanceledViaNewOutgoingCallBroadcast(final Call call) {
mPendingCallsToDisconnect.add(call);
- mHandler.postDelayed(new Runnable("CM.oCVNOCB") {
+ mHandler.postDelayed(new Runnable("CM.oCVNOCB", mLock) {
@Override
public void loggedRun() {
- synchronized (mLock) {
- if (mPendingCallsToDisconnect.remove(call)) {
- Log.i(this, "Delayed disconnection of call: %s", call);
- call.disconnect();
- }
+ if (mPendingCallsToDisconnect.remove(call)) {
+ Log.i(this, "Delayed disconnection of call: %s", call);
+ call.disconnect();
}
}
}.prepare(), Timeouts.getNewOutgoingCallCancelMillis(mContext.getContentResolver()));
@@ -635,7 +631,8 @@
return false;
}
- CallAudioState getAudioState() {
+ @VisibleForTesting
+ public CallAudioState getAudioState() {
return mCallAudioManager.getCallAudioState();
}
@@ -747,9 +744,10 @@
// Check to see if we can reuse any of the calls that are waiting to disconnect.
// See {@link Call#abort} and {@link #onCanceledViaNewOutgoingCall} for more information.
Call reusedCall = null;
- for (Call pendingCall : mPendingCallsToDisconnect) {
+ for (Iterator<Call> callIter = mPendingCallsToDisconnect.iterator(); callIter.hasNext();) {
+ Call pendingCall = callIter.next();
if (reusedCall == null && areHandlesEqual(pendingCall.getHandle(), handle)) {
- mPendingCallsToDisconnect.remove(pendingCall);
+ callIter.remove();
Log.i(this, "Reusing disconnected call %s", pendingCall);
reusedCall = pendingCall;
} else {
@@ -1495,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) {
diff --git a/src/com/android/server/telecom/CreateConnectionTimeout.java b/src/com/android/server/telecom/CreateConnectionTimeout.java
index 9bfeb7f..8bc3373 100644
--- a/src/com/android/server/telecom/CreateConnectionTimeout.java
+++ b/src/com/android/server/telecom/CreateConnectionTimeout.java
@@ -40,7 +40,7 @@
CreateConnectionTimeout(Context context, PhoneAccountRegistrar phoneAccountRegistrar,
ConnectionServiceWrapper service, Call call) {
- super("CCT");
+ super("CCT", null /*lock*/);
mContext = context;
mPhoneAccountRegistrar = phoneAccountRegistrar;
mConnectionService = service;
diff --git a/src/com/android/server/telecom/InCallController.java b/src/com/android/server/telecom/InCallController.java
index 5e1b887..f24ffc0 100644
--- a/src/com/android/server/telecom/InCallController.java
+++ b/src/com/android/server/telecom/InCallController.java
@@ -217,7 +217,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 +599,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(
@@ -661,17 +663,15 @@
* give them enough time to process all the pending messages.
*/
Handler handler = new Handler(Looper.getMainLooper());
- handler.postDelayed(new Runnable("ICC.oCR") {
+ handler.postDelayed(new Runnable("ICC.oCR", mLock) {
@Override
public void loggedRun() {
- synchronized (mLock) {
- // Check again to make sure there are no active calls.
- if (mCallsManager.getCalls().isEmpty()) {
- unbindFromServices();
- }
+ // Check again to make sure there are no active calls.
+ if (mCallsManager.getCalls().isEmpty()) {
+ unbindFromServices();
}
}
- }.prepare(), Timeouts.getCallRemoveUnbindInCallServicesDelay(
+ }.prepare(), mTimeoutsAdapter.getCallRemoveUnbindInCallServicesDelay(
mContext.getContentResolver()));
}
call.removeListener(mCallListener);
@@ -842,10 +842,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;
+ }
}
}
@@ -1079,34 +1083,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/InCallTonePlayer.java b/src/com/android/server/telecom/InCallTonePlayer.java
index 9b97087..e0b0dc0 100644
--- a/src/com/android/server/telecom/InCallTonePlayer.java
+++ b/src/com/android/server/telecom/InCallTonePlayer.java
@@ -297,15 +297,13 @@
private void cleanUpTonePlayer() {
// Release focus on the main thread.
- mMainThreadHandler.post(new Runnable("ICTP.cUTP") {
+ mMainThreadHandler.post(new Runnable("ICTP.cUTP", mLock) {
@Override
public void loggedRun() {
- synchronized (mLock) {
- if (sTonesPlaying == 0) {
- Log.wtf(this, "Over-releasing focus for tone player.");
- } else if (--sTonesPlaying == 0) {
- mCallAudioManager.setIsTonePlaying(false);
- }
+ if (sTonesPlaying == 0) {
+ Log.wtf(this, "Over-releasing focus for tone player.");
+ } else if (--sTonesPlaying == 0) {
+ mCallAudioManager.setIsTonePlaying(false);
}
}
}.prepare());
diff --git a/src/com/android/server/telecom/ParcelableCallUtils.java b/src/com/android/server/telecom/ParcelableCallUtils.java
index ca8c436..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;
diff --git a/src/com/android/server/telecom/Runnable.java b/src/com/android/server/telecom/Runnable.java
index 41415fd..c7ace72 100644
--- a/src/com/android/server/telecom/Runnable.java
+++ b/src/com/android/server/telecom/Runnable.java
@@ -24,7 +24,7 @@
private Session mSubsession;
private final String mSubsessionName;
- private final Object mLock = new Object();
+ private final Object mLock;
private final java.lang.Runnable mRunnable = new java.lang.Runnable() {
@Override
public void run() {
@@ -42,7 +42,18 @@
}
};
- public Runnable(String subsessionName) {
+ /**
+ * Creates a new Telecom Runnable that incorporates Session Logging into it. Useful for carrying
+ * Logging Sessions through different threads as well as through handlers.
+ * @param subsessionName The name that will be used in the Logs to mark this Session
+ * @param lock The synchronization lock that will be used to lock loggedRun().
+ */
+ public Runnable(String subsessionName, Object lock) {
+ if (lock == null) {
+ mLock = new Object();
+ } else {
+ mLock = lock;
+ }
mSubsessionName = subsessionName;
}
@@ -78,7 +89,8 @@
}
/**
- * The method that will be run in the handler/thread.
+ * The method that will be run in the handler/thread. This method will be called with mLock
+ * held.
*/
abstract public void loggedRun();
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 7026084..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,15 +57,6 @@
}
/**
- * 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
@@ -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..6b63255 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);
diff --git a/src/com/android/server/telecom/callfiltering/IncomingCallFilter.java b/src/com/android/server/telecom/callfiltering/IncomingCallFilter.java
index 4685ec0..6e0c684 100644
--- a/src/com/android/server/telecom/callfiltering/IncomingCallFilter.java
+++ b/src/com/android/server/telecom/callfiltering/IncomingCallFilter.java
@@ -70,17 +70,15 @@
for (CallFilter filter : mFilters) {
filter.startFilterLookup(mCall, this);
}
- mHandler.postDelayed(new Runnable("ICF.pFTO") { // performFiltering time-out
+ // synchronized to prevent a race on mResult and to enter into Telecom.
+ mHandler.postDelayed(new Runnable("ICF.pFTO", mTelecomLock) { // performFiltering time-out
@Override
public void loggedRun() {
- // synchronized to prevent a race on mResult and to enter into Telecom.
- synchronized (mTelecomLock) {
- if (mIsPending) {
- Log.i(IncomingCallFilter.this, "Call filtering has timed out.");
- Log.event(mCall, Log.Events.FILTERING_TIMED_OUT);
- mListener.onCallFilteringComplete(mCall, mResult);
- mIsPending = false;
- }
+ if (mIsPending) {
+ Log.i(IncomingCallFilter.this, "Call filtering has timed out.");
+ Log.event(mCall, Log.Events.FILTERING_TIMED_OUT);
+ mListener.onCallFilteringComplete(mCall, mResult);
+ mIsPending = false;
}
}
}.prepare(), mTimeoutsAdapter.getCallScreeningTimeoutMillis(mContext.getContentResolver()));
@@ -91,16 +89,14 @@
mNumPendingFilters--;
mResult = result.combine(mResult);
if (mNumPendingFilters == 0) {
- mHandler.post(new Runnable("ICF.oCFC") {
+ // synchronized on mTelecomLock to enter into Telecom.
+ mHandler.post(new Runnable("ICF.oCFC", mTelecomLock) {
@Override
public void loggedRun() {
- // synchronized to enter into Telecom.
- synchronized (mTelecomLock) {
- if (mIsPending) {
- Log.event(mCall, Log.Events.FILTERING_COMPLETED, mResult);
- mListener.onCallFilteringComplete(mCall, mResult);
- mIsPending = false;
- }
+ if (mIsPending) {
+ Log.event(mCall, Log.Events.FILTERING_COMPLETED, mResult);
+ mListener.onCallFilteringComplete(mCall, mResult);
+ mIsPending = false;
}
}
}.prepare());
diff --git a/src/com/android/server/telecom/ui/MissedCallNotifierImpl.java b/src/com/android/server/telecom/ui/MissedCallNotifierImpl.java
index 3786724..89ef95f 100644
--- a/src/com/android/server/telecom/ui/MissedCallNotifierImpl.java
+++ b/src/com/android/server/telecom/ui/MissedCallNotifierImpl.java
@@ -175,7 +175,7 @@
}
private void markMissedCallsAsRead(final UserHandle userHandle) {
- AsyncTask.execute(new Runnable("MCNI.mMCAR") {
+ AsyncTask.execute(new Runnable("MCNI.mMCAR", null /*lock*/) {
@Override
public void loggedRun() {
// Clear the list of new missed calls from the call log.
diff --git a/tests/Android.mk b/tests/Android.mk
index e639b55..1065ad1 100644
--- a/tests/Android.mk
+++ b/tests/Android.mk
@@ -25,7 +25,12 @@
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 +51,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..d8e152a 100644
--- a/tests/src/com/android/server/telecom/tests/AnalyticsTests.java
+++ b/tests/src/com/android/server/telecom/tests/AnalyticsTests.java
@@ -18,21 +18,34 @@
import android.content.Context;
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
@@ -110,6 +123,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 +182,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};
@@ -190,4 +252,55 @@
Analytics.sSessionIdToLogSession.get(s.getKey())))
.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));
+ }
+
+ private void assertIsRoundedToOneSigFig(long x) {
+ assertEquals(x, Analytics.roundToOneSigFig(x));
+ }
}
diff --git a/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java b/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java
index c4526e4..615fd0e 100644
--- a/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java
@@ -50,6 +50,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;
@@ -177,7 +178,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 +210,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 +231,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 +744,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);
diff --git a/tests/src/com/android/server/telecom/tests/InCallControllerTests.java b/tests/src/com/android/server/telecom/tests/InCallControllerTests.java
index 0173ed8..cf72225 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;
@@ -46,6 +48,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 +56,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 +83,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";
@@ -100,7 +105,7 @@
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
@@ -278,10 +283,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 +395,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);