resolve merge conflicts of 253b129 to nyc-mr1-dev-plus-aosp
Change-Id: I9d37a418982377eb1b94596998e22467d6c86402
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/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/res/values/strings.xml b/res/values/strings.xml
index e46aa54..11b2d50 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -109,12 +109,13 @@
<!-- Title of dialog used to comfirm whether the user intends to change the default dialer
application [CHAR LIMIT=55]-->
- <string name="change_default_dialer_dialog_title">Change default Dialer app?</string>
- <!-- Text in dialog used to confirm whether or not the user intends to change the default dialer, if a different default dialer has been previously set. -->
- <string name="change_default_dialer_with_previous_app_set_text">Use <xliff:g id="new_app">%1$s</xliff:g> instead of <xliff:g id="current_app">%2$s</xliff:g> as your default dialer app?</string>
- <!-- Text in dialog used to confirm whether or not the user intends to change the default dialer, if a different default dialer has not been previously set. -->
- <string name="change_default_dialer_no_previous_app_set_text">Use <xliff:g id="new_app">%s</xliff:g> as your default dialer app?</string>
-
+ <string name="change_default_dialer_dialog_title">Make <xliff:g id="new_app">%s</xliff:g> your default Phone app?</string>
+ <!-- Confirmation text that a user taps on to change the Default Phone App-->
+ <string name="change_default_dialer_dialog_affirmative">Set Default</string>
+ <!-- Cancel text that a user taps on to not change the Default Phone App-->
+ <string name="change_default_dialer_dialog_negative">Cancel</string>
+ <!-- Warning message indicating what may happen if a user allows a 3rd party app to become the default dialer.-->
+ <string name="change_default_dialer_warning_message"><xliff:g id="new_app">%s</xliff:g> will be able to place and control all aspects of calls. Only apps you trust should be set as the default Phone app.</string>
<!-- Blocked numbers -->
<string name="blocked_numbers">Blocked numbers</string>
diff --git a/src/com/android/server/telecom/Analytics.java b/src/com/android/server/telecom/Analytics.java
index 1d76dfc..4456734 100644
--- a/src/com/android/server/telecom/Analytics.java
+++ b/src/com/android/server/telecom/Analytics.java
@@ -16,45 +16,172 @@
package com.android.server.telecom;
-import android.os.Parcelable;
-import android.telecom.ParcelableCallAnalytics;
+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.telephony.CallerInfo;
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;
+import java.util.List;
import java.util.Map;
+import java.util.stream.Collectors;
+
+import static android.telecom.ParcelableCallAnalytics.AnalyticsEvent;
+import static android.telecom.TelecomAnalytics.SessionTiming;
/**
* A class that collects and stores data on how calls are being made, in order to
* aggregate these into useful statistics.
*/
public class Analytics {
- public static class CallInfo {
- void setCallStartTime(long startTime) {
+ 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);
+ put(Log.Events.REQUEST_HOLD, AnalyticsEvent.REQUEST_HOLD);
+ put(Log.Events.REQUEST_UNHOLD, AnalyticsEvent.REQUEST_UNHOLD);
+ put(Log.Events.SWAP, AnalyticsEvent.SWAP);
+ put(Log.Events.SKIP_RINGING, AnalyticsEvent.SKIP_RINGING);
+ put(Log.Events.CONFERENCE_WITH, AnalyticsEvent.CONFERENCE_WITH);
+ put(Log.Events.SPLIT_FROM_CONFERENCE, AnalyticsEvent.SPLIT_CONFERENCE);
+ put(Log.Events.SET_PARENT, AnalyticsEvent.SET_PARENT);
+ put(Log.Events.MUTE, AnalyticsEvent.MUTE);
+ put(Log.Events.UNMUTE, AnalyticsEvent.UNMUTE);
+ put(Log.Events.AUDIO_ROUTE_BT, AnalyticsEvent.AUDIO_ROUTE_BT);
+ put(Log.Events.AUDIO_ROUTE_EARPIECE, AnalyticsEvent.AUDIO_ROUTE_EARPIECE);
+ put(Log.Events.AUDIO_ROUTE_HEADSET, AnalyticsEvent.AUDIO_ROUTE_HEADSET);
+ put(Log.Events.AUDIO_ROUTE_SPEAKER, AnalyticsEvent.AUDIO_ROUTE_SPEAKER);
+ put(Log.Events.SILENCE, AnalyticsEvent.SILENCE);
+ put(Log.Events.SCREENING_COMPLETED, AnalyticsEvent.SCREENING_COMPLETED);
+ put(Log.Events.BLOCK_CHECK_FINISHED, AnalyticsEvent.BLOCK_CHECK_FINISHED);
+ put(Log.Events.DIRECT_TO_VM_FINISHED, AnalyticsEvent.DIRECT_TO_VM_FINISHED);
+ put(Log.Events.REMOTELY_HELD, AnalyticsEvent.REMOTELY_HELD);
+ put(Log.Events.REMOTELY_UNHELD, AnalyticsEvent.REMOTELY_UNHELD);
+ put(Log.Events.REQUEST_PULL, AnalyticsEvent.REQUEST_PULL);
+ put(Log.Events.REQUEST_ACCEPT, AnalyticsEvent.REQUEST_ACCEPT);
+ put(Log.Events.REQUEST_REJECT, AnalyticsEvent.REQUEST_REJECT);
+ put(Log.Events.SET_ACTIVE, AnalyticsEvent.SET_ACTIVE);
+ put(Log.Events.SET_DISCONNECTED, AnalyticsEvent.SET_DISCONNECTED);
+ put(Log.Events.SET_HOLD, AnalyticsEvent.SET_HOLD);
+ put(Log.Events.SET_DIALING, AnalyticsEvent.SET_DIALING);
+ put(Log.Events.START_CONNECTION, AnalyticsEvent.START_CONNECTION);
+ put(Log.Events.BIND_CS, AnalyticsEvent.BIND_CS);
+ put(Log.Events.CS_BOUND, AnalyticsEvent.CS_BOUND);
+ put(Log.Events.SCREENING_SENT, AnalyticsEvent.SCREENING_SENT);
+ put(Log.Events.DIRECT_TO_VM_INITIATED, AnalyticsEvent.DIRECT_TO_VM_INITIATED);
+ put(Log.Events.BLOCK_CHECK_INITIATED, AnalyticsEvent.BLOCK_CHECK_INITIATED);
+ put(Log.Events.FILTERING_INITIATED, AnalyticsEvent.FILTERING_INITIATED);
+ put(Log.Events.FILTERING_COMPLETED, AnalyticsEvent.FILTERING_COMPLETED);
+ put(Log.Events.FILTERING_TIMED_OUT, AnalyticsEvent.FILTERING_TIMED_OUT);
+ }};
+
+ public static final Map<String, Integer> sLogSessionToSessionId =
+ new HashMap<String, Integer> () {{
+ put(Log.Sessions.ICA_ANSWER_CALL, SessionTiming.ICA_ANSWER_CALL);
+ put(Log.Sessions.ICA_REJECT_CALL, SessionTiming.ICA_REJECT_CALL);
+ put(Log.Sessions.ICA_DISCONNECT_CALL, SessionTiming.ICA_DISCONNECT_CALL);
+ put(Log.Sessions.ICA_HOLD_CALL, SessionTiming.ICA_HOLD_CALL);
+ put(Log.Sessions.ICA_UNHOLD_CALL, SessionTiming.ICA_UNHOLD_CALL);
+ put(Log.Sessions.ICA_MUTE, SessionTiming.ICA_MUTE);
+ put(Log.Sessions.ICA_SET_AUDIO_ROUTE, SessionTiming.ICA_SET_AUDIO_ROUTE);
+ put(Log.Sessions.ICA_CONFERENCE, SessionTiming.ICA_CONFERENCE);
+ put(Log.Sessions.CSW_HANDLE_CREATE_CONNECTION_COMPLETE,
+ SessionTiming.CSW_HANDLE_CREATE_CONNECTION_COMPLETE);
+ put(Log.Sessions.CSW_SET_ACTIVE, SessionTiming.CSW_SET_ACTIVE);
+ put(Log.Sessions.CSW_SET_RINGING, SessionTiming.CSW_SET_RINGING);
+ put(Log.Sessions.CSW_SET_DIALING, SessionTiming.CSW_SET_DIALING);
+ put(Log.Sessions.CSW_SET_DISCONNECTED, SessionTiming.CSW_SET_DISCONNECTED);
+ put(Log.Sessions.CSW_SET_ON_HOLD, SessionTiming.CSW_SET_ON_HOLD);
+ put(Log.Sessions.CSW_REMOVE_CALL, SessionTiming.CSW_REMOVE_CALL);
+ put(Log.Sessions.CSW_SET_IS_CONFERENCED, SessionTiming.CSW_SET_IS_CONFERENCED);
+ put(Log.Sessions.CSW_ADD_CONFERENCE_CALL, SessionTiming.CSW_ADD_CONFERENCE_CALL);
+
+ }};
+
+ public static final Map<String, Integer> sLogEventTimingToAnalyticsEventTiming =
+ new HashMap<String, Integer>() {{
+ put(Log.Events.Timings.ACCEPT_TIMING,
+ ParcelableCallAnalytics.EventTiming.ACCEPT_TIMING);
+ put(Log.Events.Timings.REJECT_TIMING,
+ ParcelableCallAnalytics.EventTiming.REJECT_TIMING);
+ put(Log.Events.Timings.DISCONNECT_TIMING,
+ ParcelableCallAnalytics.EventTiming.DISCONNECT_TIMING);
+ put(Log.Events.Timings.HOLD_TIMING,
+ ParcelableCallAnalytics.EventTiming.HOLD_TIMING);
+ put(Log.Events.Timings.UNHOLD_TIMING,
+ ParcelableCallAnalytics.EventTiming.UNHOLD_TIMING);
+ put(Log.Events.Timings.OUTGOING_TIME_TO_DIALING_TIMING,
+ ParcelableCallAnalytics.EventTiming.OUTGOING_TIME_TO_DIALING_TIMING);
+ put(Log.Events.Timings.BIND_CS_TIMING,
+ ParcelableCallAnalytics.EventTiming.BIND_CS_TIMING);
+ put(Log.Events.Timings.SCREENING_COMPLETED_TIMING,
+ ParcelableCallAnalytics.EventTiming.SCREENING_COMPLETED_TIMING);
+ put(Log.Events.Timings.DIRECT_TO_VM_FINISHED_TIMING,
+ ParcelableCallAnalytics.EventTiming.DIRECT_TO_VM_FINISHED_TIMING);
+ put(Log.Events.Timings.BLOCK_CHECK_FINISHED_TIMING,
+ ParcelableCallAnalytics.EventTiming.BLOCK_CHECK_FINISHED_TIMING);
+ put(Log.Events.Timings.FILTERING_COMPLETED_TIMING,
+ ParcelableCallAnalytics.EventTiming.FILTERING_COMPLETED_TIMING);
+ put(Log.Events.Timings.FILTERING_TIMED_OUT_TIMING,
+ ParcelableCallAnalytics.EventTiming.FILTERING_TIMED_OUT_TIMING);
+ }};
+
+ public static final Map<Integer, String> sSessionIdToLogSession = new HashMap<>();
+ static {
+ for (Map.Entry<String, Integer> e : sLogSessionToSessionId.entrySet()) {
+ sSessionIdToLogSession.put(e.getValue(), e.getKey());
+ }
+ }
+
+ public static class CallInfo {
+ public void setCallStartTime(long startTime) {
}
- void setCallEndTime(long endTime) {
+ public void setCallEndTime(long endTime) {
}
- void setCallIsAdditional(boolean isAdditional) {
+ public void setCallIsAdditional(boolean isAdditional) {
}
- void setCallIsInterrupted(boolean isInterrupted) {
+ public void setCallIsInterrupted(boolean isInterrupted) {
}
- void setCallDisconnectCause(DisconnectCause disconnectCause) {
+ public void setCallDisconnectCause(DisconnectCause disconnectCause) {
}
- void addCallTechnology(int callTechnology) {
+ public void addCallTechnology(int callTechnology) {
}
- void setCreatedFromExistingConnection(boolean createdFromExistingConnection) {
+ public void setCreatedFromExistingConnection(boolean createdFromExistingConnection) {
}
- void setCallConnectionService(String connectionServiceName) {
+ public void setCallConnectionService(String connectionServiceName) {
+ }
+
+ public void setCallEvents(Log.CallEventRecord records) {
+ }
+
+ public void setCallIsVideo(boolean isVideo) {
+ }
+
+ public void addVideoEvent(int eventId, int videoState) {
+ }
+
+ public void addInCallService(String serviceName, int type) {
+ }
+
+ public void addCallProperties(int properties) {
}
}
@@ -83,6 +210,15 @@
public String connectionService;
public boolean isEmergency = false;
+ public Log.CallEventRecord callEvents;
+
+ public boolean isVideo = false;
+ public List<TelecomLogClass.VideoEvent> videoEvents;
+ public List<TelecomLogClass.InCallServiceInfo> inCallServiceInfos;
+ public int callProperties = 0;
+
+ private long mTimeOfLastVideoEvent = -1;
+
CallInfoImpl(String callId, int callDirection) {
this.callId = callId;
startTime = 0;
@@ -90,6 +226,8 @@
this.callDirection = callDirection;
callTechnologies = 0;
connectionService = "";
+ videoEvents = new LinkedList<>();
+ inCallServiceInfos = new LinkedList<>();
}
CallInfoImpl(CallInfoImpl other) {
@@ -103,6 +241,10 @@
this.createdFromExistingConnection = other.createdFromExistingConnection;
this.connectionService = other.connectionService;
this.isEmergency = other.isEmergency;
+ this.callEvents = other.callEvents;
+ this.isVideo = other.isVideo;
+ this.videoEvents = other.videoEvents;
+ this.callProperties = other.callProperties;
if (other.callTerminationReason != null) {
this.callTerminationReason = new DisconnectCause(
@@ -167,6 +309,45 @@
}
@Override
+ public void setCallEvents(Log.CallEventRecord records) {
+ this.callEvents = records;
+ }
+
+ @Override
+ public void setCallIsVideo(boolean isVideo) {
+ this.isVideo = isVideo;
+ }
+
+ @Override
+ public void addVideoEvent(int eventId, int videoState) {
+ long timeSinceLastEvent;
+ long currentTime = System.currentTimeMillis();
+ if (mTimeOfLastVideoEvent < 0) {
+ timeSinceLastEvent = -1;
+ } else {
+ timeSinceLastEvent = roundToOneSigFig(currentTime - mTimeOfLastVideoEvent);
+ }
+ mTimeOfLastVideoEvent = currentTime;
+
+ 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
public String toString() {
return "{\n"
+ " startTime: " + startTime + '\n'
@@ -177,28 +358,93 @@
+ " callTechnologies: " + getCallTechnologiesAsString() + '\n'
+ " 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);
- return 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);
+ result.setCallDurationMillis(callDuration);
+
+ 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) {
+ result.callEvents = convertLogEventsToProtoEvents(callEvents.getEvents());
+ result.callTimings = callEvents.extractEventTimings().stream()
+ .map(Analytics::logEventTimingToProtoEventTiming)
+ .toArray(TelecomLogClass.EventTimingEntry[]::new);
+ }
+ result.videoEvents =
+ videoEvents.toArray(new TelecomLogClass.VideoEvent[videoEvents.size()]);
+ result.inCallServices = inCallServiceInfos.toArray(
+ new TelecomLogClass.InCallServiceInfo[inCallServiceInfos.size()]);
+
+ return result;
}
private String getCallDirectionString() {
@@ -233,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";
@@ -248,10 +509,30 @@
public static final int SIP_PHONE = ParcelableCallAnalytics.SIP_PHONE;
public static final int THIRD_PARTY_PHONE = ParcelableCallAnalytics.THIRD_PARTY_PHONE;
+ // Constants for video events
+ public static final int SEND_LOCAL_SESSION_MODIFY_REQUEST =
+ ParcelableCallAnalytics.VideoEvent.SEND_LOCAL_SESSION_MODIFY_REQUEST;
+ public static final int SEND_LOCAL_SESSION_MODIFY_RESPONSE =
+ ParcelableCallAnalytics.VideoEvent.SEND_LOCAL_SESSION_MODIFY_RESPONSE;
+ public static final int RECEIVE_REMOTE_SESSION_MODIFY_REQUEST =
+ ParcelableCallAnalytics.VideoEvent.RECEIVE_REMOTE_SESSION_MODIFY_REQUEST;
+ public static final int RECEIVE_REMOTE_SESSION_MODIFY_RESPONSE =
+ ParcelableCallAnalytics.VideoEvent.RECEIVE_REMOTE_SESSION_MODIFY_RESPONSE;
+
public static final long MILLIS_IN_1_SECOND = ParcelableCallAnalytics.MILLIS_IN_1_SECOND;
private static final Object sLock = new Object(); // Coarse lock for all of analytics
private static final Map<String, CallInfoImpl> sCallIdToInfo = new HashMap<>();
+ private static final List<SessionTiming> sSessionTimings = new LinkedList<>();
+
+ public static void addSessionTiming(String sessionName, long time) {
+ if (sLogSessionToSessionId.containsKey(sessionName)) {
+ synchronized (sLock) {
+ sSessionTimings.add(new SessionTiming(sLogSessionToSessionId.get(sessionName),
+ time));
+ }
+ }
+ }
public static CallInfo initiateCallAnalytics(String callId, int direction) {
Log.d(TAG, "Starting analytics for call " + callId);
@@ -262,26 +543,77 @@
return callInfo;
}
- public static ParcelableCallAnalytics[] dumpToParcelableAnalytics() {
- ParcelableCallAnalytics[] result;
+ public static TelecomAnalytics dumpToParcelableAnalytics() {
+ List<ParcelableCallAnalytics> calls = new LinkedList<>();
+ List<SessionTiming> sessionTimings = new LinkedList<>();
synchronized (sLock) {
- result = new ParcelableCallAnalytics[sCallIdToInfo.size()];
- int idx = 0;
- for (CallInfoImpl entry : sCallIdToInfo.values()) {
- result[idx] = entry.toParcelableAnalytics();
- idx++;
- }
+ calls.addAll(sCallIdToInfo.values().stream()
+ .map(CallInfoImpl::toParcelableAnalytics)
+ .collect(Collectors.toList()));
+ sessionTimings.addAll(sSessionTimings);
sCallIdToInfo.clear();
+ sSessionTimings.clear();
}
- return result;
+ 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) {
- for (Map.Entry<String, CallInfoImpl> entry : sCallIdToInfo.entrySet()) {
- writer.printf("Call %s: ", entry.getKey());
- writer.println(entry.getValue().toString());
+ int prefixLength = CallsManager.TELECOM_CALL_ID_PREFIX.length();
+ List<String> callIds = new ArrayList<>(sCallIdToInfo.keySet());
+ // Sort the analytics in increasing order of call IDs
+ try {
+ Collections.sort(callIds, (id1, id2) -> {
+ int i1, i2;
+ try {
+ i1 = Integer.valueOf(id1.substring(prefixLength));
+ } catch (NumberFormatException e) {
+ i1 = Integer.MAX_VALUE;
+ }
+
+ try {
+ i2 = Integer.valueOf(id2.substring(prefixLength));
+ } catch (NumberFormatException e) {
+ i2 = Integer.MAX_VALUE;
+ }
+ return i1 - i2;
+ });
+ } catch (IllegalArgumentException e) {
+ // do nothing, leave the list in a partially sorted state.
}
+
+ for (String callId : callIds) {
+ writer.printf("Call %s: ", callId);
+ writer.println(sCallIdToInfo.get(callId).toString());
+ }
+
+ Map<Integer, Double> averageTimings = SessionTiming.averageTimings(sSessionTimings);
+ averageTimings.entrySet().stream()
+ .filter(e -> sSessionIdToLogSession.containsKey(e.getKey()))
+ .forEach(e -> writer.printf("%s: %.2f\n",
+ sSessionIdToLogSession.get(e.getKey()), e.getValue()));
}
}
@@ -292,8 +624,9 @@
}
/**
- * Returns a deep copy of callIdToInfo that's safe to read/write without synchronization
+ * Returns a copy of callIdToInfo. Use only for testing.
*/
+ @VisibleForTesting
public static Map<String, CallInfoImpl> cloneData() {
synchronized (sLock) {
Map<String, CallInfoImpl> result = new HashMap<>(sCallIdToInfo.size());
@@ -303,4 +636,43 @@
return result;
}
}
+
+ private static TelecomLogClass.Event[] convertLogEventsToProtoEvents(
+ List<Log.CallEvent> logEvents) {
+ long timeOfLastEvent = -1;
+ ArrayList<TelecomLogClass.Event> events = new ArrayList<>(logEvents.size());
+ for (Log.CallEvent logEvent : logEvents) {
+ if (sLogEventToAnalyticsEvent.containsKey(logEvent.eventId)) {
+ 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.toArray(new TelecomLogClass.Event[events.size()]);
+ }
+
+ private static TelecomLogClass.EventTimingEntry logEventTimingToProtoEventTiming(
+ Log.CallEventRecord.EventTiming logEventTiming) {
+ int analyticsEventTimingName =
+ sLogEventTimingToAnalyticsEventTiming.containsKey(logEventTiming.name) ?
+ sLogEventTimingToAnalyticsEventTiming.get(logEventTiming.name) :
+ ParcelableCallAnalytics.EventTiming.INVALID;
+ return new TelecomLogClass.EventTimingEntry()
+ .setTimingName(analyticsEventTimingName)
+ .setTimeMillis(logEventTiming.time);
+ }
+
+ @VisibleForTesting
+ public static long roundToOneSigFig(long val) {
+ if (val == 0) {
+ return val;
+ }
+ int logVal = (int) Math.floor(Math.log10(val < 0 ? -val : val));
+ double s = Math.pow(10, logVal);
+ double dec = val / s;
+ return (long) (Math.round(dec) * s);
+ }
}
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/BluetoothPhoneServiceImpl.java b/src/com/android/server/telecom/BluetoothPhoneServiceImpl.java
index a72a21b..c79b036 100644
--- a/src/com/android/server/telecom/BluetoothPhoneServiceImpl.java
+++ b/src/com/android/server/telecom/BluetoothPhoneServiceImpl.java
@@ -30,6 +30,7 @@
import android.os.RemoteException;
import android.telecom.Connection;
import android.telecom.PhoneAccount;
+import android.telecom.VideoProfile;
import android.telephony.PhoneNumberUtils;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
@@ -98,7 +99,7 @@
Log.i(TAG, "BT - answering call");
Call call = mCallsManager.getRingingCall();
if (call != null) {
- mCallsManager.answerCall(call, call.getVideoState());
+ mCallsManager.answerCall(call, VideoProfile.STATE_AUDIO_ONLY);
return true;
}
return false;
@@ -291,11 +292,17 @@
public CallsManagerListener mCallsManagerListener = new CallsManagerListenerBase() {
@Override
public void onCallAdded(Call call) {
+ if (call.isExternalCall()) {
+ return;
+ }
updateHeadsetWithCallState(false /* force */);
}
@Override
public void onCallRemoved(Call call) {
+ if (call.isExternalCall()) {
+ return;
+ }
mClccIndexMap.remove(call);
updateHeadsetWithCallState(false /* force */);
}
@@ -318,6 +325,9 @@
@Override
public void onCallStateChanged(Call call, int oldState, int newState) {
+ if (call.isExternalCall()) {
+ return;
+ }
// If a call is being put on hold because of a new connecting call, ignore the
// CONNECTING since the BT state update needs to send out the numHeld = 1 + dialing
// state atomically.
@@ -335,7 +345,8 @@
// state. We can assume that the active call will be automatically held which will
// send another update at which point we will be in the right state.
if (mCallsManager.getActiveCall() != null
- && oldState == CallState.CONNECTING && newState == CallState.DIALING) {
+ && oldState == CallState.CONNECTING &&
+ (newState == CallState.DIALING || newState == CallState.PULLING)) {
return;
}
updateHeadsetWithCallState(false /* force */);
@@ -343,6 +354,9 @@
@Override
public void onIsConferencedChanged(Call call) {
+ if (call.isExternalCall()) {
+ return;
+ }
/*
* Filter certain onIsConferencedChanged callbacks. Unfortunately this needs to be done
* because conference change events are not atomic and multiple callbacks get fired
@@ -487,6 +501,12 @@
return false;
if (activeCall != null) {
mCallsManager.disconnectCall(activeCall);
+ if (ringingCall != null) {
+ mCallsManager.answerCall(ringingCall, VideoProfile.STATE_AUDIO_ONLY);
+ } else if (heldCall != null) {
+ mCallsManager.unholdCall(heldCall);
+ }
+ return true;
}
if (ringingCall != null) {
mCallsManager.answerCall(ringingCall, ringingCall.getVideoState());
@@ -501,7 +521,7 @@
updateHeadsetWithCallState(true /* force */);
return true;
} else if (ringingCall != null) {
- mCallsManager.answerCall(ringingCall, ringingCall.getVideoState());
+ mCallsManager.answerCall(ringingCall, VideoProfile.STATE_AUDIO_ONLY);
return true;
} else if (heldCall != null) {
// CallsManager will hold any active calls when unhold() is called on a
@@ -811,6 +831,7 @@
case CallState.CONNECTING:
case CallState.SELECT_PHONE_ACCOUNT:
case CallState.DIALING:
+ case CallState.PULLING:
// Yes, this is correctly returning ALERTING.
// "Dialing" for BT means that we have sent information to the service provider
// to place the call but there is no confirmation that the call is going through.
diff --git a/src/com/android/server/telecom/Call.java b/src/com/android/server/telecom/Call.java
index 4c70913..98310bd 100644
--- a/src/com/android/server/telecom/Call.java
+++ b/src/com/android/server/telecom/Call.java
@@ -103,7 +103,7 @@
void onExtrasRemoved(Call c, int source, List<String> keys);
void onHandleChanged(Call call);
void onCallerDisplayNameChanged(Call call);
- void onVideoStateChanged(Call call);
+ void onVideoStateChanged(Call call, int previousVideoState, int newVideoState);
void onTargetPhoneAccountChanged(Call call);
void onConnectionManagerPhoneAccountChanged(Call call);
void onPhoneAccountChanged(Call call);
@@ -160,7 +160,7 @@
@Override
public void onCallerDisplayNameChanged(Call call) {}
@Override
- public void onVideoStateChanged(Call call) {}
+ public void onVideoStateChanged(Call call, int previousVideoState, int newVideoState) {}
@Override
public void onTargetPhoneAccountChanged(Call call) {}
@Override
@@ -343,6 +343,7 @@
private final CallsManager mCallsManager;
private final TelecomSystem.SyncRoot mLock;
private final String mId;
+ private String mConnectionId;
private Analytics.CallInfo mAnalytics;
private boolean mWasConferencePreviouslyMerged = false;
@@ -379,6 +380,8 @@
*/
private boolean mIsVideoCallingSupported = false;
+ private PhoneNumberUtilsAdapter mPhoneNumberUtilsAdapter;
+
/**
* Persists the specified parameters and initializes the new instance.
*
@@ -404,6 +407,7 @@
ConnectionServiceRepository repository,
ContactsAsyncHelper contactsAsyncHelper,
CallerInfoAsyncQueryFactory callerInfoAsyncQueryFactory,
+ PhoneNumberUtilsAdapter phoneNumberUtilsAdapter,
Uri handle,
GatewayInfo gatewayInfo,
PhoneAccountHandle connectionManagerPhoneAccountHandle,
@@ -412,11 +416,13 @@
boolean shouldAttachToExistingConnection,
boolean isConference) {
mId = callId;
+ mConnectionId = callId;
mState = isConference ? CallState.ACTIVE : CallState.NEW;
mContext = context;
mCallsManager = callsManager;
mLock = lock;
mRepository = repository;
+ mPhoneNumberUtilsAdapter = phoneNumberUtilsAdapter;
setHandle(handle);
mPostDialDigits = handle != null
? PhoneNumberUtils.extractPostDialPortion(handle.getSchemeSpecificPart()) : "";
@@ -430,7 +436,6 @@
maybeLoadCannedSmsResponses();
mAnalytics = new Analytics.CallInfo();
- Log.event(this, Log.Events.CREATED);
}
/**
@@ -459,6 +464,7 @@
ConnectionServiceRepository repository,
ContactsAsyncHelper contactsAsyncHelper,
CallerInfoAsyncQueryFactory callerInfoAsyncQueryFactory,
+ PhoneNumberUtilsAdapter phoneNumberUtilsAdapter,
Uri handle,
GatewayInfo gatewayInfo,
PhoneAccountHandle connectionManagerPhoneAccountHandle,
@@ -468,7 +474,7 @@
boolean isConference,
long connectTimeMillis) {
this(callId, context, callsManager, lock, repository, contactsAsyncHelper,
- callerInfoAsyncQueryFactory, handle, gatewayInfo,
+ callerInfoAsyncQueryFactory, phoneNumberUtilsAdapter, handle, gatewayInfo,
connectionManagerPhoneAccountHandle, targetPhoneAccountHandle, callDirection,
shouldAttachToExistingConnection, isConference);
@@ -501,6 +507,7 @@
analyticsDirection = Analytics.UNKNOWN_DIRECTION;
}
mAnalytics = Analytics.initiateCallAnalytics(mId, analyticsDirection);
+ Log.event(this, Log.Events.CREATED);
}
public Analytics.CallInfo getAnalytics() {
@@ -570,6 +577,11 @@
return false;
}
+ // Only Redial a Call in the case of it being an Emergency Call.
+ if(!isEmergencyCall()) {
+ return false;
+ }
+
// Make sure that there are additional connection services to process.
if (mCreateConnectionProcessor == null
|| !mCreateConnectionProcessor.isProcessingComplete()
@@ -595,6 +607,21 @@
}
/**
+ * Returns the unique ID for this call (see {@link #getId}) along with an attempt indicator that
+ * iterates based on attempts to establish a {@link Connection} using createConnectionProcessor.
+ * @return The call ID with an appended attempt id.
+ */
+ public String getConnectionId() {
+ if(mCreateConnectionProcessor != null) {
+ mConnectionId = mId + "_" +
+ String.valueOf(mCreateConnectionProcessor.getConnectionAttempt());
+ return mConnectionId;
+ } else {
+ return mConnectionId;
+ }
+ }
+
+ /**
* Sets the call state. Although there exists the notion of appropriate state transitions
* (see {@link CallState}), in practice those expectations break down when cellular systems
* misbehave and they do this very often. The result is that we do not enforce state transitions
@@ -655,6 +682,9 @@
case CallState.DIALING:
event = Log.Events.SET_DIALING;
break;
+ case CallState.PULLING:
+ event = Log.Events.SET_PULLING;
+ break;
case CallState.DISCONNECTED:
event = Log.Events.SET_DISCONNECTED;
data = getDisconnectCause();
@@ -747,8 +777,9 @@
// Let's not allow resetting of the emergency flag. Once a call becomes an emergency
// call, it will remain so for the rest of it's lifetime.
if (!mIsEmergencyCall) {
- mIsEmergencyCall = mHandle != null && PhoneNumberUtils.isLocalEmergencyNumber(
- mContext, mHandle.getSchemeSpecificPart());
+ mIsEmergencyCall = mHandle != null &&
+ mPhoneNumberUtilsAdapter.isLocalEmergencyNumber(mContext,
+ mHandle.getSchemeSpecificPart());
}
startCallerInfoLookup();
for (Listener l : mListeners) {
@@ -982,10 +1013,18 @@
connectionCapabilities = removeVideoCapabilities(connectionCapabilities);
}
+ int previousCapabilities = mConnectionCapabilities;
mConnectionCapabilities = connectionCapabilities;
for (Listener l : mListeners) {
l.onConnectionCapabilitiesChanged(this);
}
+
+ int xorCaps = previousCapabilities ^ mConnectionCapabilities;
+ Log.event(this, Log.Events.CAPABILITY_CHANGE,
+ "Current: [%s], Removed [%s], Added [%s]",
+ Connection.capabilitiesToStringShort(mConnectionCapabilities),
+ Connection.capabilitiesToStringShort(previousCapabilities & xorCaps),
+ Connection.capabilitiesToStringShort(mConnectionCapabilities & xorCaps));
}
}
@@ -1006,12 +1045,21 @@
if (wasExternal != isExternal) {
Log.v(this, "setConnectionProperties: external call changed isExternal = %b",
isExternal);
-
+ Log.event(this, Log.Events.IS_EXTERNAL, isExternal);
for (Listener l : mListeners) {
l.onExternalCallChanged(this, isExternal);
}
}
+
+ mAnalytics.addCallProperties(mConnectionProperties);
+
+ int xorProps = previousProperties ^ mConnectionProperties;
+ Log.event(this, Log.Events.PROPERTY_CHANGE,
+ "Current: [%s], Removed [%s], Added [%s]",
+ Connection.propertiesToStringShort(mConnectionProperties),
+ Connection.propertiesToStringShort(previousProperties & xorProps),
+ Connection.propertiesToStringShort(mConnectionProperties & xorProps));
}
}
@@ -1282,16 +1330,24 @@
*/
@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")) {
+ if (!isVideoCallingSupported() && VideoProfile.isVideo(videoState)) {
+ // Video calling is not supported, yet the InCallService is attempting to answer as
+ // video. We will simply answer as audio-only.
+ videoState = VideoProfile.STATE_AUDIO_ONLY;
+ }
// At this point, we are asking the connection service to answer but we don't assume
// 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);
}
}
@@ -1304,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);
+
}
}
@@ -1321,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);
}
}
@@ -1333,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);
}
}
@@ -1390,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());
+ }
}
}
@@ -1420,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());
+ }
}
}
@@ -1461,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) {
@@ -1477,7 +1558,7 @@
if (mConnectionService == null) {
Log.w(this, "splitting from conference call without a connection service");
} else {
- Log.event(this, Log.Events.SPLIT_CONFERENCE);
+ Log.event(this, Log.Events.SPLIT_FROM_CONFERENCE);
mConnectionService.splitFromConference(this);
}
}
@@ -1525,7 +1606,7 @@
* {@link android.telecom.Connection#PROPERTY_IS_EXTERNAL_CALL} property set.
* <p>
* An external call is a representation of a call which is taking place on another device
- * associated with a PhoneAccount on this device. Issuing a request to pull the external call
+ * associated with a PhoneAccount on this device. Issuing a request to pull the external call
* tells the {@link android.telecom.ConnectionService} that it should move the call from the
* other device to this one. An example of this is the IMS multi-endpoint functionality. A
* user may have two phones with the same phone number. If the user is engaged in an active
@@ -1550,7 +1631,7 @@
return;
}
- Log.event(this, Log.Events.PULL);
+ Log.event(this, Log.Events.REQUEST_PULL);
mConnectionService.pullExternalCall(this);
}
@@ -1563,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) {
@@ -1671,7 +1757,7 @@
return false;
}
- if (PhoneNumberUtils.isUriNumber(getHandle().toString())) {
+ if (mPhoneNumberUtilsAdapter.isUriNumber(getHandle().toString())) {
// The incoming number is actually a URI (i.e. a SIP address),
// not a regular PSTN phone number, and we can't send SMSes to
// SIP addresses.
@@ -1745,7 +1831,10 @@
*/
private void setCallerInfo(Uri handle, CallerInfo callerInfo) {
Trace.beginSection("setCallerInfo");
- Preconditions.checkNotNull(callerInfo);
+ if (callerInfo == null) {
+ Log.i(this, "CallerInfo lookup returned null, skipping update");
+ return;
+ }
if (!handle.equals(mHandle)) {
Log.i(this, "setCallerInfo received stale caller info for an old handle. Ignoring.");
@@ -1884,6 +1973,12 @@
* @param videoState The video state for the call.
*/
public void setVideoState(int videoState) {
+ // If the phone account associated with this call does not support video calling, then we
+ // will automatically set the video state to audio-only.
+ if (!isVideoCallingSupported()) {
+ videoState = VideoProfile.STATE_AUDIO_ONLY;
+ }
+
// Track which video states were applicable over the duration of the call.
// Only track the call state when the call is active or disconnected. This ensures we do
// not include the video state when:
@@ -1895,9 +1990,18 @@
mVideoStateHistory = mVideoStateHistory | videoState;
}
+ int previousVideoState = mVideoState;
mVideoState = videoState;
- for (Listener l : mListeners) {
- l.onVideoStateChanged(this);
+ if (mVideoState != previousVideoState) {
+ Log.event(this, Log.Events.VIDEO_STATE_CHANGED,
+ VideoProfile.videoStateToString(videoState));
+ for (Listener l : mListeners) {
+ l.onVideoStateChanged(this, previousVideoState, mVideoState);
+ }
+ }
+
+ if (VideoProfile.isVideo(videoState)) {
+ mAnalytics.setCallIsVideo(true);
}
}
@@ -1969,6 +2073,8 @@
return CallState.ACTIVE;
case Connection.STATE_DIALING:
return CallState.DIALING;
+ case Connection.STATE_PULLING_CALL:
+ return CallState.PULLING;
case Connection.STATE_DISCONNECTED:
return CallState.DISCONNECTED;
case Connection.STATE_HOLDING:
@@ -2045,6 +2151,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 7bc727c..236df5f 100644
--- a/src/com/android/server/telecom/CallAudioManager.java
+++ b/src/com/android/server/telecom/CallAudioManager.java
@@ -72,6 +72,7 @@
put(CallState.CONNECTING, mActiveDialingOrConnectingCalls);
put(CallState.ACTIVE, mActiveDialingOrConnectingCalls);
put(CallState.DIALING, mActiveDialingOrConnectingCalls);
+ put(CallState.PULLING, mActiveDialingOrConnectingCalls);
put(CallState.RINGING, mRingingCalls);
put(CallState.ON_HOLD, mHoldingCalls);
}};
@@ -105,7 +106,7 @@
}
updateForegroundCall();
- if (newState == CallState.DISCONNECTED) {
+ if (shouldPlayDisconnectTone(oldState, newState)) {
playToneForDisconnectedCall(call);
}
@@ -177,12 +178,20 @@
*/
@Override
public void onExternalCallChanged(Call call, boolean isExternalCall) {
- if (isExternalCall) {
+ if (isExternalCall) {
Log.d(LOG_TAG, "Removing call which became external ID %s", call.getId());
removeCall(call);
} else if (!isExternalCall) {
Log.d(LOG_TAG, "Adding external call which was pulled with ID %s", call.getId());
addCall(call);
+
+ if (mCallsManager.isSpeakerphoneAutoEnabledForVideoCalls(call.getVideoState())) {
+ // When pulling a video call, automatically enable the speakerphone.
+ Log.d(LOG_TAG, "Switching to speaker because external video call %s was pulled." +
+ call.getId());
+ mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
+ CallAudioRouteStateMachine.SWITCH_SPEAKER);
+ }
}
}
@@ -314,6 +323,25 @@
CallAudioRouteStateMachine.UPDATE_SYSTEM_AUDIO_ROUTE);
}
+ @Override
+ public void onVideoStateChanged(Call call, int previousVideoState, int newVideoState) {
+ if (call != getForegroundCall()) {
+ Log.d(LOG_TAG, "Ignoring video state change from %s to %s for call %s -- not " +
+ "foreground.", VideoProfile.videoStateToString(previousVideoState),
+ VideoProfile.videoStateToString(newVideoState), call.getId());
+ return;
+ }
+
+ if (!VideoProfile.isVideo(previousVideoState) &&
+ mCallsManager.isSpeakerphoneAutoEnabledForVideoCalls(newVideoState)) {
+ Log.d(LOG_TAG, "Switching to speaker because call %s transitioned video state from %s" +
+ " to %s", call.getId(), VideoProfile.videoStateToString(previousVideoState),
+ VideoProfile.videoStateToString(newVideoState));
+ mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
+ CallAudioRouteStateMachine.SWITCH_SPEAKER);
+ }
+ }
+
public CallAudioState getCallAudioState() {
return mCallAudioRouteStateMachine.getCurrentCallAudioState();
}
@@ -394,8 +422,8 @@
}
@VisibleForTesting
- public void startRinging() {
- mRinger.startRinging(mForegroundCall);
+ public boolean startRinging() {
+ return mRinger.startRinging(mForegroundCall);
}
@VisibleForTesting
@@ -424,6 +452,11 @@
return mCallAudioRouteStateMachine;
}
+ @VisibleForTesting
+ public CallAudioModeStateMachine getCallAudioModeStateMachine() {
+ return mCallAudioModeStateMachine;
+ }
+
void dump(IndentingPrintWriter pw) {
pw.println("All calls:");
pw.increaseIndent();
@@ -470,9 +503,13 @@
case CallState.ON_HOLD:
onCallLeavingHold();
break;
+ case CallState.PULLING:
+ onCallLeavingActiveDialingOrConnecting();
+ break;
case CallState.DIALING:
stopRingbackForCall(call);
onCallLeavingActiveDialingOrConnecting();
+ break;
}
}
@@ -488,6 +525,9 @@
case CallState.ON_HOLD:
onCallEnteringHold();
break;
+ case CallState.PULLING:
+ onCallEnteringActiveDialingOrConnecting();
+ break;
case CallState.DIALING:
onCallEnteringActiveDialingOrConnecting();
playRingbackForCall(call);
@@ -643,7 +683,7 @@
if (shouldPlayHoldTone()) {
if (mHoldTonePlayer == null) {
mHoldTonePlayer = mPlayerFactory.createPlayer(InCallTonePlayer.TONE_CALL_WAITING);
- mHoldTonePlayer.start();
+ mHoldTonePlayer.startTone();
}
} else {
if (mHoldTonePlayer != null) {
@@ -697,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 33700f7..ce03e61 100644
--- a/src/com/android/server/telecom/CallAudioRouteStateMachine.java
+++ b/src/com/android/server/telecom/CallAudioRouteStateMachine.java
@@ -18,7 +18,11 @@
import android.app.ActivityManagerNative;
+import android.app.NotificationManager;
+import android.content.BroadcastReceiver;
import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
import android.content.pm.UserInfo;
import android.media.AudioManager;
import android.media.IAudioService;
@@ -105,7 +109,15 @@
/** 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);
+ put(CallAudioState.ROUTE_EARPIECE, Log.Events.AUDIO_ROUTE_EARPIECE);
+ put(CallAudioState.ROUTE_SPEAKER, Log.Events.AUDIO_ROUTE_SPEAKER);
+ put(CallAudioState.ROUTE_WIRED_HEADSET, Log.Events.AUDIO_ROUTE_HEADSET);
+ }};
private static final SparseArray<String> MESSAGE_CODE_TO_NAME = new SparseArray<String>() {{
put(CONNECT_WIRED_HEADSET, "CONNECT_WIRED_HEADSET");
@@ -128,6 +140,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");
@@ -137,10 +151,39 @@
put(RUN_RUNNABLE, "RUN_RUNNABLE");
}};
+ /**
+ * BroadcastReceiver used to track changes in the notification interruption filter. This
+ * ensures changes to the notification interruption filter made by the user during a call are
+ * respected when restoring the notification interruption filter state.
+ */
+ private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ Log.startSession("CARSM.oR");
+ try {
+ String action = intent.getAction();
+
+ if (action.equals(NotificationManager.ACTION_INTERRUPTION_FILTER_CHANGED)) {
+ if (mAreNotificationSuppressed) {
+ // If we've already set the interruption filter, and the user changes it to
+ // something other than INTERRUPTION_FILTER_ALARMS, assume we will no longer
+ // try to change it back if the audio route changes.
+ mAreNotificationSuppressed =
+ mInterruptionFilterProxy.getCurrentInterruptionFilter()
+ == NotificationManager.INTERRUPTION_FILTER_ALARMS;
+ }
+ }
+ } finally {
+ Log.endSession();
+ }
+ }
+ };
+
private static final String ACTIVE_EARPIECE_ROUTE_NAME = "ActiveEarpieceRoute";
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";
@@ -153,7 +196,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);
}
}
@@ -210,6 +253,9 @@
case USER_SWITCH_BASELINE_ROUTE:
sendInternalMessage(calculateBaselineRouteMessage(true));
return HANDLED;
+ case SWITCH_FOCUS:
+ mAudioFocusType = msg.arg1;
+ return NOT_HANDLED;
default:
return NOT_HANDLED;
}
@@ -236,6 +282,9 @@
super.enter();
setSpeakerphoneOn(false);
setBluetoothOn(false);
+ if (mAudioFocusType == ACTIVE_FOCUS) {
+ setNotificationsSuppressed(true);
+ }
CallAudioState newState = new CallAudioState(mIsMuted, ROUTE_EARPIECE,
mAvailableRoutes);
setSystemAudioState(newState);
@@ -243,6 +292,12 @@
}
@Override
+ public void exit() {
+ super.exit();
+ setNotificationsSuppressed(false);
+ }
+
+ @Override
public void updateSystemAudioState() {
updateInternalCallAudioState();
setSystemAudioState(mCurrentCallAudioState);
@@ -261,7 +316,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.");
}
@@ -279,6 +335,10 @@
transitionTo(mActiveSpeakerRoute);
return HANDLED;
case SWITCH_FOCUS:
+ if (msg.arg1 == ACTIVE_FOCUS) {
+ setNotificationsSuppressed(true);
+ }
+
if (msg.arg1 == NO_FOCUS) {
reinitialize();
}
@@ -319,9 +379,11 @@
}
switch (msg.what) {
case SWITCH_EARPIECE:
+ case USER_SWITCH_EARPIECE:
// Nothing to do here
return HANDLED;
case SWITCH_BLUETOOTH:
+ case USER_SWITCH_BLUETOOTH:
if ((mAvailableRoutes & ROUTE_BLUETOOTH) != 0) {
transitionTo(mQuiescentBluetoothRoute);
} else {
@@ -329,6 +391,7 @@
}
return HANDLED;
case SWITCH_HEADSET:
+ case USER_SWITCH_HEADSET:
if ((mAvailableRoutes & ROUTE_WIRED_HEADSET) != 0) {
transitionTo(mQuiescentHeadsetRoute);
} else {
@@ -336,10 +399,11 @@
}
return HANDLED;
case SWITCH_SPEAKER:
+ case USER_SWITCH_SPEAKER:
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;
@@ -438,7 +502,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.");
}
@@ -492,6 +557,7 @@
}
switch (msg.what) {
case SWITCH_EARPIECE:
+ case USER_SWITCH_EARPIECE:
if ((mAvailableRoutes & ROUTE_EARPIECE) != 0) {
transitionTo(mQuiescentEarpieceRoute);
} else {
@@ -499,6 +565,7 @@
}
return HANDLED;
case SWITCH_BLUETOOTH:
+ case USER_SWITCH_BLUETOOTH:
if ((mAvailableRoutes & ROUTE_BLUETOOTH) != 0) {
transitionTo(mQuiescentBluetoothRoute);
} else {
@@ -506,13 +573,15 @@
}
return HANDLED;
case SWITCH_HEADSET:
+ case USER_SWITCH_HEADSET:
// Nothing to do
return HANDLED;
case SWITCH_SPEAKER:
+ case USER_SWITCH_SPEAKER:
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;
@@ -637,6 +706,8 @@
case SWITCH_FOCUS:
if (msg.arg1 == NO_FOCUS) {
reinitialize();
+ } else if (msg.arg1 == RINGING_FOCUS) {
+ transitionTo(mRingingBluetoothRoute);
}
return HANDLED;
case BT_AUDIO_DISCONNECT:
@@ -648,6 +719,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() {
@@ -678,6 +830,7 @@
}
switch (msg.what) {
case SWITCH_EARPIECE:
+ case USER_SWITCH_EARPIECE:
if ((mAvailableRoutes & ROUTE_EARPIECE) != 0) {
transitionTo(mQuiescentEarpieceRoute);
} else {
@@ -685,9 +838,11 @@
}
return HANDLED;
case SWITCH_BLUETOOTH:
+ case USER_SWITCH_BLUETOOTH:
// Nothing to do
return HANDLED;
case SWITCH_HEADSET:
+ case USER_SWITCH_HEADSET:
if ((mAvailableRoutes & ROUTE_WIRED_HEADSET) != 0) {
transitionTo(mQuiescentHeadsetRoute);
} else {
@@ -695,11 +850,14 @@
}
return HANDLED;
case SWITCH_SPEAKER:
+ case USER_SWITCH_SPEAKER:
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:
@@ -797,7 +955,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.");
}
@@ -859,6 +1018,7 @@
}
switch(msg.what) {
case SWITCH_EARPIECE:
+ case USER_SWITCH_EARPIECE:
if ((mAvailableRoutes & ROUTE_EARPIECE) != 0) {
transitionTo(mQuiescentEarpieceRoute);
} else {
@@ -866,6 +1026,7 @@
}
return HANDLED;
case SWITCH_BLUETOOTH:
+ case USER_SWITCH_BLUETOOTH:
if ((mAvailableRoutes & ROUTE_BLUETOOTH) != 0) {
transitionTo(mQuiescentBluetoothRoute);
} else {
@@ -873,6 +1034,7 @@
}
return HANDLED;
case SWITCH_HEADSET:
+ case USER_SWITCH_HEADSET:
if ((mAvailableRoutes & ROUTE_WIRED_HEADSET) != 0) {
transitionTo(mQuiescentHeadsetRoute);
} else {
@@ -880,10 +1042,11 @@
}
return HANDLED;
case SWITCH_SPEAKER:
+ case USER_SWITCH_SPEAKER:
// 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;
@@ -939,6 +1102,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();
@@ -949,8 +1113,10 @@
* states
*/
private int mAvailableRoutes;
+ private int mAudioFocusType;
private boolean mWasOnSpeaker;
private boolean mIsMuted;
+ private boolean mAreNotificationSuppressed = false;
private final Context mContext;
private final CallsManager mCallsManager;
@@ -959,7 +1125,9 @@
private final WiredHeadsetManager mWiredHeadsetManager;
private final StatusBarNotifier mStatusBarNotifier;
private final CallAudioManager.AudioServiceFactory mAudioServiceFactory;
+ private final InterruptionFilterProxy mInterruptionFilterProxy;
private final boolean mDoesDeviceSupportEarpieceRoute;
+ private final TelecomSystem.SyncRoot mLock;
private boolean mHasUserExplicitlyLeftBluetooth = false;
private HashMap<String, Integer> mStateNameToRouteCode;
@@ -977,12 +1145,14 @@
WiredHeadsetManager wiredHeadsetManager,
StatusBarNotifier statusBarNotifier,
CallAudioManager.AudioServiceFactory audioServiceFactory,
+ InterruptionFilterProxy interruptionFilterProxy,
boolean doesDeviceSupportEarpieceRoute) {
super(NAME);
addState(mActiveEarpieceRoute);
addState(mActiveHeadsetRoute);
addState(mActiveBluetoothRoute);
addState(mActiveSpeakerRoute);
+ addState(mRingingBluetoothRoute);
addState(mQuiescentEarpieceRoute);
addState(mQuiescentHeadsetRoute);
addState(mQuiescentBluetoothRoute);
@@ -995,13 +1165,20 @@
mWiredHeadsetManager = wiredHeadsetManager;
mStatusBarNotifier = statusBarNotifier;
mAudioServiceFactory = audioServiceFactory;
+ mInterruptionFilterProxy = interruptionFilterProxy;
+ // Register for misc other intent broadcasts.
+ IntentFilter intentFilter =
+ new IntentFilter(NotificationManager.ACTION_INTERRUPTION_FILTER_CHANGED);
+ context.registerReceiver(mReceiver, intentFilter);
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);
@@ -1100,10 +1277,54 @@
quitNow();
}
+ /**
+ * Sets whether notifications should be suppressed or not. Used when in a call to ensure the
+ * device will not vibrate due to notifications.
+ * Alarm-only filtering is activated when
+ *
+ * @param on {@code true} when notification suppression should be activated, {@code false} when
+ * it should be deactivated.
+ */
+ private void setNotificationsSuppressed(boolean on) {
+ if (mInterruptionFilterProxy == null) {
+ return;
+ }
+
+ Log.i(this, "setNotificationsSuppressed: on=%s; suppressed=%s", (on ? "yes" : "no"),
+ (mAreNotificationSuppressed ? "yes" : "no"));
+ if (on) {
+ if (!mAreNotificationSuppressed) {
+ // Enabling suppression of notifications.
+ int interruptionFilter = mInterruptionFilterProxy.getCurrentInterruptionFilter();
+ if (interruptionFilter == NotificationManager.INTERRUPTION_FILTER_ALL) {
+ // No interruption filter is specified, so suppress notifications by setting the
+ // current filter to alarms-only.
+ mAreNotificationSuppressed = true;
+ mInterruptionFilterProxy.setInterruptionFilter(
+ NotificationManager.INTERRUPTION_FILTER_ALARMS);
+ } else {
+ // Interruption filter is already chosen by the user, so do not attempt to change
+ // it.
+ mAreNotificationSuppressed = false;
+ }
+ }
+ } else {
+ // Disabling suppression of notifications.
+ if (mAreNotificationSuppressed) {
+ // We have implemented the alarms-only policy and the user has not changed it since
+ // we originally set it, so reset the notification filter.
+ mInterruptionFilterProxy.setInterruptionFilter(
+ NotificationManager.INTERRUPTION_FILTER_ALL);
+ }
+ mAreNotificationSuppressed = false;
+ }
+ }
+
private void setSpeakerphoneOn(boolean on) {
if (mAudioManager.isSpeakerphoneOn() != on) {
Log.i(this, "turning speaker phone %s", on);
mAudioManager.setSpeakerphoneOn(on);
+ mStatusBarNotifier.notifySpeakerphone(on);
}
}
@@ -1123,8 +1344,8 @@
private void setMuteOn(boolean mute) {
mIsMuted = mute;
- Log.event(mCallsManager.getForegroundCall(), Log.Events.MUTE,
- mute ? "on" : "off");
+ Log.event(mCallsManager.getForegroundCall(), mute ? Log.Events.MUTE : Log.Events.UNMUTE);
+
if (mute != mAudioManager.isMicrophoneMute() && isInActiveState()) {
IAudioService audio = mAudioServiceFactory.getAudioService();
Log.i(this, "changing microphone mute state to: %b [serviceIsNull=%b]",
@@ -1138,6 +1359,7 @@
// user and not the current foreground, which we want to avoid.
audio.setMicrophoneMute(
mute, mContext.getOpPackageName(), getCurrentUserId());
+ mStatusBarNotifier.notifyMute(mute);
} catch (RemoteException e) {
Log.e(this, e, "Remote exception while toggling mute.");
@@ -1174,15 +1396,20 @@
}
private void setSystemAudioState(CallAudioState newCallAudioState, boolean force) {
- Log.i(this, "setSystemAudioState: changing from %s to %s", mLastKnownCallAudioState,
- newCallAudioState);
- Log.event(mCallsManager.getForegroundCall(), Log.Events.AUDIO_ROUTE,
- CallAudioState.audioRouteToString(newCallAudioState.getRoute()));
+ 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));
+ }
- if (force || !newCallAudioState.equals(mLastKnownCallAudioState)) {
- mCallsManager.onCallAudioStateChanged(mLastKnownCallAudioState, newCallAudioState);
- updateAudioForForegroundCall(newCallAudioState);
- mLastKnownCallAudioState = newCallAudioState;
+ mCallsManager.onCallAudioStateChanged(mLastKnownCallAudioState, newCallAudioState);
+ updateAudioForForegroundCall(newCallAudioState);
+ mLastKnownCallAudioState = newCallAudioState;
+ }
}
}
@@ -1301,6 +1528,7 @@
setMuteOn(mIsMuted);
mWasOnSpeaker = false;
mHasUserExplicitlyLeftBluetooth = false;
+ mLastKnownCallAudioState = initState;
transitionTo(mRouteCodeToQuiescentState.get(initState.getRoute()));
}
-}
\ No newline at end of file
+}
diff --git a/src/com/android/server/telecom/CallIdMapper.java b/src/com/android/server/telecom/CallIdMapper.java
index b097bea..da2c199 100644
--- a/src/com/android/server/telecom/CallIdMapper.java
+++ b/src/com/android/server/telecom/CallIdMapper.java
@@ -77,7 +77,16 @@
}
}
+ public interface ICallInfo {
+ String getCallId(Call call);
+ }
+
private final BiMap<String, Call> mCalls = new BiMap<>();
+ private ICallInfo mCallInfo;
+
+ public CallIdMapper(ICallInfo callInfo) {
+ mCallInfo = callInfo;
+ }
void replaceCall(Call newCall, Call callToReplace) {
// Use the old call's ID for the new call.
@@ -93,7 +102,7 @@
}
void addCall(Call call) {
- addCall(call, call.getId());
+ addCall(call, mCallInfo.getCallId(call));
}
void removeCall(Call call) {
@@ -111,7 +120,7 @@
if (call == null || mCalls.getKey(call) == null) {
return null;
}
- return call.getId();
+ return mCallInfo.getCallId(call);
}
Call getCall(Object objId) {
diff --git a/src/com/android/server/telecom/CallIntentProcessor.java b/src/com/android/server/telecom/CallIntentProcessor.java
index ffa76ce..1219216 100644
--- a/src/com/android/server/telecom/CallIntentProcessor.java
+++ b/src/com/android/server/telecom/CallIntentProcessor.java
@@ -148,7 +148,7 @@
// process will be running throughout the duration of the phone call and should never
// be killed.
NewOutgoingCallIntentBroadcaster broadcaster = new NewOutgoingCallIntentBroadcaster(
- context, callsManager, call, intent, new PhoneNumberUtilsAdapterImpl(),
+ context, callsManager, call, intent, callsManager.getPhoneNumberUtilsAdapter(),
isPrivilegedDialer);
final int result = broadcaster.processIntent();
final boolean success = result == DisconnectCause.NOT_DISCONNECTED;
diff --git a/src/com/android/server/telecom/CallLogManager.java b/src/com/android/server/telecom/CallLogManager.java
index 57b373b..64e266d 100755
--- a/src/com/android/server/telecom/CallLogManager.java
+++ b/src/com/android/server/telecom/CallLogManager.java
@@ -144,15 +144,21 @@
// 1) It was not in the "choose account" phase when disconnected
// 2) It is a conference call
// 3) Call was not explicitly canceled
+ // 4) Call is not an external call
if (isNewlyDisconnected &&
(oldState != CallState.SELECT_PHONE_ACCOUNT &&
!call.isConference() &&
- !isCallCanceled)) {
+ !isCallCanceled) &&
+ !call.isExternalCall()) {
int type;
if (!call.isIncoming()) {
type = Calls.OUTGOING_TYPE;
} else if (disconnectCause == DisconnectCause.MISSED) {
type = Calls.MISSED_TYPE;
+ } else if (disconnectCause == DisconnectCause.ANSWERED_ELSEWHERE) {
+ type = Calls.ANSWERED_EXTERNALLY_TYPE;
+ } else if (disconnectCause == DisconnectCause.REJECTED) {
+ type = Calls.REJECTED_TYPE;
} else {
type = Calls.INCOMING_TYPE;
}
@@ -210,7 +216,8 @@
Long callDataUsage = call.getCallDataUsage() == Call.DATA_USAGE_NOT_SET ? null :
call.getCallDataUsage();
- int callFeatures = getCallFeatures(call.getVideoStateHistory());
+ int callFeatures = getCallFeatures(call.getVideoStateHistory(),
+ call.getDisconnectCause().getCode() == DisconnectCause.CALL_PULLED);
logCall(call.getCallerInfo(), logNumber, call.getPostDialDigits(), formattedViaNumber,
call.getHandlePresentation(), callLogType, callFeatures, accountHandle,
creationTime, age, callDataUsage, call.isEmergencyCall(), call.getInitiatingUser(),
@@ -283,13 +290,18 @@
* Based on the video state of the call, determines the call features applicable for the call.
*
* @param videoState The video state.
+ * @param isPulledCall {@code true} if this call was pulled to another device.
* @return The call features.
*/
- private static int getCallFeatures(int videoState) {
+ private static int getCallFeatures(int videoState, boolean isPulledCall) {
+ int features = 0;
if (VideoProfile.isVideo(videoState)) {
- return Calls.FEATURES_VIDEO;
+ features |= Calls.FEATURES_VIDEO;
}
- return 0;
+ if (isPulledCall) {
+ features |= Calls.FEATURES_PULLED_EXTERNALLY;
+ }
+ return features;
}
/**
diff --git a/src/com/android/server/telecom/CallState.java b/src/com/android/server/telecom/CallState.java
index 0d2ca48..0aa928f 100644
--- a/src/com/android/server/telecom/CallState.java
+++ b/src/com/android/server/telecom/CallState.java
@@ -103,6 +103,15 @@
*/
public static final int DISCONNECTING = 9;
+ /**
+ * Indicates that the call is in the process of being pulled to the local device.
+ * <p>
+ * This state should only be set on a call with
+ * {@link android.telecom.Connection#PROPERTY_IS_EXTERNAL_CALL} and
+ * {@link android.telecom.Connection#CAPABILITY_CAN_PULL_CALL}.
+ */
+ public static final int PULLING = 10;
+
public static String toString(int callState) {
switch (callState) {
case NEW:
@@ -125,6 +134,8 @@
return "ABORTED";
case DISCONNECTING:
return "DISCONNECTING";
+ case PULLING:
+ return "PULLING";
default:
return "UNKNOWN";
}
diff --git a/src/com/android/server/telecom/CallerInfoLookupHelper.java b/src/com/android/server/telecom/CallerInfoLookupHelper.java
index 4561c1c..244a802 100644
--- a/src/com/android/server/telecom/CallerInfoLookupHelper.java
+++ b/src/com/android/server/telecom/CallerInfoLookupHelper.java
@@ -16,6 +16,7 @@
package com.android.server.telecom;
+import android.annotation.Nullable;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
@@ -41,7 +42,7 @@
* @param info
* @return true if the value should be cached, false otherwise.
*/
- void onCallerInfoQueryComplete(Uri handle, CallerInfo info);
+ void onCallerInfoQueryComplete(Uri handle, @Nullable CallerInfo info);
void onContactPhotoQueryComplete(Uri handle, CallerInfo info);
}
@@ -54,6 +55,7 @@
listeners = new LinkedList<>();
}
}
+
private final Map<Uri, CallerInfoQueryInfo> mQueryEntries = new HashMap<>();
private final CallerInfoAsyncQueryFactory mCallerInfoAsyncQueryFactory;
@@ -74,11 +76,13 @@
public void startLookup(final Uri handle, OnQueryCompleteListener listener) {
if (handle == null) {
+ listener.onCallerInfoQueryComplete(handle, null);
return;
}
final String number = handle.getSchemeSpecificPart();
if (TextUtils.isEmpty(number)) {
+ listener.onCallerInfoQueryComplete(handle, null);
return;
}
@@ -93,7 +97,7 @@
info.callerInfo.cachedPhotoIcon != null)) {
listener.onContactPhotoQueryComplete(handle, info.callerInfo);
} else if (info.imageQueryPending) {
- Log.i(this, "There is a previously incomplete query for handle %s. " +
+ Log.i(this, "There is a pending photo query for handle %s. " +
"Adding to listeners for this query.", Log.piiHandle(handle));
info.listeners.add(listener);
}
@@ -110,7 +114,7 @@
}
}
- mHandler.post(new Runnable("CILH.sL") {
+ mHandler.post(new Runnable("CILH.sL", mLock) {
@Override
public void loggedRun() {
Session continuedSession = Log.createSubsession();
@@ -137,11 +141,15 @@
Log.continueSession((Session) cookie, "CILH.oQC");
try {
if (mQueryEntries.containsKey(handle)) {
+ Log.i(CallerInfoLookupHelper.this, "CI query for handle %s has completed;" +
+ " notifying all listeners.", Log.piiHandle(handle));
CallerInfoQueryInfo info = mQueryEntries.get(handle);
for (OnQueryCompleteListener l : info.listeners) {
l.onCallerInfoQueryComplete(handle, ci);
}
if (ci.contactDisplayPhotoUri == null) {
+ Log.i(CallerInfoLookupHelper.this, "There is no photo for this " +
+ "contact, skipping photo query");
mQueryEntries.remove(handle);
} else {
info.callerInfo = ci;
@@ -150,7 +158,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();
@@ -160,7 +168,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();
@@ -187,6 +195,7 @@
if (info.callerInfo == null) {
Log.w(CallerInfoLookupHelper.this, "Photo query finished, but the " +
"CallerInfo object previously looked up was not cached.");
+ mQueryEntries.remove(handle);
return;
}
info.callerInfo.cachedPhoto = photo;
@@ -197,7 +206,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 8d43842..5318f16 100644
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -17,6 +17,7 @@
package com.android.server.telecom;
import android.app.ActivityManager;
+import android.app.NotificationManager;
import android.content.Context;
import android.content.pm.UserInfo;
import android.content.Intent;
@@ -69,6 +70,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;
@@ -102,7 +104,7 @@
void onRingbackRequested(Call call, boolean ringback);
void onIsConferencedChanged(Call call);
void onIsVoipAudioModeChanged(Call call);
- void onVideoStateChanged(Call call);
+ void onVideoStateChanged(Call call, int previousVideoState, int newVideoState);
void onCanAddCallChanged(boolean canAddCall);
void onSessionModifyRequestReceived(Call call, VideoProfile videoProfile);
void onHoldToneRequested(Call call);
@@ -119,11 +121,13 @@
private static final int MAXIMUM_TOP_LEVEL_CALLS = 2;
private static final int[] OUTGOING_CALL_STATES =
- {CallState.CONNECTING, CallState.SELECT_PHONE_ACCOUNT, CallState.DIALING};
+ {CallState.CONNECTING, CallState.SELECT_PHONE_ACCOUNT, CallState.DIALING,
+ CallState.PULLING};
private static final int[] LIVE_CALL_STATES =
{CallState.CONNECTING, CallState.SELECT_PHONE_ACCOUNT, CallState.DIALING,
- CallState.ACTIVE};
+ CallState.PULLING, CallState.ACTIVE};
+
public static final String TELECOM_CALL_ID_PREFIX = "TC@";
// Maps call technologies in PhoneConstants to those in Analytics.
@@ -189,6 +193,8 @@
private final CallerInfoLookupHelper mCallerInfoLookupHelper;
private final DefaultDialerManagerAdapter mDefaultDialerManagerAdapter;
private final Timeouts.Adapter mTimeoutsAdapter;
+ private final PhoneNumberUtilsAdapter mPhoneNumberUtilsAdapter;
+ private final NotificationManager mNotificationManager;
private final Set<Call> mLocallyDisconnectingCalls = new HashSet<>();
private final Set<Call> mPendingCallsToDisconnect = new HashSet<>();
/* Handler tied to thread in which CallManager was initialized. */
@@ -219,9 +225,12 @@
SystemStateProvider systemStateProvider,
DefaultDialerManagerAdapter defaultDialerAdapter,
Timeouts.Adapter timeoutsAdapter,
- AsyncRingtonePlayer asyncRingtonePlayer) {
+ AsyncRingtonePlayer asyncRingtonePlayer,
+ PhoneNumberUtilsAdapter phoneNumberUtilsAdapter,
+ InterruptionFilterProxy interruptionFilterProxy) {
mContext = context;
mLock = lock;
+ mPhoneNumberUtilsAdapter = phoneNumberUtilsAdapter;
mContactsAsyncHelper = contactsAsyncHelper;
mCallerInfoAsyncQueryFactory = callerInfoAsyncQueryFactory;
mPhoneAccountRegistrar = phoneAccountRegistrar;
@@ -236,6 +245,8 @@
mContactsAsyncHelper, mLock);
mDtmfLocalTonePlayer = new DtmfLocalTonePlayer();
+ mNotificationManager = (NotificationManager) context.getSystemService(
+ Context.NOTIFICATION_SERVICE);
CallAudioRouteStateMachine callAudioRouteStateMachine = new CallAudioRouteStateMachine(
context,
this,
@@ -243,6 +254,7 @@
wiredHeadsetManager,
statusBarNotifier,
audioServiceFactory,
+ interruptionFilterProxy,
CallAudioRouteStateMachine.doesDeviceSupportEarpieceRoute()
);
callAudioRouteStateMachine.initialize();
@@ -261,7 +273,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);
@@ -342,6 +354,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()));
@@ -363,6 +381,7 @@
result.shouldAllowCall ? "successful incoming call" : "blocking call");
} else {
Log.i(this, "onCallFilteringCompleted: call already disconnected.");
+ return;
}
if (result.shouldAllowCall) {
@@ -441,15 +460,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(),
@@ -471,7 +487,7 @@
@Override
public void onParentChanged(Call call) {
// parent-child relationship affects which call should be foreground, so do an update.
- updateCallsManagerState();
+ updateCanAddCall();
for (CallsManagerListener listener : mListeners) {
listener.onIsConferencedChanged(call);
}
@@ -480,7 +496,7 @@
@Override
public void onChildrenChanged(Call call) {
// parent-child relationship affects which call should be foreground, so do an update.
- updateCallsManagerState();
+ updateCanAddCall();
for (CallsManagerListener listener : mListeners) {
listener.onIsConferencedChanged(call);
}
@@ -494,23 +510,21 @@
}
@Override
- public void onVideoStateChanged(Call call) {
+ public void onVideoStateChanged(Call call, int previousVideoState, int newVideoState) {
for (CallsManagerListener listener : mListeners) {
- listener.onVideoStateChanged(call);
+ listener.onVideoStateChanged(call, previousVideoState, newVideoState);
}
}
@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()));
@@ -625,7 +639,8 @@
return false;
}
- CallAudioState getAudioState() {
+ @VisibleForTesting
+ public CallAudioState getAudioState() {
return mCallAudioManager.getCallAudioState();
}
@@ -668,6 +683,7 @@
mConnectionServiceRepository,
mContactsAsyncHelper,
mCallerInfoAsyncQueryFactory,
+ mPhoneNumberUtilsAdapter,
handle,
null /* gatewayInfo */,
null /* connectionManagerPhoneAccount */,
@@ -700,6 +716,7 @@
mConnectionServiceRepository,
mContactsAsyncHelper,
mCallerInfoAsyncQueryFactory,
+ mPhoneNumberUtilsAdapter,
handle,
null /* gatewayInfo */,
null /* connectionManagerPhoneAccount */,
@@ -735,9 +752,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 {
@@ -772,6 +790,7 @@
mConnectionServiceRepository,
mContactsAsyncHelper,
mCallerInfoAsyncQueryFactory,
+ mPhoneNumberUtilsAdapter,
handle,
null /* gatewayInfo */,
null /* connectionManagerPhoneAccount */,
@@ -780,10 +799,10 @@
false /* forceAttachToExistingConnection */,
false /* isConference */
);
- call.setInitiatingUser(initiatingUser);
-
call.initAnalytics();
+ call.setInitiatingUser(initiatingUser);
+
isReusedCall = false;
}
@@ -795,11 +814,13 @@
// If this is an emergency video call, we need to check if the phone account supports
// emergency video calling.
- if (call.isEmergencyCall() && VideoProfile.isVideo(videoState)) {
+ // Also, ensure we don't try to place an outgoing call with video if video is not
+ // supported.
+ if (VideoProfile.isVideo(videoState)) {
PhoneAccount account =
mPhoneAccountRegistrar.getPhoneAccount(phoneAccountHandle, initiatingUser);
- if (account != null &&
+ if (call.isEmergencyCall() && account != null &&
!account.hasCapabilities(PhoneAccount.CAPABILITY_EMERGENCY_VIDEO_CALLING)) {
// Phone account doesn't support emergency video calling, so fallback to
// audio-only now to prevent the InCall UI from setting up video surfaces
@@ -807,6 +828,12 @@
Log.i(this, "startOutgoingCall - emergency video calls not supported; " +
"falling back to audio-only");
videoState = VideoProfile.STATE_AUDIO_ONLY;
+ } else if (account != null &&
+ !account.hasCapabilities(PhoneAccount.CAPABILITY_VIDEO_CALLING)) {
+ // Phone account doesn't support video calling, so fallback to audio-only.
+ Log.i(this, "startOutgoingCall - video calls not supported; fallback to " +
+ "audio-only.");
+ videoState = VideoProfile.STATE_AUDIO_ONLY;
}
}
@@ -825,7 +852,7 @@
}
}
- if (phoneAccountHandle == null && accounts.size() > 0 && !call.isEmergencyCall()) {
+ if (phoneAccountHandle == null && accounts.size() > 0) {
// No preset account, check if default exists that supports the URI scheme for the
// handle and verify it can be used.
if(accounts.size() > 1) {
@@ -924,26 +951,24 @@
final boolean useSpeakerWhenDocked = mContext.getResources().getBoolean(
R.bool.use_speaker_when_docked);
- final boolean isDocked = mDockManager.isDocked();
- final boolean useSpeakerForVideoCall = isSpeakerphoneAutoEnabled(videoState);
+ final boolean useSpeakerForDock = isSpeakerphoneEnabledForDock();
+ final boolean useSpeakerForVideoCall = isSpeakerphoneAutoEnabledForVideoCalls(videoState);
// Auto-enable speakerphone if the originating intent specified to do so, if the call
// is a video call, of if using speaker when docked
call.setStartWithSpeakerphoneOn(speakerphoneOn || useSpeakerForVideoCall
- || (useSpeakerWhenDocked && isDocked));
+ || (useSpeakerWhenDocked && useSpeakerForDock));
call.setVideoState(videoState);
if (speakerphoneOn) {
Log.i(this, "%s Starting with speakerphone as requested", call);
- } else if (useSpeakerWhenDocked && useSpeakerWhenDocked) {
+ } else if (useSpeakerWhenDocked && useSpeakerForDock) {
Log.i(this, "%s Starting with speakerphone because car is docked.", call);
} else if (useSpeakerForVideoCall) {
Log.i(this, "%s Starting with speakerphone because its a video call.", call);
}
if (call.isEmergencyCall()) {
- // Emergency -- CreateConnectionProcessor will choose accounts automatically
- call.setTargetPhoneAccount(null);
new AsyncEmergencyContactNotifier(mContext).execute();
}
@@ -993,7 +1018,8 @@
// STATE_DIALING, put it on hold before answering the call.
if (foregroundCall != null && foregroundCall != call &&
(foregroundCall.isActive() ||
- foregroundCall.getState() == CallState.DIALING)) {
+ foregroundCall.getState() == CallState.DIALING ||
+ foregroundCall.getState() == CallState.PULLING)) {
if (0 == (foregroundCall.getConnectionCapabilities()
& Connection.CAPABILITY_HOLD)) {
// This call does not support hold. If it is from a different connection
@@ -1026,7 +1052,7 @@
// We do not update the UI until we get confirmation of the answer() through
// {@link #markCallAsActive}.
call.answer(videoState);
- if (isSpeakerphoneAutoEnabled(videoState)) {
+ if (isSpeakerphoneAutoEnabledForVideoCalls(videoState)) {
call.setStartWithSpeakerphoneOn(true);
}
}
@@ -1040,7 +1066,7 @@
* @param videoState The video state of the call.
* @return {@code true} if the speakerphone should be enabled.
*/
- private boolean isSpeakerphoneAutoEnabled(int videoState) {
+ public boolean isSpeakerphoneAutoEnabledForVideoCalls(int videoState) {
return VideoProfile.isVideo(videoState) &&
!mWiredHeadsetManager.isPluggedIn() &&
!mBluetoothManager.isBluetoothAvailable() &&
@@ -1048,6 +1074,19 @@
}
/**
+ * Determines if the speakerphone should be enabled for when docked. Speakerphone
+ * should be enabled if the device is docked and bluetooth or the wired headset are
+ * not in use.
+ *
+ * @return {@code true} if the speakerphone should be enabled for the dock.
+ */
+ private boolean isSpeakerphoneEnabledForDock() {
+ return mDockManager.isDocked() &&
+ !mWiredHeadsetManager.isPluggedIn() &&
+ !mBluetoothManager.isBluetoothAvailable();
+ }
+
+ /**
* Determines if the speakerphone should be automatically enabled for video calls.
*
* @return {@code true} if the speakerphone should automatically be enabled.
@@ -1168,13 +1207,19 @@
if (!mCalls.contains(call)) {
Log.w(this, "Unknown call (%s) asked to be removed from hold", call);
} else {
+ boolean otherCallHeld = false;
Log.d(this, "unholding call: (%s)", call);
for (Call c : mCalls) {
// Only attempt to hold parent calls and not the individual children.
if (c != null && c.isAlive() && c != call && c.getParentCall() == null) {
+ otherCallHeld = true;
+ Log.event(c, Log.Events.SWAP);
c.hold();
}
}
+ if (otherCallHeld) {
+ Log.event(call, Log.Events.SWAP);
+ }
call.unhold();
}
}
@@ -1186,6 +1231,7 @@
}
handleCallTechnologyChange(c);
handleChildAddressChange(c);
+ updateCanAddCall();
}
// Construct the list of possible PhoneAccounts that the outgoing call can use based on the
@@ -1334,6 +1380,11 @@
maybeMoveToSpeakerPhone(call);
}
+ void markCallAsPulling(Call call) {
+ setCallState(call, CallState.PULLING, "pulling set explicitly");
+ maybeMoveToSpeakerPhone(call);
+ }
+
void markCallAsActive(Call call) {
setCallState(call, CallState.ACTIVE, "active set explicitly");
maybeMoveToSpeakerPhone(call);
@@ -1359,12 +1410,21 @@
*/
void markCallAsRemoved(Call call) {
removeCall(call);
+ Call foregroundCall = mCallAudioManager.getPossiblyHeldForegroundCall();
if (mLocallyDisconnectingCalls.contains(call)) {
mLocallyDisconnectingCalls.remove(call);
- Call foregroundCall = mCallAudioManager.getPossiblyHeldForegroundCall();
if (foregroundCall != null && foregroundCall.getState() == CallState.ON_HOLD) {
foregroundCall.unhold();
}
+ } else if (foregroundCall != null &&
+ !foregroundCall.can(Connection.CAPABILITY_SUPPORT_HOLD) &&
+ foregroundCall.getState() == CallState.ON_HOLD) {
+
+ // The new foreground call is on hold, however the carrier does not display the hold
+ // button in the UI. Therefore, we need to auto unhold the held call since the user has
+ // no means of unholding it themselves.
+ Log.i(this, "Auto-unholding held foreground call (call doesn't support hold)");
+ foregroundCall.unhold();
}
}
@@ -1421,13 +1481,14 @@
mCallAudioManager.toggleMute();
return true;
} else {
- ringingCall.answer(ringingCall.getVideoState());
+ ringingCall.answer(VideoProfile.STATE_AUDIO_ONLY);
return true;
}
} else if (HeadsetMediaButton.LONG_PRESS == type) {
Log.d(this, "handleHeadsetHook: longpress -> hangup");
Call callToHangup = getFirstCallWithState(
- CallState.RINGING, CallState.DIALING, CallState.ACTIVE, CallState.ON_HOLD);
+ CallState.RINGING, CallState.DIALING, CallState.PULLING, CallState.ACTIVE,
+ CallState.ON_HOLD);
if (callToHangup != null) {
callToHangup.disconnect();
return true;
@@ -1440,7 +1501,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) {
@@ -1457,9 +1519,18 @@
if (call.isEmergencyCall()) {
// We never support add call if one of the calls is an emergency call.
return false;
+ } else if (call.isExternalCall()) {
+ // External calls don't count.
+ continue;
} else if (call.getParentCall() == null) {
count++;
}
+ Bundle extras = call.getExtras();
+ if (extras != null) {
+ if (extras.getBoolean(Connection.EXTRA_DISABLE_ADD_CALL, false)) {
+ return false;
+ }
+ }
// We do not check states for canAddCall. We treat disconnected calls the same
// and wait until they are removed instead. If we didn't count disconnected calls,
@@ -1470,6 +1541,7 @@
return false;
}
}
+
return true;
}
@@ -1513,6 +1585,11 @@
return getFirstCallWithState(null, states);
}
+ @VisibleForTesting
+ public PhoneNumberUtilsAdapter getPhoneNumberUtilsAdapter() {
+ return mPhoneNumberUtilsAdapter;
+ }
+
/**
* Returns the first call that it finds with the given states. The states are treated as having
* priority order so that any call with the first state will be returned before any call with
@@ -1538,6 +1615,10 @@
continue;
}
+ if (call.isExternalCall()) {
+ continue;
+ }
+
if (currentState == call.getState()) {
return call;
}
@@ -1566,6 +1647,7 @@
mConnectionServiceRepository,
mContactsAsyncHelper,
mCallerInfoAsyncQueryFactory,
+ mPhoneNumberUtilsAdapter,
null /* handle */,
null /* gatewayInfo */,
null /* connectionManagerPhoneAccount */,
@@ -1619,7 +1701,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?
@@ -1644,7 +1735,7 @@
extras.putLong(TelecomManager.EXTRA_CALL_TELECOM_ROUTING_END_TIME_MILLIS,
SystemClock.elapsedRealtime());
- updateCallsManagerState();
+ updateCanAddCall();
// onCallAdded for calls which immediately take the foreground (like the first call).
for (CallsManagerListener listener : mListeners) {
if (Log.SYSTRACE_DEBUG) {
@@ -1676,7 +1767,7 @@
// Only broadcast changes for calls that are being tracked.
if (shouldNotify) {
- updateCallsManagerState();
+ updateCanAddCall();
for (CallsManagerListener listener : mListeners) {
if (Log.SYSTRACE_DEBUG) {
Trace.beginSection(listener.getClass().toString() + " onCallRemoved");
@@ -1717,7 +1808,7 @@
Trace.beginSection("onCallStateChanged");
// Only broadcast state change for calls that are being tracked.
if (mCalls.contains(call)) {
- updateCallsManagerState();
+ updateCanAddCall();
for (CallsManagerListener listener : mListeners) {
if (Log.SYSTRACE_DEBUG) {
Trace.beginSection(listener.getClass().toString() + " onCallStateChanged");
@@ -1748,10 +1839,6 @@
}
}
- private void updateCallsManagerState() {
- updateCanAddCall();
- }
-
private boolean isPotentialMMICode(Uri handle) {
return (handle != null && handle.getSchemeSpecificPart() != null
&& handle.getSchemeSpecificPart().contains("#"));
@@ -1787,7 +1874,9 @@
int count = 0;
for (int state : states) {
for (Call call : mCalls) {
- if (call.getParentCall() == null && call.getState() == state) {
+ if (call.getParentCall() == null && call.getState() == state &&
+ !call.isExternalCall()) {
+
count++;
}
}
@@ -1812,7 +1901,7 @@
}
private boolean hasMaximumDialingCalls() {
- return MAXIMUM_DIALING_CALLS <= getNumCallsWithState(CallState.DIALING);
+ return MAXIMUM_DIALING_CALLS <= getNumCallsWithState(CallState.DIALING, CallState.PULLING);
}
private boolean makeRoomForOutgoingCall(Call call, boolean isEmergency) {
@@ -1950,6 +2039,8 @@
* @return The new call.
*/
Call createCallForExistingConnection(String callId, ParcelableConnection connection) {
+ boolean isDowngradedConference = (connection.getConnectionProperties()
+ & Connection.PROPERTY_IS_DOWNGRADED_CONFERENCE) != 0;
Call call = new Call(
callId,
mContext,
@@ -1958,13 +2049,14 @@
mConnectionServiceRepository,
mContactsAsyncHelper,
mCallerInfoAsyncQueryFactory,
+ mPhoneNumberUtilsAdapter,
connection.getHandle() /* handle */,
null /* gatewayInfo */,
null /* connectionManagerPhoneAccount */,
connection.getPhoneAccount(), /* targetPhoneAccountHandle */
Call.CALL_DIRECTION_UNDEFINED /* callDirection */,
false /* forceAttachToExistingConnection */,
- false /* isConference */,
+ isDowngradedConference /* isConference */,
connection.getConnectTimeMillis() /* connectTimeMillis */);
call.initAnalytics();
@@ -2016,6 +2108,10 @@
}
}
+ public TelecomSystem.SyncRoot getLock() {
+ return mLock;
+ }
+
private void reloadMissedCallsOfUser(UserHandle userHandle) {
mMissedCallNotifier.reloadFromDatabase(mCallerInfoLookupHelper,
new MissedCallNotifier.CallInfoFactory(), userHandle);
diff --git a/src/com/android/server/telecom/CallsManagerListenerBase.java b/src/com/android/server/telecom/CallsManagerListenerBase.java
index 9bfa098..a4c76c1 100644
--- a/src/com/android/server/telecom/CallsManagerListenerBase.java
+++ b/src/com/android/server/telecom/CallsManagerListenerBase.java
@@ -69,7 +69,7 @@
}
@Override
- public void onVideoStateChanged(Call call) {
+ public void onVideoStateChanged(Call call, int previousVideoState, int newVideoState) {
}
@Override
diff --git a/src/com/android/server/telecom/ConnectionServiceWrapper.java b/src/com/android/server/telecom/ConnectionServiceWrapper.java
index 563d9e1..bf82a99 100644
--- a/src/com/android/server/telecom/ConnectionServiceWrapper.java
+++ b/src/com/android/server/telecom/ConnectionServiceWrapper.java
@@ -16,6 +16,7 @@
package com.android.server.telecom;
+import android.app.AppOpsManager;
import android.content.ComponentName;
import android.content.Context;
import android.net.Uri;
@@ -32,6 +33,7 @@
import android.telecom.GatewayInfo;
import android.telecom.ParcelableConference;
import android.telecom.ParcelableConnection;
+import android.telecom.PhoneAccount;
import android.telecom.PhoneAccountHandle;
import android.telecom.StatusHints;
import android.telecom.TelecomManager;
@@ -66,7 +68,7 @@
@Override
public void handleCreateConnectionComplete(String callId, ConnectionRequest request,
ParcelableConnection connection) {
- Log.startSession("CSW.hCCC");
+ Log.startSession(Log.Sessions.CSW_HANDLE_CREATE_CONNECTION_COMPLETE);
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
@@ -82,7 +84,7 @@
@Override
public void setActive(String callId) {
- Log.startSession("CSW.sA");
+ Log.startSession(Log.Sessions.CSW_SET_ACTIVE);
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
@@ -102,7 +104,7 @@
@Override
public void setRinging(String callId) {
- Log.startSession("CSW.sR");
+ Log.startSession(Log.Sessions.CSW_SET_RINGING);
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
@@ -140,7 +142,7 @@
@Override
public void setDialing(String callId) {
- Log.startSession("CSW.sD");
+ Log.startSession(Log.Sessions.CSW_SET_DIALING);
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
@@ -159,8 +161,26 @@
}
@Override
+ public void setPulling(String callId) {
+ Log.startSession(Log.Sessions.CSW_SET_PULLING);
+ long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ logIncoming("setPulling %s", callId);
+ Call call = mCallIdMapper.getCall(callId);
+ if (call != null) {
+ mCallsManager.markCallAsPulling(call);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ Log.endSession();
+ }
+ }
+
+ @Override
public void setDisconnected(String callId, DisconnectCause disconnectCause) {
- Log.startSession("CSW.sD");
+ Log.startSession(Log.Sessions.CSW_SET_DISCONNECTED);
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
@@ -181,7 +201,7 @@
@Override
public void setOnHold(String callId) {
- Log.startSession("CSW.sOH");
+ Log.startSession(Log.Sessions.CSW_SET_ON_HOLD);
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
@@ -221,7 +241,7 @@
@Override
public void removeCall(String callId) {
- Log.startSession("CSW.rC");
+ Log.startSession(Log.Sessions.CSW_REMOVE_CALL);
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
@@ -283,7 +303,7 @@
@Override
public void setIsConferenced(String callId, String conferenceCallId) {
- Log.startSession("CSW.sIC");
+ Log.startSession(Log.Sessions.CSW_SET_IS_CONFERENCED);
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
@@ -319,15 +339,7 @@
// deliver the message anyway that they want. b/20530631.
Call call = mCallIdMapper.getCall(callId);
if (call != null) {
- // Just refresh the connection capabilities so that the UI
- // is forced to reenable the merge button as the capability
- // is still on the connection. Note when b/20530631 is fixed, we need
- // to revisit this fix to remove this hacky way of unhiding the merge
- // button (side effect of reprocessing the capabilities) and plumb
- // the failure event all the way to InCallUI instead of stopping
- // it here. That way we can also handle the UI of notifying that
- // the merged has failed.
- call.setConnectionCapabilities(call.getConnectionCapabilities(), true);
+ call.onConnectionEvent(Connection.EVENT_CALL_MERGE_FAILED, null);
} else {
Log.w(this, "setConferenceMergeFailed, unknown call id: %s", callId);
}
@@ -340,7 +352,7 @@
@Override
public void addConferenceCall(String callId, ParcelableConference parcelableConference) {
- Log.startSession("CSW.aCC");
+ Log.startSession(Log.Sessions.CSW_ADD_CONFERENCE_CALL);
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
@@ -609,14 +621,39 @@
@Override
public void addExistingConnection(String callId, ParcelableConnection connection) {
Log.startSession("CSW.aEC");
+ UserHandle userHandle = Binder.getCallingUserHandle();
+ // Check that the Calling Package matches PhoneAccountHandle's Component Package
+ PhoneAccountHandle callingPhoneAccountHandle = connection.getPhoneAccount();
+ if (callingPhoneAccountHandle != null) {
+ mAppOpsManager.checkPackage(Binder.getCallingUid(),
+ callingPhoneAccountHandle.getComponentName().getPackageName());
+ }
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
- logIncoming("addExistingConnection %s %s", callId, connection);
- Call existingCall = mCallsManager
- .createCallForExistingConnection(callId, connection);
- mCallIdMapper.addCall(existingCall, callId);
- existingCall.setConnectionService(ConnectionServiceWrapper.this);
+ // Make sure that the PhoneAccount associated with the incoming
+ // ParcelableConnection is in fact registered to Telecom and is being called
+ // from the correct user.
+ List<PhoneAccountHandle> accountHandles =
+ mPhoneAccountRegistrar.getCallCapablePhoneAccounts(null /*uriScheme*/,
+ false /*includeDisabledAccounts*/, userHandle);
+ PhoneAccountHandle phoneAccountHandle = null;
+ for (PhoneAccountHandle accountHandle : accountHandles) {
+ if(accountHandle.equals(callingPhoneAccountHandle)) {
+ phoneAccountHandle = accountHandle;
+ }
+ }
+ if (phoneAccountHandle != null) {
+ logIncoming("addExistingConnection %s %s", callId, connection);
+ Call existingCall = mCallsManager
+ .createCallForExistingConnection(callId, connection);
+ mCallIdMapper.addCall(existingCall, callId);
+ existingCall.setConnectionService(ConnectionServiceWrapper.this);
+ } else {
+ Log.e(this, new RemoteException("The PhoneAccount being used is not " +
+ "currently registered with Telecom."), "Unable to " +
+ "addExistingConnection.");
+ }
}
} finally {
Binder.restoreCallingIdentity(token);
@@ -644,7 +681,7 @@
}
private final Adapter mAdapter = new Adapter();
- private final CallIdMapper mCallIdMapper = new CallIdMapper();
+ private final CallIdMapper mCallIdMapper = new CallIdMapper(Call::getConnectionId);
private final Map<String, CreateConnectionResponse> mPendingResponses = new HashMap<>();
private Binder2 mBinder = new Binder2();
@@ -652,6 +689,7 @@
private final ConnectionServiceRepository mConnectionServiceRepository;
private final PhoneAccountRegistrar mPhoneAccountRegistrar;
private final CallsManager mCallsManager;
+ private final AppOpsManager mAppOpsManager;
/**
* Creates a connection service.
@@ -679,6 +717,7 @@
});
mPhoneAccountRegistrar = phoneAccountRegistrar;
mCallsManager = callsManager;
+ mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
}
/** See {@link IConnectionService#addConnectionServiceAdapter}. */
@@ -692,6 +731,17 @@
}
}
+ /** See {@link IConnectionService#removeConnectionServiceAdapter}. */
+ private void removeConnectionServiceAdapter(IConnectionServiceAdapter adapter) {
+ if (isServiceValid("removeConnectionServiceAdapter")) {
+ try {
+ logOutgoing("removeConnectionServiceAdapter %s", adapter);
+ mServiceInterface.removeConnectionServiceAdapter(adapter);
+ } catch (RemoteException e) {
+ }
+ }
+ }
+
/**
* Creates a new connection for a new outgoing call or to attach to an existing incoming call.
*/
@@ -1011,18 +1061,23 @@
/** {@inheritDoc} */
@Override
protected void setServiceInterface(IBinder binder) {
- if (binder == null) {
- // We have lost our service connection. Notify the world that this service is done.
- // We must notify the adapter before CallsManager. The adapter will force any pending
- // outgoing calls to try the next service. This needs to happen before CallsManager
- // tries to clean up any calls still associated with this service.
- handleConnectionServiceDeath();
- mCallsManager.handleConnectionServiceDeath(this);
- mServiceInterface = null;
- } else {
- mServiceInterface = IConnectionService.Stub.asInterface(binder);
- addConnectionServiceAdapter(mAdapter);
- }
+ mServiceInterface = IConnectionService.Stub.asInterface(binder);
+ Log.v(this, "Adding Connection Service Adapter.");
+ addConnectionServiceAdapter(mAdapter);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void removeServiceInterface() {
+ Log.v(this, "Removing Connection Service Adapter.");
+ removeConnectionServiceAdapter(mAdapter);
+ // We have lost our service connection. Notify the world that this service is done.
+ // We must notify the adapter before CallsManager. The adapter will force any pending
+ // outgoing calls to try the next service. This needs to happen before CallsManager
+ // tries to clean up any calls still associated with this service.
+ handleConnectionServiceDeath();
+ mCallsManager.handleConnectionServiceDeath(this);
+ mServiceInterface = null;
}
private void handleCreateConnectionComplete(
diff --git a/src/com/android/server/telecom/CreateConnectionProcessor.java b/src/com/android/server/telecom/CreateConnectionProcessor.java
index 7f9bbab..22ec3c6 100644
--- a/src/com/android/server/telecom/CreateConnectionProcessor.java
+++ b/src/com/android/server/telecom/CreateConnectionProcessor.java
@@ -96,6 +96,7 @@
private final Context mContext;
private CreateConnectionTimeout mTimeout;
private ConnectionServiceWrapper mService;
+ private int mConnectionAttempt;
@VisibleForTesting
public CreateConnectionProcessor(
@@ -107,6 +108,7 @@
mCallResponse = response;
mPhoneAccountRegistrar = phoneAccountRegistrar;
mContext = context;
+ mConnectionAttempt = 0;
}
boolean isProcessingComplete() {
@@ -117,6 +119,10 @@
return mTimeout != null && mTimeout.isCallTimedOut();
}
+ public int getConnectionAttempt() {
+ return mConnectionAttempt;
+ }
+
@VisibleForTesting
public void process() {
Log.v(this, "process");
@@ -127,7 +133,7 @@
mCall.getTargetPhoneAccount(), mCall.getTargetPhoneAccount()));
}
adjustAttemptsForConnectionManager();
- adjustAttemptsForEmergency();
+ adjustAttemptsForEmergency(mCall.getTargetPhoneAccount());
mAttemptRecordIterator = mAttemptRecords.iterator();
attemptNextPhoneAccount();
}
@@ -200,6 +206,7 @@
Log.i(this, "Found no connection service for attempt %s", attempt);
attemptNextPhoneAccount();
} else {
+ mConnectionAttempt++;
mCall.setConnectionManagerPhoneAccount(attempt.connectionManagerPhoneAccount);
mCall.setTargetPhoneAccount(attempt.targetPhoneAccount);
mCall.setConnectionService(mService);
@@ -288,7 +295,7 @@
// If we are possibly attempting to call a local emergency number, ensure that the
// plain PSTN connection services are listed, and nothing else.
- private void adjustAttemptsForEmergency() {
+ private void adjustAttemptsForEmergency(PhoneAccountHandle preferredPAH) {
if (mCall.isEmergencyCall()) {
Log.i(this, "Emergency number detected");
mAttemptRecords.clear();
@@ -307,16 +314,29 @@
allAccounts.add(TelephonyUtil.getDefaultEmergencyPhoneAccount());
}
- // First, add SIM phone accounts which can place emergency calls.
+ // First, possibly add the SIM phone account that the user prefers
+ PhoneAccount preferredPA = mPhoneAccountRegistrar.getPhoneAccountUnchecked(
+ preferredPAH);
+ if (preferredPA != null &&
+ preferredPA.hasCapabilities(PhoneAccount.CAPABILITY_PLACE_EMERGENCY_CALLS) &&
+ preferredPA.hasCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)) {
+ Log.i(this, "Will try PSTN account %s for emergency",
+ preferredPA.getAccountHandle());
+ mAttemptRecords.add(new CallAttemptRecord(preferredPAH, preferredPAH));
+ }
+
+ // Next, add all SIM phone accounts which can place emergency calls.
+ TelephonyUtil.sortSimPhoneAccounts(mContext, allAccounts);
for (PhoneAccount phoneAccount : allAccounts) {
if (phoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_PLACE_EMERGENCY_CALLS) &&
phoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)) {
- Log.i(this, "Will try PSTN account %s for emergency",
- phoneAccount.getAccountHandle());
- mAttemptRecords.add(
- new CallAttemptRecord(
- phoneAccount.getAccountHandle(),
- phoneAccount.getAccountHandle()));
+ PhoneAccountHandle phoneAccountHandle = phoneAccount.getAccountHandle();
+ // Don't add the preferred account since it has already been added previously.
+ if (!phoneAccountHandle.equals(preferredPAH)) {
+ Log.i(this, "Will try PSTN account %s for emergency", phoneAccountHandle);
+ mAttemptRecords.add(new CallAttemptRecord(phoneAccountHandle,
+ phoneAccountHandle));
+ }
}
}
@@ -419,7 +439,7 @@
public void handleCreateConnectionFailure(DisconnectCause errorDisconnectCause) {
// Failure of some sort; record the reasons for failure and try again if possible
Log.d(CreateConnectionProcessor.this, "Connection failed: (%s)", errorDisconnectCause);
- if(shouldFailCallIfConnectionManagerFails(errorDisconnectCause)){
+ if (shouldFailCallIfConnectionManagerFails(errorDisconnectCause)) {
notifyCallConnectionFailure(errorDisconnectCause);
return;
}
diff --git a/src/com/android/server/telecom/CreateConnectionTimeout.java b/src/com/android/server/telecom/CreateConnectionTimeout.java
index 69cc129..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;
@@ -114,7 +114,8 @@
int state = call.getState();
return state == CallState.NEW
|| state == CallState.CONNECTING
- || state == CallState.DIALING;
+ || state == CallState.DIALING
+ || state == CallState.PULLING;
}
private long getTimeoutLengthMillis() {
diff --git a/src/com/android/server/telecom/HeadsetMediaButton.java b/src/com/android/server/telecom/HeadsetMediaButton.java
index a8bd354..af0ce13 100644
--- a/src/com/android/server/telecom/HeadsetMediaButton.java
+++ b/src/com/android/server/telecom/HeadsetMediaButton.java
@@ -135,6 +135,9 @@
/** ${inheritDoc} */
@Override
public void onCallRemoved(Call call) {
+ if (call.isExternalCall()) {
+ return;
+ }
if (!mCallsManager.hasAnyCalls()) {
mMediaSessionHandler.obtainMessage(MSG_MEDIA_SESSION_SET_ACTIVE, 0, 0).sendToTarget();
}
diff --git a/src/com/android/server/telecom/InCallAdapter.java b/src/com/android/server/telecom/InCallAdapter.java
index 31d268e..e47f3a2 100644
--- a/src/com/android/server/telecom/InCallAdapter.java
+++ b/src/com/android/server/telecom/InCallAdapter.java
@@ -47,7 +47,7 @@
@Override
public void answerCall(String callId, int videoState) {
try {
- Log.startSession("ICA.aC", mOwnerComponentName);
+ Log.startSession(Log.Sessions.ICA_ANSWER_CALL, mOwnerComponentName);
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
@@ -70,7 +70,7 @@
@Override
public void rejectCall(String callId, boolean rejectWithMessage, String textMessage) {
try {
- Log.startSession("ICA.rC", mOwnerComponentName);
+ Log.startSession(Log.Sessions.ICA_REJECT_CALL, mOwnerComponentName);
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
@@ -162,7 +162,7 @@
@Override
public void disconnectCall(String callId) {
try {
- Log.startSession("ICA.dC", mOwnerComponentName);
+ Log.startSession(Log.Sessions.ICA_DISCONNECT_CALL, mOwnerComponentName);
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
@@ -185,7 +185,7 @@
@Override
public void holdCall(String callId) {
try {
- Log.startSession("ICA.hC", mOwnerComponentName);
+ Log.startSession(Log.Sessions.ICA_HOLD_CALL, mOwnerComponentName);
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
@@ -207,7 +207,7 @@
@Override
public void unholdCall(String callId) {
try {
- Log.startSession("ICA.uC", mOwnerComponentName);
+ Log.startSession(Log.Sessions.ICA_UNHOLD_CALL, mOwnerComponentName);
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
@@ -252,7 +252,7 @@
@Override
public void mute(boolean shouldMute) {
try {
- Log.startSession("ICA.m", mOwnerComponentName);
+ Log.startSession(Log.Sessions.ICA_MUTE, mOwnerComponentName);
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
@@ -269,7 +269,7 @@
@Override
public void setAudioRoute(int route) {
try {
- Log.startSession("ICA.sAR", mOwnerComponentName);
+ Log.startSession(Log.Sessions.ICA_SET_AUDIO_ROUTE, mOwnerComponentName);
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
@@ -286,7 +286,7 @@
@Override
public void conference(String callId, String otherCallId) {
try {
- Log.startSession("ICA.c", mOwnerComponentName);
+ Log.startSession(Log.Sessions.ICA_CONFERENCE, mOwnerComponentName);
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
diff --git a/src/com/android/server/telecom/InCallController.java b/src/com/android/server/telecom/InCallController.java
index 0a14ffb..2157bf3 100644
--- a/src/com/android/server/telecom/InCallController.java
+++ b/src/com/android/server/telecom/InCallController.java
@@ -78,6 +78,60 @@
public void dump(IndentingPrintWriter pw) {}
}
+ private class InCallServiceInfo {
+ private final ComponentName mComponentName;
+ private boolean mIsExternalCallsSupported;
+ private final int mType;
+
+ public InCallServiceInfo(ComponentName componentName,
+ boolean isExternalCallsSupported,
+ int type) {
+ mComponentName = componentName;
+ mIsExternalCallsSupported = isExternalCallsSupported;
+ mType = type;
+ }
+
+ public ComponentName getComponentName() {
+ return mComponentName;
+ }
+
+ public boolean isExternalCallsSupported() {
+ return mIsExternalCallsSupported;
+ }
+
+ public int getType() {
+ return mType;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ InCallServiceInfo that = (InCallServiceInfo) o;
+
+ if (mIsExternalCallsSupported != that.mIsExternalCallsSupported) {
+ return false;
+ }
+ return mComponentName.equals(that.mComponentName);
+
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mComponentName, mIsExternalCallsSupported);
+ }
+
+ @Override
+ public String toString() {
+ return "[" + mComponentName + " supportsExternal? " + mIsExternalCallsSupported + "]";
+ }
+ }
+
private class InCallServiceBindingConnection extends InCallServiceConnection {
private final ServiceConnection mServiceConnection = new ServiceConnection() {
@@ -113,12 +167,12 @@
}
};
- private final ComponentName mComponentName;
+ private final InCallServiceInfo mInCallServiceInfo;
private boolean mIsConnected = false;
private boolean mIsBound = false;
- public InCallServiceBindingConnection(ComponentName componentName) {
- mComponentName = componentName;
+ public InCallServiceBindingConnection(InCallServiceInfo info) {
+ mInCallServiceInfo = info;
}
@Override
@@ -129,7 +183,7 @@
}
Intent intent = new Intent(InCallService.SERVICE_INTERFACE);
- intent.setComponent(mComponentName);
+ intent.setComponent(mInCallServiceInfo.getComponentName());
if (call != null && !call.isIncoming() && !call.isExternalCall()){
intent.putExtra(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS,
call.getIntentExtras());
@@ -137,7 +191,7 @@
call.getTargetPhoneAccount());
}
- Log.i(this, "Attempting to bind to InCall %s, with %s", mComponentName, intent);
+ Log.i(this, "Attempting to bind to InCall %s, with %s", mInCallServiceInfo, intent);
mIsConnected = true;
if (!mContext.bindServiceAsUser(intent, mServiceConnection,
Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE,
@@ -146,6 +200,12 @@
mIsConnected = false;
}
+ if (call != null && mIsConnected) {
+ call.getAnalytics().addInCallService(
+ mInCallServiceInfo.getComponentName().flattenToShortString(),
+ mInCallServiceInfo.getType());
+ }
+
return mIsConnected;
}
@@ -168,10 +228,10 @@
protected void onConnected(IBinder service) {
boolean shouldRemainConnected =
- InCallController.this.onConnected(mComponentName, service);
+ 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();
@@ -179,7 +239,7 @@
}
protected void onDisconnected() {
- InCallController.this.onDisconnected(mComponentName);
+ InCallController.this.onDisconnected(mInCallServiceInfo.getComponentName());
disconnect(); // Unbind explicitly if we get disconnected.
if (mListener != null) {
mListener.onDisconnect(InCallServiceBindingConnection.this);
@@ -214,8 +274,9 @@
};
public EmergencyInCallServiceConnection(
- ComponentName componentName, InCallServiceConnection subConnection) {
- super(componentName);
+ InCallServiceInfo info, InCallServiceConnection subConnection) {
+
+ super(info);
mSubConnection = subConnection;
if (mSubConnection != null) {
mSubConnection.setListener(mSubListener);
@@ -499,7 +560,7 @@
}
@Override
- public void onVideoStateChanged(Call call) {
+ public void onVideoStateChanged(Call call, int previousVideoState, int newVideoState) {
updateCall(call);
}
@@ -535,14 +596,14 @@
private static final int IN_CALL_SERVICE_TYPE_NON_UI = 4;
/** The in-call app implementations, see {@link IInCallService}. */
- private final Map<ComponentName, IInCallService> mInCallServices = new ArrayMap<>();
+ private final Map<InCallServiceInfo, IInCallService> mInCallServices = new ArrayMap<>();
/**
* The {@link ComponentName} of the bound In-Call UI Service.
*/
private ComponentName mInCallUIComponentName;
- private final CallIdMapper mCallIdMapper = new CallIdMapper();
+ private final CallIdMapper mCallIdMapper = new CallIdMapper(Call::getId);
/** The {@link ComponentName} of the default InCall UI. */
private final ComponentName mSystemInCallComponentName;
@@ -552,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(
@@ -583,16 +646,26 @@
// Track the call if we don't already know about it.
addCall(call);
- for (Map.Entry<ComponentName, IInCallService> entry : mInCallServices.entrySet()) {
- ComponentName componentName = entry.getKey();
+ List<ComponentName> componentsUpdated = new ArrayList<>();
+ for (Map.Entry<InCallServiceInfo, IInCallService> entry : mInCallServices.entrySet()) {
+ InCallServiceInfo info = entry.getKey();
+
+ if (call.isExternalCall() && !info.isExternalCallsSupported()) {
+ continue;
+ }
+
+ componentsUpdated.add(info.getComponentName());
IInCallService inCallService = entry.getValue();
+
ParcelableCall parcelableCall = ParcelableCallUtils.toParcelableCall(call,
- true /* includeVideoProvider */, mCallsManager.getPhoneAccountRegistrar());
+ true /* includeVideoProvider */, mCallsManager.getPhoneAccountRegistrar(),
+ info.isExternalCallsSupported());
try {
inCallService.addCall(parcelableCall);
} catch (RemoteException ignored) {
}
}
+ Log.i(this, "Call added to components: %s", componentsUpdated);
}
}
@@ -604,17 +677,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);
@@ -624,8 +695,62 @@
@Override
public void onExternalCallChanged(Call call, boolean isExternalCall) {
Log.i(this, "onExternalCallChanged: %s -> %b", call, isExternalCall);
- // TODO: Need to add logic which ensures changes to a call's external state adds or removes
- // the call from the InCallServices depending on whether they support external calls.
+
+ List<ComponentName> componentsUpdated = new ArrayList<>();
+ if (!isExternalCall) {
+ // The call was external but it is no longer external. We must now add it to any
+ // InCallServices which do not support external calls.
+ for (Map.Entry<InCallServiceInfo, IInCallService> entry : mInCallServices.entrySet()) {
+ InCallServiceInfo info = entry.getKey();
+
+ if (info.isExternalCallsSupported()) {
+ // For InCallServices which support external calls, the call will have already
+ // been added to the connection service, so we do not need to add it again.
+ continue;
+ }
+
+ componentsUpdated.add(info.getComponentName());
+ IInCallService inCallService = entry.getValue();
+
+ ParcelableCall parcelableCall = ParcelableCallUtils.toParcelableCall(call,
+ true /* includeVideoProvider */, mCallsManager.getPhoneAccountRegistrar(),
+ info.isExternalCallsSupported());
+ try {
+ inCallService.addCall(parcelableCall);
+ } catch (RemoteException ignored) {
+ }
+ }
+ Log.i(this, "Previously external call added to components: %s", componentsUpdated);
+ } else {
+ // The call was regular but it is now external. We must now remove it from any
+ // InCallServices which do not support external calls.
+ // Remove the call by sending a call update indicating the call was disconnected.
+ ParcelableCall parcelableCall = ParcelableCallUtils.toParcelableCall(
+ call,
+ false /* includeVideoProvider */,
+ mCallsManager.getPhoneAccountRegistrar(),
+ false /* supportsExternalCalls */,
+ android.telecom.Call.STATE_DISCONNECTED /* overrideState */);
+
+ Log.i(this, "Removing external call %s ==> %s", call, parcelableCall);
+ for (Map.Entry<InCallServiceInfo, IInCallService> entry : mInCallServices.entrySet()) {
+ InCallServiceInfo info = entry.getKey();
+ if (info.isExternalCallsSupported()) {
+ // For InCallServices which support external calls, we do not need to remove
+ // the call.
+ continue;
+ }
+
+ componentsUpdated.add(info.getComponentName());
+ IInCallService inCallService = entry.getValue();
+
+ try {
+ inCallService.updateCall(parcelableCall);
+ } catch (RemoteException ignored) {
+ }
+ }
+ Log.i(this, "External call removed from components: %s", componentsUpdated);
+ }
}
@Override
@@ -715,6 +840,10 @@
if (!mInCallServices.isEmpty()) {
for (IInCallService inCallService : mInCallServices.values()) {
try {
+ Log.i(this, "notifyConnectionEvent {Call: %s, Event: %s, Extras:[%s]}",
+ (call != null ? call.toString() :"null"),
+ (event != null ? event : "null") ,
+ (extras != null ? extras.toString() : "null"));
inCallService.onConnectionEvent(mCallIdMapper.getCallId(call), event, extras);
} catch (RemoteException ignored) {
}
@@ -727,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;
+ }
}
}
@@ -743,23 +876,25 @@
@VisibleForTesting
public void bindToServices(Call call) {
InCallServiceConnection dialerInCall = null;
- ComponentName defaultDialerComponent = getDefaultDialerComponent();
- Log.i(this, "defaultDialer: " + defaultDialerComponent);
- if (defaultDialerComponent != null &&
- !defaultDialerComponent.equals(mSystemInCallComponentName)) {
- dialerInCall = new InCallServiceBindingConnection(defaultDialerComponent);
+ InCallServiceInfo defaultDialerComponentInfo = getDefaultDialerComponent();
+ Log.i(this, "defaultDialer: " + defaultDialerComponentInfo);
+ if (defaultDialerComponentInfo != null &&
+ !defaultDialerComponentInfo.getComponentName().equals(mSystemInCallComponentName)) {
+ dialerInCall = new InCallServiceBindingConnection(defaultDialerComponentInfo);
}
Log.i(this, "defaultDialer: " + dialerInCall);
+ InCallServiceInfo systemInCallInfo = getInCallServiceComponent(mSystemInCallComponentName,
+ IN_CALL_SERVICE_TYPE_SYSTEM_UI);
EmergencyInCallServiceConnection systemInCall =
- new EmergencyInCallServiceConnection(mSystemInCallComponentName, dialerInCall);
+ new EmergencyInCallServiceConnection(systemInCallInfo, dialerInCall);
systemInCall.setHasEmergency(mCallsManager.hasEmergencyCall());
InCallServiceConnection carModeInCall = null;
- ComponentName carModeComponent = getCarModeComponent();
- if (carModeComponent != null &&
- !carModeComponent.equals(mSystemInCallComponentName)) {
- carModeInCall = new InCallServiceBindingConnection(carModeComponent);
+ InCallServiceInfo carModeComponentInfo = getCarModeComponent();
+ if (carModeComponentInfo != null &&
+ !carModeComponentInfo.getComponentName().equals(mSystemInCallComponentName)) {
+ carModeInCall = new InCallServiceBindingConnection(carModeComponentInfo);
}
mInCallServiceConnection =
@@ -767,18 +902,17 @@
mInCallServiceConnection.setCarMode(shouldUseCarModeUI());
mInCallServiceConnection.connect(call);
-
- List<ComponentName> nonUIInCallComponents =
- getInCallServiceComponents(null, IN_CALL_SERVICE_TYPE_NON_UI);
+ List<InCallServiceInfo> nonUIInCallComponents =
+ getInCallServiceComponents(IN_CALL_SERVICE_TYPE_NON_UI);
List<InCallServiceBindingConnection> nonUIInCalls = new LinkedList<>();
- for (ComponentName componentName : nonUIInCallComponents) {
- nonUIInCalls.add(new InCallServiceBindingConnection(componentName));
+ for (InCallServiceInfo serviceInfo : nonUIInCallComponents) {
+ nonUIInCalls.add(new InCallServiceBindingConnection(serviceInfo));
}
mNonUIInCallServiceConnections = new NonUIInCallServiceConnectionCollection(nonUIInCalls);
mNonUIInCallServiceConnections.connect(call);
}
- private ComponentName getDefaultDialerComponent() {
+ private InCallServiceInfo getDefaultDialerComponent() {
String packageName = mDefaultDialerAdapter.getDefaultDialerApplication(
mContext, mCallsManager.getCurrentUserHandle().getIdentifier());
Log.d(this, "Default Dialer package: " + packageName);
@@ -786,25 +920,57 @@
return getInCallServiceComponent(packageName, IN_CALL_SERVICE_TYPE_DIALER_UI);
}
- private ComponentName getCarModeComponent() {
- return getInCallServiceComponent(null, IN_CALL_SERVICE_TYPE_CAR_MODE_UI);
+ private InCallServiceInfo getCarModeComponent() {
+ // Seems strange to cast a String to null, but the signatures of getInCallServiceComponent
+ // differ in the types of the first parameter, and passing in null is inherently ambiguous.
+ return getInCallServiceComponent((String) null, IN_CALL_SERVICE_TYPE_CAR_MODE_UI);
}
- private ComponentName getInCallServiceComponent(String packageName, int type) {
- List<ComponentName> list = getInCallServiceComponents(packageName, type);
+ private InCallServiceInfo getInCallServiceComponent(ComponentName componentName, int type) {
+ List<InCallServiceInfo> list = getInCallServiceComponents(componentName, type);
+ if (list != null && !list.isEmpty()) {
+ return list.get(0);
+ } else {
+ // 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, type);
+ }
+ }
+
+ private InCallServiceInfo getInCallServiceComponent(String packageName, int type) {
+ List<InCallServiceInfo> list = getInCallServiceComponents(packageName, type);
if (list != null && !list.isEmpty()) {
return list.get(0);
}
return null;
}
- private List<ComponentName> getInCallServiceComponents(String packageName, int type) {
- List<ComponentName> retval = new LinkedList<>();
+ private List<InCallServiceInfo> getInCallServiceComponents(int type) {
+ return getInCallServiceComponents(null, null, type);
+ }
+
+ private List<InCallServiceInfo> getInCallServiceComponents(String packageName, int type) {
+ return getInCallServiceComponents(packageName, null, type);
+ }
+
+ private List<InCallServiceInfo> getInCallServiceComponents(ComponentName componentName,
+ int type) {
+ return getInCallServiceComponents(null, componentName, type);
+ }
+
+ private List<InCallServiceInfo> getInCallServiceComponents(String packageName,
+ ComponentName componentName, int requestedType) {
+
+ List<InCallServiceInfo> retval = new LinkedList<>();
Intent serviceIntent = new Intent(InCallService.SERVICE_INTERFACE);
if (packageName != null) {
serviceIntent.setPackage(packageName);
}
+ if (componentName != null) {
+ serviceIntent.setComponent(componentName);
+ }
PackageManager packageManager = mContext.getPackageManager();
for (ResolveInfo entry : packageManager.queryIntentServicesAsUser(
@@ -814,8 +980,15 @@
ServiceInfo serviceInfo = entry.serviceInfo;
if (serviceInfo != null) {
- if (type == 0 || type == getInCallServiceType(entry.serviceInfo, packageManager)) {
- retval.add(new ComponentName(serviceInfo.packageName, serviceInfo.name));
+ boolean isExternalCallsSupported = serviceInfo.metaData != null &&
+ serviceInfo.metaData.getBoolean(
+ TelecomManager.METADATA_INCLUDE_EXTERNAL_CALLS, false);
+ if (requestedType == 0 || requestedType == getInCallServiceType(entry.serviceInfo,
+ packageManager)) {
+
+ retval.add(new InCallServiceInfo(
+ new ComponentName(serviceInfo.packageName, serviceInfo.name),
+ isExternalCallsSupported, requestedType));
}
}
}
@@ -861,7 +1034,6 @@
return IN_CALL_SERVICE_TYPE_CAR_MODE_UI;
}
-
// Check to see that it is the default dialer package
boolean isDefaultDialerPackage = Objects.equals(serviceInfo.packageName,
mDefaultDialerAdapter.getDefaultDialerApplication(
@@ -899,16 +1071,16 @@
* this class and in-call app by sending the first update to in-call app. This method is
* called after a successful binding connection is established.
*
- * @param componentName The service {@link ComponentName}.
+ * @param info Info about the service, including its {@link ComponentName}.
* @param service The {@link IInCallService} implementation.
* @return True if we successfully connected.
*/
- private boolean onConnected(ComponentName componentName, IBinder service) {
- Trace.beginSection("onConnected: " + componentName);
- Log.i(this, "onConnected to %s", componentName);
+ private boolean onConnected(InCallServiceInfo info, IBinder service) {
+ Trace.beginSection("onConnected: " + info.getComponentName());
+ Log.i(this, "onConnected to %s", info.getComponentName());
IInCallService inCallService = IInCallService.Stub.asInterface(service);
- mInCallServices.put(componentName, inCallService);
+ mInCallServices.put(info, inCallService);
try {
inCallService.setInCallAdapter(
@@ -916,7 +1088,7 @@
mCallsManager,
mCallIdMapper,
mLock,
- componentName.getPackageName()));
+ info.getComponentName().getPackageName()));
} catch (RemoteException e) {
Log.e(this, e, "Failed to set the in-call adapter.");
Trace.endSection();
@@ -925,28 +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(),
- componentName);
- for (Call call : calls) {
- try {
- // Track the call if we don't already know about it.
- addCall(call);
- inCallService.addCall(ParcelableCallUtils.toParcelableCall(
- call,
- true /* includeVideoProvider */,
- mCallsManager.getPhoneAccountRegistrar()));
- } 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;
}
@@ -980,16 +1156,23 @@
*/
private void updateCall(Call call, boolean videoProviderChanged) {
if (!mInCallServices.isEmpty()) {
- ParcelableCall parcelableCall = ParcelableCallUtils.toParcelableCall(
- call,
- videoProviderChanged /* includeVideoProvider */,
- mCallsManager.getPhoneAccountRegistrar());
- Log.i(this, "Sending updateCall %s ==> %s", call, parcelableCall);
+ Log.i(this, "Sending updateCall %s", call);
List<ComponentName> componentsUpdated = new ArrayList<>();
- for (Map.Entry<ComponentName, IInCallService> entry : mInCallServices.entrySet()) {
- ComponentName componentName = entry.getKey();
+ for (Map.Entry<InCallServiceInfo, IInCallService> entry : mInCallServices.entrySet()) {
+ InCallServiceInfo info = entry.getKey();
+ if (call.isExternalCall() && !info.isExternalCallsSupported()) {
+ continue;
+ }
+
+ ParcelableCall parcelableCall = ParcelableCallUtils.toParcelableCall(
+ call,
+ videoProviderChanged /* includeVideoProvider */,
+ mCallsManager.getPhoneAccountRegistrar(),
+ info.isExternalCallsSupported());
+ ComponentName componentName = info.getComponentName();
IInCallService inCallService = entry.getValue();
componentsUpdated.add(componentName);
+
try {
inCallService.updateCall(parcelableCall);
} catch (RemoteException ignored) {
@@ -1022,8 +1205,8 @@
public void dump(IndentingPrintWriter pw) {
pw.println("mInCallServices (InCalls registered):");
pw.increaseIndent();
- for (ComponentName componentName : mInCallServices.keySet()) {
- pw.println(componentName);
+ for (InCallServiceInfo info : mInCallServices.keySet()) {
+ pw.println(info);
}
pw.decreaseIndent();
diff --git a/src/com/android/server/telecom/InCallTonePlayer.java b/src/com/android/server/telecom/InCallTonePlayer.java
index 0b0e78b..e0b0dc0 100644
--- a/src/com/android/server/telecom/InCallTonePlayer.java
+++ b/src/com/android/server/telecom/InCallTonePlayer.java
@@ -273,7 +273,12 @@
mSession = Log.createSubsession();
}
- start();
+ super.start();
+ }
+
+ @Override
+ public void start() {
+ Log.w(this, "Do not call the start method directly; use startTone instead.");
}
/**
@@ -292,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/InCallWakeLockController.java b/src/com/android/server/telecom/InCallWakeLockController.java
index ffa6a3f..0de8123 100644
--- a/src/com/android/server/telecom/InCallWakeLockController.java
+++ b/src/com/android/server/telecom/InCallWakeLockController.java
@@ -37,16 +37,25 @@
@Override
public void onCallAdded(Call call) {
+ if (call.isExternalCall()) {
+ return;
+ }
handleWakeLock();
}
@Override
public void onCallRemoved(Call call) {
+ if (call.isExternalCall()) {
+ return;
+ }
handleWakeLock();
}
@Override
public void onCallStateChanged(Call call, int oldState, int newState) {
+ if (call.isExternalCall()) {
+ return;
+ }
handleWakeLock();
}
diff --git a/src/com/android/server/telecom/InterruptionFilterProxy.java b/src/com/android/server/telecom/InterruptionFilterProxy.java
new file mode 100644
index 0000000..434c341
--- /dev/null
+++ b/src/com/android/server/telecom/InterruptionFilterProxy.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.telecom;
+
+/**
+ * Defines common functionality used by {@link CallAudioRouteStateMachine} to control the current
+ * interruption filter for notifications while in a call. Used to ensure that this functionality
+ * can be mocked out in unit tests.
+ */
+public interface InterruptionFilterProxy {
+ void setInterruptionFilter(int interruptionFilter);
+ int getCurrentInterruptionFilter();
+}
diff --git a/src/com/android/server/telecom/Log.java b/src/com/android/server/telecom/Log.java
index fda21fd..2282ff0 100644
--- a/src/com/android/server/telecom/Log.java
+++ b/src/com/android/server/telecom/Log.java
@@ -22,6 +22,7 @@
import android.os.Looper;
import android.os.AsyncTask;
import android.telecom.PhoneAccount;
+import android.telecom.TimedEvent;
import android.telephony.PhoneNumberUtils;
import android.text.TextUtils;
import android.util.Base64;
@@ -34,7 +35,9 @@
import java.security.NoSuchAlgorithmException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
+import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.IllegalFormatException;
@@ -52,16 +55,61 @@
@VisibleForTesting
public class Log {
+ public static final class Sessions {
+ public static final String ICA_ANSWER_CALL = "ICA.aC";
+ public static final String ICA_REJECT_CALL = "ICA.rC";
+ public static final String ICA_DISCONNECT_CALL = "ICA.dC";
+ public static final String ICA_HOLD_CALL = "ICA.hC";
+ public static final String ICA_UNHOLD_CALL = "ICA.uC";
+ public static final String ICA_MUTE = "ICA.m";
+ public static final String ICA_SET_AUDIO_ROUTE = "ICA.sAR";
+ public static final String ICA_CONFERENCE = "ICA.c";
+ public static final String CSW_HANDLE_CREATE_CONNECTION_COMPLETE = "CSW.hCCC";
+ public static final String CSW_SET_ACTIVE = "CSW.sA";
+ public static final String CSW_SET_RINGING = "CSW.sR";
+ public static final String CSW_SET_DIALING = "CSW.sD";
+ public static final String CSW_SET_PULLING = "CSW.sP";
+ public static final String CSW_SET_DISCONNECTED = "CSW.sDc";
+ public static final String CSW_SET_ON_HOLD = "CSW.sOH";
+ public static final String CSW_REMOVE_CALL = "CSW.rC";
+ public static final String CSW_SET_IS_CONFERENCED = "CSW.sIC";
+ public static final String CSW_ADD_CONFERENCE_CALL = "CSW.aCC";
+ }
+
/**
* Stores the various events associated with {@link Call}s. Also stores all request-response
* pairs amongst the events.
*/
public final static class Events {
+ public static class TimedEventPair {
+ private static final long DEFAULT_TIMEOUT = 3000L;
+
+ String mRequest;
+ String mResponse;
+ String mName;
+ long mTimeoutMillis = DEFAULT_TIMEOUT;
+
+ public TimedEventPair(String request, String response,
+ String name) {
+ this.mRequest = request;
+ this.mResponse = response;
+ this.mName = name;
+ }
+
+ public TimedEventPair(String request, String response,
+ String name, long timeoutMillis) {
+ this.mRequest = request;
+ this.mResponse = response;
+ this.mName = name;
+ this.mTimeoutMillis = timeoutMillis;
+ }
+ }
+
public static final String CREATED = "CREATED";
public static final String DESTROYED = "DESTROYED";
- public static final String SET_NEW = "SET_NEW";
public static final String SET_CONNECTING = "SET_CONNECTING";
public static final String SET_DIALING = "SET_DIALING";
+ public static final String SET_PULLING = "SET_PULLING";
public static final String SET_ACTIVE = "SET_ACTIVE";
public static final String SET_HOLD = "SET_HOLD";
public static final String SET_RINGING = "SET_RINGING";
@@ -84,13 +132,18 @@
public static final String BIND_CS = "BIND_CS";
public static final String CS_BOUND = "CS_BOUND";
public static final String CONFERENCE_WITH = "CONF_WITH";
- public static final String SPLIT_CONFERENCE = "CONF_SPLIT";
+ public static final String SPLIT_FROM_CONFERENCE = "CONF_SPLIT";
public static final String SWAP = "SWAP";
public static final String ADD_CHILD = "ADD_CHILD";
public static final String REMOVE_CHILD = "REMOVE_CHILD";
public static final String SET_PARENT = "SET_PARENT";
public static final String MUTE = "MUTE";
+ public static final String UNMUTE = "UNMUTE";
public static final String AUDIO_ROUTE = "AUDIO_ROUTE";
+ public static final String AUDIO_ROUTE_EARPIECE = "AUDIO_ROUTE_EARPIECE";
+ public static final String AUDIO_ROUTE_HEADSET = "AUDIO_ROUTE_HEADSET";
+ public static final String AUDIO_ROUTE_BT = "AUDIO_ROUTE_BT";
+ public static final String AUDIO_ROUTE_SPEAKER = "AUDIO_ROUTE_SPEAKER";
public static final String ERROR_LOG = "ERROR";
public static final String USER_LOG_MARK = "USER_LOG_MARK";
public static final String SILENCE = "SILENCE";
@@ -107,29 +160,71 @@
public static final String FILTERING_TIMED_OUT = "FILTERING_TIMED_OUT";
public static final String REMOTELY_HELD = "REMOTELY_HELD";
public static final String REMOTELY_UNHELD = "REMOTELY_UNHELD";
- public static final String PULL = "PULL";
+ public static final String REQUEST_PULL = "PULL";
public static final String INFO = "INFO";
+ public static final String VIDEO_STATE_CHANGED = "VIDEO_STATE_CHANGED";
+ public static final String RECEIVE_VIDEO_REQUEST = "RECEIVE_VIDEO_REQUEST";
+ public static final String RECEIVE_VIDEO_RESPONSE = "RECEIVE_VIDEO_RESPONSE";
+ public static final String SEND_VIDEO_REQUEST = "SEND_VIDEO_REQUEST";
+ public static final String SEND_VIDEO_RESPONSE = "SEND_VIDEO_RESPONSE";
+ 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";
+ public static final String REJECT_TIMING = "reject";
+ public static final String DISCONNECT_TIMING = "disconnect";
+ public static final String HOLD_TIMING = "hold";
+ public static final String UNHOLD_TIMING = "unhold";
+ public static final String OUTGOING_TIME_TO_DIALING_TIMING = "outgoing_time_to_dialing";
+ public static final String BIND_CS_TIMING = "bind_cs";
+ public static final String SCREENING_COMPLETED_TIMING = "screening_completed";
+ public static final String DIRECT_TO_VM_FINISHED_TIMING = "direct_to_vm_finished";
+ public static final String BLOCK_CHECK_FINISHED_TIMING = "block_check_finished";
+ public static final String FILTERING_COMPLETED_TIMING = "filtering_completed";
+ public static final String FILTERING_TIMED_OUT_TIMING = "filtering_timed_out";
+
+ private static final TimedEventPair[] sTimedEvents = {
+ new TimedEventPair(REQUEST_ACCEPT, SET_ACTIVE, ACCEPT_TIMING),
+ new TimedEventPair(REQUEST_REJECT, SET_DISCONNECTED, REJECT_TIMING),
+ new TimedEventPair(REQUEST_DISCONNECT, SET_DISCONNECTED, DISCONNECT_TIMING),
+ new TimedEventPair(REQUEST_HOLD, SET_HOLD, HOLD_TIMING),
+ new TimedEventPair(REQUEST_UNHOLD, SET_ACTIVE, UNHOLD_TIMING),
+ new TimedEventPair(START_CONNECTION, SET_DIALING,
+ OUTGOING_TIME_TO_DIALING_TIMING),
+ new TimedEventPair(BIND_CS, CS_BOUND, BIND_CS_TIMING),
+ new TimedEventPair(SCREENING_SENT, SCREENING_COMPLETED,
+ SCREENING_COMPLETED_TIMING),
+ new TimedEventPair(DIRECT_TO_VM_INITIATED, DIRECT_TO_VM_FINISHED,
+ DIRECT_TO_VM_FINISHED_TIMING),
+ new TimedEventPair(BLOCK_CHECK_INITIATED, BLOCK_CHECK_FINISHED,
+ BLOCK_CHECK_FINISHED_TIMING),
+ new TimedEventPair(FILTERING_INITIATED, FILTERING_COMPLETED,
+ FILTERING_COMPLETED_TIMING),
+ new TimedEventPair(FILTERING_INITIATED, FILTERING_TIMED_OUT,
+ FILTERING_TIMED_OUT_TIMING, 6000L),
+ };
+ }
/**
- * Maps from a request to a response. The same event could be listed as the
- * response for multiple requests (e.g. REQUEST_ACCEPT and REQUEST_UNHOLD both map to the
- * SET_ACTIVE response). This map is used to print out the amount of time it takes between
- * a request and a response.
+ * Maps from request events to a list of possible response events. Used to track
+ * end-to-end timing for critical user-facing operations in Telecom.
*/
- public static final Map<String, String> requestResponsePairs =
- new HashMap<String, String>() {{
- put(REQUEST_ACCEPT, SET_ACTIVE);
- put(REQUEST_REJECT, SET_DISCONNECTED);
- put(REQUEST_DISCONNECT, SET_DISCONNECTED);
- put(REQUEST_HOLD, SET_HOLD);
- put(REQUEST_UNHOLD, SET_ACTIVE);
- put(START_CONNECTION, SET_DIALING);
- put(BIND_CS, CS_BOUND);
- put(SCREENING_SENT, SCREENING_COMPLETED);
- put(BLOCK_CHECK_INITIATED, BLOCK_CHECK_FINISHED);
- put(DIRECT_TO_VM_INITIATED, DIRECT_TO_VM_FINISHED);
- put(FILTERING_INITIATED, FILTERING_COMPLETED);
- }};
+ public static final Map<String, List<TimedEventPair>> requestResponsePairs;
+ static {
+ requestResponsePairs = new HashMap<>();
+ for (TimedEventPair p : Timings.sTimedEvents) {
+ if (requestResponsePairs.containsKey(p.mRequest)) {
+ requestResponsePairs.get(p.mRequest).add(p);
+ } else {
+ ArrayList<TimedEventPair> responses = new ArrayList<>();
+ responses.add(p);
+ requestResponsePairs.put(p.mRequest, responses);
+ }
+ }
+ }
}
public static class CallEvent {
@@ -147,10 +242,42 @@
}
public static class CallEventRecord {
+ public static class EventTiming extends TimedEvent<String> {
+ public String name;
+ public long time;
+
+ public EventTiming(String name, long time) {
+ this.name = name;
+ this.time = time;
+ }
+
+ public String getKey() {
+ return name;
+ }
+
+ public long getTime() {
+ return time;
+ }
+ }
+
+ private static class PendingResponse {
+ String requestEventId;
+ long requestEventTimeMillis;
+ long timeoutMillis;
+ String name;
+
+ public PendingResponse(String requestEventId, long requestEventTimeMillis,
+ long timeoutMillis, String name) {
+ this.requestEventId = requestEventId;
+ this.requestEventTimeMillis = requestEventTimeMillis;
+ this.timeoutMillis = timeoutMillis;
+ this.name = name;
+ }
+ }
+
private static final DateFormat sLongDateFormat = new SimpleDateFormat(
"yyyy-MM-dd HH:mm:ss.SSS");
private static final DateFormat sDateFormat = new SimpleDateFormat("HH:mm:ss.SSS");
- private static int sNextId = 1;
private final List<CallEvent> mEvents = new LinkedList<>();
private final Call mCall;
@@ -167,9 +294,40 @@
Log.i("Event", "Call %s: %s, %s", mCall.getId(), event, data);
}
- public void dump(IndentingPrintWriter pw) {
- Map<String, CallEvent> pendingResponses = new HashMap<>();
+ public List<CallEvent> getEvents() {
+ return mEvents;
+ }
+ public List<EventTiming> extractEventTimings() {
+ if (mEvents == null) {
+ return Collections.emptyList();
+ }
+
+ LinkedList<EventTiming> result = new LinkedList<>();
+ Map<String, PendingResponse> pendingResponses = new HashMap<>();
+ for (CallEvent event : mEvents) {
+ if (Events.requestResponsePairs.containsKey(event.eventId)) {
+ // This event expects a response, so add that expected response to the maps
+ // of pending events.
+ for (Events.TimedEventPair p : Events.requestResponsePairs.get(event.eventId)) {
+ pendingResponses.put(p.mResponse, new PendingResponse(event.eventId,
+ event.time, p.mTimeoutMillis, p.mName));
+ }
+ }
+
+ PendingResponse pendingResponse = pendingResponses.remove(event.eventId);
+ if (pendingResponse != null) {
+ long elapsedTime = event.time - pendingResponse.requestEventTimeMillis;
+ if (elapsedTime < pendingResponse.timeoutMillis) {
+ result.add(new EventTiming(pendingResponse.name, elapsedTime));
+ }
+ }
+ }
+
+ return result;
+ }
+
+ public void dump(IndentingPrintWriter pw) {
pw.print("Call ");
pw.print(mCall.getId());
pw.print(" [");
@@ -181,20 +339,6 @@
pw.println("To address: " + piiHandle(mCall.getHandle()));
for (CallEvent event : mEvents) {
-
- // We print out events in chronological order. During that process we look at each
- // event and see if it maps to a request on the Request-Response pairs map. If it
- // does, then we effectively start 'listening' for the response. We do that by
- // storing the response event ID in {@code pendingResponses}. When we find the
- // response in a later iteration of the loop, we grab the original request and
- // calculate the time it took to get a response.
- if (Events.requestResponsePairs.containsKey(event.eventId)) {
- // This event expects a response, so add that response to the maps
- // of pending events.
- String pendingResponse = Events.requestResponsePairs.get(event.eventId);
- pendingResponses.put(pendingResponse, event);
- }
-
pw.print(sDateFormat.format(new Date(event.time)));
pw.print(" - ");
pw.print(event.eventId);
@@ -214,26 +358,25 @@
pw.print(data);
pw.print(")");
}
-
- // If this event is a response event that we've been waiting for, calculate the time
- // it took for the response to complete and print that out as well.
- CallEvent requestEvent = pendingResponses.remove(event.eventId);
- if (requestEvent != null) {
- pw.print(", time since ");
- pw.print(requestEvent.eventId);
- pw.print(": ");
- pw.print(event.time - requestEvent.time);
- pw.print(" ms");
- }
pw.print(":");
pw.print(event.sessionId);
pw.println();
}
+
+ pw.println("Timings (average for this call, milliseconds):");
+ pw.increaseIndent();
+ Map<String, Double> avgEventTimings = EventTiming.averageTimings(extractEventTimings());
+ List<String> eventNames = new ArrayList<>(avgEventTimings.keySet());
+ Collections.sort(eventNames);
+ for (String eventName : eventNames) {
+ pw.printf("%s: %.2f\n", eventName, avgEventTimings.get(eventName));
+ }
+ pw.decreaseIndent();
pw.decreaseIndent();
}
}
- public static final int MAX_CALLS_TO_CACHE = 5; // Arbitrarily chosen.
+ public static final int MAX_CALLS_TO_CACHE = 10; // Arbitrarily chosen.
public static final int MAX_CALLS_TO_CACHE_DEBUG = 20; // Arbitrarily chosen.
private static final long EXTENDED_LOGGING_DURATION_MILLIS = 60000 * 30; // 30 minutes
@@ -548,6 +691,7 @@
// running time of the session.
long fullSessionTimeMs =
System.currentTimeMillis() - subsession.getExecutionStartTimeMilliseconds();
+ Analytics.addSessionTiming(subsession.getShortMethodName(), fullSessionTimeMs);
Log.v(LOGGING_TAG, Session.END_SESSION + " (dur: " + fullSessionTimeMs + " ms): " +
subsession.toString());
}
@@ -601,6 +745,20 @@
}
}
+ public static void event(Call call, String event, String format, Object... args) {
+ String msg;
+ try {
+ msg = (args == null || args.length == 0) ? format
+ : String.format(Locale.US, format, args);
+ } catch (IllegalFormatException ife) {
+ e("Log", ife, "IllegalFormatException: formatString='%s' numArgs=%d", format,
+ args.length);
+ msg = format + " (An error occurred while formatting the message.)";
+ }
+
+ event(call, event, msg);
+ }
+
@VisibleForTesting
public static synchronized void cleanupStaleSessions(long timeoutMs) {
String logMessage = "Stale Sessions Cleaned:\n";
@@ -641,6 +799,13 @@
// Now add a new entry
mCallEventRecords.add(newRecord);
mCallEventRecordMap.put(call, newRecord);
+
+ // Register the events with Analytics
+ if (call.getAnalytics() != null) {
+ call.getAnalytics().setCallEvents(newRecord);
+ } else {
+ Log.w(LOGGING_TAG, "Call analytics is null");
+ }
}
/**
diff --git a/src/com/android/server/telecom/NewOutgoingCallIntentBroadcaster.java b/src/com/android/server/telecom/NewOutgoingCallIntentBroadcaster.java
index 4d3fb84..8b5604b 100644
--- a/src/com/android/server/telecom/NewOutgoingCallIntentBroadcaster.java
+++ b/src/com/android/server/telecom/NewOutgoingCallIntentBroadcaster.java
@@ -55,9 +55,6 @@
*/
@VisibleForTesting
public class NewOutgoingCallIntentBroadcaster {
- private static final String EXTRA_ACTUAL_NUMBER_TO_DIAL =
- "android.telecom.extra.ACTUAL_NUMBER_TO_DIAL";
-
/**
* Legacy string constants used to retrieve gateway provider extras from intents. These still
* need to be copied from the source call intent to the destination intent in order to
@@ -67,14 +64,13 @@
public static final String EXTRA_GATEWAY_PROVIDER_PACKAGE =
"com.android.phone.extra.GATEWAY_PROVIDER_PACKAGE";
public static final String EXTRA_GATEWAY_URI = "com.android.phone.extra.GATEWAY_URI";
- public static final String EXTRA_GATEWAY_ORIGINAL_URI =
- "com.android.phone.extra.GATEWAY_ORIGINAL_URI";
private final CallsManager mCallsManager;
private final Call mCall;
private final Intent mIntent;
private final Context mContext;
private final PhoneNumberUtilsAdapter mPhoneNumberUtilsAdapter;
+ private final TelecomSystem.SyncRoot mLock;
/*
* Whether or not the outgoing call intent originated from the default phone application. If
@@ -92,6 +88,7 @@
mIntent = intent;
mPhoneNumberUtilsAdapter = phoneNumberUtilsAdapter;
mIsDefaultOrSystemPhoneApp = isDefaultPhoneApp;
+ mLock = mCallsManager.getLock();
}
/**
@@ -105,63 +102,65 @@
try {
Log.startSession("NOCBIR.oR");
Trace.beginSection("onReceiveNewOutgoingCallBroadcast");
- Log.v(this, "onReceive: %s", intent);
+ synchronized (mLock) {
+ Log.v(this, "onReceive: %s", intent);
- // Once the NEW_OUTGOING_CALL broadcast is finished, the resultData is used as the
- // actual number to call. (If null, no call will be placed.)
- String resultNumber = getResultData();
- Log.i(this, "Received new-outgoing-call-broadcast for %s with data %s", mCall,
- Log.pii(resultNumber));
+ // Once the NEW_OUTGOING_CALL broadcast is finished, the resultData is
+ // used as the actual number to call. (If null, no call will be placed.)
+ String resultNumber = getResultData();
+ Log.i(this, "Received new-outgoing-call-broadcast for %s with data %s", mCall,
+ Log.pii(resultNumber));
- boolean endEarly = false;
- if (resultNumber == null) {
- Log.v(this, "Call cancelled (null number), returning...");
- endEarly = true;
- } else if (mPhoneNumberUtilsAdapter.isPotentialLocalEmergencyNumber(
- mContext, resultNumber)) {
- Log.w(this, "Cannot modify outgoing call to emergency number %s.",
- resultNumber);
- endEarly = true;
- }
-
- if (endEarly) {
- if (mCall != null) {
- mCall.disconnect(true /* wasViaNewOutgoingCall */);
+ boolean endEarly = false;
+ if (resultNumber == null) {
+ Log.v(this, "Call cancelled (null number), returning...");
+ endEarly = true;
+ } else if (mPhoneNumberUtilsAdapter.isPotentialLocalEmergencyNumber(
+ mContext, resultNumber)) {
+ Log.w(this, "Cannot modify outgoing call to emergency number %s.",
+ resultNumber);
+ endEarly = true;
}
- return;
+
+ if (endEarly) {
+ if (mCall != null) {
+ mCall.disconnect(true /* wasViaNewOutgoingCall */);
+ }
+ return;
+ }
+
+ // If this call is already disconnected then we have nothing more to do.
+ if (mCall.isDisconnected()) {
+ Log.w(this, "Call has already been disconnected," +
+ " ignore the broadcast Call %s", mCall);
+ return;
+ }
+
+ Uri resultHandleUri = Uri.fromParts(
+ mPhoneNumberUtilsAdapter.isUriNumber(resultNumber) ?
+ PhoneAccount.SCHEME_SIP : PhoneAccount.SCHEME_TEL,
+ resultNumber, null);
+
+ Uri originalUri = mIntent.getData();
+
+ if (originalUri.getSchemeSpecificPart().equals(resultNumber)) {
+ Log.v(this, "Call number unmodified after" +
+ " new outgoing call intent broadcast.");
+ } else {
+ Log.v(this, "Retrieved modified handle after outgoing call intent" +
+ " broadcast: Original: %s, Modified: %s",
+ Log.pii(originalUri),
+ Log.pii(resultHandleUri));
+ }
+
+ GatewayInfo gatewayInfo = getGateWayInfoFromIntent(intent, resultHandleUri);
+ mCall.setNewOutgoingCallIntentBroadcastIsDone();
+ mCallsManager.placeOutgoingCall(mCall, resultHandleUri, gatewayInfo,
+ mIntent.getBooleanExtra(
+ TelecomManager.EXTRA_START_CALL_WITH_SPEAKERPHONE, false),
+ mIntent.getIntExtra(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE,
+ VideoProfile.STATE_AUDIO_ONLY));
}
-
- // If this call is already disconnected then we have nothing more to do.
- if (mCall.isDisconnected()) {
- Log.w(this,
- "Call has already been disconnected, ignore the broadcast Call %s", mCall);
- return;
- }
-
- Uri resultHandleUri = Uri.fromParts(
- mPhoneNumberUtilsAdapter.isUriNumber(resultNumber) ?
- PhoneAccount.SCHEME_SIP : PhoneAccount.SCHEME_TEL,
- resultNumber, null);
-
- Uri originalUri = mIntent.getData();
-
- if (originalUri.getSchemeSpecificPart().equals(resultNumber)) {
- Log.v(this, "Call number unmodified after new outgoing call intent broadcast.");
- } else {
- Log.v(this, "Retrieved modified handle after outgoing call intent broadcast: "
- + "Original: %s, Modified: %s",
- Log.pii(originalUri),
- Log.pii(resultHandleUri));
- }
-
- GatewayInfo gatewayInfo = getGateWayInfoFromIntent(intent, resultHandleUri);
- mCall.setNewOutgoingCallIntentBroadcastIsDone();
- mCallsManager.placeOutgoingCall(mCall, resultHandleUri, gatewayInfo,
- mIntent.getBooleanExtra(TelecomManager.EXTRA_START_CALL_WITH_SPEAKERPHONE,
- false),
- mIntent.getIntExtra(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE,
- VideoProfile.STATE_AUDIO_ONLY));
-
} finally {
Trace.endSection();
Log.endSession();
diff --git a/src/com/android/server/telecom/ParcelableCallUtils.java b/src/com/android/server/telecom/ParcelableCallUtils.java
index 9b65197..9cc61b3 100644
--- a/src/com/android/server/telecom/ParcelableCallUtils.java
+++ b/src/com/android/server/telecom/ParcelableCallUtils.java
@@ -28,11 +28,13 @@
* Utilities dealing with {@link ParcelableCall}.
*/
public class ParcelableCallUtils {
+ private static final int CALL_STATE_OVERRIDE_NONE = -1;
+
public static class Converter {
public ParcelableCall toParcelableCall(Call call, boolean includeVideoProvider,
PhoneAccountRegistrar phoneAccountRegistrar) {
return ParcelableCallUtils.toParcelableCall(
- call, includeVideoProvider, phoneAccountRegistrar);
+ call, includeVideoProvider, phoneAccountRegistrar, false);
}
}
@@ -45,13 +47,45 @@
* method creates a {@link VideoCallImpl} instance on access it is important for the
* recipient of the {@link ParcelableCall} to know if the video provider changed.
* @param phoneAccountRegistrar The {@link PhoneAccountRegistrar}.
+ * @param supportsExternalCalls Indicates whether the call should be parcelled for an
+ * {@link InCallService} which supports external calls or not.
+ */
+ public static ParcelableCall toParcelableCall(
+ Call call,
+ boolean includeVideoProvider,
+ PhoneAccountRegistrar phoneAccountRegistrar,
+ boolean supportsExternalCalls) {
+ return toParcelableCall(call, includeVideoProvider, phoneAccountRegistrar,
+ supportsExternalCalls, CALL_STATE_OVERRIDE_NONE /* overrideState */);
+ }
+
+ /**
+ * Parcels all information for a {@link Call} into a new {@link ParcelableCall} instance.
+ *
+ * @param call The {@link Call} to parcel.
+ * @param includeVideoProvider {@code true} if the video provider should be parcelled with the
+ * {@link Call}, {@code false} otherwise. Since the {@link ParcelableCall#getVideoCall()}
+ * method creates a {@link VideoCallImpl} instance on access it is important for the
+ * recipient of the {@link ParcelableCall} to know if the video provider changed.
+ * @param phoneAccountRegistrar The {@link PhoneAccountRegistrar}.
+ * @param supportsExternalCalls Indicates whether the call should be parcelled for an
+ * {@link InCallService} which supports external calls or not.
+ * @param overrideState When not {@link #CALL_STATE_OVERRIDE_NONE}, use the provided state as an
+ * override to whatever is defined in the call.
* @return The {@link ParcelableCall} containing all call information from the {@link Call}.
*/
public static ParcelableCall toParcelableCall(
Call call,
boolean includeVideoProvider,
- PhoneAccountRegistrar phoneAccountRegistrar) {
- int state = getParcelableState(call);
+ PhoneAccountRegistrar phoneAccountRegistrar,
+ boolean supportsExternalCalls,
+ int overrideState) {
+ int state;
+ if (overrideState == CALL_STATE_OVERRIDE_NONE) {
+ state = getParcelableState(call, supportsExternalCalls);
+ } else {
+ state = overrideState;
+ }
int capabilities = convertConnectionToCallCapabilities(call.getConnectionCapabilities());
int properties = convertConnectionToCallProperties(call.getConnectionProperties());
if (call.isConference()) {
@@ -63,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;
@@ -141,7 +175,7 @@
call.getExtras());
}
- private static int getParcelableState(Call call) {
+ private static int getParcelableState(Call call, boolean supportsExternalCalls) {
int state = CallState.NEW;
switch (call.getState()) {
case CallState.ABORTED:
@@ -157,6 +191,19 @@
case CallState.DIALING:
state = android.telecom.Call.STATE_DIALING;
break;
+ case CallState.PULLING:
+ if (supportsExternalCalls) {
+ // The InCallService supports external calls, so it must handle
+ // STATE_PULLING_CALL.
+ state = android.telecom.Call.STATE_PULLING_CALL;
+ } else {
+ // The InCallService does NOT support external calls, so remap
+ // STATE_PULLING_CALL to STATE_DIALING. In essence, pulling a call can be seen
+ // as a form of dialing, so it is appropriate for InCallServices which do not
+ // handle external calls.
+ state = android.telecom.Call.STATE_DIALING;
+ }
+ break;
case CallState.DISCONNECTING:
state = android.telecom.Call.STATE_DISCONNECTING;
break;
@@ -267,11 +314,14 @@
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,
- android.telecom.Call.Details.PROPERTY_IS_EXTERNAL_CALL
+ android.telecom.Call.Details.PROPERTY_IS_EXTERNAL_CALL,
+
+ Connection.PROPERTY_HAS_CDMA_VOICE_PRIVACY,
+ android.telecom.Call.Details.PROPERTY_HAS_CDMA_VOICE_PRIVACY
};
private static int convertConnectionToCallProperties(int connectionProperties) {
diff --git a/src/com/android/server/telecom/PhoneAccountRegistrar.java b/src/com/android/server/telecom/PhoneAccountRegistrar.java
index 19c1a01..41eb9e4 100644
--- a/src/com/android/server/telecom/PhoneAccountRegistrar.java
+++ b/src/com/android/server/telecom/PhoneAccountRegistrar.java
@@ -59,7 +59,6 @@
import org.xmlpull.v1.XmlSerializer;
import java.io.BufferedInputStream;
-import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
@@ -76,8 +75,12 @@
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.stream.Collector;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
/**
* Handles writing and reading PhoneAccountHandle registration entries. This is a simple verbatim
@@ -244,6 +247,46 @@
}
/**
+ * @return The {@link DefaultPhoneAccountHandle} containing the user-selected default calling
+ * account and group Id for the {@link UserHandle} specified.
+ */
+ private DefaultPhoneAccountHandle getUserSelectedDefaultPhoneAccount(UserHandle userHandle) {
+ if (userHandle == null) {
+ return null;
+ }
+ DefaultPhoneAccountHandle defaultPhoneAccountHandle = mState.defaultOutgoingAccountHandles
+ .get(userHandle);
+ if (defaultPhoneAccountHandle == null) {
+ return null;
+ }
+
+ return defaultPhoneAccountHandle;
+ }
+
+ /**
+ * @return The currently registered PhoneAccount in Telecom that has the same group Id.
+ */
+ private PhoneAccount getPhoneAccountByGroupId(String groupId, ComponentName groupComponentName,
+ UserHandle userHandle, PhoneAccountHandle excludePhoneAccountHandle) {
+ if (groupId == null || groupId.isEmpty() || userHandle == null) {
+ return null;
+ }
+ // Get the PhoneAccount with the same group Id (and same ComponentName) that is not the
+ // newAccount that was just added
+ List<PhoneAccount> accounts = getAllPhoneAccounts(userHandle).stream()
+ .filter(account -> groupId.equals(account.getGroupId()) &&
+ !account.getAccountHandle().equals(excludePhoneAccountHandle) &&
+ Objects.equals(account.getAccountHandle().getComponentName(),
+ groupComponentName))
+ .collect(Collectors.toList());
+ // There should be one or no PhoneAccounts with the same group Id
+ if (accounts.size() > 1) {
+ Log.w(this, "Found multiple PhoneAccounts registered to the same Group Id!");
+ }
+ return accounts.isEmpty() ? null : accounts.get(0);
+ }
+
+ /**
* Sets the phone account with which to place all calls by default. Set by the user
* within phone settings.
*/
@@ -277,7 +320,8 @@
}
mState.defaultOutgoingAccountHandles
- .put(userHandle, new DefaultPhoneAccountHandle(userHandle, accountHandle));
+ .put(userHandle, new DefaultPhoneAccountHandle(userHandle, accountHandle,
+ account.getGroupId()));
}
write();
@@ -591,6 +635,8 @@
}
mState.accounts.add(account);
+ // Set defaults and replace based on the group Id.
+ maybeReplaceOldAccount(account);
// Reset enabled state to whatever the value was if the account was already registered,
// or _true_ if this is a SIM-based account. All SIM-based accounts are always enabled.
account.setIsEnabled(
@@ -697,6 +743,40 @@
}
}
+ private void maybeReplaceOldAccount(PhoneAccount newAccount) {
+ UserHandle newAccountUserHandle = newAccount.getAccountHandle().getUserHandle();
+ DefaultPhoneAccountHandle defaultHandle =
+ getUserSelectedDefaultPhoneAccount(newAccountUserHandle);
+ if (defaultHandle == null || defaultHandle.groupId.isEmpty()) {
+ Log.v(this, "maybeReplaceOldAccount: Not replacing PhoneAccount, no group Id or " +
+ "default.");
+ return;
+ }
+ if (!defaultHandle.groupId.equals(newAccount.getGroupId())) {
+ Log.v(this, "maybeReplaceOldAccount: group Ids are not equal.");
+ return;
+ }
+ if (Objects.equals(newAccount.getAccountHandle().getComponentName(),
+ defaultHandle.phoneAccountHandle.getComponentName())) {
+ // Move default calling account over to new user, since the ComponentNames and Group Ids
+ // are the same.
+ setUserSelectedOutgoingPhoneAccount(newAccount.getAccountHandle(),
+ newAccountUserHandle);
+ } else {
+ Log.v(this, "maybeReplaceOldAccount: group Ids are equal, but ComponentName is not" +
+ " the same as the default. Not replacing default PhoneAccount.");
+ }
+ PhoneAccount replacementAccount = getPhoneAccountByGroupId(newAccount.getGroupId(),
+ newAccount.getAccountHandle().getComponentName(), newAccountUserHandle,
+ newAccount.getAccountHandle());
+ if (replacementAccount != null) {
+ // Unregister the old PhoneAccount.
+ Log.v(this, "maybeReplaceOldAccount: Unregistering old PhoneAccount: " +
+ replacementAccount.getAccountHandle());
+ unregisterPhoneAccount(replacementAccount.getAccountHandle());
+ }
+ }
+
/**
* Determines if the connection service specified by a {@link PhoneAccountHandle} requires the
* {@link Manifest.permission#BIND_TELECOM_CONNECTION_SERVICE} permission.
@@ -904,10 +984,13 @@
public final PhoneAccountHandle phoneAccountHandle;
+ public final String groupId;
+
public DefaultPhoneAccountHandle(UserHandle userHandle,
- PhoneAccountHandle phoneAccountHandle) {
+ PhoneAccountHandle phoneAccountHandle, String groupId) {
this.userHandle = userHandle;
this.phoneAccountHandle = phoneAccountHandle;
+ this.groupId = groupId;
}
}
@@ -1154,6 +1237,13 @@
serializer.endTag(null, tagName);
}
+ protected void writeNonNullString(String tagName, String value, XmlSerializer serializer)
+ throws IOException {
+ serializer.startTag(null, tagName);
+ serializer.text(value != null ? value : "");
+ serializer.endTag(null, tagName);
+ }
+
/**
* Reads a string array from the XML parser.
*
@@ -1292,8 +1382,9 @@
while (XmlUtils.nextElementWithin(parser, outerDepth)) {
if (parser.getName().equals(DEFAULT_OUTGOING)) {
if (s.versionNumber < 9) {
- // Migration old default phone account handle here by assuming the
- // default phone account handle is belong to primary user.
+ // Migrate old default phone account handle here by assuming the
+ // default phone account handle belongs to the primary user. Also,
+ // assume there are no groups.
parser.nextTag();
PhoneAccountHandle phoneAccountHandle = sPhoneAccountHandleXml
.readFromXml(parser, s.versionNumber, context);
@@ -1303,7 +1394,7 @@
UserHandle userHandle = primaryUser.getUserHandle();
DefaultPhoneAccountHandle defaultPhoneAccountHandle
= new DefaultPhoneAccountHandle(userHandle,
- phoneAccountHandle);
+ phoneAccountHandle, "" /* groupId */);
s.defaultOutgoingAccountHandles
.put(userHandle, defaultPhoneAccountHandle);
}
@@ -1343,6 +1434,7 @@
private static final String CLASS_DEFAULT_OUTGOING_PHONE_ACCOUNT_HANDLE
= "default_outgoing_phone_account_handle";
private static final String USER_SERIAL_NUMBER = "user_serial_number";
+ private static final String GROUP_ID = "group_id";
private static final String ACCOUNT_HANDLE = "account_handle";
@Override
@@ -1354,6 +1446,7 @@
if (serialNumber != -1) {
serializer.startTag(null, CLASS_DEFAULT_OUTGOING_PHONE_ACCOUNT_HANDLE);
writeLong(USER_SERIAL_NUMBER, serialNumber, serializer);
+ writeNonNullString(GROUP_ID, o.groupId, serializer);
serializer.startTag(null, ACCOUNT_HANDLE);
sPhoneAccountHandleXml.writeToXml(o.phoneAccountHandle, serializer,
context);
@@ -1371,6 +1464,7 @@
int outerDepth = parser.getDepth();
PhoneAccountHandle accountHandle = null;
String userSerialNumberString = null;
+ String groupId = "";
while (XmlUtils.nextElementWithin(parser, outerDepth)) {
if (parser.getName().equals(ACCOUNT_HANDLE)) {
parser.nextTag();
@@ -1379,6 +1473,9 @@
} else if (parser.getName().equals(USER_SERIAL_NUMBER)) {
parser.next();
userSerialNumberString = parser.getText();
+ } else if (parser.getName().equals(GROUP_ID)) {
+ parser.next();
+ groupId = parser.getText();
}
}
UserHandle userHandle = null;
@@ -1392,8 +1489,9 @@
"Could not parse UserHandle " + userSerialNumberString);
}
}
- if (accountHandle != null && userHandle != null) {
- return new DefaultPhoneAccountHandle(userHandle, accountHandle);
+ if (accountHandle != null && userHandle != null && groupId != null) {
+ return new DefaultPhoneAccountHandle(userHandle, accountHandle,
+ groupId);
}
}
return null;
diff --git a/src/com/android/server/telecom/PhoneNumberUtilsAdapter.java b/src/com/android/server/telecom/PhoneNumberUtilsAdapter.java
index 41284cb..8e59a64 100644
--- a/src/com/android/server/telecom/PhoneNumberUtilsAdapter.java
+++ b/src/com/android/server/telecom/PhoneNumberUtilsAdapter.java
@@ -24,6 +24,7 @@
* refactoring.
*/
public interface PhoneNumberUtilsAdapter {
+ boolean isLocalEmergencyNumber(Context context, String number);
boolean isPotentialLocalEmergencyNumber(Context context, String number);
boolean isUriNumber(String number);
String getNumberFromIntent(Intent intent, Context context);
diff --git a/src/com/android/server/telecom/PhoneNumberUtilsAdapterImpl.java b/src/com/android/server/telecom/PhoneNumberUtilsAdapterImpl.java
index 640d814..fa316a5 100644
--- a/src/com/android/server/telecom/PhoneNumberUtilsAdapterImpl.java
+++ b/src/com/android/server/telecom/PhoneNumberUtilsAdapterImpl.java
@@ -22,6 +22,11 @@
public class PhoneNumberUtilsAdapterImpl implements PhoneNumberUtilsAdapter {
@Override
+ public boolean isLocalEmergencyNumber(Context context, String number) {
+ return PhoneNumberUtils.isLocalEmergencyNumber(context, number);
+ }
+
+ @Override
public boolean isPotentialLocalEmergencyNumber(Context context, String number) {
return PhoneNumberUtils.isPotentialLocalEmergencyNumber(context, number);
}
diff --git a/src/com/android/server/telecom/PhoneStateBroadcaster.java b/src/com/android/server/telecom/PhoneStateBroadcaster.java
index 413f06c..0781ca2 100644
--- a/src/com/android/server/telecom/PhoneStateBroadcaster.java
+++ b/src/com/android/server/telecom/PhoneStateBroadcaster.java
@@ -43,6 +43,9 @@
@Override
public void onCallStateChanged(Call call, int oldState, int newState) {
+ if (call.isExternalCall()) {
+ return;
+ }
updateStates(call);
}
@@ -83,8 +86,8 @@
int callState = TelephonyManager.CALL_STATE_IDLE;
if (mCallsManager.hasRingingCall()) {
callState = TelephonyManager.CALL_STATE_RINGING;
- } else if (mCallsManager.getFirstCallWithState(CallState.DIALING, CallState.ACTIVE,
- CallState.ON_HOLD) != null) {
+ } else if (mCallsManager.getFirstCallWithState(CallState.DIALING, CallState.PULLING,
+ CallState.ACTIVE, CallState.ON_HOLD) != null) {
callState = TelephonyManager.CALL_STATE_OFFHOOK;
}
sendPhoneStateChangedBroadcast(call, callState);
diff --git a/src/com/android/server/telecom/ProximitySensorManager.java b/src/com/android/server/telecom/ProximitySensorManager.java
index dd336c4..e53b1d5 100644
--- a/src/com/android/server/telecom/ProximitySensorManager.java
+++ b/src/com/android/server/telecom/ProximitySensorManager.java
@@ -38,6 +38,9 @@
@Override
public void onCallRemoved(Call call) {
+ if (call.isExternalCall()) {
+ return;
+ }
if (mCallsManager.getCalls().isEmpty()) {
Log.i(this, "All calls removed, resetting proximity sensor to default state");
turnOff(true);
diff --git a/src/com/android/server/telecom/RespondViaSmsManager.java b/src/com/android/server/telecom/RespondViaSmsManager.java
index 29ea53c..7609b08 100644
--- a/src/com/android/server/telecom/RespondViaSmsManager.java
+++ b/src/com/android/server/telecom/RespondViaSmsManager.java
@@ -30,6 +30,7 @@
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
+import android.telecom.Connection;
import android.telecom.Response;
import android.telephony.PhoneNumberUtils;
import android.telephony.SubscriptionManager;
@@ -131,8 +132,7 @@
public void onIncomingCallRejected(Call call, boolean rejectWithMessage, String textMessage) {
if (rejectWithMessage
&& call.getHandle() != null
- && !call.can(
- android.telecom.Call.Details.CAPABILITY_CAN_SEND_RESPONSE_VIA_CONNECTION)) {
+ && !call.can(Connection.CAPABILITY_CAN_SEND_RESPONSE_VIA_CONNECTION)) {
int subId = mCallsManager.getPhoneAccountRegistrar().getSubscriptionIdForPhoneAccount(
call.getTargetPhoneAccount());
rejectCallWithMessage(call.getContext(), call.getHandle().getSchemeSpecificPart(),
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/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/ServiceBinder.java b/src/com/android/server/telecom/ServiceBinder.java
index 18d6581..9a0f7b4 100644
--- a/src/com/android/server/telecom/ServiceBinder.java
+++ b/src/com/android/server/telecom/ServiceBinder.java
@@ -336,22 +336,28 @@
*/
private void setBinder(IBinder binder) {
if (mBinder != binder) {
- mBinder = binder;
-
- setServiceInterface(binder);
-
if (binder == null) {
+ removeServiceInterface();
+ mBinder = null;
for (Listener l : mListeners) {
l.onUnbind(this);
}
+ } else {
+ mBinder = binder;
+ setServiceInterface(binder);
}
}
}
/**
- * Sets the service interface after the service is bound or unbound.
+ * Sets the service interface after the service is bound.
*
- * @param binder The actual bound service implementation.
+ * @param binder The new binder interface that is being set.
*/
protected abstract void setServiceInterface(IBinder binder);
+
+ /**
+ * Removes the service interface before the service is unbound.
+ */
+ protected abstract void removeServiceInterface();
}
diff --git a/src/com/android/server/telecom/TelecomServiceImpl.java b/src/com/android/server/telecom/TelecomServiceImpl.java
index 577d93a..c93a752 100644
--- a/src/com/android/server/telecom/TelecomServiceImpl.java
+++ b/src/com/android/server/telecom/TelecomServiceImpl.java
@@ -39,9 +39,9 @@
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;
import android.telecom.TelecomManager;
import android.telecom.VideoProfile;
import android.telephony.SubscriptionManager;
@@ -56,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;
@@ -1145,11 +1143,11 @@
}
@Override
- public List<ParcelableCallAnalytics> dumpCallAnalytics() {
+ public TelecomAnalytics dumpCallAnalytics() {
try {
Log.startSession("TSI.dCA");
enforcePermission(DUMP);
- return Arrays.asList(Analytics.dumpToParcelableAnalytics());
+ return Analytics.dumpToParcelableAnalytics();
} finally {
Log.endSession();
}
@@ -1173,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: ");
@@ -1293,6 +1296,7 @@
call = mCallsManager.getFirstCallWithState(
CallState.ACTIVE,
CallState.DIALING,
+ CallState.PULLING,
CallState.RINGING,
CallState.ON_HOLD);
}
diff --git a/src/com/android/server/telecom/TelecomSystem.java b/src/com/android/server/telecom/TelecomSystem.java
index 7134fe5..d665a82 100644
--- a/src/com/android/server/telecom/TelecomSystem.java
+++ b/src/com/android/server/telecom/TelecomSystem.java
@@ -178,7 +178,9 @@
BluetoothPhoneServiceImplFactory
bluetoothPhoneServiceImplFactory,
Timeouts.Adapter timeoutsAdapter,
- AsyncRingtonePlayer asyncRingtonePlayer) {
+ AsyncRingtonePlayer asyncRingtonePlayer,
+ PhoneNumberUtilsAdapter phoneNumberUtilsAdapter,
+ InterruptionFilterProxy interruptionFilterProxy) {
mContext = context.getApplicationContext();
Log.setContext(mContext);
Log.initMd5Sum();
@@ -220,7 +222,9 @@
systemStateProvider,
defaultDialerAdapter,
timeoutsAdapter,
- asyncRingtonePlayer);
+ asyncRingtonePlayer,
+ phoneNumberUtilsAdapter,
+ interruptionFilterProxy);
mRespondViaSmsManager = new RespondViaSmsManager(mCallsManager, mLock);
mCallsManager.setRespondViaSmsManager(mRespondViaSmsManager);
diff --git a/src/com/android/server/telecom/TelephonyUtil.java b/src/com/android/server/telecom/TelephonyUtil.java
index 69adaf9..50b8901 100644
--- a/src/com/android/server/telecom/TelephonyUtil.java
+++ b/src/com/android/server/telecom/TelephonyUtil.java
@@ -22,9 +22,15 @@
import android.telecom.PhoneAccount;
import android.telecom.PhoneAccountHandle;
import android.telephony.PhoneNumberUtils;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
import com.android.internal.annotations.VisibleForTesting;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
/**
* Utilities to deal with the system telephony services. The system telephony services are treated
* differently from 3rd party services in some situations (emergency calls, audio focus, etc...).
@@ -67,4 +73,55 @@
return handle != null && PhoneNumberUtils.isLocalEmergencyNumber(
context, handle.getSchemeSpecificPart());
}
+
+ public static void sortSimPhoneAccounts(Context context, List<PhoneAccount> accounts) {
+ final TelephonyManager telephonyManager = TelephonyManager.from(context);
+
+ // Sort the accounts according to how we want to display them.
+ Collections.sort(accounts, new Comparator<PhoneAccount>() {
+ @Override
+ public int compare(PhoneAccount account1, PhoneAccount account2) {
+ int retval = 0;
+
+ // SIM accounts go first
+ boolean isSim1 = account1.hasCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION);
+ boolean isSim2 = account2.hasCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION);
+ if (isSim1 != isSim2) {
+ retval = isSim1 ? -1 : 1;
+ }
+
+ int subId1 = telephonyManager.getSubIdForPhoneAccount(account1);
+ int subId2 = telephonyManager.getSubIdForPhoneAccount(account2);
+ if (subId1 != SubscriptionManager.INVALID_SUBSCRIPTION_ID &&
+ subId2 != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+ retval = (SubscriptionManager.getSlotId(subId1) <
+ SubscriptionManager.getSlotId(subId2)) ? -1 : 1;
+ }
+
+ // Then order by package
+ if (retval == 0) {
+ String pkg1 = account1.getAccountHandle().getComponentName().getPackageName();
+ String pkg2 = account2.getAccountHandle().getComponentName().getPackageName();
+ retval = pkg1.compareTo(pkg2);
+ }
+
+ // Finally, order by label
+ if (retval == 0) {
+ String label1 = nullToEmpty(account1.getLabel().toString());
+ String label2 = nullToEmpty(account2.getLabel().toString());
+ retval = label1.compareTo(label2);
+ }
+
+ // Then by hashcode
+ if (retval == 0) {
+ retval = account1.hashCode() - account2.hashCode();
+ }
+ return retval;
+ }
+ });
+ }
+
+ private static String nullToEmpty(String str) {
+ return str == null ? "" : str;
+ }
}
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 9d17139..3722b59 100644
--- a/src/com/android/server/telecom/VideoProviderProxy.java
+++ b/src/com/android/server/telecom/VideoProviderProxy.java
@@ -29,7 +29,6 @@
import com.android.internal.telecom.IVideoProvider;
import java.util.Collections;
-import java.util.HashMap;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
@@ -130,6 +129,31 @@
Log.startSession("VPP.rSMR");
synchronized (mLock) {
logFromVideoProvider("receiveSessionModifyRequest: " + videoProfile);
+ Log.event(mCall, Log.Events.RECEIVE_VIDEO_REQUEST,
+ VideoProfile.videoStateToString(videoProfile.getVideoState()));
+
+ mCall.getAnalytics().addVideoEvent(
+ Analytics.RECEIVE_REMOTE_SESSION_MODIFY_REQUEST,
+ videoProfile.getVideoState());
+
+ if (!mCall.isVideoCallingSupported() &&
+ VideoProfile.isVideo(videoProfile.getVideoState())) {
+ // If video calling is not supported by the phone account, and we receive
+ // a request to upgrade to video, automatically reject it without informing
+ // the InCallService.
+
+ Log.event(mCall, Log.Events.SEND_VIDEO_RESPONSE, "video not supported");
+ VideoProfile responseProfile = new VideoProfile(
+ VideoProfile.STATE_AUDIO_ONLY);
+ try {
+ mConectionServiceVideoProvider.sendSessionModifyResponse(
+ responseProfile);
+ } catch (RemoteException e) {
+ }
+
+ // Don't want to inform listeners of the request as we've just rejected it.
+ return;
+ }
// Inform other Telecom components of the session modification request.
for (Listener listener : mListeners) {
@@ -154,10 +178,19 @@
@Override
public void receiveSessionModifyResponse(int status, VideoProfile requestProfile,
VideoProfile responseProfile) {
+ logFromVideoProvider("receiveSessionModifyResponse: status=" + status +
+ " requestProfile=" + requestProfile + " responseProfile=" + responseProfile);
+ String eventMessage = "Status Code : " + status + " Video State: " +
+ (responseProfile != null ? responseProfile.getVideoState() : "null");
+ Log.event(mCall, Log.Events.RECEIVE_VIDEO_RESPONSE, eventMessage);
synchronized (mLock) {
- logFromVideoProvider("receiveSessionModifyResponse: status=" + status +
- " requestProfile=" + requestProfile + " responseProfile=" +
- responseProfile);
+ if (status == Connection.VideoProvider.SESSION_MODIFY_REQUEST_SUCCESS) {
+ mCall.getAnalytics().addVideoEvent(
+ Analytics.RECEIVE_REMOTE_SESSION_MODIFY_RESPONSE,
+ responseProfile == null ?
+ VideoProfile.STATE_AUDIO_ONLY :
+ responseProfile.getVideoState());
+ }
VideoProviderProxy.this.receiveSessionModifyResponse(status, requestProfile,
responseProfile);
}
@@ -172,7 +205,8 @@
@Override
public void handleCallSessionEvent(int event) {
synchronized (mLock) {
- logFromVideoProvider("handleCallSessionEvent: " + event);
+ logFromVideoProvider("handleCallSessionEvent: " +
+ Connection.VideoProvider.sessionEventToString(event));
VideoProviderProxy.this.handleCallSessionEvent(event);
}
}
@@ -337,6 +371,11 @@
public void onSendSessionModifyRequest(VideoProfile fromProfile, VideoProfile toProfile) {
synchronized (mLock) {
logFromInCall("sendSessionModifyRequest: from=" + fromProfile + " to=" + toProfile);
+ Log.event(mCall, Log.Events.SEND_VIDEO_REQUEST,
+ VideoProfile.videoStateToString(toProfile.getVideoState()));
+ mCall.getAnalytics().addVideoEvent(
+ Analytics.SEND_LOCAL_SESSION_MODIFY_REQUEST,
+ toProfile.getVideoState());
try {
mConectionServiceVideoProvider.sendSessionModifyRequest(fromProfile, toProfile);
} catch (RemoteException e) {
@@ -354,6 +393,11 @@
public void onSendSessionModifyResponse(VideoProfile responseProfile) {
synchronized (mLock) {
logFromInCall("sendSessionModifyResponse: " + responseProfile);
+ Log.event(mCall, Log.Events.SEND_VIDEO_RESPONSE,
+ VideoProfile.videoStateToString(responseProfile.getVideoState()));
+ mCall.getAnalytics().addVideoEvent(
+ Analytics.SEND_LOCAL_SESSION_MODIFY_RESPONSE,
+ responseProfile.getVideoState());
try {
mConectionServiceVideoProvider.sendSessionModifyResponse(responseProfile);
} catch (RemoteException e) {
@@ -434,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);
}
/**
@@ -444,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/src/com/android/server/telecom/callfiltering/AsyncBlockCheckFilter.java b/src/com/android/server/telecom/callfiltering/AsyncBlockCheckFilter.java
index 8872297..51fc390 100644
--- a/src/com/android/server/telecom/callfiltering/AsyncBlockCheckFilter.java
+++ b/src/com/android/server/telecom/callfiltering/AsyncBlockCheckFilter.java
@@ -33,7 +33,8 @@
private final Context mContext;
private final BlockCheckerAdapter mBlockCheckerAdapter;
private Call mIncomingCall;
- private Session mLogSubsession;
+ private Session mBackgroundTaskSubsession;
+ private Session mPostExecuteSubsession;
private CallFilterResultCallback mCallback;
public AsyncBlockCheckFilter(Context context, BlockCheckerAdapter blockCheckerAdapter) {
@@ -52,13 +53,14 @@
@Override
protected void onPreExecute() {
- mLogSubsession = Log.createSubsession();
+ mBackgroundTaskSubsession = Log.createSubsession();
+ mPostExecuteSubsession = Log.createSubsession();
}
@Override
protected Boolean doInBackground(String... params) {
try {
- Log.continueSession(mLogSubsession, "ABCF.dIB");
+ Log.continueSession(mBackgroundTaskSubsession, "ABCF.dIB");
Log.event(mIncomingCall, Log.Events.BLOCK_CHECK_INITIATED);
return mBlockCheckerAdapter.isBlocked(mContext, params[0]);
} finally {
@@ -68,23 +70,28 @@
@Override
protected void onPostExecute(Boolean isBlocked) {
- CallFilteringResult result;
- if (isBlocked) {
- result = new CallFilteringResult(
- false, // shouldAllowCall
- true, //shouldReject
- false, //shouldAddToCallLog
- false // shouldShowNotification
- );
- } else {
- result = new CallFilteringResult(
- true, // shouldAllowCall
- false, // shouldReject
- true, // shouldAddToCallLog
- true // shouldShowNotification
- );
+ Log.continueSession(mPostExecuteSubsession, "ABCF.oPE");
+ try {
+ CallFilteringResult result;
+ if (isBlocked) {
+ result = new CallFilteringResult(
+ false, // shouldAllowCall
+ true, //shouldReject
+ false, //shouldAddToCallLog
+ false // shouldShowNotification
+ );
+ } else {
+ result = new CallFilteringResult(
+ true, // shouldAllowCall
+ false, // shouldReject
+ true, // shouldAddToCallLog
+ true // shouldShowNotification
+ );
+ }
+ Log.event(mIncomingCall, Log.Events.BLOCK_CHECK_FINISHED, result);
+ mCallback.onCallFilteringComplete(mIncomingCall, result);
+ } finally {
+ Log.endSession();
}
- Log.event(mIncomingCall, Log.Events.BLOCK_CHECK_FINISHED, result);
- mCallback.onCallFilteringComplete(mIncomingCall, result);
}
}
diff --git a/src/com/android/server/telecom/callfiltering/DirectToVoicemailCallFilter.java b/src/com/android/server/telecom/callfiltering/DirectToVoicemailCallFilter.java
index 1363d62..1aaae46 100644
--- a/src/com/android/server/telecom/callfiltering/DirectToVoicemailCallFilter.java
+++ b/src/com/android/server/telecom/callfiltering/DirectToVoicemailCallFilter.java
@@ -23,6 +23,8 @@
import com.android.server.telecom.CallerInfoLookupHelper;
import com.android.server.telecom.Log;
+import java.util.Objects;
+
public class DirectToVoicemailCallFilter implements IncomingCallFilter.CallFilter {
private final CallerInfoLookupHelper mCallerInfoLookupHelper;
@@ -34,13 +36,14 @@
public void startFilterLookup(final Call call, CallFilterResultCallback callback) {
Log.event(call, Log.Events.DIRECT_TO_VM_INITIATED);
final Uri callHandle = call.getHandle();
+
mCallerInfoLookupHelper.startLookup(callHandle,
new CallerInfoLookupHelper.OnQueryCompleteListener() {
@Override
public void onCallerInfoQueryComplete(Uri handle, CallerInfo info) {
CallFilteringResult result;
- if (callHandle.equals(handle)) {
- if (info.shouldSendToVoicemail) {
+ if (Objects.equals(callHandle, handle)) {
+ if (info != null && info.shouldSendToVoicemail) {
result = new CallFilteringResult(
false, // shouldAllowCall
true, // shouldReject
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/components/ChangeDefaultDialerDialog.java b/src/com/android/server/telecom/components/ChangeDefaultDialerDialog.java
index 5fd7904..107389b 100644
--- a/src/com/android/server/telecom/components/ChangeDefaultDialerDialog.java
+++ b/src/com/android/server/telecom/components/ChangeDefaultDialerDialog.java
@@ -21,11 +21,18 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
+import android.graphics.Color;
+import android.graphics.Typeface;
import android.os.Bundle;
import android.telecom.DefaultDialerManager;
import android.telecom.TelecomManager;
import android.telephony.TelephonyManager;
+import android.text.Spannable;
+import android.text.SpannableString;
+import android.text.SpannableStringBuilder;
import android.text.TextUtils;
+import android.text.style.ForegroundColorSpan;
+import android.text.style.StyleSpan;
import android.util.Log;
import com.android.internal.app.AlertActivity;
@@ -57,7 +64,7 @@
}
// Show dialog to require user confirmation.
- buildDialog(oldPackage, mNewPackage);
+ buildDialog(mNewPackage);
}
@Override
@@ -81,36 +88,26 @@
}
if (!DefaultDialerManager.getInstalledDialerApplications(this).contains(newPackage)) {
- Log.w(TAG, "Provided package name does not correspond to an installed Dialer "
+ Log.w(TAG, "Provided package name does not correspond to an installed Phone "
+ "application.");
return false;
}
if (!TextUtils.isEmpty(oldPackage) && TextUtils.equals(oldPackage, newPackage)) {
- Log.w(TAG, "Provided package name is already the current default Dialer application.");
+ Log.w(TAG, "Provided package name is already the current default Phone application.");
return false;
}
return true;
}
- private boolean buildDialog(String oldPackage, String newPackage) {
+ private boolean buildDialog(String newPackage) {
final PackageManager pm = getPackageManager();
- final String newPackageLabel =
- getApplicationLabelForPackageName(pm, newPackage);
+ final String newPackageLabel = getApplicationLabelForPackageName(pm, newPackage);
final AlertController.AlertParams p = mAlertParams;
- p.mTitle = getString(R.string.change_default_dialer_dialog_title);
- if (!TextUtils.isEmpty(oldPackage)) {
- String oldPackageLabel =
- getApplicationLabelForPackageName(pm, oldPackage);
- p.mMessage = getString(R.string.change_default_dialer_with_previous_app_set_text,
- newPackageLabel,
- oldPackageLabel);
- } else {
- p.mMessage = getString(R.string.change_default_dialer_no_previous_app_set_text,
- newPackageLabel);
- }
- p.mPositiveButtonText = getString(android.R.string.yes);
- p.mNegativeButtonText = getString(android.R.string.no);
+ p.mTitle = getString(R.string.change_default_dialer_dialog_title, newPackageLabel);
+ p.mMessage = getString(R.string.change_default_dialer_warning_message, newPackageLabel);
+ p.mPositiveButtonText = getString(R.string.change_default_dialer_dialog_affirmative);
+ p.mNegativeButtonText = getString(R.string.change_default_dialer_dialog_negative);
p.mPositiveButtonListener = this;
p.mNegativeButtonListener = this;
setupAlert();
diff --git a/src/com/android/server/telecom/components/TelecomService.java b/src/com/android/server/telecom/components/TelecomService.java
index bef8453..c7fd9e0 100644
--- a/src/com/android/server/telecom/components/TelecomService.java
+++ b/src/com/android/server/telecom/components/TelecomService.java
@@ -16,6 +16,8 @@
package com.android.server.telecom.components;
+import android.app.Notification;
+import android.app.NotificationManager;
import android.app.Service;
import android.bluetooth.BluetoothAdapter;
import android.content.Context;
@@ -35,7 +37,10 @@
import com.android.server.telecom.HeadsetMediaButtonFactory;
import com.android.server.telecom.InCallWakeLockControllerFactory;
import com.android.server.telecom.CallAudioManager;
+import com.android.server.telecom.InterruptionFilterProxy;
import com.android.server.telecom.PhoneAccountRegistrar;
+import com.android.server.telecom.PhoneNumberUtilsAdapter;
+import com.android.server.telecom.PhoneNumberUtilsAdapterImpl;
import com.android.server.telecom.ProximitySensorManagerFactory;
import com.android.server.telecom.InCallWakeLockController;
import com.android.server.telecom.Log;
@@ -71,6 +76,9 @@
*/
static void initializeTelecomSystem(Context context) {
if (TelecomSystem.getInstance() == null) {
+ final NotificationManager notificationManager =
+ (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
+
TelecomSystem.setInstance(
new TelecomSystem(
context,
@@ -148,7 +156,19 @@
}
},
new Timeouts.Adapter(),
- new AsyncRingtonePlayer()
+ new AsyncRingtonePlayer(),
+ new PhoneNumberUtilsAdapterImpl(),
+ new InterruptionFilterProxy() {
+ @Override
+ public void setInterruptionFilter(int interruptionFilter) {
+ notificationManager.setInterruptionFilter(interruptionFilter);
+ }
+
+ @Override
+ public int getCurrentInterruptionFilter() {
+ return notificationManager.getCurrentInterruptionFilter();
+ }
+ }
));
}
if (BluetoothAdapter.getDefaultAdapter() != null) {
diff --git a/src/com/android/server/telecom/ui/MissedCallNotifierImpl.java b/src/com/android/server/telecom/ui/MissedCallNotifierImpl.java
index 21af1aa..9530f63 100644
--- a/src/com/android/server/telecom/ui/MissedCallNotifierImpl.java
+++ b/src/com/android/server/telecom/ui/MissedCallNotifierImpl.java
@@ -31,6 +31,7 @@
import com.android.server.telecom.Log;
import com.android.server.telecom.MissedCallNotifier;
import com.android.server.telecom.PhoneAccountRegistrar;
+import com.android.server.telecom.PhoneNumberUtilsAdapter;
import com.android.server.telecom.R;
import com.android.server.telecom.Runnable;
import com.android.server.telecom.TelecomBroadcastIntentProcessor;
@@ -167,7 +168,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/testapps/src/com/android/server/telecom/testapps/TestConnectionService.java b/testapps/src/com/android/server/telecom/testapps/TestConnectionService.java
index b30e372..0150dbe 100644
--- a/testapps/src/com/android/server/telecom/testapps/TestConnectionService.java
+++ b/testapps/src/com/android/server/telecom/testapps/TestConnectionService.java
@@ -165,6 +165,9 @@
capabilities |= CAPABILITY_RESPOND_VIA_TEXT;
setConnectionCapabilities(capabilities);
+ if (isIncoming) {
+ putExtra(Connection.EXTRA_ANSWERING_DROPS_FG_CALL, true);
+ }
LocalBroadcastManager.getInstance(getApplicationContext()).registerReceiver(
mHangupReceiver, new IntentFilter(TestCallActivity.ACTION_HANGUP_CALLS));
final IntentFilter filter =
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
new file mode 100644
index 0000000..81d4aa5
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/AnalyticsTests.java
@@ -0,0 +1,343 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.telecom.tests;
+
+import android.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
+ public void testAnalyticsSingleCall() throws Exception {
+ IdPair testCall = startAndMakeActiveIncomingCall(
+ "650-555-1212",
+ mPhoneAccountA0.getAccountHandle(),
+ mConnectionServiceFixtureA);
+
+ Map<String, Analytics.CallInfoImpl> analyticsMap = Analytics.cloneData();
+
+ assertTrue(analyticsMap.containsKey(testCall.mCallId));
+
+ Analytics.CallInfoImpl callAnalytics = analyticsMap.get(testCall.mCallId);
+ assertTrue(callAnalytics.startTime > 0);
+ assertEquals(0, callAnalytics.endTime);
+ assertEquals(Analytics.INCOMING_DIRECTION, callAnalytics.callDirection);
+ assertFalse(callAnalytics.isInterrupted);
+ assertNull(callAnalytics.callTerminationReason);
+ assertEquals(mConnectionServiceComponentNameA.flattenToShortString(),
+ callAnalytics.connectionService);
+
+ mConnectionServiceFixtureA.
+ sendSetDisconnected(testCall.mConnectionId, DisconnectCause.ERROR);
+
+ analyticsMap = Analytics.cloneData();
+ callAnalytics = analyticsMap.get(testCall.mCallId);
+ assertTrue(callAnalytics.endTime > 0);
+ assertNotNull(callAnalytics.callTerminationReason);
+ assertEquals(DisconnectCause.ERROR, callAnalytics.callTerminationReason.getCode());
+
+ StringWriter sr = new StringWriter();
+ IndentingPrintWriter ip = new IndentingPrintWriter(sr, " ");
+ Analytics.dump(ip);
+ String dumpResult = sr.toString();
+ String[] expectedFields = {"startTime", "endTime", "direction", "isAdditionalCall",
+ "isInterrupted", "callTechnologies", "callTerminationReason", "connectionService"};
+ for (String field : expectedFields) {
+ assertTrue(dumpResult.contains(field));
+ }
+ }
+
+ @MediumTest
+ public void testAnalyticsDumping() 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);
+
+ TelecomManager tm = (TelecomManager) mSpyContext.getSystemService(Context.TELECOM_SERVICE);
+ List<ParcelableCallAnalytics> analyticsList = tm.dumpAnalytics().getCallAnalytics();
+
+ assertEquals(1, analyticsList.size());
+ ParcelableCallAnalytics pCA = analyticsList.get(0);
+
+ assertTrue(Math.abs(expectedAnalytics.startTime - pCA.getStartTimeMillis()) <
+ ParcelableCallAnalytics.MILLIS_IN_5_MINUTES);
+ assertEquals(0, pCA.getStartTimeMillis() % ParcelableCallAnalytics.MILLIS_IN_5_MINUTES);
+ assertTrue(Math.abs((expectedAnalytics.endTime - expectedAnalytics.startTime) -
+ pCA.getCallDurationMillis()) < ParcelableCallAnalytics.MILLIS_IN_1_SECOND);
+ assertEquals(0, pCA.getCallDurationMillis() % ParcelableCallAnalytics.MILLIS_IN_1_SECOND);
+
+ assertEquals(expectedAnalytics.callDirection, pCA.getCallType());
+ assertEquals(expectedAnalytics.isAdditionalCall, pCA.isAdditionalCall());
+ assertEquals(expectedAnalytics.isInterrupted, pCA.isInterrupted());
+ assertEquals(expectedAnalytics.callTechnologies, pCA.getCallTechnologies());
+ assertEquals(expectedAnalytics.callTerminationReason.getCode(),
+ pCA.getCallTerminationCode());
+ assertEquals(expectedAnalytics.connectionService, pCA.getConnectionService());
+ List<ParcelableCallAnalytics.AnalyticsEvent> analyticsEvents = pCA.analyticsEvents();
+ 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(
+ ParcelableCallAnalytics.AnalyticsEvent.FILTERING_INITIATED));
+ }
+
+ @MediumTest
+ public void testAnalyticsTwoCalls() throws Exception {
+ IdPair testCall1 = startAndMakeActiveIncomingCall(
+ "650-555-1212",
+ mPhoneAccountA0.getAccountHandle(),
+ mConnectionServiceFixtureA);
+ IdPair testCall2 = startAndMakeActiveOutgoingCall(
+ "650-555-1213",
+ mPhoneAccountA0.getAccountHandle(),
+ mConnectionServiceFixtureA);
+
+ Map<String, Analytics.CallInfoImpl> analyticsMap = Analytics.cloneData();
+ assertTrue(analyticsMap.containsKey(testCall1.mCallId));
+ assertTrue(analyticsMap.containsKey(testCall2.mCallId));
+
+ Analytics.CallInfoImpl callAnalytics1 = analyticsMap.get(testCall1.mCallId);
+ Analytics.CallInfoImpl callAnalytics2 = analyticsMap.get(testCall2.mCallId);
+ assertTrue(callAnalytics1.startTime > 0);
+ assertTrue(callAnalytics2.startTime > 0);
+ assertEquals(0, callAnalytics1.endTime);
+ assertEquals(0, callAnalytics2.endTime);
+
+ assertEquals(Analytics.INCOMING_DIRECTION, callAnalytics1.callDirection);
+ assertEquals(Analytics.OUTGOING_DIRECTION, callAnalytics2.callDirection);
+
+ assertTrue(callAnalytics1.isInterrupted);
+ assertTrue(callAnalytics2.isAdditionalCall);
+
+ assertNull(callAnalytics1.callTerminationReason);
+ assertNull(callAnalytics2.callTerminationReason);
+
+ assertEquals(mConnectionServiceComponentNameA.flattenToShortString(),
+ callAnalytics1.connectionService);
+ assertEquals(mConnectionServiceComponentNameA.flattenToShortString(),
+ callAnalytics1.connectionService);
+
+ mConnectionServiceFixtureA.
+ sendSetDisconnected(testCall2.mConnectionId, DisconnectCause.REMOTE);
+ mConnectionServiceFixtureA.
+ sendSetDisconnected(testCall1.mConnectionId, DisconnectCause.ERROR);
+
+ analyticsMap = Analytics.cloneData();
+ callAnalytics1 = analyticsMap.get(testCall1.mCallId);
+ callAnalytics2 = analyticsMap.get(testCall2.mCallId);
+ assertTrue(callAnalytics1.endTime > 0);
+ assertTrue(callAnalytics2.endTime > 0);
+ assertNotNull(callAnalytics1.callTerminationReason);
+ assertNotNull(callAnalytics2.callTerminationReason);
+ assertEquals(DisconnectCause.ERROR, callAnalytics1.callTerminationReason.getCode());
+ 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};
+ long[] expected = {0, -1, -10, -100, -60000, 1, 10, 100, 1000, 500000};
+ for (int i = 0; i < testVals.length; i++) {
+ assertEquals(expected[i], Analytics.roundToOneSigFig(testVals[i]));
+ }
+ }
+
+ @SmallTest
+ public void testAnalyticsLogSessionTiming() throws Exception {
+ long minTime = 50;
+ Log.startSession(Log.Sessions.CSW_ADD_CONFERENCE_CALL);
+ Thread.sleep(minTime);
+ Log.endSession();
+ TelecomManager tm = (TelecomManager) mSpyContext.getSystemService(Context.TELECOM_SERVICE);
+ List<TelecomAnalytics.SessionTiming> sessions = tm.dumpAnalytics().getSessionTimings();
+ sessions.stream()
+ .filter(s -> Log.Sessions.CSW_ADD_CONFERENCE_CALL.equals(
+ 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));
+ }
+
+ @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 49ff43a..1c096f9 100644
--- a/tests/src/com/android/server/telecom/tests/BasicCallTests.java
+++ b/tests/src/com/android/server/telecom/tests/BasicCallTests.java
@@ -27,7 +27,6 @@
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
-import android.content.ContentResolver;
import android.content.Context;
import android.content.IContentProvider;
import android.media.AudioManager;
@@ -43,19 +42,15 @@
import android.telecom.ConnectionRequest;
import android.telecom.DisconnectCause;
import android.telecom.ParcelableCall;
-import android.telecom.ParcelableCallAnalytics;
import android.telecom.PhoneAccount;
import android.telecom.PhoneAccountHandle;
import android.telecom.TelecomManager;
import android.telecom.VideoProfile;
import android.test.suitebuilder.annotation.LargeTest;
import android.test.suitebuilder.annotation.MediumTest;
-import android.test.suitebuilder.annotation.SmallTest;
import com.android.internal.telecom.IInCallAdapter;
import com.android.internal.telephony.CallerInfo;
-import com.android.internal.util.IndentingPrintWriter;
-import com.android.server.telecom.Analytics;
import com.android.server.telecom.Log;
import com.google.common.base.Predicate;
@@ -63,9 +58,6 @@
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
-import java.io.StringWriter;
-import java.util.List;
-import java.util.Map;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.CyclicBarrier;
@@ -73,13 +65,6 @@
import org.mockito.ArgumentCaptor;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.timeout;
-import static org.mockito.Mockito.verify;
-
/**
* Performs various basic call tests in Telecom.
*/
@@ -137,7 +122,7 @@
telecomManager.acceptRingingCall();
verify(mConnectionServiceFixtureA.getTestDouble(), timeout(TEST_TIMEOUT))
- .answer(ids.mCallId);
+ .answer(ids.mConnectionId);
mConnectionServiceFixtureA.sendSetActive(ids.mConnectionId);
mInCallServiceFixtureX.mInCallAdapter.disconnectCall(ids.mCallId);
@@ -165,7 +150,7 @@
// Answer video API should be called
verify(mConnectionServiceFixtureA.getTestDouble(), timeout(TEST_TIMEOUT))
- .answerVideo(eq(ids.mCallId), eq(VideoProfile.STATE_BIDIRECTIONAL));
+ .answerVideo(eq(ids.mConnectionId), eq(VideoProfile.STATE_BIDIRECTIONAL));
mConnectionServiceFixtureA.sendSetActive(ids.mConnectionId);
mInCallServiceFixtureX.mInCallAdapter.disconnectCall(ids.mCallId);
@@ -192,7 +177,7 @@
// The generic answer method on the ConnectionService is used to answer audio-only calls.
verify(mConnectionServiceFixtureA.getTestDouble(), timeout(TEST_TIMEOUT))
- .answer(eq(ids.mCallId));
+ .answer(eq(ids.mConnectionId));
mConnectionServiceFixtureA.sendSetActive(ids.mConnectionId);
mInCallServiceFixtureX.mInCallAdapter.disconnectCall(ids.mCallId);
@@ -220,7 +205,7 @@
// Answer video API should be called
verify(mConnectionServiceFixtureA.getTestDouble(), timeout(TEST_TIMEOUT))
- .answerVideo(eq(ids.mCallId), eq(VideoProfile.STATE_BIDIRECTIONAL));
+ .answerVideo(eq(ids.mConnectionId), eq(VideoProfile.STATE_BIDIRECTIONAL));
mConnectionServiceFixtureA.sendSetActive(ids.mConnectionId);
mInCallServiceFixtureX.mInCallAdapter.disconnectCall(ids.mCallId);
}
@@ -293,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) {
@@ -329,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());
@@ -372,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) {
@@ -659,7 +649,7 @@
mInCallServiceFixtureX.mInCallAdapter.sendCallEvent(ids.mCallId, TEST_EVENT, null);
verify(mConnectionServiceFixtureA.getTestDouble(), timeout(TEST_TIMEOUT))
- .sendCallEvent(ids.mCallId, TEST_EVENT, null);
+ .sendCallEvent(ids.mConnectionId, TEST_EVENT, null);
}
/**
@@ -680,136 +670,11 @@
mInCallServiceFixtureX.mInCallAdapter.sendCallEvent(ids.mCallId, TEST_EVENT,
testBundle);
verify(mConnectionServiceFixtureA.getTestDouble(), timeout(TEST_TIMEOUT))
- .sendCallEvent(eq(ids.mCallId), eq(TEST_EVENT),
+ .sendCallEvent(eq(ids.mConnectionId), eq(TEST_EVENT),
bundleArgumentCaptor.capture());
assert (bundleArgumentCaptor.getValue().containsKey(TEST_BUNDLE_KEY));
}
- @MediumTest
- public void testAnalyticsSingleCall() throws Exception {
- IdPair testCall = startAndMakeActiveIncomingCall(
- "650-555-1212",
- mPhoneAccountA0.getAccountHandle(),
- mConnectionServiceFixtureA);
- Map<String, Analytics.CallInfoImpl> analyticsMap = Analytics.cloneData();
-
- assertTrue(analyticsMap.containsKey(testCall.mCallId));
-
- Analytics.CallInfoImpl callAnalytics = analyticsMap.get(testCall.mCallId);
- assertTrue(callAnalytics.startTime > 0);
- assertEquals(0, callAnalytics.endTime);
- assertEquals(Analytics.INCOMING_DIRECTION, callAnalytics.callDirection);
- assertFalse(callAnalytics.isInterrupted);
- assertNull(callAnalytics.callTerminationReason);
- assertEquals(mConnectionServiceComponentNameA.flattenToShortString(),
- callAnalytics.connectionService);
-
- mConnectionServiceFixtureA.
- sendSetDisconnected(testCall.mConnectionId, DisconnectCause.ERROR);
-
- analyticsMap = Analytics.cloneData();
- callAnalytics = analyticsMap.get(testCall.mCallId);
- assertTrue(callAnalytics.endTime > 0);
- assertNotNull(callAnalytics.callTerminationReason);
- assertEquals(DisconnectCause.ERROR, callAnalytics.callTerminationReason.getCode());
-
- StringWriter sr = new StringWriter();
- IndentingPrintWriter ip = new IndentingPrintWriter(sr, " ");
- Analytics.dump(ip);
- String dumpResult = sr.toString();
- String[] expectedFields = {"startTime", "endTime", "direction", "isAdditionalCall",
- "isInterrupted", "callTechnologies", "callTerminationReason", "connectionService"};
- for (String field : expectedFields) {
- assertTrue(dumpResult.contains(field));
- }
- }
-
- @SmallTest
- public void testAnalyticsDumping() 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);
-
- TelecomManager tm = (TelecomManager) mSpyContext.getSystemService(Context.TELECOM_SERVICE);
- List<ParcelableCallAnalytics> analyticsList = tm.dumpAnalytics();
-
- assertEquals(1, analyticsList.size());
- ParcelableCallAnalytics pCA = analyticsList.get(0);
-
- assertTrue(Math.abs(expectedAnalytics.startTime - pCA.getStartTimeMillis()) <
- ParcelableCallAnalytics.MILLIS_IN_5_MINUTES);
- assertEquals(0, pCA.getStartTimeMillis() % ParcelableCallAnalytics.MILLIS_IN_5_MINUTES);
- assertTrue(Math.abs((expectedAnalytics.endTime - expectedAnalytics.startTime) -
- pCA.getCallDurationMillis()) < ParcelableCallAnalytics.MILLIS_IN_1_SECOND);
- assertEquals(0, pCA.getCallDurationMillis() % ParcelableCallAnalytics.MILLIS_IN_1_SECOND);
-
- assertEquals(expectedAnalytics.callDirection, pCA.getCallType());
- assertEquals(expectedAnalytics.isAdditionalCall, pCA.isAdditionalCall());
- assertEquals(expectedAnalytics.isInterrupted, pCA.isInterrupted());
- assertEquals(expectedAnalytics.callTechnologies, pCA.getCallTechnologies());
- assertEquals(expectedAnalytics.callTerminationReason.getCode(),
- pCA.getCallTerminationCode());
- assertEquals(expectedAnalytics.connectionService, pCA.getConnectionService());
- }
-
- @MediumTest
- public void testAnalyticsTwoCalls() throws Exception {
- IdPair testCall1 = startAndMakeActiveIncomingCall(
- "650-555-1212",
- mPhoneAccountA0.getAccountHandle(),
- mConnectionServiceFixtureA);
- IdPair testCall2 = startAndMakeActiveOutgoingCall(
- "650-555-1213",
- mPhoneAccountA0.getAccountHandle(),
- mConnectionServiceFixtureA);
-
- Map<String, Analytics.CallInfoImpl> analyticsMap = Analytics.cloneData();
- assertTrue(analyticsMap.containsKey(testCall1.mCallId));
- assertTrue(analyticsMap.containsKey(testCall2.mCallId));
-
- Analytics.CallInfoImpl callAnalytics1 = analyticsMap.get(testCall1.mCallId);
- Analytics.CallInfoImpl callAnalytics2 = analyticsMap.get(testCall2.mCallId);
- assertTrue(callAnalytics1.startTime > 0);
- assertTrue(callAnalytics2.startTime > 0);
- assertEquals(0, callAnalytics1.endTime);
- assertEquals(0, callAnalytics2.endTime);
-
- assertEquals(Analytics.INCOMING_DIRECTION, callAnalytics1.callDirection);
- assertEquals(Analytics.OUTGOING_DIRECTION, callAnalytics2.callDirection);
-
- assertTrue(callAnalytics1.isInterrupted);
- assertTrue(callAnalytics2.isAdditionalCall);
-
- assertNull(callAnalytics1.callTerminationReason);
- assertNull(callAnalytics2.callTerminationReason);
-
- assertEquals(mConnectionServiceComponentNameA.flattenToShortString(),
- callAnalytics1.connectionService);
- assertEquals(mConnectionServiceComponentNameA.flattenToShortString(),
- callAnalytics1.connectionService);
-
- mConnectionServiceFixtureA.
- sendSetDisconnected(testCall2.mConnectionId, DisconnectCause.REMOTE);
- mConnectionServiceFixtureA.
- sendSetDisconnected(testCall1.mConnectionId, DisconnectCause.ERROR);
-
- analyticsMap = Analytics.cloneData();
- callAnalytics1 = analyticsMap.get(testCall1.mCallId);
- callAnalytics2 = analyticsMap.get(testCall2.mCallId);
- assertTrue(callAnalytics1.endTime > 0);
- assertTrue(callAnalytics2.endTime > 0);
- assertNotNull(callAnalytics1.callTerminationReason);
- assertNotNull(callAnalytics2.callTerminationReason);
- assertEquals(DisconnectCause.ERROR, callAnalytics1.callTerminationReason.getCode());
- assertEquals(DisconnectCause.REMOTE, callAnalytics2.callTerminationReason.getCode());
- }
-
private void blockNumber(String phoneNumber) throws Exception {
blockNumberWithAnswer(phoneNumber, new Answer<Bundle>() {
@Override
@@ -844,6 +709,44 @@
}
/**
+ * Tests to make sure that the Call.Details.PROPERTY_HAS_CDMA_VOICE_PRIVACY property is set on a
+ * Call that is based on a Connection with the Connection.PROPERTY_HAS_CDMA_VOICE_PRIVACY
+ * property set.
+ */
+ @MediumTest
+ public void testCdmaEnhancedPrivacyVoiceCall() throws Exception {
+ mConnectionServiceFixtureA.mConnectionServiceDelegate.mProperties =
+ Connection.PROPERTY_HAS_CDMA_VOICE_PRIVACY;
+
+ IdPair ids = startAndMakeActiveOutgoingCall("650-555-1212",
+ mPhoneAccountA0.getAccountHandle(), mConnectionServiceFixtureA);
+ assertEquals(Call.STATE_ACTIVE, mInCallServiceFixtureX.getCall(ids.mCallId).getState());
+
+ assertTrue(Call.Details.hasProperty(
+ mInCallServiceFixtureX.getCall(ids.mCallId).getProperties(),
+ Call.Details.PROPERTY_HAS_CDMA_VOICE_PRIVACY));
+ }
+
+ /**
+ * Tests to make sure that Call.Details.PROPERTY_HAS_CDMA_VOICE_PRIVACY is dropped
+ * when the Connection.PROPERTY_HAS_CDMA_VOICE_PRIVACY property is removed from the Connection.
+ */
+ @MediumTest
+ public void testDropCdmaEnhancedPrivacyVoiceCall() throws Exception {
+ mConnectionServiceFixtureA.mConnectionServiceDelegate.mProperties =
+ Connection.PROPERTY_HAS_CDMA_VOICE_PRIVACY;
+
+ IdPair ids = startAndMakeActiveOutgoingCall("650-555-1212",
+ mPhoneAccountA0.getAccountHandle(), mConnectionServiceFixtureA);
+ assertEquals(Call.STATE_ACTIVE, mInCallServiceFixtureX.getCall(ids.mCallId).getState());
+ mConnectionServiceFixtureA.mLatestConnection.setConnectionProperties(0);
+
+ assertFalse(Call.Details.hasProperty(
+ mInCallServiceFixtureX.getCall(ids.mCallId).getProperties(),
+ Call.Details.PROPERTY_HAS_CDMA_VOICE_PRIVACY));
+ }
+
+ /**
* Tests the {@link Call#pullExternalCall()} API. Ensures that an external call which is
* pullable can be pulled.
*
@@ -865,7 +768,7 @@
// Attempt to pull the call and verify the API call makes it through
mInCallServiceFixtureX.mInCallAdapter.pullExternalCall(ids.mCallId);
verify(mConnectionServiceFixtureA.getTestDouble(), timeout(TEST_TIMEOUT))
- .pullExternalCall(ids.mCallId);
+ .pullExternalCall(ids.mConnectionId);
}
/**
@@ -892,4 +795,54 @@
verify(mConnectionServiceFixtureA.getTestDouble(), never())
.pullExternalCall(ids.mConnectionId);
}
+
+ public void testMergeFailedAndNotifyInCallUi() throws Exception {
+ IdPair testCall1 = startAndMakeActiveOutgoingCall(
+ "650-555-1212",
+ mPhoneAccountA0.getAccountHandle(),
+ mConnectionServiceFixtureA);
+ IdPair testCall2 = startAndMakeActiveOutgoingCall(
+ "650-555-1213",
+ mPhoneAccountA0.getAccountHandle(),
+ mConnectionServiceFixtureA);
+
+ assertEquals(Call.STATE_ACTIVE,
+ mInCallServiceFixtureX.getCall(testCall1.mCallId).getState());
+ assertEquals(Call.STATE_ACTIVE,
+ mInCallServiceFixtureX.getCall(testCall2.mCallId).getState());
+ assertEquals(Call.STATE_ACTIVE,
+ mInCallServiceFixtureY.getCall(testCall1.mCallId).getState());
+ assertEquals(Call.STATE_ACTIVE,
+ mInCallServiceFixtureY.getCall(testCall2.mCallId).getState());
+
+ // Conference will not occur and instead will send setConferenceMergeFailed
+ ((ConnectionServiceFixture.FakeConnection)
+ mConnectionServiceFixtureA.mLatestConnection).setIsConferenceCreated(false);
+ mInCallServiceFixtureX.getInCallAdapter().conference(testCall2.mCallId, testCall1.mCallId);
+
+ verify(mInCallServiceFixtureX.getTestDouble(), timeout(TEST_TIMEOUT)).onConnectionEvent(
+ eq(testCall2.mCallId), eq(Connection.EVENT_CALL_MERGE_FAILED), any(Bundle.class));
+ verify(mInCallServiceFixtureY.getTestDouble(), timeout(TEST_TIMEOUT)).onConnectionEvent(
+ eq(testCall2.mCallId), eq(Connection.EVENT_CALL_MERGE_FAILED), any(Bundle.class));
+ }
+
+ @LargeTest
+ public void testEmergencyCallFailMoveToSecondSim() throws Exception {
+ IdPair ids = startAndMakeDialingEmergencyCall("650-555-1212",
+ mPhoneAccountE0.getAccountHandle(), mConnectionServiceFixtureA);
+ assertEquals(Call.STATE_DIALING, mInCallServiceFixtureX.getCall(ids.mCallId).getState());
+ assertEquals(Call.STATE_DIALING, mInCallServiceFixtureY.getCall(ids.mCallId).getState());
+
+ // The Emergency Call has failed on the default SIM with an ERROR Disconnect Cause. Retry
+ // with the other SIM PhoneAccount
+ IdPair newIds = triggerEmergencyRedial(mPhoneAccountE1.getAccountHandle(),
+ mConnectionServiceFixtureA, ids);
+
+ // Call should be active on the E1 PhoneAccount
+ mConnectionServiceFixtureA.sendSetActive(newIds.mConnectionId);
+ assertEquals(Call.STATE_ACTIVE, mInCallServiceFixtureX.getCall(newIds.mCallId).getState());
+ assertEquals(Call.STATE_ACTIVE, mInCallServiceFixtureY.getCall(newIds.mCallId).getState());
+ assertEquals(mInCallServiceFixtureX.getCall(ids.mCallId).getAccountHandle(),
+ mPhoneAccountE1.getAccountHandle());
+ }
}
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..dddf9e6 100644
--- a/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java
@@ -16,6 +16,7 @@
package com.android.server.telecom.tests;
+import android.app.NotificationManager;
import android.content.Context;
import android.media.AudioManager;
import android.media.IAudioService;
@@ -31,7 +32,9 @@
import com.android.server.telecom.CallsManager;
import com.android.server.telecom.ConnectionServiceWrapper;
import com.android.server.telecom.CallAudioManager;
+import com.android.server.telecom.InterruptionFilterProxy;
import com.android.server.telecom.StatusBarNotifier;
+import com.android.server.telecom.TelecomSystem;
import com.android.server.telecom.WiredHeadsetManager;
import org.mockito.ArgumentCaptor;
@@ -42,14 +45,20 @@
import java.util.ArrayList;
import java.util.List;
+import java.util.Objects;
import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.eq;
import static org.mockito.Matchers.same;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
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;
@@ -63,27 +72,37 @@
static class RoutingTestParameters extends TestParameters {
public String name;
public int initialRoute;
+ public int initialNotificationFilter;
public int availableRoutes; // may excl. speakerphone, because that's always available
public int speakerInteraction; // one of NONE, ON, or OFF
public int bluetoothInteraction; // one of NONE, ON, or OFF
public int action;
public int expectedRoute;
public int expectedAvailableRoutes; // also may exclude the speakerphone.
+ public int expectedNotificationFilter; // expected end notification filter.
+ public boolean isNotificationChangeExpected; // indicates whether we expect the notification
+ // filter to change during the process of the
+ // test.
public boolean doesDeviceSupportEarpiece; // set to false in the case of Wear devices
public boolean shouldRunWithFocus;
- public RoutingTestParameters(String name, int initialRoute, int availableRoutes, int
- speakerInteraction, int bluetoothInteraction, int action, int expectedRoute, int
- expectedAvailableRoutes, boolean doesDeviceSupportEarpiece,
+ public RoutingTestParameters(String name, int initialRoute,
+ int initialNotificationFilter, int availableRoutes, int speakerInteraction,
+ int bluetoothInteraction, int action, int expectedRoute,
+ int expectedAvailableRoutes, int expectedNotificationFilter,
+ boolean isNotificationChangeExpected, boolean doesDeviceSupportEarpiece,
boolean shouldRunWithFocus) {
this.name = name;
this.initialRoute = initialRoute;
+ this.initialNotificationFilter = initialNotificationFilter;
this.availableRoutes = availableRoutes;
this.speakerInteraction = speakerInteraction;
this.bluetoothInteraction = bluetoothInteraction;
this.action = action;
this.expectedRoute = expectedRoute;
this.expectedAvailableRoutes = expectedAvailableRoutes;
+ this.expectedNotificationFilter = expectedNotificationFilter;
+ this.isNotificationChangeExpected = isNotificationChangeExpected;
this.doesDeviceSupportEarpiece = doesDeviceSupportEarpiece;
this.shouldRunWithFocus = shouldRunWithFocus;
}
@@ -93,12 +112,15 @@
return "RoutingTestParameters{" +
"name='" + name + '\'' +
", initialRoute=" + initialRoute +
+ ", initialNotificationFilter=" + initialNotificationFilter +
", availableRoutes=" + availableRoutes +
", speakerInteraction=" + speakerInteraction +
", bluetoothInteraction=" + bluetoothInteraction +
", action=" + action +
", expectedRoute=" + expectedRoute +
", expectedAvailableRoutes=" + expectedAvailableRoutes +
+ ", expectedNotificationFilter= " + expectedNotificationFilter +
+ ", isNotificationChangeExpected=" + isNotificationChangeExpected +
", doesDeviceSupportEarpiece=" + doesDeviceSupportEarpiece +
", shouldRunWithFocus=" + shouldRunWithFocus +
'}';
@@ -112,10 +134,12 @@
@Mock WiredHeadsetManager mockWiredHeadsetManager;
@Mock StatusBarNotifier mockStatusBarNotifier;
@Mock Call fakeCall;
+ @Mock InterruptionFilterProxy mMockInterruptionFilterProxy;
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,12 +156,32 @@
};
when(mockCallsManager.getForegroundCall()).thenReturn(fakeCall);
+ when(mockCallsManager.getLock()).thenReturn(mLock);
when(fakeCall.getConnectionService()).thenReturn(mockConnectionServiceWrapper);
when(fakeCall.isAlive()).thenReturn(true);
+ setupInterruptionFilterMocks();
+
doNothing().when(mockConnectionServiceWrapper).onCallAudioStateChanged(any(Call.class),
any(CallAudioState.class));
}
+ private void setupInterruptionFilterMocks() {
+ // These mock implementations keep track of when the caller sets the current notification
+ // filter, and ensures the same value is returned via getCurrentInterruptionFilter.
+ final int objId = Objects.hashCode(mMockInterruptionFilterProxy);
+ when(mMockInterruptionFilterProxy.getCurrentInterruptionFilter()).thenReturn(
+ NotificationManager.INTERRUPTION_FILTER_ALL);
+ doAnswer(new Answer<Void>() {
+ @Override
+ public Void answer(InvocationOnMock i) {
+ int requestedFilter = (int) i.getArguments()[0];
+ when(mMockInterruptionFilterProxy.getCurrentInterruptionFilter()).thenReturn(
+ requestedFilter);
+ return null;
+ }
+ }).when(mMockInterruptionFilterProxy).setInterruptionFilter(anyInt());
+ }
+
@LargeTest
public void testStateMachineTransitionsWithFocus() throws Throwable {
List<RoutingTestParameters> paramList = generateTransitionTests(true);
@@ -159,6 +203,7 @@
mockWiredHeadsetManager,
mockStatusBarNotifier,
mAudioServiceFactory,
+ mMockInterruptionFilterProxy,
true);
when(mockBluetoothManager.isBluetoothAudioConnectedOrPending()).thenReturn(false);
@@ -177,7 +222,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,
@@ -199,17 +244,19 @@
mockWiredHeadsetManager,
mockStatusBarNotifier,
mAudioServiceFactory,
+ mMockInterruptionFilterProxy,
true);
when(mockBluetoothManager.isBluetoothAudioConnectedOrPending()).thenReturn(false);
when(mockBluetoothManager.isBluetoothAvailable()).thenReturn(true);
when(mockAudioManager.isSpeakerphoneOn()).thenReturn(true);
+
CallAudioState initState = new CallAudioState(false, CallAudioState.ROUTE_BLUETOOTH,
CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH);
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,
@@ -218,18 +265,90 @@
waitForStateMachineActionCompletion(stateMachine, CallAudioRouteStateMachine.RUN_RUNNABLE);
verifyNewSystemCallAudioState(initState, expectedEndState);
+ // Expecting to end up in earpiece, so we expect notifications to be filtered.
+ assertEquals(NotificationManager.INTERRUPTION_FILTER_ALARMS,
+ mMockInterruptionFilterProxy.getCurrentInterruptionFilter());
resetMocks();
-
stateMachine.sendMessageWithSessionInfo(
CallAudioRouteStateMachine.DISCONNECT_BLUETOOTH);
stateMachine.sendMessageWithSessionInfo(
CallAudioRouteStateMachine.CONNECT_BLUETOOTH);
waitForStateMachineActionCompletion(stateMachine, CallAudioRouteStateMachine.RUN_RUNNABLE);
-
assertEquals(expectedEndState, stateMachine.getCurrentCallAudioState());
}
+ @MediumTest
+ public void testBluetoothRinging() {
+ CallAudioRouteStateMachine stateMachine = new CallAudioRouteStateMachine(
+ mContext,
+ mockCallsManager,
+ mockBluetoothManager,
+ mockWiredHeadsetManager,
+ mockStatusBarNotifier,
+ mAudioServiceFactory,
+ mMockInterruptionFilterProxy,
+ 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();
+ // Shouldn't change interruption filter when in bluetooth route.
+ assertEquals(NotificationManager.INTERRUPTION_FILTER_ALL,
+ mMockInterruptionFilterProxy.getCurrentInterruptionFilter());
+
+ 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,
+ mMockInterruptionFilterProxy,
+ 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,
@@ -303,6 +422,7 @@
mockWiredHeadsetManager,
mockStatusBarNotifier,
mAudioServiceFactory,
+ mMockInterruptionFilterProxy,
doesDeviceSupportEarpiece);
stateMachine.initialize();
assertEquals(expectedState, stateMachine.getCurrentCallAudioState());
@@ -313,12 +433,15 @@
params.add(new RoutingTestParameters(
"Connect headset during earpiece", // name
CallAudioState.ROUTE_EARPIECE, // initialRoute
+ NotificationManager.INTERRUPTION_FILTER_ALL, // initialNotificationFilter
CallAudioState.ROUTE_EARPIECE, // availableRoutes
NONE, // speakerInteraction
NONE, // bluetoothInteraction
CallAudioRouteStateMachine.CONNECT_WIRED_HEADSET, // action
CallAudioState.ROUTE_WIRED_HEADSET, // expectedRoute
CallAudioState.ROUTE_WIRED_HEADSET, // expectedAvailableRoutes
+ NotificationManager.INTERRUPTION_FILTER_ALL, // expectedNotificationFilter
+ true, // isNotificationChangeExpected
true, // doesDeviceSupportEarpiece
shouldRunWithFocus
));
@@ -326,12 +449,15 @@
params.add(new RoutingTestParameters(
"Connect headset during bluetooth", // name
CallAudioState.ROUTE_BLUETOOTH, // initialRoute
+ NotificationManager.INTERRUPTION_FILTER_ALL, // initialNotificationFilter
CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH, // availableRoutes
NONE, // speakerInteraction
OFF, // bluetoothInteraction
CallAudioRouteStateMachine.CONNECT_WIRED_HEADSET, // action
CallAudioState.ROUTE_WIRED_HEADSET, // expectedRoute
CallAudioState.ROUTE_WIRED_HEADSET | CallAudioState.ROUTE_BLUETOOTH, // expectedAvai
+ NotificationManager.INTERRUPTION_FILTER_ALL, // expectedNotificationFilter
+ false, // isNotificationChangeExpected
true, // doesDeviceSupportEarpiece
shouldRunWithFocus
));
@@ -339,12 +465,15 @@
params.add(new RoutingTestParameters(
"Connect headset during speakerphone", // name
CallAudioState.ROUTE_SPEAKER, // initialRoute
+ NotificationManager.INTERRUPTION_FILTER_ALL, // initialNotificationFilter
CallAudioState.ROUTE_EARPIECE, // availableRoutes
OFF, // speakerInteraction
NONE, // bluetoothInteraction
CallAudioRouteStateMachine.CONNECT_WIRED_HEADSET, // action
CallAudioState.ROUTE_WIRED_HEADSET, // expectedRoute
CallAudioState.ROUTE_WIRED_HEADSET, // expectedAvailableRoutes
+ NotificationManager.INTERRUPTION_FILTER_ALL, // expectedNotificationFilter
+ false, // isNotificationChangeExpected
true, // doesDeviceSupportEarpiece
shouldRunWithFocus
));
@@ -352,12 +481,15 @@
params.add(new RoutingTestParameters(
"Disconnect headset during headset", // name
CallAudioState.ROUTE_WIRED_HEADSET, // initialRoute
+ NotificationManager.INTERRUPTION_FILTER_ALL, // initialNotificationFilter
CallAudioState.ROUTE_WIRED_HEADSET, // availableRoutes
NONE, // speakerInteraction
NONE, // bluetoothInteraction
CallAudioRouteStateMachine.DISCONNECT_WIRED_HEADSET, // action
CallAudioState.ROUTE_EARPIECE, // expectedRoute
CallAudioState.ROUTE_EARPIECE, // expectedAvailableRoutes
+ NotificationManager.INTERRUPTION_FILTER_ALARMS, // expectedNotificationFilter
+ true, // isNotificationChangeExpected
true, // doesDeviceSupportEarpiece
shouldRunWithFocus
));
@@ -365,12 +497,15 @@
params.add(new RoutingTestParameters(
"Disconnect headset during headset with bluetooth available", // name
CallAudioState.ROUTE_WIRED_HEADSET, // initialRoute
+ NotificationManager.INTERRUPTION_FILTER_ALL, // initialNotificationFilter
CallAudioState.ROUTE_WIRED_HEADSET | CallAudioState.ROUTE_BLUETOOTH, // availableRou
NONE, // speakerInteraction
NONE, // bluetoothInteraction
CallAudioRouteStateMachine.DISCONNECT_WIRED_HEADSET, // action
CallAudioState.ROUTE_EARPIECE, // expectedRoute
CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH, // expectedAvailable
+ NotificationManager.INTERRUPTION_FILTER_ALARMS, // expectedNotificationFilter
+ true, // isNotificationChangeExpected
true, // doesDeviceSupportEarpiece
shouldRunWithFocus
));
@@ -378,12 +513,15 @@
params.add(new RoutingTestParameters(
"Disconnect headset during bluetooth", // name
CallAudioState.ROUTE_BLUETOOTH, // initialRoute
+ NotificationManager.INTERRUPTION_FILTER_ALL, // initialNotificationFilter
CallAudioState.ROUTE_WIRED_HEADSET | CallAudioState.ROUTE_BLUETOOTH, // availableRou
NONE, // speakerInteraction
NONE, // bluetoothInteraction
CallAudioRouteStateMachine.DISCONNECT_WIRED_HEADSET, // action
CallAudioState.ROUTE_BLUETOOTH, // expectedRoute
CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH, // expectedAvailable
+ NotificationManager.INTERRUPTION_FILTER_ALL, // expectedNotificationFilter
+ false, // isNotificationChangeExpected
true, // doesDeviceSupportEarpiece
shouldRunWithFocus
));
@@ -391,12 +529,15 @@
params.add(new RoutingTestParameters(
"Disconnect headset during speakerphone", // name
CallAudioState.ROUTE_SPEAKER, // initialRoute
+ NotificationManager.INTERRUPTION_FILTER_ALL, // initialNotificationFilter
CallAudioState.ROUTE_WIRED_HEADSET, // availableRoutes
NONE, // speakerInteraction
NONE, // bluetoothInteraction
CallAudioRouteStateMachine.DISCONNECT_WIRED_HEADSET, // action
CallAudioState.ROUTE_SPEAKER, // expectedRoute
CallAudioState.ROUTE_EARPIECE, // expectedAvailableRoutes
+ NotificationManager.INTERRUPTION_FILTER_ALL, // expectedNotificationFilter
+ false, // isNotificationChangeExpected
true, // doesDeviceSupportEarpiece
shouldRunWithFocus
));
@@ -404,12 +545,15 @@
params.add(new RoutingTestParameters(
"Disconnect headset during speakerphone with bluetooth available", // name
CallAudioState.ROUTE_SPEAKER, // initialRoute
+ NotificationManager.INTERRUPTION_FILTER_ALL, // initialNotificationFilter
CallAudioState.ROUTE_WIRED_HEADSET | CallAudioState.ROUTE_BLUETOOTH, // availableRou
NONE, // speakerInteraction
NONE, // bluetoothInteraction
CallAudioRouteStateMachine.DISCONNECT_WIRED_HEADSET, // action
CallAudioState.ROUTE_SPEAKER, // expectedRoute
CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH, // expectedAvailable
+ NotificationManager.INTERRUPTION_FILTER_ALL, // expectedNotificationFilter
+ false, // isNotificationChangeExpected
true, // doesDeviceSupportEarpiece
shouldRunWithFocus
));
@@ -417,12 +561,15 @@
params.add(new RoutingTestParameters(
"Connect bluetooth during earpiece", // name
CallAudioState.ROUTE_EARPIECE, // initialRoute
+ NotificationManager.INTERRUPTION_FILTER_ALL, // initialNotificationFilter
CallAudioState.ROUTE_EARPIECE, // availableRoutes
NONE, // speakerInteraction
ON, // bluetoothInteraction
CallAudioRouteStateMachine.CONNECT_BLUETOOTH, // action
CallAudioState.ROUTE_BLUETOOTH, // expectedRoute
CallAudioState.ROUTE_BLUETOOTH | CallAudioState.ROUTE_EARPIECE, // expectedAvailable
+ NotificationManager.INTERRUPTION_FILTER_ALL, // expectedNotificationFilter
+ true, // isNotificationChangeExpected
true, // doesDeviceSupportEarpiece
shouldRunWithFocus
));
@@ -430,12 +577,15 @@
params.add(new RoutingTestParameters(
"Connect bluetooth during wired headset", // name
CallAudioState.ROUTE_WIRED_HEADSET, // initialRoute
+ NotificationManager.INTERRUPTION_FILTER_ALL, // initialNotificationFilter
CallAudioState.ROUTE_WIRED_HEADSET, // availableRoutes
NONE, // speakerInteraction
ON, // bluetoothInteraction
CallAudioRouteStateMachine.CONNECT_BLUETOOTH, // action
CallAudioState.ROUTE_BLUETOOTH, // expectedRoute
CallAudioState.ROUTE_BLUETOOTH | CallAudioState.ROUTE_WIRED_HEADSET, // expectedAvai
+ NotificationManager.INTERRUPTION_FILTER_ALL, // expectedNotificationFilter
+ false, // isNotificationChangeExpected
true, // doesDeviceSupportEarpiece
shouldRunWithFocus
));
@@ -443,12 +593,15 @@
params.add(new RoutingTestParameters(
"Connect bluetooth during speakerphone", // name
CallAudioState.ROUTE_SPEAKER, // initialRoute
+ NotificationManager.INTERRUPTION_FILTER_ALL, // initialNotificationFilter
CallAudioState.ROUTE_EARPIECE, // availableRoutes
OFF, // speakerInteraction
ON, // bluetoothInteraction
CallAudioRouteStateMachine.CONNECT_BLUETOOTH, // action
CallAudioState.ROUTE_BLUETOOTH, // expectedRoute
CallAudioState.ROUTE_BLUETOOTH | CallAudioState.ROUTE_EARPIECE, // expectedAvailable
+ NotificationManager.INTERRUPTION_FILTER_ALL, // expectedNotificationFilter
+ false, // isNotificationChangeExpected
true, // doesDeviceSupportEarpiece
shouldRunWithFocus
));
@@ -456,12 +609,31 @@
params.add(new RoutingTestParameters(
"Disconnect bluetooth during bluetooth without headset in", // name
CallAudioState.ROUTE_BLUETOOTH, // initialRoute
+ NotificationManager.INTERRUPTION_FILTER_ALL, // initialNotificationFilter
CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH, // availableRoutes
NONE, // speakerInteraction
OFF, // bluetoothInteraction
CallAudioRouteStateMachine.DISCONNECT_BLUETOOTH, // action
CallAudioState.ROUTE_EARPIECE, // expectedRoute
CallAudioState.ROUTE_EARPIECE, // expectedAvailableRoutes
+ NotificationManager.INTERRUPTION_FILTER_ALARMS, // expectedNotificationFilter
+ true, // isNotificationChangeExpected
+ true, // doesDeviceSupportEarpiece
+ shouldRunWithFocus
+ ));
+
+ params.add(new RoutingTestParameters(
+ "Disconnect bluetooth during bluetooth without headset in, priority mode ", // name
+ CallAudioState.ROUTE_BLUETOOTH, // initialRoute
+ NotificationManager.INTERRUPTION_FILTER_PRIORITY, // initialNotificationFilter
+ CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH, // availableRoutes
+ NONE, // speakerInteraction
+ OFF, // bluetoothInteraction
+ CallAudioRouteStateMachine.DISCONNECT_BLUETOOTH, // action
+ CallAudioState.ROUTE_EARPIECE, // expectedRoute
+ CallAudioState.ROUTE_EARPIECE, // expectedAvailableRoutes
+ NotificationManager.INTERRUPTION_FILTER_PRIORITY, // expectedNotificationFilter
+ false, // isNotificationChangeExpected
true, // doesDeviceSupportEarpiece
shouldRunWithFocus
));
@@ -469,12 +641,15 @@
params.add(new RoutingTestParameters(
"Disconnect bluetooth during bluetooth with headset in", // name
CallAudioState.ROUTE_BLUETOOTH, // initialRoute
+ NotificationManager.INTERRUPTION_FILTER_ALL, // initialNotificationFilter
CallAudioState.ROUTE_WIRED_HEADSET | CallAudioState.ROUTE_BLUETOOTH, // availableRou
NONE, // speakerInteraction
OFF, // bluetoothInteraction
CallAudioRouteStateMachine.DISCONNECT_BLUETOOTH, // action
CallAudioState.ROUTE_WIRED_HEADSET, // expectedRoute
CallAudioState.ROUTE_WIRED_HEADSET, // expectedAvailableRoutes
+ NotificationManager.INTERRUPTION_FILTER_ALL, // expectedNotificationFilter
+ false, // isNotificationChangeExpected
true, // doesDeviceSupportEarpiece
shouldRunWithFocus
));
@@ -482,12 +657,15 @@
params.add(new RoutingTestParameters(
"Disconnect bluetooth during speakerphone", // name
CallAudioState.ROUTE_SPEAKER, // initialRoute
+ NotificationManager.INTERRUPTION_FILTER_ALL, // initialNotificationFilter
CallAudioState.ROUTE_WIRED_HEADSET | CallAudioState.ROUTE_BLUETOOTH, // availableRou
NONE, // speakerInteraction
NONE, // bluetoothInteraction
CallAudioRouteStateMachine.DISCONNECT_BLUETOOTH, // action
CallAudioState.ROUTE_SPEAKER, // expectedRoute
CallAudioState.ROUTE_WIRED_HEADSET, // expectedAvailableRoutes
+ NotificationManager.INTERRUPTION_FILTER_ALL, // expectedNotificationFilter
+ false, // isNotificationChangeExpected
true, // doesDeviceSupportEarpiece
shouldRunWithFocus
));
@@ -495,12 +673,15 @@
params.add(new RoutingTestParameters(
"Disconnect bluetooth during earpiece", // name
CallAudioState.ROUTE_EARPIECE, // initialRoute
+ NotificationManager.INTERRUPTION_FILTER_ALL, // initialNotificationFilter
CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH, // availableRoutes
NONE, // speakerInteraction
NONE, // bluetoothInteraction
CallAudioRouteStateMachine.DISCONNECT_BLUETOOTH, // action
CallAudioState.ROUTE_EARPIECE, // expectedRoute
CallAudioState.ROUTE_EARPIECE, // expectedAvailableRoutes
+ NotificationManager.INTERRUPTION_FILTER_ALARMS, // expectedNotificationFilter
+ true, // isNotificationChangeExpected
true, // doesDeviceSupportEarpiece
shouldRunWithFocus
));
@@ -508,12 +689,15 @@
params.add(new RoutingTestParameters(
"Switch to speakerphone from earpiece", // name
CallAudioState.ROUTE_EARPIECE, // initialRoute
+ NotificationManager.INTERRUPTION_FILTER_ALL, // initialNotificationFilter
CallAudioState.ROUTE_EARPIECE, // availableRoutes
ON, // speakerInteraction
NONE, // bluetoothInteraction
CallAudioRouteStateMachine.SWITCH_SPEAKER, // action
CallAudioState.ROUTE_SPEAKER, // expectedRoute
CallAudioState.ROUTE_EARPIECE, // expectedAvailableRoutes
+ NotificationManager.INTERRUPTION_FILTER_ALL, // expectedNotificationFilter
+ true, // isNotificationChangeExpected
true, // doesDeviceSupportEarpiece
shouldRunWithFocus
));
@@ -521,12 +705,15 @@
params.add(new RoutingTestParameters(
"Switch to speakerphone from headset", // name
CallAudioState.ROUTE_WIRED_HEADSET, // initialRoute
+ NotificationManager.INTERRUPTION_FILTER_ALL, // initialNotificationFilter
CallAudioState.ROUTE_WIRED_HEADSET, // availableRoutes
ON, // speakerInteraction
NONE, // bluetoothInteraction
CallAudioRouteStateMachine.SWITCH_SPEAKER, // action
CallAudioState.ROUTE_SPEAKER, // expectedRoute
CallAudioState.ROUTE_WIRED_HEADSET, // expectedAvailableRoutes
+ NotificationManager.INTERRUPTION_FILTER_ALL, // expectedNotificationFilter
+ false, // isNotificationChangeExpected
true, // doesDeviceSupportEarpiece
shouldRunWithFocus
));
@@ -534,12 +721,15 @@
params.add(new RoutingTestParameters(
"Switch to speakerphone from bluetooth", // name
CallAudioState.ROUTE_BLUETOOTH, // initialRoute
+ NotificationManager.INTERRUPTION_FILTER_ALL, // initialNotificationFilter
CallAudioState.ROUTE_WIRED_HEADSET | CallAudioState.ROUTE_BLUETOOTH, // availableRou
ON, // speakerInteraction
OFF, // bluetoothInteraction
CallAudioRouteStateMachine.SWITCH_SPEAKER, // action
CallAudioState.ROUTE_SPEAKER, // expectedRoute
CallAudioState.ROUTE_WIRED_HEADSET | CallAudioState.ROUTE_BLUETOOTH, // expectedAvai
+ NotificationManager.INTERRUPTION_FILTER_ALL, // expectedNotificationFilter
+ false, // isNotificationChangeExpected
true, // doesDeviceSupportEarpiece
shouldRunWithFocus
));
@@ -547,12 +737,15 @@
params.add(new RoutingTestParameters(
"Switch to earpiece from bluetooth", // name
CallAudioState.ROUTE_BLUETOOTH, // initialRoute
+ NotificationManager.INTERRUPTION_FILTER_ALL, // initialNotificationFilter
CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH, // availableRoutes
NONE, // speakerInteraction
OFF, // bluetoothInteraction
CallAudioRouteStateMachine.SWITCH_EARPIECE, // action
CallAudioState.ROUTE_EARPIECE, // expectedRoute
CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH, // expectedAvailable
+ NotificationManager.INTERRUPTION_FILTER_ALARMS, // expectedNotificationFilter
+ true, // isNotificationChangeExpected
true, // doesDeviceSupportEarpiece
shouldRunWithFocus
));
@@ -560,12 +753,47 @@
params.add(new RoutingTestParameters(
"Switch to earpiece from speakerphone", // name
CallAudioState.ROUTE_SPEAKER, // initialRoute
+ NotificationManager.INTERRUPTION_FILTER_ALL, // initialNotificationFilter
CallAudioState.ROUTE_EARPIECE, // availableRoutes
OFF, // speakerInteraction
NONE, // bluetoothInteraction
CallAudioRouteStateMachine.SWITCH_EARPIECE, // action
CallAudioState.ROUTE_EARPIECE, // expectedRoute
CallAudioState.ROUTE_EARPIECE, // expectedAvailableRoutes
+ NotificationManager.INTERRUPTION_FILTER_ALARMS, // expectedNotificationFilter
+ true, // isNotificationChangeExpected
+ true, // doesDeviceSupportEarpiece
+ shouldRunWithFocus
+ ));
+
+ params.add(new RoutingTestParameters(
+ "Switch to earpiece from speakerphone, priority notifications", // name
+ CallAudioState.ROUTE_SPEAKER, // initialRoute
+ NotificationManager.INTERRUPTION_FILTER_PRIORITY, // initialNotificationFilter
+ CallAudioState.ROUTE_EARPIECE, // availableRoutes
+ OFF, // speakerInteraction
+ NONE, // bluetoothInteraction
+ CallAudioRouteStateMachine.SWITCH_EARPIECE, // action
+ CallAudioState.ROUTE_EARPIECE, // expectedRoute
+ CallAudioState.ROUTE_EARPIECE, // expectedAvailableRoutes
+ NotificationManager.INTERRUPTION_FILTER_PRIORITY, // expectedNotificationFilter
+ false, // isNotificationChangeExpected
+ true, // doesDeviceSupportEarpiece
+ shouldRunWithFocus
+ ));
+
+ params.add(new RoutingTestParameters(
+ "Switch to earpiece from speakerphone, silent mode", // name
+ CallAudioState.ROUTE_SPEAKER, // initialRoute
+ NotificationManager.INTERRUPTION_FILTER_NONE, // initialNotificationFilter
+ CallAudioState.ROUTE_EARPIECE, // availableRoutes
+ OFF, // speakerInteraction
+ NONE, // bluetoothInteraction
+ CallAudioRouteStateMachine.SWITCH_EARPIECE, // action
+ CallAudioState.ROUTE_EARPIECE, // expectedRoute
+ CallAudioState.ROUTE_EARPIECE, // expectedAvailableRoutes
+ NotificationManager.INTERRUPTION_FILTER_NONE, // expectedNotificationFilter
+ false, // isNotificationChangeExpected
true, // doesDeviceSupportEarpiece
shouldRunWithFocus
));
@@ -573,12 +801,15 @@
params.add(new RoutingTestParameters(
"Switch to bluetooth from speakerphone", // name
CallAudioState.ROUTE_SPEAKER, // initialRoute
+ NotificationManager.INTERRUPTION_FILTER_ALL, // initialNotificationFilter
CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH, // availableRoutes
OFF, // speakerInteraction
ON, // bluetoothInteraction
CallAudioRouteStateMachine.SWITCH_BLUETOOTH, // action
CallAudioState.ROUTE_BLUETOOTH, // expectedRoute
CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH, // expectedAvailable
+ NotificationManager.INTERRUPTION_FILTER_ALL, // expectedNotificationFilter
+ false, // isNotificationChangeExpected
true, // doesDeviceSupportEarpiece
shouldRunWithFocus
));
@@ -586,12 +817,15 @@
params.add(new RoutingTestParameters(
"Switch to bluetooth from earpiece", // name
CallAudioState.ROUTE_EARPIECE, // initialRoute
+ NotificationManager.INTERRUPTION_FILTER_ALL, // initialNotificationFilter
CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH, // availableRoutes
NONE, // speakerInteraction
ON, // bluetoothInteraction
CallAudioRouteStateMachine.SWITCH_BLUETOOTH, // action
CallAudioState.ROUTE_BLUETOOTH, // expectedRoute
CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH, // expectedAvailable
+ NotificationManager.INTERRUPTION_FILTER_ALL, // expectedNotificationFilter
+ true, // isNotificationChangeExpected
true, // doesDeviceSupportEarpiece
shouldRunWithFocus
));
@@ -599,12 +833,15 @@
params.add(new RoutingTestParameters(
"Switch to bluetooth from wired headset", // name
CallAudioState.ROUTE_WIRED_HEADSET, // initialRoute
+ NotificationManager.INTERRUPTION_FILTER_ALL, // initialNotificationFilter
CallAudioState.ROUTE_WIRED_HEADSET | CallAudioState.ROUTE_BLUETOOTH, // availableRou
NONE, // speakerInteraction
ON, // bluetoothInteraction
CallAudioRouteStateMachine.SWITCH_BLUETOOTH, // action
CallAudioState.ROUTE_BLUETOOTH, // expectedRoute
CallAudioState.ROUTE_WIRED_HEADSET | CallAudioState.ROUTE_BLUETOOTH, // expectedAvai
+ NotificationManager.INTERRUPTION_FILTER_ALL, // expectedNotificationFilter
+ false, // isNotificationChangeExpected
true, // doesDeviceSupportEarpiece
shouldRunWithFocus
));
@@ -612,12 +849,15 @@
params.add(new RoutingTestParameters(
"Switch from bluetooth to wired/earpiece when neither are available", // name
CallAudioState.ROUTE_BLUETOOTH, // initialRoute
+ NotificationManager.INTERRUPTION_FILTER_ALL, // initialNotificationFilter
CallAudioState.ROUTE_BLUETOOTH, // availableRoutes
ON, // speakerInteraction
OFF, // bluetoothInteraction
CallAudioRouteStateMachine.SWITCH_BASELINE_ROUTE, // action
CallAudioState.ROUTE_SPEAKER, // expectedRoute
CallAudioState.ROUTE_BLUETOOTH, // expectedAvailableRoutes
+ NotificationManager.INTERRUPTION_FILTER_ALL, // expectedNotificationFilter
+ false, // isNotificationChangeExpected
false, // doesDeviceSupportEarpiece
shouldRunWithFocus
));
@@ -625,12 +865,15 @@
params.add(new RoutingTestParameters(
"Disconnect wired headset when device does not support earpiece", // name
CallAudioState.ROUTE_WIRED_HEADSET, // initialRoute
+ NotificationManager.INTERRUPTION_FILTER_ALL, // initialNotificationFilter
CallAudioState.ROUTE_WIRED_HEADSET, // availableRoutes
ON, // speakerInteraction
NONE, // bluetoothInteraction
CallAudioRouteStateMachine.DISCONNECT_WIRED_HEADSET, // action
CallAudioState.ROUTE_SPEAKER, // expectedRoute
CallAudioState.ROUTE_SPEAKER, // expectedAvailableRoutes
+ NotificationManager.INTERRUPTION_FILTER_ALL, // expectedNotificationFilter
+ false, // isNotificationChangeExpected
false, // doesDeviceSupportEarpiece
shouldRunWithFocus
));
@@ -651,6 +894,8 @@
private void runParametrizedTestCaseWithFocus(final RoutingTestParameters params)
throws Throwable {
resetMocks();
+ when(mMockInterruptionFilterProxy.getCurrentInterruptionFilter()).thenReturn(
+ params.initialNotificationFilter);
// Construct a fresh state machine on every case
final CallAudioRouteStateMachine stateMachine = new CallAudioRouteStateMachine(
@@ -660,6 +905,7 @@
mockWiredHeadsetManager,
mockStatusBarNotifier,
mAudioServiceFactory,
+ mMockInterruptionFilterProxy,
params.doesDeviceSupportEarpiece);
// Set up bluetooth and speakerphone state
@@ -668,20 +914,37 @@
when(mockBluetoothManager.isBluetoothAvailable()).thenReturn(
(params.availableRoutes & CallAudioState.ROUTE_BLUETOOTH) != 0
|| (params.expectedAvailableRoutes & CallAudioState.ROUTE_BLUETOOTH) != 0);
- when(mockAudioManager.isSpeakerphoneOn()).thenReturn(
- params.initialRoute == CallAudioState.ROUTE_SPEAKER);
+ doReturn(params.initialRoute == CallAudioState.ROUTE_SPEAKER).when(mockAudioManager).
+ isSpeakerphoneOn();
// Set the initial CallAudioState object
final CallAudioState initState = new CallAudioState(false,
params.initialRoute, (params.availableRoutes | CallAudioState.ROUTE_SPEAKER));
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);
+ // Capture the changes made to the interruption filter and verify that the last change
+ // made to it matches the expected interruption filter.
+ if (params.isNotificationChangeExpected) {
+ ArgumentCaptor<Integer> interruptionCaptor = ArgumentCaptor.forClass(Integer.class);
+ verify(mMockInterruptionFilterProxy, timeout(TEST_TIMEOUT).atLeastOnce())
+ .setInterruptionFilter(interruptionCaptor.capture());
+ List<Integer> interruptionFilterValues = interruptionCaptor.getAllValues();
+
+ int lastChange = interruptionFilterValues.get(interruptionFilterValues.size() - 1)
+ .intValue();
+ assertEquals(params.expectedNotificationFilter, lastChange);
+ } else {
+ Thread.sleep(TEST_TIMEOUT);
+ verify(mMockInterruptionFilterProxy, never()).setInterruptionFilter(anyInt());
+ }
+
stateMachine.quitStateMachine();
// Verify interactions with the speakerphone and bluetooth systems
@@ -727,6 +990,7 @@
mockWiredHeadsetManager,
mockStatusBarNotifier,
mAudioServiceFactory,
+ mMockInterruptionFilterProxy,
params.doesDeviceSupportEarpiece);
// Set up bluetooth and speakerphone state
@@ -787,8 +1051,11 @@
private void resetMocks() {
reset(mockAudioManager, mockBluetoothManager, mockCallsManager,
- mockConnectionServiceWrapper);
+ mockConnectionServiceWrapper, mMockInterruptionFilterProxy);
+ mMockInterruptionFilterProxy = mock(InterruptionFilterProxy.class);
+ setupInterruptionFilterMocks();
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 6ca32d8..590304c 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
@@ -415,6 +424,7 @@
}
@MediumTest
+ @Postsubmit
public void testLogCallDirectionOutgoingWithMultiUserCapability() {
when(mMockPhoneAccountRegistrar.getPhoneAccountUnchecked(any(PhoneAccountHandle.class)))
.thenReturn(makeFakePhoneAccount(mOtherUserAccountHandle,
@@ -508,6 +518,7 @@
}
@MediumTest
+ @Postsubmit
public void testLogCallDirectionOutgoingFromManagedProfile() {
when(mMockPhoneAccountRegistrar.getPhoneAccountUnchecked(any(PhoneAccountHandle.class)))
.thenReturn(makeFakePhoneAccount(mManagedProfileAccountHandle, 0));
@@ -658,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();
}
}
@@ -669,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();
}
}
@@ -721,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/CallerInfoLookupHelperTest.java b/tests/src/com/android/server/telecom/tests/CallerInfoLookupHelperTest.java
index 8ece571..f261d3e 100644
--- a/tests/src/com/android/server/telecom/tests/CallerInfoLookupHelperTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallerInfoLookupHelperTest.java
@@ -21,6 +21,7 @@
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
+import android.test.suitebuilder.annotation.SmallTest;
import com.android.internal.telephony.CallerInfo;
import com.android.internal.telephony.CallerInfoAsyncQuery;
@@ -43,6 +44,7 @@
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
+import static org.mockito.Matchers.isNull;
import static org.mockito.Mockito.atMost;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
@@ -93,6 +95,16 @@
}
}
+ @SmallTest
+ public void testLookupWithEmptyHandle() {
+ CallerInfoLookupHelper.OnQueryCompleteListener listener = mock(
+ CallerInfoLookupHelper.OnQueryCompleteListener.class);
+ mCallerInfoLookupHelper.startLookup(Uri.EMPTY, listener);
+
+ verify(listener).onCallerInfoQueryComplete(eq(Uri.EMPTY), isNull(CallerInfo.class));
+ verifyProperCleanup();
+ }
+
public void testSimpleLookup() {
CallerInfoLookupHelper.OnQueryCompleteListener listener = mock(
CallerInfoLookupHelper.OnQueryCompleteListener.class);
diff --git a/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java b/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java
index fba4fb0..5ed941e 100644
--- a/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java
+++ b/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java
@@ -519,6 +519,9 @@
for (ComponentName componentName : mComponentNamesByAction.get(intent.getAction())) {
ResolveInfo resolveInfo = new ResolveInfo();
resolveInfo.serviceInfo = mServiceInfoByComponentName.get(componentName);
+ resolveInfo.serviceInfo.metaData = new Bundle();
+ resolveInfo.serviceInfo.metaData.putBoolean(
+ TelecomManager.METADATA_INCLUDE_EXTERNAL_CALLS, true);
result.add(resolveInfo);
}
return result;
diff --git a/tests/src/com/android/server/telecom/tests/ConnectionServiceFixture.java b/tests/src/com/android/server/telecom/tests/ConnectionServiceFixture.java
index b58d2ce..255f3ea 100644
--- a/tests/src/com/android/server/telecom/tests/ConnectionServiceFixture.java
+++ b/tests/src/com/android/server/telecom/tests/ConnectionServiceFixture.java
@@ -102,24 +102,43 @@
@Override
public Connection onCreateOutgoingConnection(
PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request) {
- mLatestConnection = new FakeConnection(request.getVideoState(), request.getAddress());
- return mLatestConnection;
+ FakeConnection fakeConnection = new FakeConnection(request.getVideoState(),
+ request.getAddress());
+ mLatestConnection = fakeConnection;
+ if (mCapabilities != NOT_SPECIFIED) {
+ fakeConnection.setConnectionCapabilities(mCapabilities);
+ }
+ if (mProperties != NOT_SPECIFIED) {
+ fakeConnection.setConnectionProperties(mProperties);
+ }
+ return fakeConnection;
}
@Override
public void onConference(Connection cxn1, Connection cxn2) {
- // Usually, this is implemented by something in Telephony, which does a bunch of radio
- // work to conference the two connections together. Here we just short-cut that and
- // declare them conferenced.
- Conference fakeConference = new FakeConference();
- fakeConference.addConnection(cxn1);
- fakeConference.addConnection(cxn2);
- mLatestConference = fakeConference;
- addConference(fakeConference);
+ if (((FakeConnection) cxn1).getIsConferenceCreated()) {
+ // Usually, this is implemented by something in Telephony, which does a bunch of
+ // radio work to conference the two connections together. Here we just short-cut
+ // that and declare them conferenced.
+ Conference fakeConference = new FakeConference();
+ fakeConference.addConnection(cxn1);
+ fakeConference.addConnection(cxn2);
+ mLatestConference = fakeConference;
+ addConference(fakeConference);
+ } else {
+ try {
+ sendSetConferenceMergeFailed(cxn1.getTelecomCallId());
+ } catch (Exception e) {
+ Log.w(this, "Exception on sendSetConferenceMergeFailed: " + e.getMessage());
+ }
+ }
}
}
public class FakeConnection extends Connection {
+ // Set to false if you wish the Conference merge to fail.
+ boolean mIsConferenceCreated = true;
+
public FakeConnection(int videoState, Uri address) {
super();
int capabilities = getConnectionCapabilities();
@@ -128,7 +147,7 @@
capabilities |= CAPABILITY_HOLD;
setVideoState(videoState);
setConnectionCapabilities(capabilities);
- setActive();
+ setDialing();
setAddress(address, TelecomManager.PRESENTATION_ALLOWED);
}
@@ -136,6 +155,14 @@
public void onExtrasChanged(Bundle extras) {
mExtrasLock.countDown();
}
+
+ public boolean getIsConferenceCreated() {
+ return mIsConferenceCreated;
+ }
+
+ public void setIsConferenceCreated(boolean isConferenceCreated) {
+ mIsConferenceCreated = isConferenceCreated;
+ }
}
public class FakeConference extends Conference {
@@ -203,6 +230,7 @@
c.videoState = request.getVideoState();
c.mockVideoProvider = new MockVideoProvider();
c.videoProvider = c.mockVideoProvider.getInterface();
+ c.isConferenceCreated = true;
mConnectionById.put(id, c);
mConnectionServiceDelegateAdapter.createConnection(connectionManagerPhoneAccount,
id, request, isIncoming, isUnknown);
@@ -317,6 +345,7 @@
int videoState;
boolean isVoipAudioMode;
Bundle extras;
+ boolean isConferenceCreated;
}
public class ConferenceInfo {
@@ -405,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);
@@ -522,6 +556,12 @@
}
}
+ public void sendSetConferenceMergeFailed(String id) throws Exception {
+ for (IConnectionServiceAdapter a : mConnectionServiceAdapters) {
+ a.setConferenceMergeFailed(id);
+ }
+ }
+
/**
* Waits until the {@link Connection#onExtrasChanged(Bundle)} API has been called on a
* {@link Connection} or {@link Conference}.
diff --git a/tests/src/com/android/server/telecom/tests/DirectToVoicemailCallFilterTest.java b/tests/src/com/android/server/telecom/tests/DirectToVoicemailCallFilterTest.java
index ac74604..2ebb6fc 100644
--- a/tests/src/com/android/server/telecom/tests/DirectToVoicemailCallFilterTest.java
+++ b/tests/src/com/android/server/telecom/tests/DirectToVoicemailCallFilterTest.java
@@ -42,7 +42,6 @@
public void setUp() throws Exception {
super.setUp();
- when(mCall.getHandle()).thenReturn(TEST_HANDLE);
}
@SmallTest
@@ -79,13 +78,32 @@
));
}
+ @SmallTest
+ public void testNullResponseFromLookupHelper() {
+ CallerInfoLookupHelper.OnQueryCompleteListener queryListener = verifyLookupStart(null);
+
+ queryListener.onCallerInfoQueryComplete(null, null);
+ verify(mCallback).onCallFilteringComplete(mCall,
+ new CallFilteringResult(
+ true, // shouldAllowCall
+ false, // shouldReject
+ true, // shouldAddToCallLog
+ true // shouldShowNotification
+ ));
+ }
+
private CallerInfoLookupHelper.OnQueryCompleteListener verifyLookupStart() {
+ return verifyLookupStart(TEST_HANDLE);
+ }
+
+ private CallerInfoLookupHelper.OnQueryCompleteListener verifyLookupStart(Uri handle) {
+ when(mCall.getHandle()).thenReturn(handle);
DirectToVoicemailCallFilter filter =
new DirectToVoicemailCallFilter(mCallerInfoLookupHelper);
filter.startFilterLookup(mCall, mCallback);
ArgumentCaptor<CallerInfoLookupHelper.OnQueryCompleteListener> captor =
ArgumentCaptor.forClass(CallerInfoLookupHelper.OnQueryCompleteListener.class);
- verify(mCallerInfoLookupHelper).startLookup(eq(TEST_HANDLE), captor.capture());
+ verify(mCallerInfoLookupHelper).startLookup(eq(handle), captor.capture());
return captor.getValue();
}
}
diff --git a/tests/src/com/android/server/telecom/tests/InCallControllerTests.java b/tests/src/com/android/server/telecom/tests/InCallControllerTests.java
index 2468774..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;
@@ -28,15 +29,17 @@
import android.os.Bundle;
import android.os.IBinder;
import android.os.UserHandle;
-import android.telecom.ConnectionService;
import android.telecom.InCallService;
+import android.telecom.ParcelableCall;
import android.telecom.PhoneAccountHandle;
import android.telecom.TelecomManager;
import android.test.mock.MockContext;
import android.test.suitebuilder.annotation.MediumTest;
+import android.text.TextUtils;
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,22 +49,24 @@
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;
import org.mockito.MockitoAnnotations;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+import java.util.Collections;
import java.util.LinkedList;
import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyBoolean;
-import static org.mockito.Matchers.anyChar;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Matchers.isNull;
import static org.mockito.Matchers.isNull;
-import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
@@ -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";
@@ -90,17 +96,18 @@
private UserHandle mUserHandle = UserHandle.of(CURRENT_USER_ID);
private InCallController mInCallController;
- private TelecomSystem.SyncRoot mLock;
+ private TelecomSystem.SyncRoot mLock = new TelecomSystem.SyncRoot() {};
@Override
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
@@ -114,11 +121,9 @@
when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
when(mMockCallsManager.hasEmergencyCall()).thenReturn(false);
when(mMockCall.isIncoming()).thenReturn(true);
+ when(mMockCall.isExternalCall()).thenReturn(false);
- Intent queryIntent = new Intent(InCallService.SERVICE_INTERFACE);
- when(mMockPackageManager.queryIntentServicesAsUser(
- queryIntent, PackageManager.GET_META_DATA, CURRENT_USER_ID))
- .thenReturn(new LinkedList<ResolveInfo>());
+ setupMockPackageManager(false /* default */, true /* system */, false /* external calls */);
mInCallController.bindToServices(mMockCall);
ArgumentCaptor<Intent> bindIntentCaptor = ArgumentCaptor.forClass(Intent.class);
@@ -146,11 +151,10 @@
when(mMockCall.isIncoming()).thenReturn(false);
when(mMockCall.getTargetPhoneAccount()).thenReturn(PA_HANDLE);
when(mMockCall.getIntentExtras()).thenReturn(callExtras);
+ when(mMockCall.isExternalCall()).thenReturn(false);
Intent queryIntent = new Intent(InCallService.SERVICE_INTERFACE);
- when(mMockPackageManager.queryIntentServicesAsUser(
- queryIntent, PackageManager.GET_META_DATA, CURRENT_USER_ID))
- .thenReturn(new LinkedList<ResolveInfo>());
+ setupMockPackageManager(false /* default */, true /* system */, false /* external calls */);
mInCallController.bindToServices(mMockCall);
ArgumentCaptor<Intent> bindIntentCaptor = ArgumentCaptor.forClass(Intent.class);
@@ -181,36 +185,18 @@
when(mMockCall.isIncoming()).thenReturn(false);
when(mMockCall.getTargetPhoneAccount()).thenReturn(PA_HANDLE);
when(mMockCall.getIntentExtras()).thenReturn(callExtras);
+ 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(), eq(UserHandle.CURRENT))).thenReturn(true);
- Intent queryIntent = new Intent(InCallService.SERVICE_INTERFACE);
- when(mMockPackageManager.queryIntentServicesAsUser(
- any(Intent.class), eq(PackageManager.GET_META_DATA), eq(CURRENT_USER_ID)))
- .thenReturn(new LinkedList<ResolveInfo>() {{
- add(new ResolveInfo() {{
- serviceInfo = new ServiceInfo();
- serviceInfo.packageName = DEF_PKG;
- serviceInfo.name = DEF_CLASS;
- serviceInfo.permission = Manifest.permission.BIND_INCALL_SERVICE;
- serviceInfo.metaData = new Bundle();
- serviceInfo.metaData.putBoolean(
- TelecomManager.METADATA_IN_CALL_SERVICE_UI, true);
- }});
- add(new ResolveInfo() {{
- serviceInfo = new ServiceInfo();
- serviceInfo.packageName = SYS_PKG;
- serviceInfo.name = SYS_CLASS;
- serviceInfo.permission = Manifest.permission.BIND_INCALL_SERVICE;
- }});
- }});
+ setupMockPackageManager(true /* default */, true /* system */, false /* external calls */);
mInCallController.bindToServices(mMockCall);
// Query for the different InCallServices
ArgumentCaptor<Intent> queryIntentCaptor = ArgumentCaptor.forClass(Intent.class);
- verify(mMockPackageManager, times(3)).queryIntentServicesAsUser(
+ verify(mMockPackageManager, times(4)).queryIntentServicesAsUser(
queryIntentCaptor.capture(),
eq(PackageManager.GET_META_DATA), eq(CURRENT_USER_ID));
@@ -249,37 +235,20 @@
when(mMockCall.isIncoming()).thenReturn(false);
when(mMockCall.getTargetPhoneAccount()).thenReturn(PA_HANDLE);
when(mMockCall.getIntentExtras()).thenReturn(callExtras);
+ when(mMockCall.isExternalCall()).thenReturn(false);
when(mMockDefaultDialerAdapter.getDefaultDialerApplication(mMockContext, CURRENT_USER_ID))
.thenReturn(DEF_PKG);
when(mMockContext.bindServiceAsUser(any(Intent.class), any(ServiceConnection.class),
eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE),
eq(UserHandle.CURRENT))).thenReturn(true);
- Intent queryIntent = new Intent(InCallService.SERVICE_INTERFACE);
- when(mMockPackageManager.queryIntentServicesAsUser(
- any(Intent.class), eq(PackageManager.GET_META_DATA), eq(CURRENT_USER_ID)))
- .thenReturn(new LinkedList<ResolveInfo>() {{
- add(new ResolveInfo() {{
- serviceInfo = new ServiceInfo();
- serviceInfo.packageName = DEF_PKG;
- serviceInfo.name = DEF_CLASS;
- serviceInfo.permission = Manifest.permission.BIND_INCALL_SERVICE;
- serviceInfo.metaData = new Bundle();
- serviceInfo.metaData.putBoolean(
- TelecomManager.METADATA_IN_CALL_SERVICE_UI, true);
- }});
- add(new ResolveInfo() {{
- serviceInfo = new ServiceInfo();
- serviceInfo.packageName = SYS_PKG;
- serviceInfo.name = SYS_CLASS;
- serviceInfo.permission = Manifest.permission.BIND_INCALL_SERVICE;
- }});
- }});
+ setupMockPackageManager(true /* default */, true /* system */, false /* external calls */);
+
mInCallController.bindToServices(mMockCall);
// Query for the different InCallServices
ArgumentCaptor<Intent> queryIntentCaptor = ArgumentCaptor.forClass(Intent.class);
- verify(mMockPackageManager, times(3)).queryIntentServicesAsUser(
+ verify(mMockPackageManager, times(4)).queryIntentServicesAsUser(
queryIntentCaptor.capture(),
eq(PackageManager.GET_META_DATA), eq(CURRENT_USER_ID));
@@ -315,40 +284,26 @@
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(
any(Intent.class), any(ServiceConnection.class), anyInt(), any(UserHandle.class)))
.thenReturn(true);
- Intent queryIntent = new Intent(InCallService.SERVICE_INTERFACE);
- when(mMockPackageManager.queryIntentServicesAsUser(
- any(Intent.class), eq(PackageManager.GET_META_DATA), eq(CURRENT_USER_ID)))
- .thenReturn(new LinkedList<ResolveInfo>() {{
- add(new ResolveInfo() {{
- serviceInfo = new ServiceInfo();
- serviceInfo.packageName = DEF_PKG;
- serviceInfo.name = DEF_CLASS;
- serviceInfo.permission = Manifest.permission.BIND_INCALL_SERVICE;
- serviceInfo.metaData = new Bundle();
- serviceInfo.metaData.putBoolean(
- TelecomManager.METADATA_IN_CALL_SERVICE_UI, true);
- }});
- add(new ResolveInfo() {{
- serviceInfo = new ServiceInfo();
- serviceInfo.packageName = SYS_PKG;
- serviceInfo.name = SYS_CLASS;
- serviceInfo.permission = Manifest.permission.BIND_INCALL_SERVICE;
- }});
- }});
+ setupMockPackageManager(true /* default */, true /* system */, false /* external calls */);
mInCallController.bindToServices(mMockCall);
// Query for the different InCallServices
ArgumentCaptor<Intent> queryIntentCaptor = ArgumentCaptor.forClass(Intent.class);
- verify(mMockPackageManager, times(3)).queryIntentServicesAsUser(
+ verify(mMockPackageManager, times(4)).queryIntentServicesAsUser(
queryIntentCaptor.capture(),
eq(PackageManager.GET_META_DATA), eq(CURRENT_USER_ID));
@@ -404,4 +359,164 @@
assertEquals(SYS_PKG, bindIntent.getComponent().getPackageName());
assertEquals(SYS_CLASS, bindIntent.getComponent().getClassName());
}
+
+ /**
+ * Ensures that the {@link InCallController} will bind to an {@link InCallService} which
+ * supports external calls.
+ */
+ @MediumTest
+ public void testBindToService_IncludeExternal() throws Exception {
+ setupMocks(true /* isExternalCall */);
+ setupMockPackageManager(true /* default */, true /* system */, true /* external calls */);
+ mInCallController.bindToServices(mMockCall);
+
+ // Query for the different InCallServices
+ ArgumentCaptor<Intent> queryIntentCaptor = ArgumentCaptor.forClass(Intent.class);
+ verify(mMockPackageManager, times(4)).queryIntentServicesAsUser(
+ queryIntentCaptor.capture(),
+ eq(PackageManager.GET_META_DATA), eq(CURRENT_USER_ID));
+
+ // Verify call for default dialer InCallService
+ assertEquals(DEF_PKG, queryIntentCaptor.getAllValues().get(0).getPackage());
+ // Verify call for car-mode InCallService
+ assertEquals(null, queryIntentCaptor.getAllValues().get(1).getPackage());
+ // Verify call for non-UI InCallServices
+ assertEquals(null, queryIntentCaptor.getAllValues().get(2).getPackage());
+
+ ArgumentCaptor<Intent> bindIntentCaptor = ArgumentCaptor.forClass(Intent.class);
+ verify(mMockContext, times(1)).bindServiceAsUser(
+ bindIntentCaptor.capture(),
+ any(ServiceConnection.class),
+ eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE),
+ eq(UserHandle.CURRENT));
+
+ Intent bindIntent = bindIntentCaptor.getValue();
+ assertEquals(InCallService.SERVICE_INTERFACE, bindIntent.getAction());
+ assertEquals(DEF_PKG, bindIntent.getComponent().getPackageName());
+ 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);
+ when(mMockCallsManager.hasEmergencyCall()).thenReturn(false);
+ when(mMockCall.isIncoming()).thenReturn(false);
+ when(mMockCall.getTargetPhoneAccount()).thenReturn(PA_HANDLE);
+ when(mMockDefaultDialerAdapter.getDefaultDialerApplication(mMockContext, CURRENT_USER_ID))
+ .thenReturn(DEF_PKG);
+ when(mMockContext.bindServiceAsUser(any(Intent.class), any(ServiceConnection.class),
+ anyInt(), eq(UserHandle.CURRENT))).thenReturn(true);
+ when(mMockCall.isExternalCall()).thenReturn(isExternalCall);
+ }
+
+ private ResolveInfo getDefResolveInfo(final boolean includeExternalCalls) {
+ return new ResolveInfo() {{
+ serviceInfo = new ServiceInfo();
+ serviceInfo.packageName = DEF_PKG;
+ serviceInfo.name = DEF_CLASS;
+ serviceInfo.permission = Manifest.permission.BIND_INCALL_SERVICE;
+ serviceInfo.metaData = new Bundle();
+ serviceInfo.metaData.putBoolean(
+ TelecomManager.METADATA_IN_CALL_SERVICE_UI, true);
+ if (includeExternalCalls) {
+ serviceInfo.metaData.putBoolean(
+ TelecomManager.METADATA_INCLUDE_EXTERNAL_CALLS, true);
+ }
+ }};
+ }
+
+ private ResolveInfo getSysResolveinfo() {
+ return new ResolveInfo() {{
+ serviceInfo = new ServiceInfo();
+ serviceInfo.packageName = SYS_PKG;
+ serviceInfo.name = SYS_CLASS;
+ serviceInfo.permission = Manifest.permission.BIND_INCALL_SERVICE;
+ }};
+ }
+
+ private void setupMockPackageManager(final boolean useDefaultDialer,
+ final boolean useSystemDialer, final boolean includeExternalCalls) {
+
+ doAnswer(new Answer() {
+ @Override
+ public Object answer(InvocationOnMock invocation) throws Throwable {
+ Object[] args = invocation.getArguments();
+ Intent intent = (Intent) args[0];
+ String packageName = intent.getPackage();
+ ComponentName componentName = intent.getComponent();
+ if (componentName != null) {
+ packageName = componentName.getPackageName();
+ }
+ LinkedList<ResolveInfo> resolveInfo = new LinkedList<ResolveInfo>();
+ if (!TextUtils.isEmpty(packageName)) {
+ if ((TextUtils.isEmpty(packageName) || packageName.equals(DEF_PKG)) &&
+ useDefaultDialer) {
+ resolveInfo.add(getDefResolveInfo(includeExternalCalls));
+ }
+
+ if ((TextUtils.isEmpty(packageName) || packageName.equals(SYS_PKG)) &&
+ useSystemDialer) {
+ resolveInfo.add(getSysResolveinfo());
+ }
+ }
+ return resolveInfo;
+ }
+ }).when(mMockPackageManager).queryIntentServicesAsUser(
+ any(Intent.class), eq(PackageManager.GET_META_DATA), eq(CURRENT_USER_ID));
+ }
}
diff --git a/tests/src/com/android/server/telecom/tests/LogTest.java b/tests/src/com/android/server/telecom/tests/LogTest.java
index 586105f..20c4d63 100644
--- a/tests/src/com/android/server/telecom/tests/LogTest.java
+++ b/tests/src/com/android/server/telecom/tests/LogTest.java
@@ -16,29 +16,33 @@
package com.android.server.telecom.tests;
-import android.content.ContentResolver;
-import android.content.Context;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
import android.test.suitebuilder.annotation.MediumTest;
+import android.test.suitebuilder.annotation.SmallTest;
import com.android.internal.os.SomeArgs;
-import com.android.server.telecom.Runnable;
+import com.android.server.telecom.Analytics;
+import com.android.server.telecom.Call;
import com.android.server.telecom.Session;
import com.android.server.telecom.SystemLoggingContainer;
import com.android.server.telecom.Log;
import org.junit.Assert;
+import org.mockito.ArgumentCaptor;
import org.mockito.MockitoAnnotations;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
+import java.util.List;
import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger;
import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
/**
* Unit tests for Telecom's Logging system.
@@ -456,6 +460,26 @@
assertEquals(null, sessionRef.get());
}
+ @SmallTest
+ public void testEventRecordTiming() throws Exception {
+ Call call = mock(Call.class);
+ Analytics.CallInfo mockCallInfo = mock(Analytics.CallInfo.class);
+ when(call.getAnalytics()).thenReturn(mockCallInfo);
+ int minWaitTime = 40;
+ Log.event(call, Log.Events.REQUEST_ACCEPT);
+ Thread.sleep(minWaitTime);
+ Log.event(call, Log.Events.SET_ACTIVE);
+
+ ArgumentCaptor<Log.CallEventRecord> captor =
+ ArgumentCaptor.forClass(Log.CallEventRecord.class);
+ verify(mockCallInfo).setCallEvents(captor.capture());
+ List<Log.CallEventRecord.EventTiming> eventTimings =
+ captor.getValue().extractEventTimings();
+ eventTimings.stream()
+ .filter(timing -> timing.name.equals("accept"))
+ .forEach(timing -> assertTrue(timing.time > minWaitTime));
+ }
+
private void verifyMethodCall(String parentSessionName, String methodName, int sessionId,
String subsession, String shortMethodName, int timeoutMs) {
if (!parentSessionName.isEmpty()){
diff --git a/tests/src/com/android/server/telecom/tests/MissedCallNotifierImplTest.java b/tests/src/com/android/server/telecom/tests/MissedCallNotifierImplTest.java
index 12e7280..2663356 100644
--- a/tests/src/com/android/server/telecom/tests/MissedCallNotifierImplTest.java
+++ b/tests/src/com/android/server/telecom/tests/MissedCallNotifierImplTest.java
@@ -43,6 +43,7 @@
import com.android.server.telecom.Constants;
import com.android.server.telecom.MissedCallNotifier;
import com.android.server.telecom.PhoneAccountRegistrar;
+import com.android.server.telecom.PhoneNumberUtilsAdapterImpl;
import com.android.server.telecom.TelecomBroadcastIntentProcessor;
import com.android.server.telecom.TelecomSystem;
import com.android.server.telecom.components.TelecomBroadcastReceiver;
diff --git a/tests/src/com/android/server/telecom/tests/NewOutgoingCallIntentBroadcasterTest.java b/tests/src/com/android/server/telecom/tests/NewOutgoingCallIntentBroadcasterTest.java
index a7d98db..54613d4 100644
--- a/tests/src/com/android/server/telecom/tests/NewOutgoingCallIntentBroadcasterTest.java
+++ b/tests/src/com/android/server/telecom/tests/NewOutgoingCallIntentBroadcasterTest.java
@@ -38,6 +38,7 @@
import com.android.server.telecom.NewOutgoingCallIntentBroadcaster;
import com.android.server.telecom.PhoneNumberUtilsAdapter;
import com.android.server.telecom.PhoneNumberUtilsAdapterImpl;
+import com.android.server.telecom.TelecomSystem;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
@@ -77,6 +78,7 @@
mContext = mComponentContextFixture.getTestDouble().getApplicationContext();
mPhoneNumberUtilsAdapterSpy = spy(new PhoneNumberUtilsAdapterImpl());
when(mCall.getInitiatingUser()).thenReturn(UserHandle.CURRENT);
+ when(mCallsManager.getLock()).thenReturn(new TelecomSystem.SyncRoot() { });
}
@SmallTest
diff --git a/tests/src/com/android/server/telecom/tests/PhoneAccountRegistrarTest.java b/tests/src/com/android/server/telecom/tests/PhoneAccountRegistrarTest.java
index ceabc58..12d24c4 100644
--- a/tests/src/com/android/server/telecom/tests/PhoneAccountRegistrarTest.java
+++ b/tests/src/com/android/server/telecom/tests/PhoneAccountRegistrarTest.java
@@ -171,8 +171,7 @@
public void testAccounts() throws Exception {
int i = 0;
- mComponentContextFixture.addConnectionService(
- makeQuickConnectionServiceComponentName(),
+ mComponentContextFixture.addConnectionService(makeQuickConnectionServiceComponentName(),
Mockito.mock(IConnectionService.class));
registerAndEnableAccount(makeQuickAccountBuilder("id" + i, i++)
@@ -205,8 +204,7 @@
@MediumTest
public void testDefaultOutgoing() throws Exception {
- mComponentContextFixture.addConnectionService(
- makeQuickConnectionServiceComponentName(),
+ mComponentContextFixture.addConnectionService(makeQuickConnectionServiceComponentName(),
Mockito.mock(IConnectionService.class));
// By default, there is no default outgoing account (nothing has been registered)
@@ -253,6 +251,229 @@
}
@MediumTest
+ public void testReplacePhoneAccountByGroup() throws Exception {
+ mComponentContextFixture.addConnectionService(makeQuickConnectionServiceComponentName(),
+ Mockito.mock(IConnectionService.class));
+
+ // By default, there is no default outgoing account (nothing has been registered)
+ assertNull(
+ mRegistrar.getOutgoingPhoneAccountForSchemeOfCurrentUser(PhoneAccount.SCHEME_TEL));
+
+ // Register one tel: account
+ PhoneAccountHandle telAccount1 = makeQuickAccountHandle("tel_acct1");
+ registerAndEnableAccount(new PhoneAccount.Builder(telAccount1, "tel_acct1")
+ .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER)
+ .addSupportedUriScheme(PhoneAccount.SCHEME_TEL)
+ .setGroupId("testGroup")
+ .build());
+ mRegistrar.setUserSelectedOutgoingPhoneAccount(telAccount1, Process.myUserHandle());
+ PhoneAccountHandle defaultAccount =
+ mRegistrar.getOutgoingPhoneAccountForSchemeOfCurrentUser(PhoneAccount.SCHEME_TEL);
+ assertEquals(telAccount1, defaultAccount);
+
+ // Add call capable SIP account, make sure tel: doesn't change
+ PhoneAccountHandle sipAccount = makeQuickAccountHandle("sip_acct");
+ registerAndEnableAccount(new PhoneAccount.Builder(sipAccount, "sip_acct")
+ .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER)
+ .addSupportedUriScheme(PhoneAccount.SCHEME_TEL)
+ .build());
+ defaultAccount = mRegistrar.getOutgoingPhoneAccountForSchemeOfCurrentUser(
+ PhoneAccount.SCHEME_TEL);
+ assertEquals(telAccount1, defaultAccount);
+
+ // Replace tel: account with another in the same Group
+ PhoneAccountHandle telAccount2 = makeQuickAccountHandle("tel_acct2");
+ registerAndEnableAccount(new PhoneAccount.Builder(telAccount2, "tel_acct2")
+ .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER)
+ .addSupportedUriScheme(PhoneAccount.SCHEME_TEL)
+ .setGroupId("testGroup")
+ .build());
+ defaultAccount = mRegistrar.getOutgoingPhoneAccountForSchemeOfCurrentUser(
+ PhoneAccount.SCHEME_TEL);
+ assertEquals(telAccount2, defaultAccount);
+ assertNull(mRegistrar.getPhoneAccountUnchecked(telAccount1));
+ }
+
+ @MediumTest
+ public void testAddSameDefault() throws Exception {
+ mComponentContextFixture.addConnectionService(makeQuickConnectionServiceComponentName(),
+ Mockito.mock(IConnectionService.class));
+
+ // By default, there is no default outgoing account (nothing has been registered)
+ assertNull(
+ mRegistrar.getOutgoingPhoneAccountForSchemeOfCurrentUser(PhoneAccount.SCHEME_TEL));
+
+ // Register one tel: account
+ PhoneAccountHandle telAccount1 = makeQuickAccountHandle("tel_acct1");
+ registerAndEnableAccount(new PhoneAccount.Builder(telAccount1, "tel_acct1")
+ .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER)
+ .addSupportedUriScheme(PhoneAccount.SCHEME_TEL)
+ .setGroupId("testGroup")
+ .build());
+ mRegistrar.setUserSelectedOutgoingPhoneAccount(telAccount1, Process.myUserHandle());
+ PhoneAccountHandle defaultAccount =
+ mRegistrar.getUserSelectedOutgoingPhoneAccount(Process.myUserHandle());
+ assertEquals(telAccount1, defaultAccount);
+ mRegistrar.unregisterPhoneAccount(telAccount1);
+
+ // Register Emergency Account and unregister
+ PhoneAccountHandle emerAccount = makeQuickAccountHandle("emer_acct");
+ registerAndEnableAccount(new PhoneAccount.Builder(emerAccount, "emer_acct")
+ .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER)
+ .addSupportedUriScheme(PhoneAccount.SCHEME_TEL)
+ .build());
+ defaultAccount =
+ mRegistrar.getUserSelectedOutgoingPhoneAccount(Process.myUserHandle());
+ assertNull(defaultAccount);
+ mRegistrar.unregisterPhoneAccount(emerAccount);
+
+ // Re-register the same account and make sure the default is in place
+ registerAndEnableAccount(new PhoneAccount.Builder(telAccount1, "tel_acct1")
+ .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER)
+ .addSupportedUriScheme(PhoneAccount.SCHEME_TEL)
+ .setGroupId("testGroup")
+ .build());
+ defaultAccount =
+ mRegistrar.getUserSelectedOutgoingPhoneAccount(Process.myUserHandle());
+ assertEquals(telAccount1, defaultAccount);
+ }
+
+ @MediumTest
+ public void testAddSameGroup() throws Exception {
+ mComponentContextFixture.addConnectionService(makeQuickConnectionServiceComponentName(),
+ Mockito.mock(IConnectionService.class));
+
+ // By default, there is no default outgoing account (nothing has been registered)
+ assertNull(
+ mRegistrar.getOutgoingPhoneAccountForSchemeOfCurrentUser(PhoneAccount.SCHEME_TEL));
+
+ // Register one tel: account
+ PhoneAccountHandle telAccount1 = makeQuickAccountHandle("tel_acct1");
+ registerAndEnableAccount(new PhoneAccount.Builder(telAccount1, "tel_acct1")
+ .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER)
+ .addSupportedUriScheme(PhoneAccount.SCHEME_TEL)
+ .setGroupId("testGroup")
+ .build());
+ mRegistrar.setUserSelectedOutgoingPhoneAccount(telAccount1, Process.myUserHandle());
+ PhoneAccountHandle defaultAccount =
+ mRegistrar.getUserSelectedOutgoingPhoneAccount(Process.myUserHandle());
+ assertEquals(telAccount1, defaultAccount);
+ mRegistrar.unregisterPhoneAccount(telAccount1);
+
+ // Register Emergency Account and unregister
+ PhoneAccountHandle emerAccount = makeQuickAccountHandle("emer_acct");
+ registerAndEnableAccount(new PhoneAccount.Builder(emerAccount, "emer_acct")
+ .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER)
+ .addSupportedUriScheme(PhoneAccount.SCHEME_TEL)
+ .build());
+ defaultAccount =
+ mRegistrar.getUserSelectedOutgoingPhoneAccount(Process.myUserHandle());
+ assertNull(defaultAccount);
+ mRegistrar.unregisterPhoneAccount(emerAccount);
+
+ // Re-register a new account with the same group
+ PhoneAccountHandle telAccount2 = makeQuickAccountHandle("tel_acct2");
+ registerAndEnableAccount(new PhoneAccount.Builder(telAccount2, "tel_acct2")
+ .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER)
+ .addSupportedUriScheme(PhoneAccount.SCHEME_TEL)
+ .setGroupId("testGroup")
+ .build());
+ defaultAccount =
+ mRegistrar.getUserSelectedOutgoingPhoneAccount(Process.myUserHandle());
+ assertEquals(telAccount2, defaultAccount);
+ }
+
+ @MediumTest
+ public void testAddSameGroupButDifferentComponent() throws Exception {
+ mComponentContextFixture.addConnectionService(makeQuickConnectionServiceComponentName(),
+ Mockito.mock(IConnectionService.class));
+
+ // By default, there is no default outgoing account (nothing has been registered)
+ assertNull(mRegistrar.getOutgoingPhoneAccountForSchemeOfCurrentUser(
+ PhoneAccount.SCHEME_TEL));
+
+ // Register one tel: account
+ PhoneAccountHandle telAccount1 = makeQuickAccountHandle("tel_acct1");
+ registerAndEnableAccount(new PhoneAccount.Builder(telAccount1, "tel_acct1")
+ .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER)
+ .addSupportedUriScheme(PhoneAccount.SCHEME_TEL)
+ .setGroupId("testGroup")
+ .build());
+ mRegistrar.setUserSelectedOutgoingPhoneAccount(telAccount1, Process.myUserHandle());
+ PhoneAccountHandle defaultAccount =
+ mRegistrar.getUserSelectedOutgoingPhoneAccount(Process.myUserHandle());
+ assertEquals(telAccount1, defaultAccount);
+ assertNotNull(mRegistrar.getPhoneAccountUnchecked(telAccount1));
+
+ // Register a new account with the same group, but different Component, so don't replace
+ // Default
+ PhoneAccountHandle telAccount2 = makeQuickAccountHandle(
+ new ComponentName("other1", "other2"), "tel_acct2");
+ registerAndEnableAccount(new PhoneAccount.Builder(telAccount2, "tel_acct2")
+ .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER)
+ .addSupportedUriScheme(PhoneAccount.SCHEME_TEL)
+ .setGroupId("testGroup")
+ .build());
+ assertNotNull(mRegistrar.getPhoneAccountUnchecked(telAccount2));
+
+ defaultAccount =
+ mRegistrar.getUserSelectedOutgoingPhoneAccount(Process.myUserHandle());
+ assertEquals(telAccount1, defaultAccount);
+ }
+
+ @MediumTest
+ public void testAddSameGroupButDifferentComponent2() throws Exception {
+ mComponentContextFixture.addConnectionService(makeQuickConnectionServiceComponentName(),
+ Mockito.mock(IConnectionService.class));
+
+ // By default, there is no default outgoing account (nothing has been registered)
+ assertNull(mRegistrar.getOutgoingPhoneAccountForSchemeOfCurrentUser(
+ PhoneAccount.SCHEME_TEL));
+
+ // Register first tel: account
+ PhoneAccountHandle telAccount1 = makeQuickAccountHandle(
+ new ComponentName("other1", "other2"), "tel_acct1");
+ registerAndEnableAccount(new PhoneAccount.Builder(telAccount1, "tel_acct1")
+ .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER)
+ .addSupportedUriScheme(PhoneAccount.SCHEME_TEL)
+ .setGroupId("testGroup")
+ .build());
+ assertNotNull(mRegistrar.getPhoneAccountUnchecked(telAccount1));
+ mRegistrar.setUserSelectedOutgoingPhoneAccount(telAccount1, Process.myUserHandle());
+
+ // Register second account with the same group, but a second Component, so don't replace
+ // Default
+ PhoneAccountHandle telAccount2 = makeQuickAccountHandle("tel_acct2");
+ registerAndEnableAccount(new PhoneAccount.Builder(telAccount2, "tel_acct2")
+ .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER)
+ .addSupportedUriScheme(PhoneAccount.SCHEME_TEL)
+ .setGroupId("testGroup")
+ .build());
+
+ PhoneAccountHandle defaultAccount =
+ mRegistrar.getOutgoingPhoneAccountForSchemeOfCurrentUser(PhoneAccount.SCHEME_TEL);
+ assertEquals(telAccount1, defaultAccount);
+
+ // Register third account with the second component name, but same group id
+ PhoneAccountHandle telAccount3 = makeQuickAccountHandle("tel_acct3");
+ registerAndEnableAccount(new PhoneAccount.Builder(telAccount3, "tel_acct3")
+ .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER)
+ .addSupportedUriScheme(PhoneAccount.SCHEME_TEL)
+ .setGroupId("testGroup")
+ .build());
+
+ // Make sure that the default account is still the original PhoneAccount and that the
+ // second PhoneAccount with the second ComponentName was replaced by the third PhoneAccount
+ defaultAccount =
+ mRegistrar.getOutgoingPhoneAccountForSchemeOfCurrentUser(PhoneAccount.SCHEME_TEL);
+ assertEquals(telAccount1, defaultAccount);
+
+ assertNotNull(mRegistrar.getPhoneAccountUnchecked(telAccount1));
+ assertNull(mRegistrar.getPhoneAccountUnchecked(telAccount2));
+ assertNotNull(mRegistrar.getPhoneAccountUnchecked(telAccount3));
+ }
+
+ @MediumTest
public void testPhoneAccountParceling() throws Exception {
PhoneAccountHandle handle = makeQuickAccountHandle("foo");
roundTripPhoneAccount(new PhoneAccount.Builder(handle, null).build());
@@ -268,6 +489,7 @@
.setShortDescription("short description")
.setSubscriptionAddress(Uri.parse("tel:2345678"))
.setSupportedUriSchemes(Arrays.asList("tel", "sip"))
+ .setGroupId("testGroup")
.build());
roundTripPhoneAccount(
new PhoneAccount.Builder(handle, "foo")
@@ -281,6 +503,7 @@
.setShortDescription("short description")
.setSubscriptionAddress(Uri.parse("tel:2345678"))
.setSupportedUriSchemes(Arrays.asList("tel", "sip"))
+ .setGroupId("testGroup")
.build());
}
@@ -291,10 +514,11 @@
}
private static PhoneAccountHandle makeQuickAccountHandle(String id) {
- return new PhoneAccountHandle(
- makeQuickConnectionServiceComponentName(),
- id,
- Process.myUserHandle());
+ return makeQuickAccountHandle(makeQuickConnectionServiceComponentName(), id);
+ }
+
+ private static PhoneAccountHandle makeQuickAccountHandle(ComponentName name, String id) {
+ return new PhoneAccountHandle(name, id, Process.myUserHandle());
}
private PhoneAccount.Builder makeQuickAccountBuilder(String id, int idx) {
@@ -457,7 +681,8 @@
new ComponentName("pkg0", "cls0"), "id0");
UserHandle userHandle = phoneAccountHandle.getUserHandle();
s.defaultOutgoingAccountHandles
- .put(userHandle, new DefaultPhoneAccountHandle(userHandle, phoneAccountHandle));
+ .put(userHandle, new DefaultPhoneAccountHandle(userHandle, phoneAccountHandle,
+ "testGroup"));
return s;
}
}
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 6535100..d098eb9 100644
--- a/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
+++ b/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
@@ -33,6 +33,7 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.app.NotificationManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ContentResolver;
@@ -44,11 +45,13 @@
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
+import android.os.Looper;
import android.os.Process;
import android.os.UserHandle;
import android.provider.BlockedNumberContract;
import android.telecom.Call;
import android.telecom.ConnectionRequest;
+import android.telecom.DisconnectCause;
import android.telecom.ParcelableCall;
import android.telecom.PhoneAccount;
import android.telecom.PhoneAccountHandle;
@@ -59,6 +62,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.CallerInfoLookupHelper;
import com.android.server.telecom.CallsManager;
@@ -68,12 +72,17 @@
import com.android.server.telecom.HeadsetMediaButtonFactory;
import com.android.server.telecom.InCallWakeLockController;
import com.android.server.telecom.InCallWakeLockControllerFactory;
+import com.android.server.telecom.InterruptionFilterProxy;
import com.android.server.telecom.MissedCallNotifier;
import com.android.server.telecom.PhoneAccountRegistrar;
+import com.android.server.telecom.PhoneNumberUtilsAdapter;
+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;
@@ -86,6 +95,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.
@@ -146,11 +157,39 @@
}
MissedCallNotifierFakeImpl mMissedCallNotifier = new MissedCallNotifierFakeImpl();
+ private class EmergencyNumberUtilsAdapter extends PhoneNumberUtilsAdapterImpl {
+
+ @Override
+ public boolean isLocalEmergencyNumber(Context context, String number) {
+ return mIsEmergencyCall;
+ }
+
+ @Override
+ public boolean isPotentialLocalEmergencyNumber(Context context, String number) {
+ return mIsEmergencyCall;
+ }
+ }
+ PhoneNumberUtilsAdapter mPhoneNumberUtilsAdapter = new EmergencyNumberUtilsAdapter();
+
+ public static class MockInterruptionFilterProxy implements InterruptionFilterProxy {
+ private int mInterruptionFilter = NotificationManager.INTERRUPTION_FILTER_ALL;
+ @Override
+ public void setInterruptionFilter(int interruptionFilter) {
+ mInterruptionFilter = interruptionFilter;
+ }
+
+ @Override
+ public int getCurrentInterruptionFilter() {
+ return mInterruptionFilter;
+ }
+ }
+
@Mock HeadsetMediaButton mHeadsetMediaButton;
@Mock ProximitySensorManager mProximitySensorManager;
@Mock InCallWakeLockController mInCallWakeLockController;
@Mock BluetoothPhoneServiceImpl mBluetoothPhoneServiceImpl;
@Mock AsyncRingtonePlayer mAsyncRingtonePlayer;
+ @Mock InterruptionFilterProxy mInterruptionFilterProxy;
final ComponentName mInCallServiceComponentNameX =
new ComponentName(
@@ -182,7 +221,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(
@@ -193,7 +233,8 @@
.addSupportedUriScheme("tel")
.setCapabilities(
PhoneAccount.CAPABILITY_CALL_PROVIDER |
- PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)
+ PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION |
+ PhoneAccount.CAPABILITY_VIDEO_CALLING)
.build();
final PhoneAccount mPhoneAccountB0 =
PhoneAccount.builder(
@@ -204,7 +245,35 @@
.addSupportedUriScheme("tel")
.setCapabilities(
PhoneAccount.CAPABILITY_CALL_PROVIDER |
- PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)
+ PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION |
+ PhoneAccount.CAPABILITY_VIDEO_CALLING)
+ .build();
+ final PhoneAccount mPhoneAccountE0 =
+ PhoneAccount.builder(
+ new PhoneAccountHandle(
+ mConnectionServiceComponentNameA,
+ "id E 0"),
+ "Phone account service E ID 0")
+ .addSupportedUriScheme("tel")
+ .setCapabilities(
+ PhoneAccount.CAPABILITY_CALL_PROVIDER |
+ PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION |
+ PhoneAccount.CAPABILITY_PLACE_EMERGENCY_CALLS |
+ PhoneAccount.CAPABILITY_VIDEO_CALLING)
+ .build();
+
+ final PhoneAccount mPhoneAccountE1 =
+ PhoneAccount.builder(
+ new PhoneAccountHandle(
+ mConnectionServiceComponentNameA,
+ "id E 1"),
+ "Phone account service E ID 1")
+ .addSupportedUriScheme("tel")
+ .setCapabilities(
+ PhoneAccount.CAPABILITY_CALL_PROVIDER |
+ PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION |
+ PhoneAccount.CAPABILITY_PLACE_EMERGENCY_CALLS |
+ PhoneAccount.CAPABILITY_VIDEO_CALLING)
.build();
ConnectionServiceFixture mConnectionServiceFixtureA;
@@ -221,6 +290,8 @@
private int mNumOutgoingCallsMade;
+ private boolean mIsEmergencyCall;
+
class IdPair {
final String mConnectionId;
final String mCallId;
@@ -239,6 +310,8 @@
mNumOutgoingCallsMade = 0;
+ mIsEmergencyCall = false;
+
// First set up information about the In-Call services in the mock Context, since
// Telecom will search for these as soon as it is instantiated
setupInCallServices();
@@ -298,7 +371,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(),
@@ -328,7 +401,9 @@
}
},
mTimeoutsAdapter,
- mAsyncRingtonePlayer);
+ mAsyncRingtonePlayer,
+ mPhoneNumberUtilsAdapter,
+ mInterruptionFilterProxy);
mComponentContextFixture.setTelecomManager(new TelecomManager(
mComponentContextFixture.getTestDouble(),
@@ -358,6 +433,8 @@
mTelecomSystem.getPhoneAccountRegistrar().registerPhoneAccount(mPhoneAccountA0);
mTelecomSystem.getPhoneAccountRegistrar().registerPhoneAccount(mPhoneAccountA1);
mTelecomSystem.getPhoneAccountRegistrar().registerPhoneAccount(mPhoneAccountB0);
+ mTelecomSystem.getPhoneAccountRegistrar().registerPhoneAccount(mPhoneAccountE0);
+ mTelecomSystem.getPhoneAccountRegistrar().registerPhoneAccount(mPhoneAccountE1);
mTelecomSystem.getPhoneAccountRegistrar().setUserSelectedOutgoingPhoneAccount(
mPhoneAccountA0.getAccountHandle(), Process.myUserHandle());
@@ -461,10 +538,46 @@
phoneAccountHandle, connectionServiceFixture);
}
- protected String startOutgoingPhoneCallPendingCreateConnection(String number,
+ protected IdPair triggerEmergencyRedial(PhoneAccountHandle phoneAccountHandle,
+ ConnectionServiceFixture connectionServiceFixture, IdPair emergencyIds)
+ throws Exception {
+ int startingNumConnections = connectionServiceFixture.mConnectionById.size();
+ int startingNumCalls = mInCallServiceFixtureX.mCallById.size();
+
+ // Send the message to disconnect the Emergency call due to an error.
+ // CreateConnectionProcessor should now try the second SIM account
+ connectionServiceFixture.sendSetDisconnected(emergencyIds.mConnectionId,
+ DisconnectCause.ERROR);
+ waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
+ assertEquals(Call.STATE_DIALING, mInCallServiceFixtureX.getCall(
+ emergencyIds.mCallId).getState());
+ assertEquals(Call.STATE_DIALING, mInCallServiceFixtureY.getCall(
+ emergencyIds.mCallId).getState());
+
+ return redialingCallCreateConnectionComplete(startingNumConnections, startingNumCalls,
+ phoneAccountHandle, connectionServiceFixture);
+ }
+
+ protected IdPair startOutgoingEmergencyCall(String number,
PhoneAccountHandle phoneAccountHandle,
ConnectionServiceFixture connectionServiceFixture, UserHandle initiatingUser,
int videoState) throws Exception {
+ int startingNumConnections = connectionServiceFixture.mConnectionById.size();
+ int startingNumCalls = mInCallServiceFixtureX.mCallById.size();
+
+ mIsEmergencyCall = true;
+ // Call will not use the ordered broadcaster, since it is an Emergency Call
+ startOutgoingPhoneCallWaitForBroadcaster(number, phoneAccountHandle,
+ connectionServiceFixture, initiatingUser, videoState, true /*isEmergency*/);
+
+ return outgoingCallCreateConnectionComplete(startingNumConnections, startingNumCalls,
+ phoneAccountHandle, connectionServiceFixture);
+ }
+
+ protected void startOutgoingPhoneCallWaitForBroadcaster(String number,
+ PhoneAccountHandle phoneAccountHandle,
+ ConnectionServiceFixture connectionServiceFixture, UserHandle initiatingUser,
+ int videoState, boolean isEmergency) throws Exception {
reset(connectionServiceFixture.getTestDouble(), mInCallServiceFixtureX.getTestDouble(),
mInCallServiceFixtureY.getTestDouble());
@@ -480,7 +593,11 @@
Intent actionCallIntent = new Intent();
actionCallIntent.setData(Uri.parse("tel:" + number));
actionCallIntent.putExtra(Intent.EXTRA_PHONE_NUMBER, number);
- actionCallIntent.setAction(Intent.ACTION_CALL);
+ if(isEmergency) {
+ actionCallIntent.setAction(Intent.ACTION_CALL_EMERGENCY);
+ } else {
+ actionCallIntent.setAction(Intent.ACTION_CALL);
+ }
if (phoneAccountHandle != null) {
actionCallIntent.putExtra(
TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE,
@@ -508,6 +625,14 @@
.setInCallAdapter(
any(IInCallAdapter.class));
}
+ }
+
+ protected String startOutgoingPhoneCallPendingCreateConnection(String number,
+ PhoneAccountHandle phoneAccountHandle,
+ ConnectionServiceFixture connectionServiceFixture, UserHandle initiatingUser,
+ int videoState) throws Exception {
+ startOutgoingPhoneCallWaitForBroadcaster(number,phoneAccountHandle,
+ connectionServiceFixture, initiatingUser, videoState, false /*isEmergency*/);
ArgumentCaptor<Intent> newOutgoingCallIntent =
ArgumentCaptor.forClass(Intent.class);
@@ -539,6 +664,30 @@
return mInCallServiceFixtureX.mLatestCallId;
}
+ // When Telecom is redialing due to an error, we need to make sure the number of connections
+ // increase, but not the number of Calls in the InCallService.
+ protected IdPair redialingCallCreateConnectionComplete(int startingNumConnections,
+ int startingNumCalls, PhoneAccountHandle phoneAccountHandle,
+ ConnectionServiceFixture connectionServiceFixture) throws Exception {
+
+ assertEquals(startingNumConnections + 1, connectionServiceFixture.mConnectionById.size());
+
+ verify(connectionServiceFixture.getTestDouble())
+ .createConnection(eq(phoneAccountHandle), anyString(), any(ConnectionRequest.class),
+ eq(false)/*isIncoming*/, anyBoolean());
+ // Wait for handleCreateConnectionComplete
+ waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
+
+ // Make sure the number of registered InCallService Calls stays the same.
+ assertEquals(startingNumCalls, mInCallServiceFixtureX.mCallById.size());
+ assertEquals(startingNumCalls, mInCallServiceFixtureY.mCallById.size());
+
+ assertEquals(mInCallServiceFixtureX.mLatestCallId, mInCallServiceFixtureY.mLatestCallId);
+
+ return new IdPair(connectionServiceFixture.mLatestConnectionId,
+ mInCallServiceFixtureX.mLatestCallId);
+ }
+
protected IdPair outgoingCallCreateConnectionComplete(int startingNumConnections,
int startingNumCalls, PhoneAccountHandle phoneAccountHandle,
ConnectionServiceFixture connectionServiceFixture) throws Exception {
@@ -547,9 +696,11 @@
verify(connectionServiceFixture.getTestDouble())
.createConnection(eq(phoneAccountHandle), anyString(), any(ConnectionRequest.class),
- anyBoolean(), anyBoolean());
- connectionServiceFixture.sendHandleCreateConnectionComplete(
- connectionServiceFixture.mLatestConnectionId);
+ eq(false)/*isIncoming*/, anyBoolean());
+ // Wait for handleCreateConnectionComplete
+ waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
+ // Wait for the callback in ConnectionService#onAdapterAttached to execute.
+ waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
assertEquals(startingNumCalls + 1, mInCallServiceFixtureX.mCallById.size());
assertEquals(startingNumCalls + 1, mInCallServiceFixtureY.mCallById.size());
@@ -737,6 +888,20 @@
return ids;
}
+ protected IdPair startAndMakeDialingEmergencyCall(
+ String number,
+ PhoneAccountHandle phoneAccountHandle,
+ ConnectionServiceFixture connectionServiceFixture) throws Exception {
+ IdPair ids = startOutgoingEmergencyCall(number, phoneAccountHandle,
+ connectionServiceFixture, Process.myUserHandle(), VideoProfile.STATE_AUDIO_ONLY);
+
+ connectionServiceFixture.sendSetDialing(ids.mConnectionId);
+ assertEquals(Call.STATE_DIALING, mInCallServiceFixtureX.getCall(ids.mCallId).getState());
+ assertEquals(Call.STATE_DIALING, mInCallServiceFixtureY.getCall(ids.mCallId).getState());
+
+ return ids;
+ }
+
protected static void assertTrueWithTimeout(Predicate<Void> predicate) {
int elapsed = 0;
while (elapsed < TEST_TIMEOUT) {
diff --git a/tests/src/com/android/server/telecom/tests/VideoCallTests.java b/tests/src/com/android/server/telecom/tests/VideoCallTests.java
index c079857..0319e81 100644
--- a/tests/src/com/android/server/telecom/tests/VideoCallTests.java
+++ b/tests/src/com/android/server/telecom/tests/VideoCallTests.java
@@ -18,10 +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;
@@ -39,6 +43,7 @@
* Tests to ensure an incoming video-call is automatically routed to the speakerphone when
* the call is answered and neither a wired headset nor bluetooth headset are connected.
*/
+ @MediumTest
public void testAutoSpeakerphoneIncomingBidirectional() throws Exception {
// Start an incoming video call.
IdPair ids = startAndMakeActiveIncomingCall("650-555-1212",
@@ -54,6 +59,7 @@
* always answer incoming video calls as bi-directional. It is, however, possible for a third
* party dialer to answer an incoming video call a a one-way video call.
*/
+ @MediumTest
public void testAutoSpeakerphoneIncomingReceiveOnly() throws Exception {
// Start an incoming video call.
IdPair ids = startAndMakeActiveIncomingCall("650-555-1212",
@@ -67,6 +73,7 @@
* Tests audio routing for an outgoing video call made with bidirectional video. Expect to be
* in speaker mode.
*/
+ @MediumTest
public void testAutoSpeakerphoneOutgoingBidirectional() throws Exception {
// Start an incoming video call.
IdPair ids = startAndMakeActiveOutgoingCall("650-555-1212",
@@ -81,6 +88,7 @@
* in speaker mode. Note: The default UI does not support making one-way video calls, but the
* APIs do and a third party incall UI could choose to support that.
*/
+ @MediumTest
public void testAutoSpeakerphoneOutgoingTransmitOnly() throws Exception {
// Start an incoming video call.
IdPair ids = startAndMakeActiveOutgoingCall("650-555-1212",
@@ -95,6 +103,7 @@
* in speaker mode. Note: The default UI does not support making one-way video calls, but the
* APIs do and a third party incall UI could choose to support that.
*/
+ @MediumTest
public void testNoAutoSpeakerphoneOnOutgoing() throws Exception {
// Start an incoming video call.
IdPair ids = startAndMakeActiveOutgoingCall("650-555-1212",
@@ -107,6 +116,7 @@
/**
* Tests to ensure an incoming audio-only call is routed to the earpiece.
*/
+ @MediumTest
public void testNoAutoSpeakerphoneOnIncoming() throws Exception {
// Start an incoming video call.
@@ -127,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 bc7f9e1..993679e 100644
--- a/tests/src/com/android/server/telecom/tests/VideoProviderTest.java
+++ b/tests/src/com/android/server/telecom/tests/VideoProviderTest.java
@@ -43,6 +43,7 @@
import android.telecom.VideoCallImpl;
import android.telecom.VideoProfile;
import android.telecom.VideoProfile.CameraCapabilities;
+import android.test.suitebuilder.annotation.MediumTest;
import android.view.Surface;
import com.google.common.base.Predicate;
@@ -112,6 +113,7 @@
mConnectionInfo = mConnectionServiceFixtureA.mConnectionById.get(mCallIds.mConnectionId);
mVerificationLock = new CountDownLatch(1);
+ waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
}
@Override
@@ -124,6 +126,7 @@
* and {@link VideoCall.Callback#onCameraCapabilitiesChanged(CameraCapabilities)}
* APIS.
*/
+ @MediumTest
public void testCameraChange() throws Exception {
// Wait until the callback has been received before performing verification.
doAnswer(mVerification).when(mVideoCallCallback)
@@ -157,6 +160,7 @@
* Tests the {@link VideoCall#setPreviewSurface(Surface)} and
* {@link VideoProvider#onSetPreviewSurface(Surface)} APIs.
*/
+ @MediumTest
public void testSetPreviewSurface() throws Exception {
final Surface surface = new Surface(new SurfaceTexture(1));
mVideoCall.setPreviewSurface(surface);
@@ -182,6 +186,7 @@
* Tests the {@link VideoCall#setDisplaySurface(Surface)} and
* {@link VideoProvider#onSetDisplaySurface(Surface)} APIs.
*/
+ @MediumTest
public void testSetDisplaySurface() throws Exception {
final Surface surface = new Surface(new SurfaceTexture(1));
mVideoCall.setDisplaySurface(surface);
@@ -207,6 +212,7 @@
* Tests the {@link VideoCall#setDeviceOrientation(int)} and
* {@link VideoProvider#onSetDeviceOrientation(int)} APIs.
*/
+ @MediumTest
public void testSetDeviceOrientation() throws Exception {
mVideoCall.setDeviceOrientation(ORIENTATION_0);
@@ -230,6 +236,7 @@
/**
* Tests the {@link VideoCall#setZoom(float)} and {@link VideoProvider#onSetZoom(float)} APIs.
*/
+ @MediumTest
public void testSetZoom() throws Exception {
mVideoCall.setZoom(ZOOM_LEVEL);
@@ -251,6 +258,7 @@
* Emulates a scenario where an InCallService sends a request to upgrade to video, which the
* peer accepts as-is.
*/
+ @MediumTest
public void testSessionModifyRequest() throws Exception {
VideoProfile requestProfile = new VideoProfile(VideoProfile.STATE_BIDIRECTIONAL);
@@ -288,6 +296,7 @@
* Tests the {@link VideoCall#sendSessionModifyResponse(VideoProfile)},
* and {@link VideoProvider#onSendSessionModifyResponse(VideoProfile)} APIs.
*/
+ @MediumTest
public void testSessionModifyResponse() throws Exception {
VideoProfile sessionModifyResponse = new VideoProfile(VideoProfile.STATE_TX_ENABLED);
@@ -308,6 +317,7 @@
* {@link VideoProvider#onRequestCameraCapabilities()} ()}, and
* {@link VideoCall.Callback#onCameraCapabilitiesChanged(CameraCapabilities)} APIs.
*/
+ @MediumTest
public void testRequestCameraCapabilities() throws Exception {
// Wait until the callback has been received before performing verification.
doAnswer(mVerification).when(mVideoCallCallback)
@@ -325,6 +335,7 @@
* Tests the {@link VideoCall#setPauseImage(Uri)}, and
* {@link VideoProvider#onSetPauseImage(Uri)} APIs.
*/
+ @MediumTest
public void testSetPauseImage() throws Exception {
final Uri testUri = Uri.fromParts("file", "test.jpg", null);
mVideoCall.setPauseImage(testUri);
@@ -343,6 +354,7 @@
* {@link VideoProvider#onRequestConnectionDataUsage()}, and
* {@link VideoCall.Callback#onCallDataUsageChanged(long)} APIs.
*/
+ @MediumTest
public void testRequestDataUsage() throws Exception {
// Wait until the callback has been received before performing verification.
doAnswer(mVerification).when(mVideoCallCallback)
@@ -360,6 +372,7 @@
* Tests the {@link VideoProvider#receiveSessionModifyRequest(VideoProfile)},
* {@link VideoCall.Callback#onSessionModifyRequestReceived(VideoProfile)} APIs.
*/
+ @MediumTest
public void testReceiveSessionModifyRequest() throws Exception {
// Wait until the callback has been received before performing verification.
doAnswer(mVerification).when(mVideoCallCallback)
@@ -382,6 +395,7 @@
* Tests the {@link VideoProvider#handleCallSessionEvent(int)}, and
* {@link VideoCall.Callback#onCallSessionEvent(int)} APIs.
*/
+ @MediumTest
public void testSessionEvent() throws Exception {
// Wait until the callback has been received before performing verification.
doAnswer(mVerification).when(mVideoCallCallback)
@@ -400,6 +414,7 @@
* Tests the {@link VideoProvider#changePeerDimensions(int, int)} and
* {@link VideoCall.Callback#onPeerDimensionsChanged(int, int)} APIs.
*/
+ @MediumTest
public void testPeerDimensionChange() throws Exception {
// Wait until the callback has been received before performing verification.
doAnswer(mVerification).when(mVideoCallCallback)
@@ -419,6 +434,7 @@
* Tests the {@link VideoProvider#changeVideoQuality(int)} and
* {@link VideoCall.Callback#onVideoQualityChanged(int)} APIs.
*/
+ @MediumTest
public void testVideoQualityChange() throws Exception {
// Wait until the callback has been received before performing verification.
doAnswer(mVerification).when(mVideoCallCallback)