Fix test failure of CallLogManagerTest#testLogCorrectPhoneNumber am: f37635b38f am: b3df3b8c29
Original change: https://android-review.googlesource.com/c/platform/packages/services/Telecomm/+/3420401
Change-Id: I8d66d0bf18486a8ab09885e11b2c5dbb0ae594cd
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/Android.bp b/Android.bp
index 0d1c81d..65e4402 100644
--- a/Android.bp
+++ b/Android.bp
@@ -31,6 +31,7 @@
"androidx.annotation_annotation",
"androidx.core_core",
"telecom_flags_core_java_lib",
+ "modules-utils-handlerexecutor",
],
resource_dirs: ["res"],
proto: {
diff --git a/flags/Android.bp b/flags/Android.bp
index 501eba4..54b1443 100644
--- a/flags/Android.bp
+++ b/flags/Android.bp
@@ -44,6 +44,7 @@
"telecom_bluetoothdevicemanager_flags.aconfig",
"telecom_non_critical_security_flags.aconfig",
"telecom_headless_system_user_mode.aconfig",
+ "telecom_session_flags.aconfig",
"telecom_metrics_flags.aconfig",
],
}
diff --git a/flags/telecom_anomaly_report_flags.aconfig b/flags/telecom_anomaly_report_flags.aconfig
index b060ed0..5d42b86 100644
--- a/flags/telecom_anomaly_report_flags.aconfig
+++ b/flags/telecom_anomaly_report_flags.aconfig
@@ -16,3 +16,14 @@
description: "If a self-managed call is stuck in certain states, disconnect it"
bug: "360298368"
}
+
+# OWNER=tgunn TARGET=25Q2
+flag {
+ name: "dont_timeout_destroyed_calls"
+ namespace: "telecom"
+ description: "When create connection timeout is hit, if call is already destroyed, skip anomaly"
+ bug: "381684580"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/flags/telecom_api_flags.aconfig b/flags/telecom_api_flags.aconfig
index 75efdfa..2dfd878 100644
--- a/flags/telecom_api_flags.aconfig
+++ b/flags/telecom_api_flags.aconfig
@@ -73,3 +73,12 @@
description: "Formalizes the getLastKnownCellIdentity API that Telecom reliees on as a system api"
bug: "327454165"
}
+
+# OWNER=grantmenke TARGET=25Q2
+flag {
+ name: "allow_system_apps_resolve_voip_calls"
+ is_exported: true
+ namespace: "telecom"
+ description: "Allow system apps such as accessibility to accept and end VOIP calls."
+ bug: "353579043"
+}
diff --git a/flags/telecom_bluetoothdevicemanager_flags.aconfig b/flags/telecom_bluetoothdevicemanager_flags.aconfig
index 4c91491..1c8bd0c 100644
--- a/flags/telecom_bluetoothdevicemanager_flags.aconfig
+++ b/flags/telecom_bluetoothdevicemanager_flags.aconfig
@@ -8,3 +8,23 @@
description: "Fix for Log.wtf in the BinderProxy"
bug: "333417369"
}
+# OWNER=huiwang TARGET=25Q1
+flag {
+ name: "keep_bluetooth_devices_cache_updated"
+ namespace: "telecom"
+ description: "Fix the devices cache issue of BluetoothDeviceManager"
+ bug: "380320985"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+# OWNER=grantmenke TARGET=25Q2
+flag {
+ name: "skip_baseline_switch_when_route_not_bluetooth"
+ namespace: "telecom"
+ description: "Only switch back to baseline if the call audio is currently routed to bluetooth"
+ bug: "333417369"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
\ No newline at end of file
diff --git a/flags/telecom_callaudioroutestatemachine_flags.aconfig b/flags/telecom_callaudioroutestatemachine_flags.aconfig
index 33bccba..e86db31 100644
--- a/flags/telecom_callaudioroutestatemachine_flags.aconfig
+++ b/flags/telecom_callaudioroutestatemachine_flags.aconfig
@@ -17,6 +17,14 @@
bug: "306395598"
}
+# OWNER=pmadapurmath TARGET=25Q1
+flag {
+ name: "resolve_active_bt_routing_and_bt_timing_issue"
+ namespace: "telecom"
+ description: "Resolve the active BT device routing and flaky timing issues noted in BT routing."
+ bug: "372029371"
+}
+
# OWNER=tgunn TARGET=24Q3
flag {
name: "ensure_audio_mode_updates_on_foreground_call_change"
@@ -81,6 +89,17 @@
bug: "315865533"
}
+# OWNER=tgunn TARGET=24Q3
+flag {
+ name: "dont_use_communication_device_tracker"
+ namespace: "telecom"
+ description: "Do not use the communication device tracker with useRefactoredAudioRouteSwitching."
+ bug: "346472575"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
# OWNER=pmadapurmath TARGET=24Q3
flag {
name: "resolve_switching_bt_devices_computation"
@@ -99,3 +118,36 @@
purpose: PURPOSE_BUGFIX
}
}
+
+# OWNER=pmadapurmath TARGET=25Q1
+flag {
+ name: "new_audio_path_speaker_broadcast_and_unfocused_routing"
+ namespace: "telecom"
+ description: "Replace the speaker broadcasts with the communication device changed listener and resolve baseline routing issues when a call ends."
+ bug: "353419513"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+# OWNER=pmadapurmath TARGET=25Q2
+flag {
+ name: "fix_user_request_baseline_route_video_call"
+ namespace: "telecom"
+ description: "Ensure that audio is routed out of speaker in a video call when we receive USER_SWITCH_BASELINE_ROUTE."
+ bug: "374037591"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+# OWNER=tgunn TARGET=25Q2
+flag {
+ name: "only_clear_communication_device_on_inactive"
+ namespace: "telecom"
+ description: "Only clear the communication device when transitioning to an inactive route."
+ bug: "376781369"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/flags/telecom_headless_system_user_mode.aconfig b/flags/telecom_headless_system_user_mode.aconfig
index f798733..4135794 100644
--- a/flags/telecom_headless_system_user_mode.aconfig
+++ b/flags/telecom_headless_system_user_mode.aconfig
@@ -11,4 +11,28 @@
metadata {
purpose: PURPOSE_BUGFIX
}
+}
+
+# OWNER=grantmenke TARGET=25Q1
+flag {
+ name: "telecom_main_user_in_block_check"
+ is_exported: true
+ namespace: "telecom"
+ description: "Support HSUM mode by using the main user when checking if a number is blocked."
+ bug: "369062239"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+# OWNER=grantmenke TARGET=25Q2
+flag {
+ name: "telecom_app_label_proxy_hsum_aware"
+ is_exported: true
+ namespace: "telecom"
+ description: "Support HSUM mode by ensuring AppLableProxy is multiuser aware."
+ bug: "321817633"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
}
\ No newline at end of file
diff --git a/flags/telecom_non_critical_security_flags.aconfig b/flags/telecom_non_critical_security_flags.aconfig
index 37929a8..e492073 100644
--- a/flags/telecom_non_critical_security_flags.aconfig
+++ b/flags/telecom_non_critical_security_flags.aconfig
@@ -7,4 +7,15 @@
namespace: "telecom"
description: "When set, Telecom will unregister accounts if the service is not resolvable"
bug: "281061708"
+}
+
+# OWNER=tgunn TARGET=25Q2
+flag {
+ name: "enforce_transactional_exclusivity"
+ namespace: "telecom"
+ description: "When set, ensure that transactional accounts cannot also be call capable"
+ bug: "376936125"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
}
\ No newline at end of file
diff --git a/flags/telecom_resolve_hidden_dependencies.aconfig b/flags/telecom_resolve_hidden_dependencies.aconfig
index a120b85..e5bb1fb 100644
--- a/flags/telecom_resolve_hidden_dependencies.aconfig
+++ b/flags/telecom_resolve_hidden_dependencies.aconfig
@@ -16,4 +16,5 @@
description: "Fixed read only flag used for setting up BlockedNumbersManager to be retrieved via context"
bug: "325049252"
is_fixed_read_only: true
+ is_exported: true
}
diff --git a/flags/telecom_ringer_flag_declarations.aconfig b/flags/telecom_ringer_flag_declarations.aconfig
index 6517e0f..f954b09 100644
--- a/flags/telecom_ringer_flag_declarations.aconfig
+++ b/flags/telecom_ringer_flag_declarations.aconfig
@@ -15,4 +15,16 @@
namespace: "telecom"
description: "Gates whether to ensure that when a user is in their car, they are able to hear ringing for an incoming call."
bug: "348708398"
+}
+
+
+# OWNER=tjstuart TARGET=25Q1
+flag {
+ name: "get_ringer_mode_anom_report"
+ namespace: "telecom"
+ description: "getRingerMode & getRingerModeInternal should return the same val when dnd is off"
+ bug: "307389562"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
}
\ No newline at end of file
diff --git a/flags/telecom_session_flags.aconfig b/flags/telecom_session_flags.aconfig
new file mode 100644
index 0000000..5b8075c
--- /dev/null
+++ b/flags/telecom_session_flags.aconfig
@@ -0,0 +1,13 @@
+package: "com.android.server.telecom.flags"
+container: "system"
+
+# OWNER=breadley TARGET=25Q1
+flag {
+ name: "end_session_improvements"
+ namespace: "telecom"
+ description: "Ensure that ending a session doesnt cause a stack overflow"
+ bug: "370349160"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
\ No newline at end of file
diff --git a/proto/pulled_atoms.proto b/proto/pulled_atoms.proto
index 7360b6a..6c9af46 100644
--- a/proto/pulled_atoms.proto
+++ b/proto/pulled_atoms.proto
@@ -101,13 +101,13 @@
* From frameworks/proto_logging/stats/atoms/telecomm/telecom_extension_atom.proto
*/
message TelecomErrorStats {
- // The value should be converted to android.telecom.SubmoduleNameEnum
+ // The value should be converted to android.telecom.SubmoduleEnum
// From frameworks/proto_logging/stats/enums/telecomm/enums.proto
- optional int32 submodule_name = 1;
+ optional int32 submodule = 1;
- // The value should be converted to android.telecom.ErrorNameEnum
+ // The value should be converted to android.telecom.ErrorEnum
// From frameworks/proto_logging/stats/enums/telecomm/enums.proto
- optional int32 error_name = 2;
+ optional int32 error = 2;
// The number of times this error occurs
optional int32 count = 3;
diff --git a/src/com/android/server/telecom/AppLabelProxy.java b/src/com/android/server/telecom/AppLabelProxy.java
index 7c00f28..c4d83dd 100644
--- a/src/com/android/server/telecom/AppLabelProxy.java
+++ b/src/com/android/server/telecom/AppLabelProxy.java
@@ -16,8 +16,11 @@
package com.android.server.telecom;
+import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
+import com.android.server.telecom.flags.FeatureFlags;
+import android.os.UserHandle;
import android.telecom.Log;
/**
@@ -30,15 +33,34 @@
class Util {
/**
* Default impl of getAppLabel.
- * @param pm PackageManager instance
+ * @param context Context instance that is not necessarily associated with the correct user.
+ * @param userHandle UserHandle instance of the user that is associated with the app.
* @param packageName package name to look up.
*/
- public static CharSequence getAppLabel(PackageManager pm, String packageName) {
+ public static CharSequence getAppLabel(Context context, UserHandle userHandle,
+ String packageName, FeatureFlags featureFlags) {
try {
- ApplicationInfo info = pm.getApplicationInfo(packageName, 0);
- CharSequence result = pm.getApplicationLabel(info);
- Log.i(LOG_TAG, "package %s: name is %s", packageName, result);
- return result;
+ if (featureFlags.telecomAppLabelProxyHsumAware()){
+ Context userContext = context.createContextAsUser(userHandle, 0 /* flags */);
+ PackageManager userPackageManager = userContext.getPackageManager();
+ if (userPackageManager == null) {
+ Log.w(LOG_TAG, "Could not determine app label since PackageManager is "
+ + "null. Package name is %s", packageName);
+ return null;
+ }
+ ApplicationInfo info = userPackageManager.getApplicationInfo(packageName, 0);
+ CharSequence result = userPackageManager.getApplicationLabel(info);
+ Log.i(LOG_TAG, "package %s: name is %s for user = %s", packageName, result,
+ userHandle.toString());
+ return result;
+ } else {
+ // Legacy code path:
+ PackageManager pm = context.getPackageManager();
+ ApplicationInfo info = pm.getApplicationInfo(packageName, 0);
+ CharSequence result = pm.getApplicationLabel(info);
+ Log.i(LOG_TAG, "package %s: name is %s", packageName, result);
+ return result;
+ }
} catch (PackageManager.NameNotFoundException nnfe) {
Log.w(LOG_TAG, "Could not determine app label. Package name is %s", packageName);
}
@@ -47,5 +69,5 @@
}
}
- CharSequence getAppLabel(String packageName);
+ CharSequence getAppLabel(String packageName, UserHandle userHandle);
}
diff --git a/src/com/android/server/telecom/AudioRoute.java b/src/com/android/server/telecom/AudioRoute.java
index d469a43..aaded77 100644
--- a/src/com/android/server/telecom/AudioRoute.java
+++ b/src/com/android/server/telecom/AudioRoute.java
@@ -24,6 +24,7 @@
import android.annotation.IntDef;
import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHeadset;
import android.bluetooth.BluetoothStatusCodes;
import android.media.AudioDeviceInfo;
import android.media.AudioManager;
@@ -300,7 +301,8 @@
pendingAudioRoute.setCommunicationDeviceType(mAudioRouteType);
}
Log.i(this, "onDestRouteAsPendingRoute: route=%s, "
- + "AudioManager#setCommunicationDevice()=%b", this, result);
+ + "AudioManager#setCommunicationDevice(%s)=%b", this,
+ audioDeviceTypeToString(mInfo.getType()), result);
break;
}
}
@@ -314,19 +316,26 @@
}
}
- // Takes care of cleaning up original audio route (i.e. clearCommunicationDevice,
- // sending SPEAKER_OFF, or disconnecting SCO).
- void onOrigRouteAsPendingRoute(boolean active, PendingAudioRoute pendingAudioRoute,
+ /**
+ * Takes care of cleaning up original audio route (i.e. clearCommunicationDevice,
+ * sending SPEAKER_OFF, or disconnecting SCO).
+ * @param wasActive Was the origin route active or not.
+ * @param pendingAudioRoute The pending audio route change we're performing.
+ * @param audioManager Good 'ol audio manager.
+ * @param bluetoothRouteManager The BT route manager.
+ */
+ void onOrigRouteAsPendingRoute(boolean wasActive, PendingAudioRoute pendingAudioRoute,
AudioManager audioManager, BluetoothRouteManager bluetoothRouteManager) {
- Log.i(this, "onOrigRouteAsPendingRoute: active (%b), type (%d)", active, mAudioRouteType);
- if (active) {
- if (mAudioRouteType == TYPE_SPEAKER) {
- pendingAudioRoute.addMessage(SPEAKER_OFF, null);
- }
+ Log.i(this, "onOrigRouteAsPendingRoute: wasActive (%b), type (%s), pending(%s)", wasActive,
+ DEVICE_TYPE_STRINGS.get(mAudioRouteType), pendingAudioRoute);
+ if (wasActive) {
int result = clearCommunicationDevice(pendingAudioRoute, bluetoothRouteManager,
audioManager);
- // Only send BT_AUDIO_DISCONNECTED for SCO if disconnect was successful.
- if (mAudioRouteType == TYPE_BLUETOOTH_SCO && result == BluetoothStatusCodes.SUCCESS) {
+ if (mAudioRouteType == TYPE_SPEAKER) {
+ pendingAudioRoute.addMessage(SPEAKER_OFF, null);
+ } else if (mAudioRouteType == TYPE_BLUETOOTH_SCO
+ && result == BluetoothStatusCodes.SUCCESS) {
+ // Only send BT_AUDIO_DISCONNECTED for SCO if disconnect was successful.
pendingAudioRoute.addMessage(BT_AUDIO_DISCONNECTED, mBluetoothAddress);
}
}
@@ -388,6 +397,20 @@
return success;
}
+ /**
+ * Clears the communication device; this takes into account the fact that SCO devices require
+ * us to call {@link BluetoothHeadset#disconnectAudio()} rather than
+ * {@link AudioManager#clearCommunicationDevice()}.
+ * As a general rule, if we are transitioning from an active route to another active route, we
+ * do NOT need to call {@link AudioManager#clearCommunicationDevice()}, but if the device is a
+ * legacy SCO device we WILL need to call {@link BluetoothHeadset#disconnectAudio()}. We rely
+ * on the {@link PendingAudioRoute#isActive()} indicator to tell us if the destination route
+ * is going to be active or not.
+ * @param pendingAudioRoute The pending audio route transition we're implementing.
+ * @param bluetoothRouteManager The BT route manager.
+ * @param audioManager The audio manager.
+ * @return -1 if nothing was done, or the result code from the BT SCO disconnect.
+ */
int clearCommunicationDevice(PendingAudioRoute pendingAudioRoute,
BluetoothRouteManager bluetoothRouteManager, AudioManager audioManager) {
// Try to see if there's a previously set device for communication that should be cleared.
@@ -401,14 +424,59 @@
Log.i(this, "clearCommunicationDevice: Disconnecting SCO device.");
result = bluetoothRouteManager.getDeviceManager().disconnectSco();
} else {
- Log.i(this, "clearCommunicationDevice: AudioManager#clearCommunicationDevice, type=%s",
- DEVICE_TYPE_STRINGS.get(pendingAudioRoute.getCommunicationDeviceType()));
- audioManager.clearCommunicationDevice();
+ // Only clear communication device if the destination route will be inactive; route to
+ // route transitions do not require clearing the communication device.
+ boolean onlyClearCommunicationDeviceOnInactive =
+ pendingAudioRoute.getFeatureFlags().onlyClearCommunicationDeviceOnInactive();
+ if (!onlyClearCommunicationDeviceOnInactive
+ || (onlyClearCommunicationDeviceOnInactive && !pendingAudioRoute.isActive())) {
+ Log.i(this,
+ "clearCommunicationDevice: AudioManager#clearCommunicationDevice, type=%s",
+ DEVICE_TYPE_STRINGS.get(pendingAudioRoute.getCommunicationDeviceType()));
+ audioManager.clearCommunicationDevice();
+ }
}
if (result == BluetoothStatusCodes.SUCCESS) {
+ if (pendingAudioRoute.getFeatureFlags().resolveActiveBtRoutingAndBtTimingIssue()) {
+ maybeClearConnectedPendingMessages(pendingAudioRoute);
+ }
pendingAudioRoute.setCommunicationDeviceType(AudioRoute.TYPE_INVALID);
}
return result;
}
+
+ private void maybeClearConnectedPendingMessages(PendingAudioRoute pendingAudioRoute) {
+ // If we're still waiting on BT_AUDIO_CONNECTED/SPEAKER_ON but have routed out of it
+ // since and disconnected the device, then remove that message so we aren't waiting for
+ // it in the message queue.
+ if (mAudioRouteType == TYPE_BLUETOOTH_SCO) {
+ Log.i(this, "clearCommunicationDevice: Clearing pending "
+ + "BT_AUDIO_CONNECTED messages.");
+ pendingAudioRoute.clearPendingMessage(
+ new Pair<>(BT_AUDIO_CONNECTED, mBluetoothAddress));
+ } else if (mAudioRouteType == TYPE_SPEAKER) {
+ Log.i(this, "clearCommunicationDevice: Clearing pending SPEAKER_ON messages.");
+ pendingAudioRoute.clearPendingMessage(new Pair<>(SPEAKER_ON, null));
+ }
+ }
+
+ /**
+ * Get a human readable (for logs) version of an an audio device type.
+ * @param type the device type
+ * @return the human readable string
+ */
+ private static String audioDeviceTypeToString(int type) {
+ return switch (type) {
+ case AudioDeviceInfo.TYPE_BUILTIN_EARPIECE -> "earpiece";
+ case AudioDeviceInfo.TYPE_BUILTIN_SPEAKER -> "speaker";
+ case AudioDeviceInfo.TYPE_BUS -> "bus(auto speaker)";
+ case AudioDeviceInfo.TYPE_BLUETOOTH_SCO -> "bt sco";
+ case AudioDeviceInfo.TYPE_BLE_HEADSET -> "bt le";
+ case AudioDeviceInfo.TYPE_HEARING_AID -> "bt hearing aid";
+ case AudioDeviceInfo.TYPE_USB_HEADSET -> "usb headset";
+ case AudioDeviceInfo.TYPE_WIRED_HEADSET -> "wired headset";
+ default -> Integer.toString(type);
+ };
+ }
}
diff --git a/src/com/android/server/telecom/CachedVideoStateChange.java b/src/com/android/server/telecom/CachedVideoStateChange.java
index cefb92b..8aa6d40 100644
--- a/src/com/android/server/telecom/CachedVideoStateChange.java
+++ b/src/com/android/server/telecom/CachedVideoStateChange.java
@@ -16,7 +16,8 @@
package com.android.server.telecom;
-import static com.android.server.telecom.voip.VideoStateTranslation.TransactionalVideoStateToString;
+import static com.android.server.telecom.callsequencing.voip.VideoStateTranslation
+ .TransactionalVideoStateToString;
import android.telecom.Log;
diff --git a/src/com/android/server/telecom/Call.java b/src/com/android/server/telecom/Call.java
index c391641..9e566e2 100644
--- a/src/com/android/server/telecom/Call.java
+++ b/src/com/android/server/telecom/Call.java
@@ -21,8 +21,10 @@
import static com.android.server.telecom.CachedCallback.TYPE_QUEUE;
import static com.android.server.telecom.CachedCallback.TYPE_STATE;
-import static com.android.server.telecom.voip.VideoStateTranslation.TransactionalVideoStateToString;
-import static com.android.server.telecom.voip.VideoStateTranslation.VideoProfileStateToTransactionalVideoState;
+import static com.android.server.telecom.callsequencing.voip.VideoStateTranslation
+ .TransactionalVideoStateToString;
+import static com.android.server.telecom.callsequencing.voip.VideoStateTranslation
+ .VideoProfileStateToTransactionalVideoState;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -78,9 +80,10 @@
import com.android.server.telecom.stats.CallFailureCause;
import com.android.server.telecom.stats.CallStateChangedAtomWriter;
import com.android.server.telecom.ui.ToastFactory;
-import com.android.server.telecom.voip.TransactionManager;
-import com.android.server.telecom.voip.VerifyCallStateChangeTransaction;
-import com.android.server.telecom.voip.VoipCallTransactionResult;
+import com.android.server.telecom.callsequencing.CallTransaction;
+import com.android.server.telecom.callsequencing.TransactionManager;
+import com.android.server.telecom.callsequencing.VerifyCallStateChangeTransaction;
+import com.android.server.telecom.callsequencing.CallTransactionResult;
import java.io.IOException;
import java.text.SimpleDateFormat;
@@ -2830,20 +2833,20 @@
}
@VisibleForTesting
- public void disconnect() {
- disconnect(0);
+ public CompletableFuture<Boolean> disconnect() {
+ return disconnect(0);
}
- public void disconnect(String reason) {
- disconnect(0, reason);
+ public CompletableFuture<Boolean> disconnect(String reason) {
+ return disconnect(0, reason);
}
/**
* Attempts to disconnect the call through the connection service.
*/
@VisibleForTesting
- public void disconnect(long disconnectionTimeout) {
- disconnect(disconnectionTimeout, "internal" /** reason */);
+ public CompletableFuture<Boolean> disconnect(long disconnectionTimeout) {
+ return disconnect(disconnectionTimeout, "internal" /* reason */);
}
/**
@@ -2853,16 +2856,24 @@
* as TelecomManager.
*/
@VisibleForTesting
- public void disconnect(long disconnectionTimeout, String reason) {
+ public CompletableFuture<Boolean> disconnect(long disconnectionTimeout,
+ String reason) {
Log.addEvent(this, LogUtils.Events.REQUEST_DISCONNECT, reason);
// Track that the call is now locally disconnecting.
setLocallyDisconnecting(true);
maybeSetCallAsDisconnectingChild();
+ CompletableFuture<Boolean> disconnectFutureHandler =
+ CompletableFuture.completedFuture(false);
if (mState == CallState.NEW || mState == CallState.SELECT_PHONE_ACCOUNT ||
mState == CallState.CONNECTING) {
Log.i(this, "disconnect: Aborting call %s", getId());
+ if (mFlags.enableCallSequencing()) {
+ disconnectFutureHandler = awaitCallStateChangeAndMaybeDisconnectCall(
+ false /* shouldDisconnectUponTimeout */, "disconnect",
+ CallState.DISCONNECTED, CallState.ABORTED);
+ }
abort(disconnectionTimeout);
} else if (mState != CallState.ABORTED && mState != CallState.DISCONNECTED) {
if (mState == CallState.AUDIO_PROCESSING && !hasGoneActiveBefore()) {
@@ -2874,7 +2885,8 @@
setOverrideDisconnectCauseCode(new DisconnectCause(DisconnectCause.MISSED));
}
if (mTransactionalService != null) {
- mTransactionalService.onDisconnect(this, getDisconnectCause());
+ disconnectFutureHandler = mTransactionalService.onDisconnect(this,
+ getDisconnectCause());
Log.i(this, "Send Disconnect to transactional service for call");
} else if (mConnectionService == null) {
Log.e(this, new Exception(), "disconnect() request on a call without a"
@@ -2885,9 +2897,15 @@
// confirms that the call was actually disconnected. Only then is the
// association between call and connection service severed, see
// {@link CallsManager#markCallAsDisconnected}.
+ if (mFlags.enableCallSequencing()) {
+ disconnectFutureHandler = awaitCallStateChangeAndMaybeDisconnectCall(
+ false /* shouldDisconnectUponTimeout */, "disconnect",
+ CallState.DISCONNECTED);
+ }
mConnectionService.disconnect(this);
}
}
+ return disconnectFutureHandler;
}
void abort(long disconnectionTimeout) {
@@ -2930,29 +2948,35 @@
* @param videoState The video state in which to answer the call.
*/
@VisibleForTesting
- public void answer(int videoState) {
+ public CompletableFuture<Boolean> answer(int videoState) {
+ CompletableFuture<Boolean> answerCallFuture = CompletableFuture.completedFuture(false);
// 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")) {
+ Log.addEvent(this, LogUtils.Events.REQUEST_ACCEPT);
if (!isVideoCallingSupportedByPhoneAccount() && 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 it will work. Instead, we wait until confirmation from the connection service
// that the call is in a non-STATE_RINGING state before changing the UI. See
// {@link ConnectionServiceAdapter#setActive} and other set* methods.
if (mConnectionService != null) {
+ if (mFlags.enableCallSequencing()) {
+ answerCallFuture = awaitCallStateChangeAndMaybeDisconnectCall(
+ false /* shouldDisconnectUponTimeout */, "answer", CallState.ACTIVE);
+ }
mConnectionService.answer(this, videoState);
} else if (mTransactionalService != null) {
- mTransactionalService.onAnswer(this, videoState);
+ return mTransactionalService.onAnswer(this, videoState);
} else {
Log.e(this, new NullPointerException(),
"answer call failed due to null CS callId=%s", getId());
}
- Log.addEvent(this, LogUtils.Events.REQUEST_ACCEPT);
}
+ return answerCallFuture;
}
/**
@@ -3032,74 +3056,101 @@
* if the reject is initiated from an API such as TelecomManager.
*/
@VisibleForTesting
- public void reject(boolean rejectWithMessage, String textMessage, String reason) {
+ public CompletableFuture<Boolean> reject(boolean rejectWithMessage,
+ String textMessage, String reason) {
+ CompletableFuture<Boolean> rejectFutureHandler = CompletableFuture.completedFuture(false);
if (mState == CallState.SIMULATED_RINGING) {
+ Log.addEvent(this, LogUtils.Events.REQUEST_REJECT, reason);
// This handles the case where the user manually rejects a call that's in simulated
// ringing. Since the call is already active on the connectionservice side, we want to
// hangup, not reject.
setOverrideDisconnectCauseCode(new DisconnectCause(DisconnectCause.REJECTED));
if (mTransactionalService != null) {
- mTransactionalService.onDisconnect(this,
+ return mTransactionalService.onDisconnect(this,
new DisconnectCause(DisconnectCause.REJECTED));
} else if (mConnectionService != null) {
+ if (mFlags.enableCallSequencing()) {
+ rejectFutureHandler = awaitCallStateChangeAndMaybeDisconnectCall(
+ false /* shouldDisconnectUponTimeout */, "reject",
+ CallState.DISCONNECTED);
+ }
mConnectionService.disconnect(this);
+ return rejectFutureHandler;
} else {
Log.e(this, new NullPointerException(),
"reject call failed due to null CS callId=%s", getId());
}
- Log.addEvent(this, LogUtils.Events.REQUEST_REJECT, reason);
} else if (isRinging("reject") || isAnswered("reject")) {
+ Log.addEvent(this, LogUtils.Events.REQUEST_REJECT, reason);
// Ensure video state history tracks video state at time of rejection.
mVideoStateHistory |= mVideoState;
if (mTransactionalService != null) {
- mTransactionalService.onDisconnect(this,
+ return mTransactionalService.onDisconnect(this,
new DisconnectCause(DisconnectCause.REJECTED));
} else if (mConnectionService != null) {
+ if (mFlags.enableCallSequencing()) {
+ rejectFutureHandler = awaitCallStateChangeAndMaybeDisconnectCall(
+ false /* shouldDisconnectUponTimeout */, "reject",
+ CallState.DISCONNECTED);
+ }
mConnectionService.reject(this, rejectWithMessage, textMessage);
+ return rejectFutureHandler;
} else {
Log.e(this, new NullPointerException(),
"reject call failed due to null CS callId=%s", getId());
}
- Log.addEvent(this, LogUtils.Events.REQUEST_REJECT, reason);
}
+ return rejectFutureHandler;
}
/**
* Reject this Telecom call with the user-indicated reason.
* @param rejectReason The user-indicated reason fore rejecting the call.
*/
- public void reject(@android.telecom.Call.RejectReason int rejectReason) {
+ public CompletableFuture<Boolean> reject(@android.telecom.Call.RejectReason int rejectReason) {
+ CompletableFuture<Boolean> rejectFutureHandler = CompletableFuture.completedFuture(false);
if (mState == CallState.SIMULATED_RINGING) {
+ Log.addEvent(this, LogUtils.Events.REQUEST_REJECT);
// This handles the case where the user manually rejects a call that's in simulated
// ringing. Since the call is already active on the connectionservice side, we want to
// hangup, not reject.
// Since its simulated reason we can't pass along the reject reason.
setOverrideDisconnectCauseCode(new DisconnectCause(DisconnectCause.REJECTED));
if (mTransactionalService != null) {
- mTransactionalService.onDisconnect(this,
+ return mTransactionalService.onDisconnect(this,
new DisconnectCause(DisconnectCause.REJECTED));
} else if (mConnectionService != null) {
+ if (mFlags.enableCallSequencing()) {
+ rejectFutureHandler = awaitCallStateChangeAndMaybeDisconnectCall(
+ false /* shouldDisconnectUponTimeout */, "reject",
+ CallState.DISCONNECTED);
+ }
mConnectionService.disconnect(this);
} else {
Log.e(this, new NullPointerException(),
"reject call failed due to null CS callId=%s", getId());
}
- Log.addEvent(this, LogUtils.Events.REQUEST_REJECT);
} else if (isRinging("reject") || isAnswered("reject")) {
+ Log.addEvent(this, LogUtils.Events.REQUEST_REJECT, rejectReason);
// Ensure video state history tracks video state at time of rejection.
mVideoStateHistory |= mVideoState;
if (mTransactionalService != null) {
- mTransactionalService.onDisconnect(this,
+ return mTransactionalService.onDisconnect(this,
new DisconnectCause(DisconnectCause.REJECTED));
} else if (mConnectionService != null) {
+ if (mFlags.enableCallSequencing()) {
+ rejectFutureHandler = awaitCallStateChangeAndMaybeDisconnectCall(
+ false /* shouldDisconnectUponTimeout */, "reject",
+ CallState.DISCONNECTED);
+ }
mConnectionService.rejectWithReason(this, rejectReason);
} else {
Log.e(this, new NullPointerException(),
"reject call failed due to null CS callId=%s", getId());
}
- Log.addEvent(this, LogUtils.Events.REQUEST_REJECT, rejectReason);
}
+ return rejectFutureHandler;
}
/**
@@ -3149,43 +3200,48 @@
* Puts the call on hold if it is currently active.
*/
@VisibleForTesting
- public void hold() {
- hold(null /* reason */);
+ public CompletableFuture<Boolean> hold() {
+ return hold(null /* reason */);
}
/**
* This method requests the ConnectionService or TransactionalService hosting the call to put
* the call on hold
*/
- public void hold(String reason) {
+ public CompletableFuture<Boolean> hold(String reason) {
+ CompletableFuture<Boolean> holdFutureHandler = CompletableFuture.completedFuture(false);
if (mState == CallState.ACTIVE) {
+ Log.addEvent(this, LogUtils.Events.REQUEST_HOLD, reason);
if (mTransactionalService != null) {
- mTransactionalService.onSetInactive(this);
+ return mTransactionalService.onSetInactive(this);
} else if (mConnectionService != null) {
- if (mFlags.transactionalCsVerifier()) {
- awaitCallStateChangeAndMaybeDisconnectCall(CallState.ON_HOLD, isSelfManaged(),
- "hold");
+ if (mFlags.transactionalCsVerifier() || mFlags.enableCallSequencing()) {
+ holdFutureHandler = awaitCallStateChangeAndMaybeDisconnectCall(isSelfManaged(),
+ "hold", CallState.ON_HOLD);
}
mConnectionService.hold(this);
+ return holdFutureHandler;
} else {
Log.e(this, new NullPointerException(),
"hold call failed due to null CS callId=%s", getId());
}
- Log.addEvent(this, LogUtils.Events.REQUEST_HOLD, reason);
}
+ return holdFutureHandler;
}
/**
* helper that can be used for any callback that requests a call state change and wants to
* verify the change
*/
- public void awaitCallStateChangeAndMaybeDisconnectCall(int targetCallState,
- boolean shouldDisconnectUponTimeout, String callingMethod) {
+ public CompletableFuture<Boolean> awaitCallStateChangeAndMaybeDisconnectCall(
+ boolean shouldDisconnectUponTimeout, String callingMethod, int... targetCallStates) {
TransactionManager tm = TransactionManager.getInstance();
- tm.addTransaction(new VerifyCallStateChangeTransaction(mCallsManager.getLock(),
- this, targetCallState), new OutcomeReceiver<>() {
+ CallTransaction callTransaction = new VerifyCallStateChangeTransaction(
+ mCallsManager.getLock(), this, targetCallStates);
+ return tm.addTransaction(callTransaction,
+ new OutcomeReceiver<>() {
@Override
- public void onResult(VoipCallTransactionResult result) {
+ public void onResult(CallTransactionResult result) {
Log.i(this, "awaitCallStateChangeAndMaybeDisconnectCall: %s: onResult:"
+ " due to CallException=[%s]", callingMethod, result);
}
@@ -3208,22 +3264,29 @@
* Releases the call from hold if it is currently active.
*/
@VisibleForTesting
- public void unhold() {
- unhold(null /* reason */);
+ public CompletableFuture<Boolean> unhold() {
+ return unhold(null /* reason */);
}
- public void unhold(String reason) {
+ public CompletableFuture<Boolean> unhold(String reason) {
+ CompletableFuture<Boolean> unholdFutureHandler = CompletableFuture.completedFuture(false);
if (mState == CallState.ON_HOLD) {
+ Log.addEvent(this, LogUtils.Events.REQUEST_UNHOLD, reason);
if (mTransactionalService != null){
- mTransactionalService.onSetActive(this);
+ return mTransactionalService.onSetActive(this);
} else if (mConnectionService != null){
+ if (mFlags.enableCallSequencing()) {
+ unholdFutureHandler = awaitCallStateChangeAndMaybeDisconnectCall(
+ false /* shouldDisconnectUponTimeout */, "unhold", CallState.ACTIVE);
+ }
mConnectionService.unhold(this);
+ return unholdFutureHandler;
} else {
Log.e(this, new NullPointerException(),
"unhold call failed due to null CS callId=%s", getId());
}
- Log.addEvent(this, LogUtils.Events.REQUEST_UNHOLD, reason);
}
+ return unholdFutureHandler;
}
/** Checks if this is a live call or not. */
diff --git a/src/com/android/server/telecom/CallAnomalyWatchdog.java b/src/com/android/server/telecom/CallAnomalyWatchdog.java
index 384110c..c331b29 100644
--- a/src/com/android/server/telecom/CallAnomalyWatchdog.java
+++ b/src/com/android/server/telecom/CallAnomalyWatchdog.java
@@ -153,7 +153,8 @@
public static final UUID WATCHDOG_DISCONNECTED_STUCK_VOIP_CALL_UUID =
UUID.fromString("3fbecd12-059d-4fd3-87b7-6c3079891c23");
public static final String WATCHDOG_DISCONNECTED_STUCK_VOIP_CALL_MSG =
- "Telecom CallAnomalyWatchdog caught stuck VoIP call in a starting state";
+ "A VoIP call was flagged due to exceeding a one-minute threshold in the DIALING or "
+ + "RINGING state";
@VisibleForTesting
diff --git a/src/com/android/server/telecom/CallAudioCommunicationDeviceTracker.java b/src/com/android/server/telecom/CallAudioCommunicationDeviceTracker.java
index 8d5f9fd..84f7d8f 100644
--- a/src/com/android/server/telecom/CallAudioCommunicationDeviceTracker.java
+++ b/src/com/android/server/telecom/CallAudioCommunicationDeviceTracker.java
@@ -251,6 +251,7 @@
}
// Clear device and reset locally saved device type.
+ Log.i(this, "clearCommunicationDevice: AudioManager#clearCommunicationDevice()");
mAudioManager.clearCommunicationDevice();
mAudioDeviceType = sAUDIO_DEVICE_TYPE_INVALID;
diff --git a/src/com/android/server/telecom/CallAudioManager.java b/src/com/android/server/telecom/CallAudioManager.java
index 8c2f631..d156c0c 100644
--- a/src/com/android/server/telecom/CallAudioManager.java
+++ b/src/com/android/server/telecom/CallAudioManager.java
@@ -1101,6 +1101,9 @@
call.getId());
disconnectedToneFuture.complete(null);
}
+ // Make sure we schedule the unbinding of the BT ICS once the disconnected tone future has
+ // been completed.
+ mCallsManager.getInCallController().maybeScheduleBtUnbind(call);
}
@VisibleForTesting
diff --git a/src/com/android/server/telecom/CallAudioModeStateMachine.java b/src/com/android/server/telecom/CallAudioModeStateMachine.java
index fb196f2..d1fd564 100644
--- a/src/com/android/server/telecom/CallAudioModeStateMachine.java
+++ b/src/com/android/server/telecom/CallAudioModeStateMachine.java
@@ -17,7 +17,6 @@
package com.android.server.telecom;
import android.media.AudioAttributes;
-import android.media.AudioFocusRequest;
import android.media.AudioManager;
import android.os.Looper;
import android.os.Message;
@@ -47,22 +46,6 @@
}
}
- private static final AudioAttributes RING_AUDIO_ATTRIBUTES = new AudioAttributes.Builder()
- .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
- .setLegacyStreamType(AudioManager.STREAM_RING)
- .build();
- public static final AudioFocusRequest RING_AUDIO_FOCUS_REQUEST = new AudioFocusRequest
- .Builder(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT)
- .setAudioAttributes(RING_AUDIO_ATTRIBUTES).build();
-
- private static final AudioAttributes CALL_AUDIO_ATTRIBUTES = new AudioAttributes.Builder()
- .setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION)
- .setLegacyStreamType(AudioManager.STREAM_VOICE_CALL)
- .build();
- public static final AudioFocusRequest CALL_AUDIO_FOCUS_REQUEST = new AudioFocusRequest
- .Builder(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT)
- .setAudioAttributes(CALL_AUDIO_ATTRIBUTES).build();
-
public static class MessageArgs {
public boolean hasActiveOrDialingCalls;
public boolean hasRingingCalls;
@@ -232,8 +215,6 @@
public static final String STREAMING_STATE_NAME = StreamingFocusState.class.getSimpleName();
public static final String COMMS_STATE_NAME = VoipCallFocusState.class.getSimpleName();
- private AudioFocusRequest mCurrentAudioFocusRequest = null;
-
private class BaseState extends State {
@Override
public boolean processMessage(Message msg) {
@@ -348,23 +329,20 @@
+ args.toString());
return HANDLED;
case AUDIO_OPERATIONS_COMPLETE:
- if (mFeatureFlags.telecomResolveHiddenDependencies()) {
- if (mCurrentAudioFocusRequest != null) {
- Log.i(this, "AudioOperationsComplete: "
- + "AudioManager#abandonAudioFocusRequest(); now unfocused");
- mAudioManager.abandonAudioFocusRequest(mCurrentAudioFocusRequest);
- mCurrentAudioFocusRequest = null;
- } else {
- Log.i(this, "AudioOperationsComplete: already unfocused");
- }
- } else {
- mAudioManager.abandonAudioFocusForCall();
- }
+ Log.i(this, "AudioOperationsComplete: "
+ + "AudioManager#abandonAudioFocusRequest(); now unfocused");
+ mAudioManager.abandonAudioFocusForCall();
// Clear requested communication device after the call ends.
if (mFeatureFlags.clearCommunicationDeviceAfterAudioOpsComplete()) {
- mCommunicationDeviceTracker.clearCommunicationDevice(
- mCommunicationDeviceTracker
- .getCurrentLocallyRequestedCommunicationDevice());
+ // Oh flags! If we're using the refactored audio route switching, we should
+ // not be using the communication device tracker; that is exclusively for
+ // the old code path.
+ if (!mFeatureFlags.dontUseCommunicationDeviceTracker()
+ || !mFeatureFlags.useRefactoredAudioRouteSwitching()) {
+ mCommunicationDeviceTracker.clearCommunicationDevice(
+ mCommunicationDeviceTracker
+ .getCurrentLocallyRequestedCommunicationDevice());
+ }
}
return HANDLED;
default:
@@ -438,14 +416,7 @@
case AUDIO_OPERATIONS_COMPLETE:
Log.i(LOG_TAG, "AudioManager#abandonAudioFocusRequest: now "
+ "AUDIO_PROCESSING");
- if (mFeatureFlags.telecomResolveHiddenDependencies()) {
- if (mCurrentAudioFocusRequest != null) {
- mAudioManager.abandonAudioFocusRequest(mCurrentAudioFocusRequest);
- mCurrentAudioFocusRequest = null;
- }
- } else {
- mAudioManager.abandonAudioFocusForCall();
- }
+ mAudioManager.abandonAudioFocusForCall();
return HANDLED;
default:
// The forced focus switch commands are handled by BaseState.
@@ -468,14 +439,10 @@
}
if (mCallAudioManager.startRinging()) {
- if (mFeatureFlags.telecomResolveHiddenDependencies()) {
- mCurrentAudioFocusRequest = RING_AUDIO_FOCUS_REQUEST;
- Log.i(this, "tryStartRinging: AudioManager#requestAudioFocus(RING)");
- mAudioManager.requestAudioFocus(RING_AUDIO_FOCUS_REQUEST);
- } else {
- mAudioManager.requestAudioFocusForCall(
- AudioManager.STREAM_RING, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
- }
+ Log.i(this, "tryStartRinging: AudioManager#requestAudioFocus(RING)");
+ mAudioManager.requestAudioFocusForCall(
+ AudioManager.STREAM_RING, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
+
// Do not set MODE_RINGTONE if we were previously in the CALL_SCREENING mode --
// this trips up the audio system.
if (mAudioManager.getMode() != AudioManager.MODE_CALL_SCREENING) {
@@ -570,14 +537,9 @@
public void enter() {
Log.i(LOG_TAG, "Audio focus entering SIM CALL state");
mLocalLog.log("Enter SIM_CALL");
- if (mFeatureFlags.telecomResolveHiddenDependencies()) {
- mCurrentAudioFocusRequest = CALL_AUDIO_FOCUS_REQUEST;
- Log.i(this, "enter: AudioManager#requestAudioFocus(CALL)");
- mAudioManager.requestAudioFocus(CALL_AUDIO_FOCUS_REQUEST);
- } else {
- mAudioManager.requestAudioFocusForCall(AudioManager.STREAM_VOICE_CALL,
- AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
- }
+ Log.i(this, "enter: AudioManager#requestAudioFocus(CALL)");
+ mAudioManager.requestAudioFocusForCall(AudioManager.STREAM_VOICE_CALL,
+ AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
Log.i(this, "enter: AudioManager#setMode(MODE_IN_CALL)");
mAudioManager.setMode(AudioManager.MODE_IN_CALL);
mLocalLog.log("Mode MODE_IN_CALL");
@@ -660,14 +622,9 @@
public void enter() {
Log.i(LOG_TAG, "Audio focus entering VOIP CALL state");
mLocalLog.log("Enter VOIP_CALL");
- if (mFeatureFlags.telecomResolveHiddenDependencies()) {
- mCurrentAudioFocusRequest = CALL_AUDIO_FOCUS_REQUEST;
- Log.i(this, "enter: AudioManager#requestAudioFocus(CALL)");
- mAudioManager.requestAudioFocus(CALL_AUDIO_FOCUS_REQUEST);
- } else {
- mAudioManager.requestAudioFocusForCall(AudioManager.STREAM_VOICE_CALL,
- AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
- }
+ Log.i(this, "enter: AudioManager#requestAudioFocus(CALL)");
+ mAudioManager.requestAudioFocusForCall(AudioManager.STREAM_VOICE_CALL,
+ AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
Log.i(this, "enter: AudioManager#setMode(MODE_IN_COMMUNICATION)");
mAudioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
mLocalLog.log("Mode MODE_IN_COMMUNICATION");
@@ -823,14 +780,9 @@
public void enter() {
Log.i(LOG_TAG, "Audio focus entering TONE/HOLDING state");
mLocalLog.log("Enter TONE/HOLDING");
- if (mFeatureFlags.telecomResolveHiddenDependencies()) {
- mCurrentAudioFocusRequest = CALL_AUDIO_FOCUS_REQUEST;
- Log.i(this, "enter: AudioManager#requestAudioFocus(CALL)");
- mAudioManager.requestAudioFocus(CALL_AUDIO_FOCUS_REQUEST);
- } else {
- mAudioManager.requestAudioFocusForCall(AudioManager.STREAM_VOICE_CALL,
- AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
- }
+ Log.i(this, "enter: AudioManager#requestAudioFocus(CALL)");
+ mAudioManager.requestAudioFocusForCall(AudioManager.STREAM_VOICE_CALL,
+ AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
Log.i(this, "enter: AudioManager#setMode(%d)", mMostRecentMode);
mAudioManager.setMode(mMostRecentMode);
mLocalLog.log("Mode " + mMostRecentMode);
diff --git a/src/com/android/server/telecom/CallAudioRouteController.java b/src/com/android/server/telecom/CallAudioRouteController.java
index bc2c0cb..495f872 100644
--- a/src/com/android/server/telecom/CallAudioRouteController.java
+++ b/src/com/android/server/telecom/CallAudioRouteController.java
@@ -17,6 +17,7 @@
package com.android.server.telecom;
import static com.android.server.telecom.AudioRoute.BT_AUDIO_ROUTE_TYPES;
+import static com.android.server.telecom.AudioRoute.DEVICE_INFO_TYPE_TO_AUDIO_ROUTE_TYPE;
import static com.android.server.telecom.AudioRoute.TYPE_INVALID;
import static com.android.server.telecom.AudioRoute.TYPE_SPEAKER;
@@ -52,9 +53,11 @@
import com.android.internal.util.IndentingPrintWriter;
import com.android.server.telecom.bluetooth.BluetoothRouteManager;
import com.android.server.telecom.flags.FeatureFlags;
+import com.android.server.telecom.metrics.ErrorStats;
import com.android.server.telecom.metrics.TelecomMetricsController;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
@@ -62,6 +65,8 @@
import java.util.Map;
import java.util.Objects;
import java.util.Set;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
public class CallAudioRouteController implements CallAudioRouteAdapter {
private static final AudioRoute DUMMY_ROUTE = new AudioRoute(TYPE_INVALID, null, null);
@@ -106,6 +111,8 @@
private PendingAudioRoute mPendingAudioRoute;
private AudioRoute.Factory mAudioRouteFactory;
private StatusBarNotifier mStatusBarNotifier;
+ private AudioManager.OnCommunicationDeviceChangedListener mCommunicationDeviceListener;
+ private ExecutorService mCommunicationDeviceChangedExecutor;
private FeatureFlags mFeatureFlags;
private int mFocusType;
private int mCallSupportedRouteMask = -1;
@@ -199,10 +206,12 @@
handlerThread.start();
// Register broadcast receivers
- IntentFilter speakerChangedFilter = new IntentFilter(
- AudioManager.ACTION_SPEAKERPHONE_STATE_CHANGED);
- speakerChangedFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
- context.registerReceiver(mSpeakerPhoneChangeReceiver, speakerChangedFilter);
+ if (!mFeatureFlags.newAudioPathSpeakerBroadcastAndUnfocusedRouting()) {
+ IntentFilter speakerChangedFilter = new IntentFilter(
+ AudioManager.ACTION_SPEAKERPHONE_STATE_CHANGED);
+ speakerChangedFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
+ context.registerReceiver(mSpeakerPhoneChangeReceiver, speakerChangedFilter);
+ }
IntentFilter micMuteChangedFilter = new IntentFilter(
AudioManager.ACTION_MICROPHONE_MUTE_CHANGED);
@@ -213,6 +222,27 @@
muteChangedFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
context.registerReceiver(mMuteChangeReceiver, muteChangedFilter);
+ // Register AudioManager#onCommunicationDeviceChangedListener listener to receive updates
+ // to communication device (via AudioManager#setCommunicationDevice). This is a replacement
+ // to using broadcasts in the hopes of improving performance.
+ mCommunicationDeviceChangedExecutor = Executors.newSingleThreadExecutor();
+ mCommunicationDeviceListener = new AudioManager.OnCommunicationDeviceChangedListener() {
+ @Override
+ public void onCommunicationDeviceChanged(AudioDeviceInfo device) {
+ @AudioRoute.AudioRouteType int audioType = device != null
+ ? DEVICE_INFO_TYPE_TO_AUDIO_ROUTE_TYPE.get(device.getType())
+ : TYPE_INVALID;
+ Log.i(this, "onCommunicationDeviceChanged: %d", audioType);
+ if (device != null && device.getType() == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER) {
+ if (mCurrentRoute.getType() != TYPE_SPEAKER) {
+ sendMessageWithSessionInfo(SPEAKER_ON);
+ }
+ } else {
+ sendMessageWithSessionInfo(SPEAKER_OFF);
+ }
+ }
+ };
+
// Create handler
mHandler = new Handler(handlerThread.getLooper()) {
@Override
@@ -277,12 +307,12 @@
break;
case SWITCH_BASELINE_ROUTE:
address = (String) ((SomeArgs) msg.obj).arg2;
- handleSwitchBaselineRoute(msg.arg1 == INCLUDE_BLUETOOTH_IN_BASELINE,
- address);
+ handleSwitchBaselineRoute(false,
+ msg.arg1 == INCLUDE_BLUETOOTH_IN_BASELINE, address);
break;
case USER_SWITCH_BASELINE_ROUTE:
- handleSwitchBaselineRoute(msg.arg1 == INCLUDE_BLUETOOTH_IN_BASELINE,
- null);
+ handleSwitchBaselineRoute(true,
+ msg.arg1 == INCLUDE_BLUETOOTH_IN_BASELINE, null);
break;
case SPEAKER_ON:
handleSpeakerOn();
@@ -313,6 +343,9 @@
case MUTE_EXTERNALLY_CHANGED:
handleMuteChanged(mAudioManager.isMicrophoneMute());
break;
+ case TOGGLE_MUTE:
+ handleMuteChanged(!mIsMute);
+ break;
case SWITCH_FOCUS:
focus = msg.arg1;
handleEndTone = (int) ((SomeArgs) msg.obj).arg2;
@@ -342,7 +375,7 @@
public void initialize() {
mAvailableRoutes = new HashSet<>();
mCallSupportedRoutes = new HashSet<>();
- mBluetoothRoutes = new LinkedHashMap<>();
+ mBluetoothRoutes = Collections.synchronizedMap(new LinkedHashMap<>());
mActiveDeviceCache = new HashMap<>();
mActiveDeviceCache.put(AudioRoute.TYPE_BLUETOOTH_SCO, null);
mActiveDeviceCache.put(AudioRoute.TYPE_BLUETOOTH_HA, null);
@@ -410,6 +443,11 @@
mIsActive = false;
mCallAudioState = new CallAudioState(mIsMute, ROUTE_MAP.get(mCurrentRoute.getType()),
supportMask, null, new HashSet<>());
+ if (mFeatureFlags.newAudioPathSpeakerBroadcastAndUnfocusedRouting()) {
+ mAudioManager.addOnCommunicationDeviceChangedListener(
+ mCommunicationDeviceChangedExecutor,
+ mCommunicationDeviceListener);
+ }
}
@Override
@@ -512,6 +550,10 @@
if (destRoute == null || (!destRoute.equals(mStreamingRoute)
&& !getCallSupportedRoutes().contains(destRoute))) {
Log.i(this, "Ignore routing to unavailable route: %s", destRoute);
+ if (mFeatureFlags.telecomMetricsSupport()) {
+ mMetricsController.getErrorStats().log(ErrorStats.SUB_CALL_AUDIO,
+ ErrorStats.ERROR_AUDIO_ROUTE_UNAVAILABLE);
+ }
return;
}
if (mIsPending) {
@@ -522,12 +564,14 @@
+ "%s(active=%b)",
mPendingAudioRoute.getDestRoute(), mIsActive, destRoute, active);
// Ensure we don't keep waiting for SPEAKER_ON if dest route gets overridden.
- if (active && mPendingAudioRoute.getDestRoute().getType() == TYPE_SPEAKER) {
+ if (!mFeatureFlags.resolveActiveBtRoutingAndBtTimingIssue() && active
+ && mPendingAudioRoute.getDestRoute().getType() == TYPE_SPEAKER) {
mPendingAudioRoute.clearPendingMessage(new Pair<>(SPEAKER_ON, null));
}
// override pending route while keep waiting for still pending messages for the
// previous pending route
- mPendingAudioRoute.setOrigRoute(mIsActive, mPendingAudioRoute.getDestRoute());
+ mPendingAudioRoute.setOrigRoute(mIsActive /* origin */,
+ mPendingAudioRoute.getDestRoute(), active /* dest */);
} else {
if (mCurrentRoute.equals(destRoute) && (mIsActive == active)) {
return;
@@ -536,10 +580,12 @@
mIsActive, destRoute, active);
// route to pending route
if (getCallSupportedRoutes().contains(mCurrentRoute)) {
- mPendingAudioRoute.setOrigRoute(mIsActive, mCurrentRoute);
+ mPendingAudioRoute.setOrigRoute(mIsActive /* origin */, mCurrentRoute,
+ active /* dest */);
} else {
// Avoid waiting for pending messages for an unavailable route
- mPendingAudioRoute.setOrigRoute(mIsActive, DUMMY_ROUTE);
+ mPendingAudioRoute.setOrigRoute(mIsActive /* origin */, DUMMY_ROUTE,
+ active /* dest */);
}
mIsPending = true;
}
@@ -558,6 +604,10 @@
wiredHeadsetRoute = mAudioRouteFactory.create(AudioRoute.TYPE_WIRED, null,
mAudioManager);
} catch (IllegalArgumentException e) {
+ if (mFeatureFlags.telecomMetricsSupport()) {
+ mMetricsController.getErrorStats().log(ErrorStats.SUB_CALL_AUDIO,
+ ErrorStats.ERROR_EXTERNAL_EXCEPTION);
+ }
Log.e(this, e, "Can't find available audio device info for route type:"
+ AudioRoute.DEVICE_TYPE_STRINGS.get(AudioRoute.TYPE_WIRED));
}
@@ -597,6 +647,10 @@
try {
dockRoute = mAudioRouteFactory.create(AudioRoute.TYPE_DOCK, null, mAudioManager);
} catch (IllegalArgumentException e) {
+ if (mFeatureFlags.telecomMetricsSupport()) {
+ mMetricsController.getErrorStats().log(ErrorStats.SUB_CALL_AUDIO,
+ ErrorStats.ERROR_EXTERNAL_EXCEPTION);
+ }
Log.e(this, e, "Can't find available audio device info for route type:"
+ AudioRoute.DEVICE_TYPE_STRINGS.get(AudioRoute.TYPE_WIRED));
}
@@ -770,10 +824,30 @@
* Message being handled: BT_ACTIVE_DEVICE_GONE
*/
private void handleBtActiveDeviceGone(@AudioRoute.AudioRouteType int type) {
- if ((mIsPending && mPendingAudioRoute.getDestRoute().getType() == type)
- || (!mIsPending && mCurrentRoute.getType() == type)) {
- // Fallback to an available route
- routeTo(mIsActive, getBaseRoute(true, null));
+ // Determine what the active device for the BT audio type was so that we can exclude this
+ // device from being used when calculating the base route.
+ String previouslyActiveDeviceAddress = mFeatureFlags
+ .resolveActiveBtRoutingAndBtTimingIssue()
+ ? mActiveDeviceCache.get(type)
+ : null;
+ // It's possible that the dest route hasn't been set yet when the controller is first
+ // initialized.
+ boolean pendingRouteNeedsUpdate = mPendingAudioRoute.getDestRoute() != null
+ && mPendingAudioRoute.getDestRoute().getType() == type;
+ boolean currentRouteNeedsUpdate = mCurrentRoute.getType() == type;
+ if (mFeatureFlags.resolveActiveBtRoutingAndBtTimingIssue()) {
+ if (pendingRouteNeedsUpdate) {
+ pendingRouteNeedsUpdate = mPendingAudioRoute.getDestRoute().getBluetoothAddress()
+ .equals(previouslyActiveDeviceAddress);
+ }
+ if (currentRouteNeedsUpdate) {
+ currentRouteNeedsUpdate = mCurrentRoute.getBluetoothAddress()
+ .equals(previouslyActiveDeviceAddress);
+ }
+ }
+ if ((mIsPending && pendingRouteNeedsUpdate) || (!mIsPending && currentRouteNeedsUpdate)) {
+ // Fallback to an available route excluding the previously active device.
+ routeTo(mIsActive, getBaseRoute(true, previouslyActiveDeviceAddress));
}
}
@@ -789,6 +863,10 @@
mCallsManager.getCurrentUserHandle().getIdentifier(),
mContext.getAttributionTag());
} catch (RemoteException e) {
+ if (mFeatureFlags.telecomMetricsSupport()) {
+ mMetricsController.getErrorStats().log(ErrorStats.SUB_CALL_AUDIO,
+ ErrorStats.ERROR_EXTERNAL_EXCEPTION);
+ }
Log.e(this, e, "Remote exception while toggling mute.");
return;
}
@@ -802,19 +880,22 @@
mFocusType = focus;
switch (focus) {
case NO_FOCUS -> {
- if (mIsActive) {
- // Notify the CallAudioModeStateMachine that audio operations are complete so
- // that we can relinquish audio focus.
- mCallAudioManager.notifyAudioOperationsComplete();
-
- // Reset mute state after call ends.
- handleMuteChanged(false);
- // Route back to inactive route.
- routeTo(false, mCurrentRoute);
- // Clear pending messages
- mPendingAudioRoute.clearPendingMessages();
- clearRingingBluetoothAddress();
- }
+ // Notify the CallAudioModeStateMachine that audio operations are complete so
+ // that we can relinquish audio focus.
+ mCallAudioManager.notifyAudioOperationsComplete();
+ // Reset mute state after call ends. This should remain unaffected if audio routing
+ // never went active.
+ handleMuteChanged(false);
+ // Ensure we reset call audio state at the end of the call (i.e. if we're on
+ // speaker, route back to earpiece). If we're on BT, remain on BT if it's still
+ // connected.
+ AudioRoute route = mFeatureFlags.resolveActiveBtRoutingAndBtTimingIssue()
+ ? calculateBaselineRoute(false, true, null)
+ : mCurrentRoute;
+ routeTo(false, route);
+ // Clear pending messages
+ mPendingAudioRoute.clearPendingMessages();
+ clearRingingBluetoothAddress();
}
case ACTIVE_FOCUS -> {
// Route to active baseline route (we may need to change audio route in the case
@@ -903,12 +984,16 @@
* @return {@link AudioRoute} of the BT device.
*/
private AudioRoute getArbitraryBluetoothDevice() {
- if (mActiveBluetoothDevice != null) {
- return getBluetoothRoute(mActiveBluetoothDevice.first, mActiveBluetoothDevice.second);
- } else if (!mBluetoothRoutes.isEmpty()) {
- return mBluetoothRoutes.keySet().stream().toList().get(mBluetoothRoutes.size() - 1);
+ synchronized (mLock) {
+ if (mActiveBluetoothDevice != null) {
+ return getBluetoothRoute(
+ mActiveBluetoothDevice.first, mActiveBluetoothDevice.second);
+ } else if (!mBluetoothRoutes.isEmpty()) {
+ return mBluetoothRoutes.keySet().stream().toList()
+ .get(mBluetoothRoutes.size() - 1);
+ }
+ return null;
}
- return null;
}
private void handleSwitchHeadset() {
@@ -929,8 +1014,42 @@
}
}
- private void handleSwitchBaselineRoute(boolean includeBluetooth, String btAddressToExclude) {
- routeTo(mIsActive, calculateBaselineRoute(includeBluetooth, btAddressToExclude));
+ private void handleSwitchBaselineRoute(boolean isExplicitUserRequest, boolean includeBluetooth,
+ String btAddressToExclude) {
+ Log.i(this, "handleSwitchBaselineRoute: includeBluetooth: %b, "
+ + "btAddressToExclude: %s", includeBluetooth, btAddressToExclude);
+ boolean areExcludedBtAndDestBtSame = btAddressToExclude != null
+ && mPendingAudioRoute.getDestRoute() != null
+ && Objects.equals(btAddressToExclude, mPendingAudioRoute.getDestRoute()
+ .getBluetoothAddress());
+ Pair<Integer, String> btDevicePendingMsg =
+ new Pair<>(BT_AUDIO_CONNECTED, btAddressToExclude);
+
+ // If SCO is once again connected or there's a pending message for BT_AUDIO_CONNECTED, then
+ // we know that the device has reconnected or is in the middle of connecting. Ignore routing
+ // out of this BT device.
+ boolean isExcludedDeviceConnectingOrConnected = areExcludedBtAndDestBtSame
+ && (mIsScoAudioConnected || mPendingAudioRoute.getPendingMessages()
+ .contains(btDevicePendingMsg));
+ // Check if the pending audio route or current route is already different from the route
+ // including the BT device that should be excluded from route selection.
+ boolean isCurrentOrDestRouteDifferent = btAddressToExclude != null
+ && ((mIsPending && !btAddressToExclude.equals(mPendingAudioRoute.getDestRoute()
+ .getBluetoothAddress())) || (!mIsPending && !btAddressToExclude.equals(
+ mCurrentRoute.getBluetoothAddress())));
+ if (mFeatureFlags.resolveActiveBtRoutingAndBtTimingIssue()) {
+ if (isExcludedDeviceConnectingOrConnected) {
+ Log.i(this, "BT device with address (%s) is currently connecting/connected. "
+ + "Ignoring route switch.", btAddressToExclude);
+ return;
+ } else if (isCurrentOrDestRouteDifferent) {
+ Log.i(this, "Current or pending audio route isn't routed to device with address "
+ + "(%s). Ignoring route switch.", btAddressToExclude);
+ return;
+ }
+ }
+ routeTo(mIsActive, calculateBaselineRoute(isExplicitUserRequest, includeBluetooth,
+ btAddressToExclude));
}
private void handleSpeakerOn() {
@@ -941,7 +1060,8 @@
mStatusBarNotifier.notifySpeakerphone(mCallsManager.hasAnyCalls());
} else {
if (mSpeakerDockRoute != null && getCallSupportedRoutes().contains(mSpeakerDockRoute)
- && mSpeakerDockRoute.getType() == AudioRoute.TYPE_SPEAKER) {
+ && mSpeakerDockRoute.getType() == AudioRoute.TYPE_SPEAKER
+ && mCurrentRoute.getType() != AudioRoute.TYPE_SPEAKER) {
routeTo(mIsActive, mSpeakerDockRoute);
// Since the route switching triggered by this message, we need to manually send it
// again so that we won't stuck in the pending route
@@ -1112,7 +1232,7 @@
}
// Get corresponding audio route
- @AudioRoute.AudioRouteType int type = AudioRoute.DEVICE_INFO_TYPE_TO_AUDIO_ROUTE_TYPE.get(
+ @AudioRoute.AudioRouteType int type = DEVICE_INFO_TYPE_TO_AUDIO_ROUTE_TYPE.get(
deviceAttr.getType());
if (BT_AUDIO_ROUTE_TYPES.contains(type)) {
return getBluetoothRoute(type, deviceAttr.getAddress());
@@ -1140,13 +1260,18 @@
return mAudioManager.getPreferredDeviceForStrategy(strategy);
}
- private AudioRoute getPreferredAudioRouteFromDefault(boolean includeBluetooth,
- String btAddressToExclude) {
- boolean skipEarpiece;
+ private AudioRoute getPreferredAudioRouteFromDefault(boolean isExplicitUserRequest,
+ boolean includeBluetooth, String btAddressToExclude) {
+ boolean skipEarpiece = false;
Call foregroundCall = mCallAudioManager.getForegroundCall();
- synchronized (mTelecomLock) {
- skipEarpiece = foregroundCall != null
- && VideoProfile.isVideo(foregroundCall.getVideoState());
+ if (!mFeatureFlags.fixUserRequestBaselineRouteVideoCall()) {
+ isExplicitUserRequest = false;
+ }
+ if (!isExplicitUserRequest) {
+ synchronized (mTelecomLock) {
+ skipEarpiece = foregroundCall != null
+ && VideoProfile.isVideo(foregroundCall.getVideoState());
+ }
}
// Route to earpiece, wired, or speaker route if there are not bluetooth routes or if there
// are only wearables available.
@@ -1246,7 +1371,7 @@
Log.i(this, "getBaseRoute: preferred audio route is %s", destRoute);
if (destRoute == null || (destRoute.getBluetoothAddress() != null && (!includeBluetooth
|| destRoute.getBluetoothAddress().equals(btAddressToExclude)))) {
- destRoute = getPreferredAudioRouteFromDefault(includeBluetooth, btAddressToExclude);
+ destRoute = getPreferredAudioRouteFromDefault(false, includeBluetooth, btAddressToExclude);
}
if (destRoute != null && !getCallSupportedRoutes().contains(destRoute)) {
destRoute = null;
@@ -1255,8 +1380,9 @@
return destRoute;
}
- private AudioRoute calculateBaselineRoute(boolean includeBluetooth, String btAddressToExclude) {
- AudioRoute destRoute = getPreferredAudioRouteFromDefault(
+ private AudioRoute calculateBaselineRoute(boolean isExplicitUserRequest,
+ boolean includeBluetooth, String btAddressToExclude) {
+ AudioRoute destRoute = getPreferredAudioRouteFromDefault(isExplicitUserRequest,
includeBluetooth, btAddressToExclude);
if (destRoute != null && !getCallSupportedRoutes().contains(destRoute)) {
destRoute = null;
@@ -1322,7 +1448,7 @@
return getMostRecentlyActiveBtRoute(btAddressToExclude);
}
- List<AudioRoute> bluetoothRoutes = mBluetoothRoutes.keySet().stream().toList();
+ List<AudioRoute> bluetoothRoutes = getAvailableBluetoothDevicesForRouting();
// Traverse the routes from the most recently active recorded devices first.
AudioRoute nonWatchDeviceRoute = null;
for (int i = bluetoothRoutes.size() - 1; i >= 0; i--) {
@@ -1334,14 +1460,20 @@
continue;
}
// Check if the most recently active device is a watch device.
- if (i == (bluetoothRoutes.size() - 1) && device.equals(mCallAudioState
- .getActiveBluetoothDevice()) && mBluetoothRouteManager.isWatch(device)) {
+ boolean isActiveDevice;
+ synchronized (mLock) {
+ isActiveDevice = mActiveBluetoothDevice != null
+ && device.getAddress().equals(mActiveBluetoothDevice.second);
+ }
+ if (i == (bluetoothRoutes.size() - 1) && mBluetoothRouteManager.isWatch(device)
+ && (device.equals(mCallAudioState.getActiveBluetoothDevice())
+ || isActiveDevice)) {
Log.i(this, "getActiveWatchOrNonWatchDeviceRoute: Routing to active watch - %s",
bluetoothRoutes.get(0));
return bluetoothRoutes.get(0);
}
// Record the first occurrence of a non-watch device route if found.
- if (!mBluetoothRouteManager.isWatch(device) && nonWatchDeviceRoute == null) {
+ if (!mBluetoothRouteManager.isWatch(device)) {
nonWatchDeviceRoute = route;
break;
}
@@ -1351,6 +1483,22 @@
return nonWatchDeviceRoute;
}
+ private List<AudioRoute> getAvailableBluetoothDevicesForRouting() {
+ List<AudioRoute> bluetoothRoutes = new ArrayList<>(mBluetoothRoutes.keySet());
+ if (!mFeatureFlags.resolveActiveBtRoutingAndBtTimingIssue()) {
+ return bluetoothRoutes;
+ }
+ // Consider the active device (BT_ACTIVE_DEVICE_PRESENT) if it exists first.
+ AudioRoute activeDeviceRoute = getArbitraryBluetoothDevice();
+ if (activeDeviceRoute != null && (bluetoothRoutes.isEmpty()
+ || !bluetoothRoutes.get(bluetoothRoutes.size() - 1).equals(activeDeviceRoute))) {
+ Log.i(this, "getActiveWatchOrNonWatchDeviceRoute: active BT device (%s) present."
+ + "Considering this device for selection first.", activeDeviceRoute);
+ bluetoothRoutes.add(activeDeviceRoute);
+ }
+ return bluetoothRoutes;
+ }
+
/**
* Returns the most actively reported bluetooth route excluding the passed in route.
*/
@@ -1430,22 +1578,32 @@
* address of the device.
*/
public void updateActiveBluetoothDevice(Pair<Integer, String> device) {
- mActiveDeviceCache.put(device.first, device.second);
- // Update most recently active device if address isn't null (meaning some device is active).
- if (device.second != null) {
- mActiveBluetoothDevice = device;
- } else {
- // If a device was removed, check to ensure that no other device is still considered
- // active.
- boolean hasActiveDevice = false;
- for (String address : mActiveDeviceCache.values()) {
- if (address != null) {
- hasActiveDevice = true;
- break;
+ synchronized (mLock) {
+ mActiveDeviceCache.put(device.first, device.second);
+ // Update most recently active device if address isn't null (meaning
+ // some device is active).
+ if (device.second != null) {
+ mActiveBluetoothDevice = device;
+ } else {
+ // If a device was removed, check to ensure that no other device is
+ //still considered active.
+ boolean hasActiveDevice = false;
+ List<Map.Entry<Integer, String>> activeBtDevices =
+ new ArrayList<>(mActiveDeviceCache.entrySet());
+ for (Map.Entry<Integer, String> activeDevice : activeBtDevices) {
+ Integer btAudioType = activeDevice.getKey();
+ String address = activeDevice.getValue();
+ if (address != null) {
+ hasActiveDevice = true;
+ if (mFeatureFlags.resolveActiveBtRoutingAndBtTimingIssue()) {
+ mActiveBluetoothDevice = new Pair<>(btAudioType, address);
+ }
+ break;
+ }
}
- }
- if (!hasActiveDevice) {
- mActiveBluetoothDevice = null;
+ if (!hasActiveDevice) {
+ mActiveBluetoothDevice = null;
+ }
}
}
}
diff --git a/src/com/android/server/telecom/CallStreamingController.java b/src/com/android/server/telecom/CallStreamingController.java
index efd458e..d14a553 100644
--- a/src/com/android/server/telecom/CallStreamingController.java
+++ b/src/com/android/server/telecom/CallStreamingController.java
@@ -39,10 +39,10 @@
import android.telecom.Log;
import com.android.internal.telecom.ICallStreamingService;
-import com.android.server.telecom.voip.ParallelTransaction;
-import com.android.server.telecom.voip.SerialTransaction;
-import com.android.server.telecom.voip.VoipCallTransaction;
-import com.android.server.telecom.voip.VoipCallTransactionResult;
+import com.android.server.telecom.callsequencing.voip.ParallelTransaction;
+import com.android.server.telecom.callsequencing.voip.SerialTransaction;
+import com.android.server.telecom.callsequencing.CallTransaction;
+import com.android.server.telecom.callsequencing.CallTransactionResult;
import java.util.ArrayList;
import java.util.List;
@@ -112,7 +112,7 @@
}
}
- public static class QueryCallStreamingTransaction extends VoipCallTransaction {
+ public static class QueryCallStreamingTransaction extends CallTransaction {
private final CallsManager mCallsManager;
public QueryCallStreamingTransaction(CallsManager callsManager) {
@@ -121,24 +121,24 @@
}
@Override
- public CompletableFuture<VoipCallTransactionResult> processTransaction(Void v) {
+ public CompletableFuture<CallTransactionResult> processTransaction(Void v) {
Log.i(this, "processTransaction");
- CompletableFuture<VoipCallTransactionResult> future = new CompletableFuture<>();
+ CompletableFuture<CallTransactionResult> future = new CompletableFuture<>();
if (mCallsManager.getCallStreamingController().isStreaming()) {
- future.complete(new VoipCallTransactionResult(
+ future.complete(new CallTransactionResult(
CallException.CODE_ERROR_UNKNOWN /* TODO:: define error b/335703584 */,
"STREAMING_FAILED_ALREADY_STREAMING"));
} else {
- future.complete(new VoipCallTransactionResult(
- VoipCallTransactionResult.RESULT_SUCCEED, null));
+ future.complete(new CallTransactionResult(
+ CallTransactionResult.RESULT_SUCCEED, null));
}
return future;
}
}
- public static class AudioInterceptionTransaction extends VoipCallTransaction {
+ public static class AudioInterceptionTransaction extends CallTransaction {
private Call mCall;
private boolean mEnterInterception;
@@ -150,16 +150,16 @@
}
@Override
- public CompletableFuture<VoipCallTransactionResult> processTransaction(Void v) {
+ public CompletableFuture<CallTransactionResult> processTransaction(Void v) {
Log.i(this, "processTransaction");
- CompletableFuture<VoipCallTransactionResult> future = new CompletableFuture<>();
+ CompletableFuture<CallTransactionResult> future = new CompletableFuture<>();
if (mEnterInterception) {
mCall.startStreaming();
} else {
mCall.stopStreaming();
}
- future.complete(new VoipCallTransactionResult(VoipCallTransactionResult.RESULT_SUCCEED,
+ future.complete(new CallTransactionResult(CallTransactionResult.RESULT_SUCCEED,
null));
return future;
}
@@ -170,7 +170,7 @@
return new StreamingServiceTransaction(context, wrapper, call);
}
- public class StreamingServiceTransaction extends VoipCallTransaction {
+ public class StreamingServiceTransaction extends CallTransaction {
public static final String MESSAGE = "STREAMING_FAILED_NO_SENDER";
private final TransactionalServiceWrapper mWrapper;
private final Context mContext;
@@ -188,14 +188,14 @@
@SuppressLint("LongLogTag")
@Override
- public CompletionStage<VoipCallTransactionResult> processTransaction(Void v) {
+ public CompletionStage<CallTransactionResult> processTransaction(Void v) {
Log.i(this, "processTransaction");
- CompletableFuture<VoipCallTransactionResult> future = new CompletableFuture<>();
+ CompletableFuture<CallTransactionResult> future = new CompletableFuture<>();
RoleManager roleManager = mContext.getSystemService(RoleManager.class);
PackageManager packageManager = mContext.getPackageManager();
if (roleManager == null || packageManager == null) {
Log.w(this, "processTransaction: Can't find system service");
- future.complete(new VoipCallTransactionResult(
+ future.complete(new CallTransactionResult(
CallException.CODE_ERROR_UNKNOWN /* TODO:: define error b/335703584 */,
MESSAGE));
return future;
@@ -205,7 +205,7 @@
RoleManager.ROLE_SYSTEM_CALL_STREAMING, mUserHandle);
if (holders.isEmpty()) {
Log.w(this, "processTransaction: Can't find streaming app");
- future.complete(new VoipCallTransactionResult(
+ future.complete(new CallTransactionResult(
CallException.CODE_ERROR_UNKNOWN /* TODO:: define error b/335703584 */,
MESSAGE));
return future;
@@ -217,7 +217,7 @@
PackageManager.GET_META_DATA, mUserHandle);
if (infos.isEmpty()) {
Log.w(this, "processTransaction: Can't find streaming service");
- future.complete(new VoipCallTransactionResult(
+ future.complete(new CallTransactionResult(
CallException.CODE_ERROR_UNKNOWN /* TODO:: define error b/335703584 */,
MESSAGE));
return future;
@@ -229,7 +229,7 @@
Manifest.permission.BIND_CALL_STREAMING_SERVICE)) {
Log.w(this, "Must require BIND_CALL_STREAMING_SERVICE: " +
serviceInfo.packageName);
- future.complete(new VoipCallTransactionResult(
+ future.complete(new CallTransactionResult(
CallException.CODE_ERROR_UNKNOWN /* TODO:: define error b/335703584 */,
MESSAGE));
return future;
@@ -242,7 +242,7 @@
| Context.BIND_FOREGROUND_SERVICE
| Context.BIND_SCHEDULE_LIKE_TOP_APP, mUserHandle)) {
Log.w(this, "Can't bind to streaming service");
- future.complete(new VoipCallTransactionResult(
+ future.complete(new CallTransactionResult(
CallException.CODE_ERROR_UNKNOWN /* TODO:: define error b/335703584 */,
"STREAMING_FAILED_SENDER_BINDING_ERROR"));
}
@@ -254,19 +254,19 @@
return new UnbindStreamingServiceTransaction();
}
- public class UnbindStreamingServiceTransaction extends VoipCallTransaction {
+ public class UnbindStreamingServiceTransaction extends CallTransaction {
public UnbindStreamingServiceTransaction() {
super(mTelecomLock);
}
@SuppressLint("LongLogTag")
@Override
- public CompletionStage<VoipCallTransactionResult> processTransaction(Void v) {
+ public CompletionStage<CallTransactionResult> processTransaction(Void v) {
Log.i(this, "processTransaction (unbindStreaming");
- CompletableFuture<VoipCallTransactionResult> future = new CompletableFuture<>();
+ CompletableFuture<CallTransactionResult> future = new CompletableFuture<>();
resetController();
- future.complete(new VoipCallTransactionResult(VoipCallTransactionResult.RESULT_SUCCEED,
+ future.complete(new CallTransactionResult(CallTransactionResult.RESULT_SUCCEED,
null));
return future;
}
@@ -275,7 +275,7 @@
public class StartStreamingTransaction extends SerialTransaction {
private Call mCall;
- public StartStreamingTransaction(List<VoipCallTransaction> subTransactions, Call call,
+ public StartStreamingTransaction(List<CallTransaction> subTransactions, Call call,
TelecomSystem.SyncRoot lock) {
super(subTransactions, lock);
mCall = call;
@@ -287,7 +287,7 @@
}
}
- public VoipCallTransaction getStartStreamingTransaction(CallsManager callsManager,
+ public CallTransaction getStartStreamingTransaction(CallsManager callsManager,
TransactionalServiceWrapper wrapper, Call call, TelecomSystem.SyncRoot lock) {
// start streaming transaction flow:
// 1. make sure there's no ongoing streaming call --> bind to EXO
@@ -296,7 +296,7 @@
// If bind to EXO failed, add transaction for stop the streaming
// create list for multiple transactions
- List<VoipCallTransaction> transactions = new ArrayList<>();
+ List<CallTransaction> transactions = new ArrayList<>();
transactions.add(new QueryCallStreamingTransaction(callsManager));
transactions.add(new AudioInterceptionTransaction(call, true, lock));
transactions.add(getCallStreamingServiceTransaction(
@@ -304,10 +304,10 @@
return new StartStreamingTransaction(transactions, call, lock);
}
- public VoipCallTransaction getStopStreamingTransaction(Call call, TelecomSystem.SyncRoot lock) {
+ public CallTransaction getStopStreamingTransaction(Call call, TelecomSystem.SyncRoot lock) {
// TODO: implement this
// Stop streaming transaction flow:
- List<VoipCallTransaction> transactions = new ArrayList<>();
+ List<CallTransaction> transactions = new ArrayList<>();
// 1. unbind to call streaming service
transactions.add(getUnbindStreamingServiceTransaction());
@@ -352,7 +352,7 @@
mTransactionalServiceWrapper.getTransactionManager().addTransaction(transaction,
new OutcomeReceiver<>() {
@Override
- public void onResult(VoipCallTransactionResult result) {
+ public void onResult(CallTransactionResult result) {
// ignore
}
@@ -366,7 +366,7 @@
}
}
- private class CallStreamingStateChangeTransaction extends VoipCallTransaction {
+ private class CallStreamingStateChangeTransaction extends CallTransaction {
@StreamingCall.StreamingCallState int mState;
public CallStreamingStateChangeTransaction(@StreamingCall.StreamingCallState int state) {
@@ -375,14 +375,14 @@
}
@Override
- public CompletionStage<VoipCallTransactionResult> processTransaction(Void v) {
- CompletableFuture<VoipCallTransactionResult> future = new CompletableFuture<>();
+ public CompletionStage<CallTransactionResult> processTransaction(Void v) {
+ CompletableFuture<CallTransactionResult> future = new CompletableFuture<>();
try {
mService.onCallStreamingStateChanged(mState);
- future.complete(new VoipCallTransactionResult(
- VoipCallTransactionResult.RESULT_SUCCEED, null));
+ future.complete(new CallTransactionResult(
+ CallTransactionResult.RESULT_SUCCEED, null));
} catch (RemoteException e) {
- future.complete(new VoipCallTransactionResult(
+ future.complete(new CallTransactionResult(
CallException.CODE_ERROR_UNKNOWN /* TODO:: define error b/335703584 */,
"Exception when request "
+ "setting state to streaming app."));
@@ -395,10 +395,10 @@
ServiceConnection {
private Call mCall;
private TransactionalServiceWrapper mWrapper;
- private CompletableFuture<VoipCallTransactionResult> mFuture;
+ private CompletableFuture<CallTransactionResult> mFuture;
public CallStreamingServiceConnection(Call call, TransactionalServiceWrapper wrapper,
- CompletableFuture<VoipCallTransactionResult> future) {
+ CompletableFuture<CallTransactionResult> future) {
mCall = call;
mWrapper = wrapper;
mFuture = future;
@@ -409,11 +409,11 @@
try {
Log.i(this, "onServiceConnected: " + name);
onConnectedInternal(mCall, mWrapper, service);
- mFuture.complete(new VoipCallTransactionResult(
- VoipCallTransactionResult.RESULT_SUCCEED, null));
+ mFuture.complete(new CallTransactionResult(
+ CallTransactionResult.RESULT_SUCCEED, null));
} catch (RemoteException e) {
resetController();
- mFuture.complete(new VoipCallTransactionResult(
+ mFuture.complete(new CallTransactionResult(
CallException.CODE_ERROR_UNKNOWN /* TODO:: define error b/335703584 */,
StreamingServiceTransaction.MESSAGE));
}
@@ -437,7 +437,7 @@
private void clearBinding() {
resetController();
if (!mFuture.isDone()) {
- mFuture.complete(new VoipCallTransactionResult(
+ mFuture.complete(new CallTransactionResult(
CallException.CODE_ERROR_UNKNOWN /* TODO:: define error b/335703584 */,
"STREAMING_FAILED_SENDER_BINDING_ERROR"));
} else {
diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java
index 028d8c1..712c6a9 100644
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -27,6 +27,8 @@
import static android.provider.CallLog.Calls.USER_MISSED_NOT_RUNNING;
import static android.provider.CallLog.Calls.USER_MISSED_NO_ANSWER;
import static android.provider.CallLog.Calls.USER_MISSED_SHORT_RING;
+import static android.telecom.CallAttributes.DIRECTION_INCOMING;
+import static android.telecom.CallAttributes.DIRECTION_OUTGOING;
import static android.telecom.TelecomManager.ACTION_POST_CALL;
import static android.telecom.TelecomManager.DURATION_LONG;
import static android.telecom.TelecomManager.DURATION_MEDIUM;
@@ -131,9 +133,14 @@
import com.android.server.telecom.callfiltering.IncomingCallFilterGraph;
import com.android.server.telecom.callfiltering.IncomingCallFilterGraphProvider;
import com.android.server.telecom.callredirection.CallRedirectionProcessor;
+import com.android.server.telecom.callsequencing.CallSequencingController;
+import com.android.server.telecom.callsequencing.CallTransaction;
+import com.android.server.telecom.callsequencing.voip.IncomingCallTransaction;
import com.android.server.telecom.components.ErrorDialogActivity;
import com.android.server.telecom.components.TelecomBroadcastReceiver;
+import com.android.server.telecom.callsequencing.CallsManagerCallSequencingAdapter;
import com.android.server.telecom.flags.FeatureFlags;
+import com.android.server.telecom.metrics.ErrorStats;
import com.android.server.telecom.metrics.TelecomMetricsController;
import com.android.server.telecom.stats.CallFailureCause;
import com.android.server.telecom.ui.AudioProcessingNotification;
@@ -143,8 +150,8 @@
import com.android.server.telecom.ui.DisconnectedCallNotifier;
import com.android.server.telecom.ui.IncomingCallNotifier;
import com.android.server.telecom.ui.ToastFactory;
-import com.android.server.telecom.voip.VoipCallMonitor;
-import com.android.server.telecom.voip.TransactionManager;
+import com.android.server.telecom.callsequencing.voip.VoipCallMonitor;
+import com.android.server.telecom.callsequencing.TransactionManager;
import java.util.ArrayList;
import java.util.Arrays;
@@ -321,7 +328,7 @@
public static final String TELEPHONY_HAS_DEFAULT_BUT_TELECOM_DOES_NOT_MSG =
"Telephony has a default MO acct but Telecom prompted user for MO";
- private static final int[] OUTGOING_CALL_STATES =
+ public static final int[] OUTGOING_CALL_STATES =
{CallState.CONNECTING, CallState.SELECT_PHONE_ACCOUNT, CallState.DIALING,
CallState.PULLING};
@@ -490,6 +497,7 @@
private final UserManager mUserManager;
private final CallStreamingNotification mCallStreamingNotification;
private final BlockedNumbersManager mBlockedNumbersManager;
+ private final CallsManagerCallSequencingAdapter mCallSequencingAdapter;
private final FeatureFlags mFeatureFlags;
private final com.android.internal.telephony.flags.FeatureFlags mTelephonyFeatureFlags;
@@ -530,6 +538,8 @@
private AnomalyReporterAdapter mAnomalyReporter = new AnomalyReporterAdapterImpl();
private final MmiUtils mMmiUtils = new MmiUtils();
+
+ private TelecomMetricsController mMetricsController;
/**
* Listener to PhoneAccountRegistrar events.
*/
@@ -697,7 +707,7 @@
ringtoneFactory, systemVibrator,
new Ringer.VibrationEffectProxy(), mInCallController,
mContext.getSystemService(NotificationManager.class),
- accessibilityManagerAdapter, featureFlags);
+ accessibilityManagerAdapter, featureFlags, mAnomalyReporter);
if (featureFlags.telecomResolveHiddenDependencies()) {
// This is now deprecated
mCallRecordingTonePlayer = null;
@@ -733,9 +743,13 @@
mCallStreamingNotification = callStreamingNotification;
mFeatureFlags = featureFlags;
mTelephonyFeatureFlags = telephonyFlags;
+ mMetricsController = metricsController;
mBlockedNumbersManager = mFeatureFlags.telecomMainlineBlockedNumbersManager()
? mContext.getSystemService(BlockedNumbersManager.class)
: null;
+ mCallSequencingAdapter = new CallsManagerCallSequencingAdapter(this,
+ new CallSequencingController(this, mContext,
+ mFeatureFlags), mFeatureFlags);
if (mFeatureFlags.useImprovedListenerOrder()) {
mListeners.add(mInCallController);
@@ -929,8 +943,8 @@
String defaultDialerPackageName = telecomManager.getDefaultDialerPackage(userHandle);
String userChosenPackageName = getRoleManagerAdapter().
getDefaultCallScreeningApp(userHandle);
- AppLabelProxy appLabelProxy = packageName -> AppLabelProxy.Util.getAppLabel(
- mContext.getPackageManager(), packageName);
+ AppLabelProxy appLabelProxy = (packageName, user) -> AppLabelProxy.Util.getAppLabel(
+ mContext, user, packageName, mFeatureFlags);
ParcelableCallUtils.Converter converter = new ParcelableCallUtils.Converter();
IncomingCallFilterGraph graph = mIncomingCallFilterGraphProvider.createGraph(incomingCall,
@@ -938,7 +952,7 @@
DirectToVoicemailFilter voicemailFilter = new DirectToVoicemailFilter(incomingCall,
mCallerInfoLookupHelper);
BlockCheckerFilter blockCheckerFilter = new BlockCheckerFilter(mContext, incomingCall,
- mCallerInfoLookupHelper, new BlockCheckerAdapter(mFeatureFlags));
+ mCallerInfoLookupHelper, new BlockCheckerAdapter(mFeatureFlags), mFeatureFlags);
DndCallFilter dndCallFilter = new DndCallFilter(incomingCall, getRinger());
CallScreeningServiceFilter carrierCallScreeningServiceFilter =
new CallScreeningServiceFilter(incomingCall, carrierPackageName,
@@ -1864,6 +1878,37 @@
originalIntent, callingPackage, false);
}
+ /**
+ * Creates a transaction representing either the outgoing or incoming transactional call.
+ * @param callId The call id associated with the call.
+ * @param callAttributes The call attributes associated with the call.
+ * @param extras The extras that are associated with the call.
+ * @param callingPackage The calling package representing where the request was invoked from.
+ * @return The {@link CompletableFuture<CallTransaction>} that encompasses the request to
+ * place/receive the transactional call.
+ */
+ public CompletableFuture<CallTransaction> createTransactionalCall(String callId,
+ CallAttributes callAttributes, Bundle extras, String callingPackage) {
+ CompletableFuture<CallTransaction> transaction;
+ // create transaction based on the call direction
+ switch (callAttributes.getDirection()) {
+ case DIRECTION_OUTGOING:
+ transaction = mCallSequencingAdapter.createTransactionalOutgoingCall(callId,
+ callAttributes, extras, callingPackage);
+ break;
+ case DIRECTION_INCOMING:
+ transaction = CompletableFuture.completedFuture(new IncomingCallTransaction(
+ callId, callAttributes, this, extras, mFeatureFlags));
+ break;
+ default:
+ throw new IllegalArgumentException(String.format("Invalid Call Direction. "
+ + "Was [%d] but should be within [%d,%d]",
+ callAttributes.getDirection(), DIRECTION_INCOMING,
+ DIRECTION_OUTGOING));
+ }
+ return transaction;
+ }
+
private String generateNextCallId(Bundle extras) {
if (extras != null && extras.containsKey(TelecomManager.TRANSACTION_CALL_ID_KEY)) {
return extras.getString(TelecomManager.TRANSACTION_CALL_ID_KEY);
@@ -2029,10 +2074,18 @@
if (exception != null){
Log.e(TAG, exception, "Error retrieving list of potential phone accounts.");
if (finalCall.isEmergencyCall()) {
+ if (mFeatureFlags.telecomMetricsSupport()) {
+ mMetricsController.getErrorStats().log(ErrorStats.SUB_CALL_MANAGER,
+ ErrorStats.ERROR_RETRIEVING_ACCOUNT_EMERGENCY);
+ }
mAnomalyReporter.reportAnomaly(
EXCEPTION_RETRIEVING_PHONE_ACCOUNTS_EMERGENCY_ERROR_UUID,
EXCEPTION_RETRIEVING_PHONE_ACCOUNTS_EMERGENCY_ERROR_MSG);
} else {
+ if (mFeatureFlags.telecomMetricsSupport()) {
+ mMetricsController.getErrorStats().log(ErrorStats.SUB_CALL_MANAGER,
+ ErrorStats.ERROR_RETRIEVING_ACCOUNT);
+ }
mAnomalyReporter.reportAnomaly(
EXCEPTION_RETRIEVING_PHONE_ACCOUNTS_ERROR_UUID,
EXCEPTION_RETRIEVING_PHONE_ACCOUNTS_ERROR_MSG);
@@ -2071,21 +2124,18 @@
// This future checks the status of existing calls and attempts to make room for the
- // outgoing call. The future returned by the inner method will usually be pre-completed --
- // we only pause here if user interaction is required to disconnect a self-managed call.
- // It runs after the account handle is set, independently of the phone account suggestion
- // future.
- CompletableFuture<Call> makeRoomForCall = setAccountHandle.thenComposeAsync(
+ // outgoing call.
+ CompletableFuture<Boolean> makeRoomForCall = setAccountHandle.thenComposeAsync(
potentialPhoneAccounts -> {
Log.i(CallsManager.this, "make room for outgoing call stage");
if (mMmiUtils.isPotentialInCallMMICode(handle) && !isSelfManaged) {
- return CompletableFuture.completedFuture(finalCall);
+ return CompletableFuture.completedFuture(true);
}
// If a call is being reused, then it has already passed the
// makeRoomForOutgoingCall check once and will fail the second time due to the
// call transitioning into the CONNECTING state.
if (isReusedCall) {
- return CompletableFuture.completedFuture(finalCall);
+ return CompletableFuture.completedFuture(true);
} else {
Call reusableCall = reuseOutgoingCall(handle);
if (reusableCall != null) {
@@ -2112,48 +2162,75 @@
finalCall.getTargetPhoneAccount(), finalCall);
}
finalCall.setStartFailCause(CallFailureCause.IN_EMERGENCY_CALL);
- return CompletableFuture.completedFuture(null);
+ return CompletableFuture.completedFuture(false);
}
- // If we can not supportany more active calls, our options are to move a call
+ // If we can not support any more active calls, our options are to move a call
// to hold, disconnect a call, or cancel this call altogether.
- boolean isRoomForCall = finalCall.isEmergencyCall() ?
- makeRoomForOutgoingEmergencyCall(finalCall) :
- makeRoomForOutgoingCall(finalCall);
- if (!isRoomForCall) {
- Call foregroundCall = getForegroundCall();
- Log.d(CallsManager.this, "No more room for outgoing call %s ", finalCall);
- if (foregroundCall.isSelfManaged()) {
- // If the ongoing call is a self-managed call, then prompt the user to
- // ask if they'd like to disconnect their ongoing call and place the
- // outgoing call.
- Log.i(CallsManager.this, "Prompting user to disconnect "
- + "self-managed call");
- finalCall.setOriginalCallIntent(originalIntent);
- CompletableFuture<Call> completionFuture = new CompletableFuture<>();
- startCallConfirmation(finalCall, completionFuture);
- return completionFuture;
- } else {
- // If the ongoing call is a managed call, we will prevent the outgoing
- // call from dialing.
- if (isConference) {
- notifyCreateConferenceFailed(finalCall.getTargetPhoneAccount(),
- finalCall);
- } else {
- notifyCreateConnectionFailed(
- finalCall.getTargetPhoneAccount(), finalCall);
- }
+ CompletableFuture<Boolean> isRoomForCallFuture =
+ mCallSequencingAdapter.makeRoomForOutgoingCall(
+ finalCall.isEmergencyCall(), finalCall);
+ isRoomForCallFuture.exceptionally((throwable -> {
+ if (throwable != null) {
+ Log.w(CallsManager.this,
+ "Exception thrown in makeRoomForOutgoing*Call, "
+ + "returning false. Ex:" + throwable);
}
- Log.i(CallsManager.this, "Aborting call since there's no room");
+ return false;
+ }));
+ return isRoomForCallFuture;
+ }, new LoggedHandlerExecutor(outgoingCallHandler, "CM.dSMCP", mLock));
+
+ // The future returned by the inner method will usually be pre-completed --
+ // we only pause here if user interaction is required to disconnect a self-managed call.
+ // It runs after the account handle is set, independently of the phone account suggestion
+ // future.
+ CompletableFuture<Call> makeRoomResultHandler = makeRoomForCall
+ .thenComposeAsync((isRoom) -> {
+ // If we have an ongoing emergency call, we would have already notified
+ // connection failure for the new call being placed. Catch this so we don't
+ // resend it again.
+ boolean hasOngoingEmergencyCall = !finalCall.isEmergencyCall()
+ && isInEmergencyCall();
+ if (isRoom) {
+ return CompletableFuture.completedFuture(finalCall);
+ } else if (hasOngoingEmergencyCall) {
return CompletableFuture.completedFuture(null);
}
- return CompletableFuture.completedFuture(finalCall);
- }, new LoggedHandlerExecutor(outgoingCallHandler, "CM.dSMCP", mLock));
+ Call foregroundCall = getForegroundCall();
+ Log.d(CallsManager.this, "No more room for outgoing call %s ",
+ finalCall);
+ if (foregroundCall.isSelfManaged()) {
+ // If the ongoing call is a self-managed call, then prompt the
+ // user to ask if they'd like to disconnect their ongoing call
+ // and place the outgoing call.
+ Log.i(CallsManager.this, "Prompting user to disconnect "
+ + "self-managed call");
+ finalCall.setOriginalCallIntent(originalIntent);
+ CompletableFuture<Call> completionFuture =
+ new CompletableFuture<>();
+ startCallConfirmation(finalCall, completionFuture);
+ return completionFuture;
+ } else {
+ // If the ongoing call is a managed call, we will prevent the
+ // outgoing call from dialing.
+ if (isConference) {
+ notifyCreateConferenceFailed(
+ finalCall.getTargetPhoneAccount(),
+ finalCall);
+ } else {
+ notifyCreateConnectionFailed(
+ finalCall.getTargetPhoneAccount(), finalCall);
+ }
+ }
+ Log.i(CallsManager.this, "Aborting call since there's no room");
+ return CompletableFuture.completedFuture(null);
+ }, new LoggedHandlerExecutor(outgoingCallHandler, "CM.mROC", mLock));
// The outgoing call can be placed, go forward. This future glues together the results of
// the account suggestion stage and the make room for call stage.
CompletableFuture<Pair<Call, List<PhoneAccountSuggestion>>> preSelectStage =
- makeRoomForCall.thenCombine(suggestionFuture, Pair::create);
+ makeRoomResultHandler.thenCombine(suggestionFuture, Pair::create);
mLatestPreAccountSelectionFuture = preSelectStage;
// This future takes the list of suggested accounts and the call and determines if more
@@ -2194,6 +2271,11 @@
showErrorMessage(R.string.cant_call_due_to_no_supported_service);
mListeners.forEach(l -> l.onCreateConnectionFailed(callToPlace));
if (callToPlace.isEmergencyCall()) {
+ if (mFeatureFlags.telecomMetricsSupport()) {
+ mMetricsController.getErrorStats().log(
+ ErrorStats.SUB_CALL_MANAGER,
+ ErrorStats.ERROR_EMERGENCY_CALL_ABORTED_NO_ACCOUNT);
+ }
mAnomalyReporter.reportAnomaly(
EMERGENCY_CALL_ABORTED_NO_PHONE_ACCOUNTS_ERROR_UUID,
EMERGENCY_CALL_ABORTED_NO_PHONE_ACCOUNTS_ERROR_MSG);
@@ -2219,6 +2301,11 @@
PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)) {
if (SubscriptionManager.getDefaultVoiceSubscriptionId() !=
SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+ if (mFeatureFlags.telecomMetricsSupport()) {
+ mMetricsController.getErrorStats().log(
+ ErrorStats.SUB_CALL_MANAGER,
+ ErrorStats.ERROR_DEFAULT_MO_ACCOUNT_MISMATCH);
+ }
mAnomalyReporter.reportAnomaly(
TELEPHONY_HAS_DEFAULT_BUT_TELECOM_DOES_NOT_UUID,
TELEPHONY_HAS_DEFAULT_BUT_TELECOM_DOES_NOT_MSG);
@@ -2540,8 +2627,8 @@
theCall,
new AppLabelProxy() {
@Override
- public CharSequence getAppLabel(String packageName) {
- return Util.getAppLabel(mContext.getPackageManager(), packageName);
+ public CharSequence getAppLabel(String packageName, UserHandle userHandle) {
+ return Util.getAppLabel(mContext, userHandle, packageName, mFeatureFlags);
}
}).process();
future.thenApply( v -> {
@@ -3033,6 +3120,10 @@
// If an exceptions is thrown while creating the connection, prompt the user to
// generate a bugreport and force disconnect.
Log.e(TAG, exception, "Exception thrown while establishing connection.");
+ if (mFeatureFlags.telecomMetricsSupport()) {
+ mMetricsController.getErrorStats().log(ErrorStats.SUB_CALL_MANAGER,
+ ErrorStats.ERROR_ESTABLISHING_CONNECTION);
+ }
mAnomalyReporter.reportAnomaly(
EXCEPTION_WHILE_ESTABLISHING_CONNECTION_ERROR_UUID,
EXCEPTION_WHILE_ESTABLISHING_CONNECTION_ERROR_MSG);
@@ -3076,7 +3167,22 @@
public void answerCall(Call call, int videoState) {
if (!mCalls.contains(call)) {
Log.i(this, "Request to answer a non-existent call %s", call);
- } else if (call.isTransactionalCall()) {
+ }
+ mCallSequencingAdapter.answerCall(call, videoState);
+ }
+
+ /**
+ * CS: Hold any existing calls, request focus, and then set the call state to answered state.
+ * <p>
+ * T: Call TransactionalServiceWrapper, which then generates transactions to hold calls
+ * {@link #transactionHoldPotentialActiveCallForNewCall} and then move the active call focus
+ * {@link #requestNewCallFocusAndVerify} and notify the remote VOIP app of the call state
+ * moving to active.
+ * <p>
+ * Note: This is only used when {@link FeatureFlags#enableCallSequencing()} is false.
+ */
+ public void answerCallOld(Call call, int videoState) {
+ if (call.isTransactionalCall()) {
// InCallAdapter is requesting to answer the given transactioanl call. Must get an ack
// from the client via a transaction before answering.
call.answer(videoState);
@@ -3143,7 +3249,7 @@
}
CharSequence requestingAppName = AppLabelProxy.Util.getAppLabel(
- mContext.getPackageManager(), requestingPackageName);
+ mContext, call.getAssociatedUser(), requestingPackageName, mFeatureFlags);
if (requestingAppName == null) {
requestingAppName = requestingPackageName;
}
@@ -3359,35 +3465,42 @@
Log.w(this, "Unknown call (%s) asked to disconnect", call);
} else {
mLocallyDisconnectingCalls.add(call);
- int previousState = call.getState();
- call.disconnect();
- for (CallsManagerListener listener : mListeners) {
- listener.onCallStateChanged(call, previousState, call.getState());
- }
- // Cancel any of the outgoing call futures if they're still around.
- if (mPendingCallConfirm != null && !mPendingCallConfirm.isDone()) {
- mPendingCallConfirm.complete(null);
- mPendingCallConfirm = null;
- }
- if (mPendingAccountSelection != null && !mPendingAccountSelection.isDone()) {
- mPendingAccountSelection.complete(null);
- mPendingAccountSelection = null;
- }
+ mCallSequencingAdapter.disconnectCall(call);
}
}
/**
- * Instructs Telecom to disconnect all calls.
+ * Disconnects the provided call. This is only used when
+ * {@link FeatureFlags#enableCallSequencing()} is false.
+ * @param call The call to disconnect.
+ * @param previousState The previous call state before the call is disconnected.
*/
- void disconnectAllCalls() {
- Log.v(this, "disconnectAllCalls");
-
- for (Call call : mCalls) {
- disconnectCall(call);
- }
+ public void disconnectCallOld(Call call, int previousState) {
+ call.disconnect();
+ processDisconnectCallAndCleanup(call, previousState);
}
/**
+ * Helper to process the call state change upon disconnecting the provided call and performs
+ * local cleanup to clear the outgoing call futures, if they exist.
+ * @param call The call to disconnect.
+ * @param previousState The previous call state before the call is disconnected.
+ */
+ public void processDisconnectCallAndCleanup(Call call, int previousState) {
+ for (CallsManagerListener listener : mListeners) {
+ listener.onCallStateChanged(call, previousState, call.getState());
+ }
+ // Cancel any of the outgoing call futures if they're still around.
+ if (mPendingCallConfirm != null && !mPendingCallConfirm.isDone()) {
+ mPendingCallConfirm.complete(null);
+ mPendingCallConfirm = null;
+ }
+ if (mPendingAccountSelection != null && !mPendingAccountSelection.isDone()) {
+ mPendingAccountSelection.complete(null);
+ mPendingAccountSelection = null;
+ }
+ }
+ /**
* Disconnects calls for any other {@link PhoneAccountHandle} but the one specified.
* Note: As a protective measure, will NEVER disconnect an emergency call. Although that
* situation should never arise, its a good safeguard.
@@ -3410,10 +3523,10 @@
public void holdCall(Call call) {
if (!mCalls.contains(call)) {
Log.w(this, "Unknown call (%s) asked to be put on hold", call);
- } else {
- Log.d(this, "Putting call on hold: (%s)", call);
- call.hold();
+ return;
}
+ Log.d(this, "Putting call on hold: (%s)", call);
+ mCallSequencingAdapter.holdCall(call);
}
/**
@@ -3425,44 +3538,67 @@
public void unholdCall(Call call) {
if (!mCalls.contains(call)) {
Log.w(this, "Unknown call (%s) asked to be removed from hold", call);
- } else {
- if (getOutgoingCall() != null) {
- Log.w(this, "There is an outgoing call, so it is unable to unhold this call %s",
- call);
- return;
- }
- Call activeCall = (Call) mConnectionSvrFocusMgr.getCurrentFocusCall();
- String activeCallId = null;
- if (activeCall != null && !activeCall.isLocallyDisconnecting()) {
- activeCallId = activeCall.getId();
- if (canHold(activeCall)) {
- activeCall.hold("Swap to " + call.getId());
- Log.addEvent(activeCall, LogUtils.Events.SWAP, "To " + call.getId());
- Log.addEvent(call, LogUtils.Events.SWAP, "From " + activeCall.getId());
- } else {
- // This call does not support hold. If it is from a different connection
- // service or connection manager, then disconnect it, otherwise invoke
- // call.hold() and allow the connection service or connection manager to handle
- // the situation.
- if (!areFromSameSource(activeCall, call)) {
- if (!activeCall.isEmergencyCall()) {
- activeCall.disconnect("Swap to " + call.getId());
- } else {
- Log.w(this, "unholdCall: % is an emergency call, aborting swap to %s",
- activeCall.getId(), call.getId());
- // Don't unhold the call as requested; we don't want to drop an
- // emergency call.
- return;
- }
+ return;
+ }
+ if (getOutgoingCall() != null) {
+ Log.w(this, "There is an outgoing call, so it is unable to unhold this call %s",
+ call);
+ return;
+ }
+ mCallSequencingAdapter.unholdCall(call);
+ }
+
+ /**
+ * Instructs telecom to hold any ongoing active calls and bring this call to the active state.
+ * <p>
+ * Note: This is only used when {@link FeatureFlags#enableCallSequencing()} is false.
+ */
+ public void unholdCallOld(Call call) {
+ Call activeCall = (Call) mConnectionSvrFocusMgr.getCurrentFocusCall();
+ String activeCallId = null;
+ if (activeCall != null && !activeCall.isLocallyDisconnecting()) {
+ activeCallId = activeCall.getId();
+ if (canHold(activeCall)) {
+ activeCall.hold("Swap to " + call.getId());
+ Log.addEvent(activeCall, LogUtils.Events.SWAP, "To " + call.getId());
+ Log.addEvent(call, LogUtils.Events.SWAP, "From " + activeCall.getId());
+ } else {
+ // This call does not support hold. If it is from a different connection
+ // service or connection manager, then disconnect it, otherwise invoke
+ // call.hold() and allow the connection service or connection manager to handle
+ // the situation.
+ if (!areFromSameSource(activeCall, call)) {
+ if (!activeCall.isEmergencyCall()) {
+ activeCall.disconnect("Swap to " + call.getId());
} else {
- activeCall.hold("Swap to " + call.getId());
+ Log.w(this, "unholdCall: % is an emergency call, aborting swap to %s",
+ activeCall.getId(), call.getId());
+ // Don't unhold the call as requested; we don't want to drop an
+ // emergency call.
+ return;
}
+ } else {
+ activeCall.hold("Swap to " + call.getId());
}
}
- mConnectionSvrFocusMgr.requestFocus(
- call,
- new RequestCallback(new ActionUnHoldCall(call, activeCallId)));
}
+ requestActionUnholdCall(call, activeCallId);
+ }
+
+ public void requestActionUnholdCall(Call call, String activeCallId) {
+ mConnectionSvrFocusMgr.requestFocus(
+ call,
+ new RequestCallback(new ActionUnHoldCall(call, activeCallId)));
+ }
+
+ public void requestActionSetActiveCall(Call call, String tag) {
+ mConnectionSvrFocusMgr.requestFocus(call,
+ new RequestCallback(new ActionSetCallState(call, CallState.ACTIVE, tag)));
+ }
+
+ public void requestFocusActionAnswerCall(Call call, int videoState) {
+ mConnectionSvrFocusMgr.requestFocus(call, new CallsManager.RequestCallback(
+ new ActionAnswerCall(call, videoState)));
}
@Override
@@ -3834,7 +3970,7 @@
/**
* Returns true if the active call is held.
*/
- boolean holdActiveCallForNewCall(Call call) {
+ public boolean holdActiveCallForNewCall(Call call) {
Call activeCall = (Call) mConnectionSvrFocusMgr.getCurrentFocusCall();
Log.i(this, "holdActiveCallForNewCall, newCall: %s, activeCall: %s", call.getId(),
(activeCall == null ? "<none>" : activeCall.getId()));
@@ -3920,23 +4056,8 @@
return;
}
- if (holdActiveCallForNewCall(newCall)) {
- // Transactional clients do not call setHold but the request was sent to set the
- // call as inactive and it has already been acked by this point.
- markCallAsOnHold(activeCall);
- callback.onResult(true);
- } else {
- // It's possible that holdActiveCallForNewCall disconnected the activeCall.
- // Therefore, the activeCalls state should be checked before failing.
- if (activeCall.isLocallyDisconnecting()) {
- callback.onResult(true);
- } else {
- Log.i(this, mTag + "active call could not be held or disconnected");
- callback.onError(
- new CallException("activeCall could not be held or disconnected",
- CallException.CODE_CANNOT_HOLD_CURRENT_ACTIVE_CALL));
- }
- }
+ mCallSequencingAdapter.transactionHoldPotentialActiveCallForNewCall(newCall,
+ activeCall, callback);
} else {
// before attempting CallsManager#holdActiveCallForNewCall(Call), check if it'll fail
// early
@@ -3964,6 +4085,28 @@
}
}
+ public void transactionHoldPotentialActiveCallForNewCallOld(Call newCall,
+ Call activeCall, OutcomeReceiver<Boolean, CallException> callback) {
+ if (holdActiveCallForNewCall(newCall)) {
+ // Transactional clients do not call setHold but the request was sent to set the
+ // call as inactive and it has already been acked by this point.
+ markCallAsOnHold(activeCall);
+ callback.onResult(true);
+ } else {
+ // It's possible that holdActiveCallForNewCall disconnected the activeCall.
+ // Therefore, the activeCalls state should be checked before failing.
+ if (activeCall.isLocallyDisconnecting()) {
+ callback.onResult(true);
+ } else {
+ Log.i(this, "transactionHoldPotentialActiveCallForNewCallOld: active call could "
+ + "not be held or disconnected");
+ callback.onError(
+ new CallException("activeCall could not be held or disconnected",
+ CallException.CODE_CANNOT_HOLD_CURRENT_ACTIVE_CALL));
+ }
+ }
+ }
+
private boolean canHoldOrSwapActiveCall(Call activeCall, Call newCall) {
return canHold(activeCall) || sameSourceHoldCase(activeCall, newCall);
}
@@ -3972,6 +4115,10 @@
return supportsHold(activeCall) && areFromSameSource(activeCall, call);
}
+ /**
+ * CS: Mark a call as active. If the call is self-mangaed, we will also hold any active call
+ * before moving the self-managed call to active.
+ */
@VisibleForTesting
public void markCallAsActive(Call call) {
Log.i(this, "markCallAsActive, isSelfManaged: " + call.isSelfManaged());
@@ -3980,13 +4127,7 @@
// to active directly. We should hold or disconnect the current active call based on the
// holdability, and request the call focus for the self-managed call before the state
// change.
- holdActiveCallForNewCall(call);
- mConnectionSvrFocusMgr.requestFocus(
- call,
- new RequestCallback(new ActionSetCallState(
- call,
- CallState.ACTIVE,
- "active set explicitly for self-managed")));
+ mCallSequencingAdapter.markCallAsActiveSelfManagedCall(call);
} else {
if (mPendingAudioProcessingCall == call) {
if (mCalls.contains(call)) {
@@ -4006,6 +4147,9 @@
}
}
+ /**
+ * Mark a call as on hold after the hold operation has already completed.
+ */
public void markCallAsOnHold(Call call) {
setCallState(call, CallState.ON_HOLD, "on-hold set explicitly");
}
@@ -4160,6 +4304,10 @@
}, new LoggedHandlerExecutor(mHandler, "CM.pR", mLock))
.exceptionally((throwable) -> {
Log.e(TAG, throwable, "Error while executing call removal");
+ if (mFeatureFlags.telecomMetricsSupport()) {
+ mMetricsController.getErrorStats().log(ErrorStats.SUB_CALL_MANAGER,
+ ErrorStats.ERROR_REMOVING_CALL);
+ }
mAnomalyReporter.reportAnomaly(CALL_REMOVAL_EXECUTION_ERROR_UUID,
CALL_REMOVAL_EXECUTION_ERROR_MSG);
return null;
@@ -4177,12 +4325,26 @@
private void doRemoval(Call call) {
call.maybeCleanupHandover();
removeCall(call);
+ boolean isLocallyDisconnecting = mLocallyDisconnectingCalls.contains(call);
+ mLocallyDisconnectingCalls.remove(call);
+ maybeMoveHeldCallToForeground(call, isLocallyDisconnecting);
+ }
+
+ /**
+ * Move the held call to foreground in the event that there is a held call and the disconnected
+ * call was disconnected locally or the held call has no way to auto-unhold because it does not
+ * support hold capability.
+ *
+ * Note: If {@link FeatureFlags#enableCallSequencing()} is enabled, we will verify that the
+ * transaction to unhold the call succeeded or failed.
+ */
+ public void maybeMoveHeldCallToForeground(Call removedCall, boolean isLocallyDisconnecting) {
+ CompletableFuture<Boolean> unholdForegroundCallFuture = null;
Call foregroundCall = mCallAudioManager.getPossiblyHeldForegroundCall();
- if (mLocallyDisconnectingCalls.contains(call)) {
- boolean isDisconnectingChildCall = call.isDisconnectingChildCall();
- Log.v(this, "performRemoval: isDisconnectingChildCall = "
- + isDisconnectingChildCall + "call -> %s", call);
- mLocallyDisconnectingCalls.remove(call);
+ if (isLocallyDisconnecting) {
+ boolean isDisconnectingChildCall = removedCall.isDisconnectingChildCall();
+ Log.v(this, "maybeMoveHeldCallToForeground: isDisconnectingChildCall = "
+ + isDisconnectingChildCall + "call -> %s", removedCall);
// Auto-unhold the foreground call due to a locally disconnected call, except if the
// call which was disconnected is a member of a conference (don't want to auto
// un-hold the conference if we remove a member of the conference).
@@ -4191,8 +4353,9 @@
// implementations, especially if one is managed and the other is a VoIP CS.
if (!isDisconnectingChildCall && foregroundCall != null
&& foregroundCall.getState() == CallState.ON_HOLD
- && areFromSameSource(foregroundCall, call)) {
- foregroundCall.unhold();
+ && areFromSameSource(foregroundCall, removedCall)) {
+
+ unholdForegroundCallFuture = foregroundCall.unhold();
}
} else if (foregroundCall != null &&
!foregroundCall.can(Connection.CAPABILITY_SUPPORT_HOLD) &&
@@ -4201,9 +4364,16 @@
// 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, "performRemoval: Auto-unholding held foreground call (call doesn't "
- + "support hold)");
- foregroundCall.unhold();
+ Log.i(this, "maybeMoveHeldCallToForeground: Auto-unholding held foreground call (call "
+ + "doesn't support hold)");
+ unholdForegroundCallFuture = foregroundCall.unhold();
+ }
+
+ if (mFeatureFlags.enableCallSequencing() && unholdForegroundCallFuture != null) {
+ mCallSequencingAdapter.logFutureResultTransaction(unholdForegroundCallFuture,
+ "maybeMoveHeldCallToForeground", "CM.mMHCTF",
+ "Successfully unheld the foreground call.",
+ "Failed to unhold the foreground call.");
}
}
@@ -4278,7 +4448,7 @@
return getFirstCallWithState(CallState.RINGING, CallState.ANSWERED) != null;
}
- boolean hasRingingOrSimulatedRingingCall() {
+ public boolean hasRingingOrSimulatedRingingCall() {
return getFirstCallWithState(
CallState.SIMULATED_RINGING, CallState.RINGING, CallState.ANSWERED) != null;
}
@@ -4309,12 +4479,14 @@
return true;
}
} else {
+ Log.addEvent(ringingCall, LogUtils.Events.INFO,
+ "media btn short press - answer call.");
answerCall(ringingCall, VideoProfile.STATE_AUDIO_ONLY);
return true;
}
} else if (HeadsetMediaButton.LONG_PRESS == type) {
if (ringingCall != null) {
- Log.addEvent(getForegroundCall(),
+ Log.addEvent(ringingCall,
LogUtils.Events.INFO, "media btn long press - reject");
ringingCall.reject(false, null);
} else {
@@ -4335,6 +4507,7 @@
return true;
}
}
+ Log.i(this, "onMediaButton: type=%d; no active calls", type);
return false;
}
@@ -4426,6 +4599,10 @@
return getFirstCallWithState(null, states);
}
+ public Call getFirstCallWithLiveState() {
+ return getFirstCallWithState(null, LIVE_CALL_STATES);
+ }
+
@VisibleForTesting
public PhoneNumberUtilsAdapter getPhoneNumberUtilsAdapter() {
return mPhoneNumberUtilsAdapter;
@@ -5007,7 +5184,7 @@
return (int) callsStream.count();
}
- private boolean hasMaximumLiveCalls(Call exceptCall) {
+ public boolean hasMaximumLiveCalls(Call exceptCall) {
return MAXIMUM_LIVE_CALLS <= getNumCallsWithState(CALL_FILTER_ALL,
exceptCall, null /* phoneAccountHandle*/, LIVE_CALL_STATES);
}
@@ -5023,7 +5200,7 @@
exceptCall, phoneAccountHandle, ANY_CALL_STATE);
}
- private boolean hasMaximumManagedHoldingCalls(Call exceptCall) {
+ public boolean hasMaximumManagedHoldingCalls(Call exceptCall) {
return MAXIMUM_HOLD_CALLS <= getNumCallsWithState(false /* isSelfManaged */, exceptCall,
null /* phoneAccountHandle */, CallState.ON_HOLD);
}
@@ -5039,7 +5216,7 @@
phoneAccountHandle, CallState.RINGING, CallState.ANSWERED);
}
- private boolean hasMaximumOutgoingCalls(Call exceptCall) {
+ public boolean hasMaximumOutgoingCalls(Call exceptCall) {
return MAXIMUM_LIVE_CALLS <= getNumCallsWithState(CALL_FILTER_ALL,
exceptCall, null /* phoneAccountHandle */, OUTGOING_CALL_STATES);
}
@@ -5151,6 +5328,14 @@
&& incomingCall.getHandoverSourceCall() == null;
}
+ /**
+ * Make room for a pending outgoing emergency {@link Call}.
+ * <p>
+ * Note: This method is only applicable when {@link FeatureFlags#enableCallSequencing()}
+ * is false.
+ * @param emergencyCall The new pending outgoing call.
+ * @return true if room was made, false if no room could be made.
+ */
@VisibleForTesting
public boolean makeRoomForOutgoingEmergencyCall(Call emergencyCall) {
// Always disconnect any ringing/incoming calls when an emergency call is placed to minimize
@@ -5227,6 +5412,10 @@
// If the live call is stuck in a connecting state, prompt the user to generate a bugreport.
if (liveCall.getState() == CallState.CONNECTING) {
+ if (mFeatureFlags.telecomMetricsSupport()) {
+ mMetricsController.getErrorStats().log(ErrorStats.SUB_CALL_MANAGER,
+ ErrorStats.ERROR_STUCK_CONNECTING_EMERGENCY);
+ }
mAnomalyReporter.reportAnomaly(LIVE_CALL_STUCK_CONNECTING_EMERGENCY_ERROR_UUID,
LIVE_CALL_STUCK_CONNECTING_EMERGENCY_ERROR_MSG);
}
@@ -5313,6 +5502,14 @@
return false;
}
+ /**
+ * Make room for a pending outgoing {@link Call}.
+ * <p>
+ * Note: This method is only applicable when {@link FeatureFlags#enableCallSequencing()}
+ * is false.
+ * @param call The new pending outgoing call.
+ * @return true if room was made, false if no room could be made.
+ */
@VisibleForTesting
public boolean makeRoomForOutgoingCall(Call call) {
// Already room!
@@ -5331,37 +5528,12 @@
return true;
}
- // If the live call is stuck in a connecting state for longer than the transitory timeout,
- // then we should disconnect it in favor of the new outgoing call and prompt the user to
- // generate a bugreport.
- // TODO: In the future we should let the CallAnomalyWatchDog do this disconnection of the
- // live call stuck in the connecting state. Unfortunately that code will get tripped up by
- // calls that have a longer than expected new outgoing call broadcast response time. This
- // mitigation is intended to catch calls stuck in a CONNECTING state for a long time that
- // block outgoing calls. However, if the user dials two calls in quick succession it will
- // result in both calls getting disconnected, which is not optimal.
- if (liveCall.getState() == CallState.CONNECTING
- && ((mClockProxy.elapsedRealtime() - liveCall.getCreationElapsedRealtimeMillis())
- > mTimeoutsAdapter.getNonVoipCallTransitoryStateTimeoutMillis())) {
- mAnomalyReporter.reportAnomaly(LIVE_CALL_STUCK_CONNECTING_ERROR_UUID,
- LIVE_CALL_STUCK_CONNECTING_ERROR_MSG);
- liveCall.disconnect("Force disconnect CONNECTING call.");
- return true;
- }
-
- if (hasMaximumOutgoingCalls(call)) {
- Call outgoingCall = getFirstCallWithState(OUTGOING_CALL_STATES);
- if (outgoingCall.getState() == CallState.SELECT_PHONE_ACCOUNT) {
- // If there is an orphaned call in the {@link CallState#SELECT_PHONE_ACCOUNT}
- // state, just disconnect it since the user has explicitly started a new call.
- call.getAnalytics().setCallIsAdditional(true);
- outgoingCall.getAnalytics().setCallIsInterrupted(true);
- outgoingCall.disconnect("Disconnecting call in SELECT_PHONE_ACCOUNT in favor"
- + " of new outgoing call.");
- return true;
- }
- call.setStartFailCause(CallFailureCause.MAX_OUTGOING_CALLS);
- return false;
+ CompletableFuture<Boolean> disconnectFuture =
+ maybeDisconnectExistingCallForNewOutgoingCall(call, liveCall);
+ // If future is instantiated, it will always be completed when call sequencing
+ // isn't enabled.
+ if (!mFeatureFlags.enableCallSequencing() && disconnectFuture != null) {
+ return disconnectFuture.getNow(false);
}
// TODO: Remove once b/23035408 has been corrected.
@@ -5427,12 +5599,70 @@
}
/**
+ * Potentially disconnects the live call if it has been stuck in a connecting state for more
+ * than the designated timeout or the outgoing call if it's stuck in the
+ * {@link CallState#SELECT_PHONE_ACCOUNT} stage.
+ *
+ * @param call The new outgoing call that is being placed.
+ * @param liveCall The first live call that has been detected.
+ * @return The {@link CompletableFuture<Boolean>} representing if room for the outgoing call
+ * could be made, null if further processing is required.
+ */
+ public CompletableFuture<Boolean> maybeDisconnectExistingCallForNewOutgoingCall(Call call,
+ Call liveCall) {
+ // If the live call is stuck in a connecting state for longer than the transitory timeout,
+ // then we should disconnect it in favor of the new outgoing call and prompt the user to
+ // generate a bugreport.
+ // TODO: In the future we should let the CallAnomalyWatchDog do this disconnection of the
+ // live call stuck in the connecting state. Unfortunately that code will get tripped up by
+ // calls that have a longer than expected new outgoing call broadcast response time. This
+ // mitigation is intended to catch calls stuck in a CONNECTING state for a long time that
+ // block outgoing calls. However, if the user dials two calls in quick succession it will
+ // result in both calls getting disconnected, which is not optimal.
+ if (liveCall.getState() == CallState.CONNECTING
+ && ((mClockProxy.elapsedRealtime() - liveCall.getCreationElapsedRealtimeMillis())
+ > mTimeoutsAdapter.getNonVoipCallTransitoryStateTimeoutMillis())) {
+ if (mFeatureFlags.telecomMetricsSupport()) {
+ mMetricsController.getErrorStats().log(ErrorStats.SUB_CALL_MANAGER,
+ ErrorStats.ERROR_STUCK_CONNECTING);
+ }
+ mAnomalyReporter.reportAnomaly(LIVE_CALL_STUCK_CONNECTING_ERROR_UUID,
+ LIVE_CALL_STUCK_CONNECTING_ERROR_MSG);
+ CompletableFuture<Boolean> disconnectFuture =
+ liveCall.disconnect("Force disconnect CONNECTING call.");
+ return mFeatureFlags.enableCallSequencing()
+ ? disconnectFuture
+ : CompletableFuture.completedFuture(true);
+ }
+
+ if (hasMaximumOutgoingCalls(call)) {
+ Call outgoingCall = getFirstCallWithState(OUTGOING_CALL_STATES);
+ if (outgoingCall.getState() == CallState.SELECT_PHONE_ACCOUNT) {
+ // If there is an orphaned call in the {@link CallState#SELECT_PHONE_ACCOUNT}
+ // state, just disconnect it since the user has explicitly started a new call.
+ call.getAnalytics().setCallIsAdditional(true);
+ outgoingCall.getAnalytics().setCallIsInterrupted(true);
+ CompletableFuture<Boolean> disconnectFuture = outgoingCall.disconnect(
+ "Disconnecting call in SELECT_PHONE_ACCOUNT in favor of new "
+ + "outgoing call.");
+ return mFeatureFlags.enableCallSequencing()
+ ? disconnectFuture
+ : CompletableFuture.completedFuture(true);
+ }
+ call.setStartFailCause(CallFailureCause.MAX_OUTGOING_CALLS);
+ return CompletableFuture.completedFuture(false);
+ }
+
+ return null;
+ }
+
+ /**
* Given a call, find the first non-null phone account handle of its children.
*
* @param parentCall The parent call.
* @return The first non-null phone account handle of the children, or {@code null} if none.
*/
- private PhoneAccountHandle getFirstChildPhoneAccount(Call parentCall) {
+ public PhoneAccountHandle getFirstChildPhoneAccount(Call parentCall) {
for (Call childCall : parentCall.getChildCalls()) {
PhoneAccountHandle childPhoneAccount = childCall.getTargetPhoneAccount();
if (childPhoneAccount != null) {
@@ -6472,7 +6702,7 @@
call.can(Connection.CAPABILITY_HOLD)) && call.getState() != CallState.DIALING;
}
- private boolean supportsHold(Call call) {
+ public boolean supportsHold(Call call) {
return call.can(Connection.CAPABILITY_SUPPORT_HOLD);
}
@@ -6514,7 +6744,15 @@
public void performAction() {
synchronized (mLock) {
Log.d(this, "perform unhold call for %s", mCall);
- mCall.unhold("held " + mPreviouslyHeldCallId);
+ CompletableFuture<Boolean> unholdFuture =
+ mCall.unhold("held " + mPreviouslyHeldCallId);
+ if (mFeatureFlags.enableCallSequencing() && unholdFuture != null) {
+ mCallSequencingAdapter.logFutureResultTransaction(unholdFuture,
+ "performAction", "AUC.pA", "performAction: unhold call transaction "
+ + "succeeded. Call state is active.",
+ "performAction: unhold call transaction failed. Call state did not "
+ + "move to active in designated time.");
+ }
}
}
}
@@ -6536,10 +6774,11 @@
listener.onIncomingCallAnswered(mCall);
}
+ CompletableFuture<Boolean> answerCallFuture = null;
// We do not update the UI until we get confirmation of the answer() through
// {@link #markCallAsActive}.
if (mCall.getState() == CallState.RINGING) {
- mCall.answer(mVideoState);
+ answerCallFuture = mCall.answer(mVideoState);
setCallState(mCall, CallState.ANSWERED, "answered");
} else if (mCall.getState() == CallState.SIMULATED_RINGING) {
// If the call's in simulated ringing, we don't have to wait for the CS --
@@ -6550,12 +6789,19 @@
// In certain circumstances, the connection service can lose track of a request
// to answer a call. Therefore, if the user presses answer again, still send it
// on down, but log a warning in the process and don't change the call state.
- mCall.answer(mVideoState);
+ answerCallFuture = mCall.answer(mVideoState);
Log.w(this, "Duplicate answer request for call %s", mCall.getId());
}
if (isSpeakerphoneAutoEnabledForVideoCalls(mVideoState)) {
mCall.setStartWithSpeakerphoneOn(true);
}
+ if (mFeatureFlags.enableCallSequencing() && answerCallFuture != null) {
+ mCallSequencingAdapter.logFutureResultTransaction(answerCallFuture,
+ "performAction", "AAC.pA", "performAction: answer call transaction "
+ + "succeeded. Call state is active.",
+ "performAction: answer call transaction failed. Call state did not "
+ + "move to active in designated time.");
+ }
}
}
}
diff --git a/src/com/android/server/telecom/ConnectionServiceWrapper.java b/src/com/android/server/telecom/ConnectionServiceWrapper.java
index 44686b7..260c238 100644
--- a/src/com/android/server/telecom/ConnectionServiceWrapper.java
+++ b/src/com/android/server/telecom/ConnectionServiceWrapper.java
@@ -45,6 +45,7 @@
import android.telecom.GatewayInfo;
import android.telecom.Log;
import android.telecom.Logging.Session;
+import android.telecom.Logging.Runnable;
import android.telecom.ParcelableConference;
import android.telecom.ParcelableConnection;
import android.telecom.PhoneAccountHandle;
@@ -73,10 +74,13 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.Objects;
@@ -90,10 +94,28 @@
public class ConnectionServiceWrapper extends ServiceBinder implements
ConnectionServiceFocusManager.ConnectionServiceFocus, CallSourceService {
+ /**
+ * Anomaly Report UUIDs and corresponding error descriptions specific to CallsManager.
+ */
+ public static final UUID CREATE_CONNECTION_TIMEOUT_ERROR_UUID =
+ UUID.fromString("54b7203d-a79f-4cbd-b639-85cd93a39cbb");
+ public static final String CREATE_CONNECTION_TIMEOUT_ERROR_MSG =
+ "Timeout expired before Telecom connection was created.";
+ public static final UUID CREATE_CONFERENCE_TIMEOUT_ERROR_UUID =
+ UUID.fromString("caafe5ea-2472-4c61-b2d8-acb9d47e13dd");
+ public static final String CREATE_CONFERENCE_TIMEOUT_ERROR_MSG =
+ "Timeout expired before Telecom conference was created.";
+
private static final String TELECOM_ABBREVIATION = "cast";
+ private static final long SERVICE_BINDING_TIMEOUT = 15000L;
private CompletableFuture<Pair<Integer, Location>> mQueryLocationFuture = null;
private @Nullable CancellationSignal mOngoingQueryLocationRequest = null;
private final ExecutorService mQueryLocationExecutor = Executors.newSingleThreadExecutor();
+ private ScheduledExecutorService mScheduledExecutor =
+ Executors.newSingleThreadScheduledExecutor();
+ // Pre-allocate space for 2 calls; realistically thats all we should ever need (tm)
+ private final Map<Call, ScheduledFuture<?>> mScheduledFutureMap = new ConcurrentHashMap<>(2);
+ private AnomalyReporterAdapter mAnomalyReporter = new AnomalyReporterAdapterImpl();
private final class Adapter extends IConnectionServiceAdapter.Stub {
@@ -107,6 +129,8 @@
try {
synchronized (mLock) {
logIncoming("handleCreateConnectionComplete %s", callId);
+ Call call = mCallIdMapper.getCall(callId);
+ maybeRemoveCleanupFuture(call);
// Check status hints image for cross user access
if (connection.getStatusHints() != null) {
Icon icon = connection.getStatusHints().getIcon();
@@ -145,6 +169,8 @@
try {
synchronized (mLock) {
logIncoming("handleCreateConferenceComplete %s", callId);
+ Call call = mCallIdMapper.getCall(callId);
+ maybeRemoveCleanupFuture(call);
// Check status hints image for cross user access
if (conference.getStatusHints() != null) {
Icon icon = conference.getStatusHints().getIcon();
@@ -1611,6 +1637,29 @@
.setParticipants(call.getParticipants())
.setIsAdhocConferenceCall(call.isAdhocConferenceCall())
.build();
+ Runnable r = new Runnable("CSW.cC", mLock) {
+ @Override
+ public void loggedRun() {
+ if (!call.isCreateConnectionComplete()) {
+ Log.e(this, new Exception(),
+ "Conference %s creation timeout",
+ getComponentName());
+ Log.addEvent(call, LogUtils.Events.CREATE_CONFERENCE_TIMEOUT,
+ Log.piiHandle(call.getHandle()) + " via:" +
+ getComponentName().getPackageName());
+ mAnomalyReporter.reportAnomaly(
+ CREATE_CONFERENCE_TIMEOUT_ERROR_UUID,
+ CREATE_CONFERENCE_TIMEOUT_ERROR_MSG);
+ response.handleCreateConferenceFailure(
+ new DisconnectCause(DisconnectCause.ERROR));
+ }
+ }
+ };
+ // Post cleanup to the executor service and cache the future, so we can cancel it if
+ // needed.
+ ScheduledFuture<?> future = mScheduledExecutor.schedule(r.getRunnableToCancel(),
+ SERVICE_BINDING_TIMEOUT, TimeUnit.MILLISECONDS);
+ mScheduledFutureMap.put(call, future);
try {
mServiceInterface.createConference(
call.getConnectionManagerPhoneAccount(),
@@ -1621,6 +1670,9 @@
Log.getExternalSession(TELECOM_ABBREVIATION));
} catch (RemoteException e) {
Log.e(this, e, "Failure to createConference -- %s", getComponentName());
+ if (mFlags.dontTimeoutDestroyedCalls()) {
+ maybeRemoveCleanupFuture(call);
+ }
mPendingResponses.remove(callId).handleCreateConferenceFailure(
new DisconnectCause(DisconnectCause.ERROR, e.toString()));
}
@@ -1651,6 +1703,9 @@
Log.i(ConnectionServiceWrapper.this, "Call not present"
+ " in call id mapper, maybe it was aborted before the bind"
+ " completed successfully?");
+ if (mFlags.dontTimeoutDestroyedCalls()) {
+ maybeRemoveCleanupFuture(call);
+ }
response.handleCreateConnectionFailure(
new DisconnectCause(DisconnectCause.CANCELED));
return;
@@ -1711,8 +1766,34 @@
.setRttPipeFromInCall(call.getInCallToCsRttPipeForCs())
.setRttPipeToInCall(call.getCsToInCallRttPipeForCs())
.build();
+ Runnable r = new Runnable("CSW.cC", mLock) {
+ @Override
+ public void loggedRun() {
+ if (!call.isCreateConnectionComplete()) {
+ Log.e(this, new Exception(),
+ "Connection %s creation timeout",
+ getComponentName());
+ Log.addEvent(call, LogUtils.Events.CREATE_CONNECTION_TIMEOUT,
+ Log.piiHandle(call.getHandle()) + " via:" +
+ getComponentName().getPackageName());
+ mAnomalyReporter.reportAnomaly(
+ CREATE_CONNECTION_TIMEOUT_ERROR_UUID,
+ CREATE_CONNECTION_TIMEOUT_ERROR_MSG);
+ response.handleCreateConnectionFailure(
+ new DisconnectCause(DisconnectCause.ERROR));
+ }
+ }
+ };
+ // Post cleanup to the executor service and cache the future, so we can cancel it if
+ // needed.
+ ScheduledFuture<?> future = mScheduledExecutor.schedule(r.getRunnableToCancel(),
+ SERVICE_BINDING_TIMEOUT, TimeUnit.MILLISECONDS);
+ mScheduledFutureMap.put(call, future);
try {
if (mFlags.cswServiceInterfaceIsNull() && mServiceInterface == null) {
+ if (mFlags.dontTimeoutDestroyedCalls()) {
+ maybeRemoveCleanupFuture(call);
+ }
mPendingResponses.remove(callId).handleCreateConnectionFailure(
new DisconnectCause(DisconnectCause.ERROR,
"CSW#oCC ServiceInterface is null"));
@@ -1727,6 +1808,9 @@
}
} catch (RemoteException e) {
Log.e(this, e, "Failure to createConnection -- %s", getComponentName());
+ if (mFlags.dontTimeoutDestroyedCalls()) {
+ maybeRemoveCleanupFuture(call);
+ }
mPendingResponses.remove(callId).handleCreateConnectionFailure(
new DisconnectCause(DisconnectCause.ERROR, e.toString()));
}
@@ -2182,7 +2266,8 @@
}
}
- void addCall(Call call) {
+ @VisibleForTesting
+ public void addCall(Call call) {
if (mCallIdMapper.getCallId(call) == null) {
mCallIdMapper.addCall(call);
}
@@ -2205,6 +2290,9 @@
if (response != null) {
response.handleCreateConnectionFailure(disconnectCause);
}
+ if (mFlags.dontTimeoutDestroyedCalls()) {
+ maybeRemoveCleanupFuture(mCallIdMapper.getCall(callId));
+ }
mCallIdMapper.removeCall(callId);
}
@@ -2214,6 +2302,9 @@
if (response != null) {
response.handleCreateConnectionFailure(disconnectCause);
}
+ if (mFlags.dontTimeoutDestroyedCalls()) {
+ maybeRemoveCleanupFuture(call);
+ }
mCallIdMapper.removeCall(call);
}
@@ -2399,6 +2490,13 @@
@Override
protected void removeServiceInterface() {
Log.v(this, "Removing Connection Service Adapter.");
+ if (mServiceInterface == null) {
+ // In some cases, we may receive multiple calls to
+ // remoteServiceInterface, such as when the remote process crashes
+ // (onBinderDied & onServiceDisconnected)
+ Log.w(this, "removeServiceInterface: mServiceInterface is null");
+ return;
+ }
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
@@ -2407,6 +2505,10 @@
handleConnectionServiceDeath();
mCallsManager.handleConnectionServiceDeath(this);
mServiceInterface = null;
+ if (mScheduledExecutor != null) {
+ mScheduledExecutor.shutdown();
+ mScheduledExecutor = null;
+ }
}
@Override
@@ -2526,6 +2628,7 @@
}
}
mCallIdMapper.clear();
+ mScheduledFutureMap.clear();
if (mConnSvrFocusListener != null) {
mConnSvrFocusListener.onConnectionServiceDeath(this);
@@ -2651,4 +2754,30 @@
sb.append("]");
return sb.toString();
}
+
+ @VisibleForTesting
+ public void setScheduledExecutorService(ScheduledExecutorService service) {
+ mScheduledExecutor = service;
+ }
+
+ @VisibleForTesting
+ public void setAnomalyReporterAdapter(AnomalyReporterAdapter mAnomalyReporterAdapter){
+ mAnomalyReporter = mAnomalyReporterAdapter;
+ }
+
+ /**
+ * Given a call, unschedule and cancel the cleanup future.
+ * @param call the call.
+ */
+ private void maybeRemoveCleanupFuture(Call call) {
+ if (call == null) {
+ return;
+ }
+ ScheduledFuture<?> future = mScheduledFutureMap.remove(call);
+ if (future == null) {
+ return;
+ }
+ future.cancel(false /* interrupt */);
+
+ }
}
diff --git a/src/com/android/server/telecom/DefaultDialerCache.java b/src/com/android/server/telecom/DefaultDialerCache.java
index 44b426a..98289ed 100644
--- a/src/com/android/server/telecom/DefaultDialerCache.java
+++ b/src/com/android/server/telecom/DefaultDialerCache.java
@@ -31,76 +31,58 @@
import android.provider.Settings;
import android.telecom.DefaultDialerManager;
import android.telecom.Log;
-import android.util.SparseArray;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.IndentingPrintWriter;
import java.util.Objects;
+import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.function.IntConsumer;
public class DefaultDialerCache {
- public interface DefaultDialerManagerAdapter {
- String getDefaultDialerApplication(Context context);
- String getDefaultDialerApplication(Context context, int userId);
- boolean setDefaultDialerApplication(Context context, String packageName, int userId);
- }
-
- static class DefaultDialerManagerAdapterImpl implements DefaultDialerManagerAdapter {
- @Override
- public String getDefaultDialerApplication(Context context) {
- return DefaultDialerManager.getDefaultDialerApplication(context);
- }
-
- @Override
- public String getDefaultDialerApplication(Context context, int userId) {
- return DefaultDialerManager.getDefaultDialerApplication(context, userId);
- }
-
- @Override
- public boolean setDefaultDialerApplication(Context context, String packageName,
- int userId) {
- return DefaultDialerManager.setDefaultDialerApplication(context, packageName, userId);
- }
- }
-
private static final String LOG_TAG = "DefaultDialerCache";
+ @VisibleForTesting
+ public final Handler mHandler = new Handler(Looper.getMainLooper());
+ private final Context mContext;
+ private final DefaultDialerManagerAdapter mDefaultDialerManagerAdapter;
+ private final ComponentName mSystemDialerComponentName;
+ private final RoleManagerAdapter mRoleManagerAdapter;
+ private final ConcurrentHashMap<Integer, String> mCurrentDefaultDialerPerUser =
+ new ConcurrentHashMap<>();
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- Log.startSession("DDC.oR");
- try {
- String packageName;
- if (Intent.ACTION_PACKAGE_CHANGED.equals(intent.getAction())) {
- packageName = null;
- } else if (Intent.ACTION_PACKAGE_REMOVED.equals(intent.getAction())
- && !intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
- packageName = intent.getData().getSchemeSpecificPart();
- } else if (Intent.ACTION_PACKAGE_ADDED.equals(intent.getAction())) {
- packageName = null;
- } else if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) {
- packageName = null;
- } else {
- return;
- }
+ mHandler.post(() -> {
+ Log.startSession("DDC.oR");
+ try {
+ String packageName;
+ if (Intent.ACTION_PACKAGE_CHANGED.equals(intent.getAction())) {
+ packageName = null;
+ } else if (Intent.ACTION_PACKAGE_REMOVED.equals(intent.getAction())
+ && !intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
+ packageName = intent.getData().getSchemeSpecificPart();
+ } else if (Intent.ACTION_PACKAGE_ADDED.equals(intent.getAction())) {
+ packageName = null;
+ } else if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) {
+ packageName = null;
+ } else {
+ return;
+ }
- synchronized (mLock) {
refreshCachesForUsersWithPackage(packageName);
+ } finally {
+ Log.endSession();
}
-
- } finally {
- Log.endSession();
- }
+ });
}
};
-
private final BroadcastReceiver mUserRemovedReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (Intent.ACTION_USER_REMOVED.equals(intent.getAction())) {
int removedUser = intent.getIntExtra(Intent.EXTRA_USER_HANDLE,
- UserHandle.USER_NULL);
+ UserHandle.USER_NULL);
if (removedUser == UserHandle.USER_NULL) {
Log.w(LOG_TAG, "Expected EXTRA_USER_HANDLE with ACTION_USER_REMOVED");
} else {
@@ -110,8 +92,6 @@
}
}
};
-
- private final Handler mHandler = new Handler(Looper.getMainLooper());
private final ContentObserver mDefaultDialerObserver = new ContentObserver(mHandler) {
@Override
public void onChange(boolean selfChange) {
@@ -119,9 +99,7 @@
try {
// We don't get the user ID of the user that changed here, so we'll have to
// refresh all of the users.
- synchronized (mLock) {
- refreshCachesForUsersWithPackage(null);
- }
+ refreshCachesForUsersWithPackage(null);
} finally {
Log.endSession();
}
@@ -132,29 +110,21 @@
return true;
}
};
-
- private final Context mContext;
- private final DefaultDialerManagerAdapter mDefaultDialerManagerAdapter;
- private final TelecomSystem.SyncRoot mLock;
- private final ComponentName mSystemDialerComponentName;
- private final RoleManagerAdapter mRoleManagerAdapter;
- private SparseArray<String> mCurrentDefaultDialerPerUser = new SparseArray<>();
private ComponentName mOverrideSystemDialerComponentName;
public DefaultDialerCache(Context context,
- DefaultDialerManagerAdapter defaultDialerManagerAdapter,
- RoleManagerAdapter roleManagerAdapter,
- TelecomSystem.SyncRoot lock) {
+ DefaultDialerManagerAdapter defaultDialerManagerAdapter,
+ RoleManagerAdapter roleManagerAdapter,
+ TelecomSystem.SyncRoot lock) {
mContext = context;
mDefaultDialerManagerAdapter = defaultDialerManagerAdapter;
mRoleManagerAdapter = roleManagerAdapter;
- mLock = lock;
+
Resources resources = mContext.getResources();
mSystemDialerComponentName = new ComponentName(resources.getString(
com.android.internal.R.string.config_defaultDialer),
resources.getString(R.string.incall_default_class));
-
IntentFilter packageIntentFilter = new IntentFilter();
packageIntentFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
packageIntentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
@@ -195,7 +165,7 @@
//
//synchronized (mLock) {
// String defaultDialer = mCurrentDefaultDialerPerUser.get(userId);
- // if (defaultDialer != null) {
+ // if (!TextUtils.isEmpty(defaultDialer)) {
// return defaultDialer;
// }
//}
@@ -241,11 +211,9 @@
public boolean setDefaultDialer(String packageName, int userId) {
boolean isChanged = mDefaultDialerManagerAdapter.setDefaultDialerApplication(
mContext, packageName, userId);
- if(isChanged) {
- synchronized (mLock) {
- // Update the cache synchronously so that there is no delay in cache update.
- mCurrentDefaultDialerPerUser.put(userId, packageName);
- }
+ if (isChanged) {
+ // Update the cache synchronously so that there is no delay in cache update.
+ mCurrentDefaultDialerPerUser.put(userId, packageName == null ? "" : packageName);
}
return isChanged;
}
@@ -253,47 +221,39 @@
private String refreshCacheForUser(int userId) {
String currentDefaultDialer =
mRoleManagerAdapter.getDefaultDialerApp(userId);
- synchronized (mLock) {
- mCurrentDefaultDialerPerUser.put(userId, currentDefaultDialer);
- }
+ mCurrentDefaultDialerPerUser.put(userId, currentDefaultDialer == null ? "" :
+ currentDefaultDialer);
return currentDefaultDialer;
}
/**
* Refreshes the cache for users that currently have packageName as their cached default dialer.
* If packageName is null, refresh all caches.
+ *
* @param packageName Name of the affected package.
*/
private void refreshCachesForUsersWithPackage(String packageName) {
- for (int i = 0; i < mCurrentDefaultDialerPerUser.size(); i++) {
- int userId = mCurrentDefaultDialerPerUser.keyAt(i);
- if (packageName == null ||
- Objects.equals(packageName, mCurrentDefaultDialerPerUser.get(userId))) {
+ mCurrentDefaultDialerPerUser.forEach((userId, currentName) -> {
+ if (packageName == null || Objects.equals(packageName, currentName)) {
String newDefaultDialer = refreshCacheForUser(userId);
Log.v(LOG_TAG, "Refreshing default dialer for user %d: now %s",
userId, newDefaultDialer);
}
- }
+ });
}
public void dumpCache(IndentingPrintWriter pw) {
- synchronized (mLock) {
- for (int i = 0; i < mCurrentDefaultDialerPerUser.size(); i++) {
- pw.printf("User %d: %s\n", mCurrentDefaultDialerPerUser.keyAt(i),
- mCurrentDefaultDialerPerUser.valueAt(i));
- }
- }
+ mCurrentDefaultDialerPerUser.forEach((k, v) -> pw.printf("User %d: %s\n", k, v));
}
private void removeUserFromCache(int userId) {
- synchronized (mLock) {
- mCurrentDefaultDialerPerUser.remove(userId);
- }
+ mCurrentDefaultDialerPerUser.remove(userId);
}
/**
* registerContentObserver is really hard to mock out, so here is a getter method for the
* content observer for testing instead.
+ *
* @return The content observer
*/
@VisibleForTesting
@@ -304,4 +264,30 @@
public RoleManagerAdapter getRoleManagerAdapter() {
return mRoleManagerAdapter;
}
-}
\ No newline at end of file
+
+ public interface DefaultDialerManagerAdapter {
+ String getDefaultDialerApplication(Context context);
+
+ String getDefaultDialerApplication(Context context, int userId);
+
+ boolean setDefaultDialerApplication(Context context, String packageName, int userId);
+ }
+
+ static class DefaultDialerManagerAdapterImpl implements DefaultDialerManagerAdapter {
+ @Override
+ public String getDefaultDialerApplication(Context context) {
+ return DefaultDialerManager.getDefaultDialerApplication(context);
+ }
+
+ @Override
+ public String getDefaultDialerApplication(Context context, int userId) {
+ return DefaultDialerManager.getDefaultDialerApplication(context, userId);
+ }
+
+ @Override
+ public boolean setDefaultDialerApplication(Context context, String packageName,
+ int userId) {
+ return DefaultDialerManager.setDefaultDialerApplication(context, packageName, userId);
+ }
+ }
+}
diff --git a/src/com/android/server/telecom/HeadsetMediaButton.java b/src/com/android/server/telecom/HeadsetMediaButton.java
index 7458f54..afc82ae 100644
--- a/src/com/android/server/telecom/HeadsetMediaButton.java
+++ b/src/com/android/server/telecom/HeadsetMediaButton.java
@@ -103,7 +103,7 @@
if ((event != null) && ((event.getKeyCode() == KeyEvent.KEYCODE_HEADSETHOOK) ||
(event.getKeyCode() == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE))) {
synchronized (mLock) {
- Log.v(this, "SessionCallback: HEADSETHOOK/MEDIA_PLAY_PAUSE");
+ Log.i(this, "onMediaButton: event=%s", event);
boolean consumed = handleCallMediaButton(event);
Log.v(this, "==> handleCallMediaButton(): consumed = %b.", consumed);
return consumed;
diff --git a/src/com/android/server/telecom/InCallController.java b/src/com/android/server/telecom/InCallController.java
index 529bc79..3f8f579 100644
--- a/src/com/android/server/telecom/InCallController.java
+++ b/src/com/android/server/telecom/InCallController.java
@@ -305,7 +305,7 @@
//this is really used for cases where the userhandle for a call
//does not match what we want to use for bindAsUser
- private final UserHandle mUserHandleToUseForBinding;
+ private UserHandle mUserHandleToUseForBinding;
public InCallServiceBindingConnection(InCallServiceInfo info) {
mInCallServiceInfo = info;
@@ -388,6 +388,8 @@
+ "INTERACT_ACROSS_USERS permission");
}
}
+ // Used for referencing what user we used to bind to the given ICS.
+ mUserHandleToUseForBinding = userToBind;
Log.i(this, "using user id: %s for binding. User from Call is: %s", userToBind,
userFromCall);
if (!mContext.bindServiceAsUser(intent, mServiceConnection,
@@ -1230,7 +1232,7 @@
mCombinedInCallServiceMap = new ArrayMap<>();
private final CallIdMapper mCallIdMapper = new CallIdMapper(Call::getId);
- private final Collection<Call> mPendingEndToneCall = new ArraySet<>();
+ private final Collection<Call> mBtIcsCallTracker = new ArraySet<>();
private final Context mContext;
private final AppOpsManager mAppOpsManager;
@@ -1246,7 +1248,7 @@
mInCallServiceConnections = new ArrayMap<>();
private final Map<UserHandle, NonUIInCallServiceConnectionCollection>
mNonUIInCallServiceConnections = new ArrayMap<>();
- private final Map<UserHandle, InCallServiceConnection> mBTInCallServiceConnections =
+ private final Map<UserHandle, InCallServiceBindingConnection> mBTInCallServiceConnections =
new ArrayMap<>();
private final ClockProxy mClockProxy;
private final IBinder mToken = new Binder();
@@ -1421,6 +1423,7 @@
bindingToBtRequired = true;
bindToBTService(call, null);
}
+
if (!isBoundAndConnectedToServices(userFromCall)) {
Log.i(this, "onCallAdded: %s; not bound or connected to other ICS.", call);
// We are not bound, or we're not connected.
@@ -1565,39 +1568,85 @@
+ "disconnected tone future");
mDisconnectedToneBtFutures.get(call.getId()).complete(null);
}
- mPendingEndToneCall.remove(call);
- if (!mPendingEndToneCall.isEmpty()) {
- return;
- }
- UserHandle userHandle = getUserFromCall(call);
- if (mBTInCallServiceConnections.containsKey(userHandle)) {
- Log.i(this, "onDisconnectedTonePlaying: Schedule unbind BT service");
- final InCallServiceConnection connection =
- mBTInCallServiceConnections.get(userHandle);
-
- // Similar to in onCallRemoved when we unbind from the other ICS, we need to
- // delay unbinding from the BT ICS because we need to give the ICS a
- // moment to finish the onCallRemoved signal it got just prior.
- mHandler.postDelayed(new Runnable("ICC.oDCTP", mLock) {
- @Override
- public void loggedRun() {
- Log.i(this, "onDisconnectedTonePlaying: unbinding");
- connection.disconnect();
- }
- }.prepare(), mTimeoutsAdapter.getCallRemoveUnbindInCallServicesDelay(
- mContext.getContentResolver()));
-
- mBTInCallServiceConnections.remove(userHandle);
- }
- // Ensure that BT ICS instance is cleaned up
- if (mBTInCallServices.remove(userHandle) != null) {
- updateCombinedInCallServiceMap(userHandle);
- }
+ // Schedule unbinding of BT ICS.
+ maybeScheduleBtUnbind(call);
}
}
}
}
+ public void maybeScheduleBtUnbind(Call call) {
+ mBtIcsCallTracker.remove(call);
+ // Track the current calls that are being tracked by the BT ICS and determine the
+ // associated users of those calls as well as the users which have been used to bind to the
+ // ICS.
+ Set<UserHandle> usersFromOngoingCalls = new ArraySet<>();
+ Set<UserHandle> usersCurrentlyBound = new ArraySet<>();
+ for (Call pendingCall : mBtIcsCallTracker) {
+ UserHandle userFromPendingCall = getUserFromCall(pendingCall);
+ final InCallServiceBindingConnection pendingCallConnection =
+ mBTInCallServiceConnections.get(userFromPendingCall);
+ usersFromOngoingCalls.add(userFromPendingCall);
+ if (pendingCallConnection != null) {
+ usersCurrentlyBound.add(pendingCallConnection.mUserHandleToUseForBinding);
+ }
+ }
+
+ UserHandle userHandle = getUserFromCall(call);
+ // Refrain from unbinding ICS and clearing the ICS mapping if there's an ongoing call under
+ // the same associated user. Make sure we keep the internal mappings so that they aren't
+ // cleared until that call is disconnected. Note here that if the associated users are the
+ // same, the user used for the binding will also be the same.
+ if (usersFromOngoingCalls.contains(userHandle)) {
+ Log.i(this, "scheduleBtUnbind: Refraining from unbinding BT service due to an ongoing "
+ + "call detected under the same user (%s).", userHandle);
+ return;
+ }
+
+ if (mBTInCallServiceConnections.containsKey(userHandle)) {
+ Log.i(this, "scheduleBtUnbind: Schedule unbind BT service");
+ final InCallServiceBindingConnection connection =
+ mBTInCallServiceConnections.get(userHandle);
+ // The user that was used for binding may be different than the user from call
+ // (associated user), which is what we use to reference the BT ICS bindings. For
+ // example, consider the work profile scenario where the BT ICS is only available under
+ // User 0: in this case, the user to bind to will be User 0 whereas we store the
+ // references to this connection and BT ICS under the work user. This logic ensures
+ // that we prevent unbinding the BT ICS if there is a personal (associatedUser: 0) call
+ // + work call (associatedUser: 10) and one of them gets disconnected.
+ if (usersCurrentlyBound.contains(connection.mUserHandleToUseForBinding)) {
+ Log.i(this, "scheduleBtUnbind: Refraining from unbinding BT service to an "
+ + "ongoing call detected which is bound to the same user (%s).",
+ connection.mUserHandleToUseForBinding);
+ } else {
+ // Similar to in onCallRemoved when we unbind from the other ICS, we need to
+ // delay unbinding from the BT ICS because we need to give the ICS a
+ // moment to finish the onCallRemoved signal it got just prior.
+ mHandler.postDelayed(new Runnable("ICC.sBU", mLock) {
+ @Override
+ public void loggedRun() {
+ Log.i(this, "onDisconnectedTonePlaying: unbinding from BT ICS.");
+ // Prevent unbinding in the case that this is run while another call
+ // has been placed/received. Otherwise, we will early unbind from
+ // the BT ICS and not be able to properly relay call state updates.
+ if (!mBTInCallServiceConnections.containsKey(userHandle)) {
+ connection.disconnect();
+ } else {
+ Log.i(this, "onDisconnectedTonePlaying: Refraining from "
+ + "unbinding BT ICS. Another call is ongoing.");
+ }
+ }
+ }.prepare(), mTimeoutsAdapter.getCallRemoveUnbindInCallServicesDelay(
+ mContext.getContentResolver()));
+ }
+ mBTInCallServiceConnections.remove(userHandle);
+ }
+ // Ensure that BT ICS instance is cleaned up
+ if (mBTInCallServices.remove(userHandle) != null) {
+ updateCombinedInCallServiceMap(userHandle);
+ }
+ }
+
@Override
public void onExternalCallChanged(Call call, boolean isExternalCall) {
Log.i(this, "onExternalCallChanged: %s -> %b", call, isExternalCall);
@@ -1873,7 +1922,6 @@
}
}
- @VisibleForTesting
public void bringToForeground(boolean showDialpad, UserHandle callingUser) {
KeyguardManager keyguardManager = mContext.getSystemService(KeyguardManager.class);
boolean isLockscreenRestricted = keyguardManager != null
@@ -2778,7 +2826,9 @@
"updateCall: (deferred) Sending call disconnected update "
+ "to BT ICS.");
updateCallToIcs(inCallService, info, parcelableCall, componentName);
- mDisconnectedToneBtFutures.remove(call.getId());
+ synchronized (mLock) {
+ mDisconnectedToneBtFutures.remove(call.getId());
+ }
});
mDisconnectedToneBtFutures.put(call.getId(), disconnectedToneFuture);
} else {
@@ -2832,7 +2882,7 @@
mCallIdMapper.addCall(call);
call.addListener(mCallListener);
if (mFeatureFlags.separatelyBindToBtIncallService()) {
- mPendingEndToneCall.add(call);
+ mBtIcsCallTracker.add(call);
}
}
diff --git a/src/com/android/server/telecom/LogUtils.java b/src/com/android/server/telecom/LogUtils.java
index 0d6acd5..d98ebfe 100644
--- a/src/com/android/server/telecom/LogUtils.java
+++ b/src/com/android/server/telecom/LogUtils.java
@@ -139,8 +139,10 @@
public static final String STOP_CALL_WAITING_TONE = "STOP_CALL_WAITING_TONE";
public static final String START_CONNECTION = "START_CONNECTION";
public static final String CREATE_CONNECTION_FAILED = "CREATE_CONNECTION_FAILED";
+ public static final String CREATE_CONNECTION_TIMEOUT = "CREATE_CONNECTION_TIMEOUT";
public static final String START_CONFERENCE = "START_CONFERENCE";
public static final String CREATE_CONFERENCE_FAILED = "CREATE_CONFERENCE_FAILED";
+ public static final String CREATE_CONFERENCE_TIMEOUT = "CREATE_CONFERENCE_TIMEOUT";
public static final String BIND_CS = "BIND_CS";
public static final String CS_BOUND = "CS_BOUND";
public static final String CONFERENCE_WITH = "CONF_WITH";
diff --git a/src/com/android/server/telecom/PendingAudioRoute.java b/src/com/android/server/telecom/PendingAudioRoute.java
index ffde964..dde1d8d 100644
--- a/src/com/android/server/telecom/PendingAudioRoute.java
+++ b/src/com/android/server/telecom/PendingAudioRoute.java
@@ -70,8 +70,23 @@
mCommunicationDeviceType = AudioRoute.TYPE_INVALID;
}
- void setOrigRoute(boolean active, AudioRoute origRoute) {
- origRoute.onOrigRouteAsPendingRoute(active, this, mAudioManager, mBluetoothRouteManager);
+ /**
+ * Sets the originating route information, and begins the process of transitioning OUT of the
+ * originating route.
+ * Note: We also pass in whether the destination route is going to be active. This is so that
+ * {@link AudioRoute#onOrigRouteAsPendingRoute(boolean, PendingAudioRoute, AudioManager,
+ * BluetoothRouteManager)} knows whether or not the destination route will be active or not and
+ * can determine whether or not it needs to call {@link AudioManager#clearCommunicationDevice()}
+ * or not. To optimize audio performance we only need to clear the communication device if the
+ * end result is going to be that we are in an inactive state.
+ * @param isOriginActive Whether the origin is active.
+ * @param origRoute The origin.
+ * @param isDestActive Whether the destination will be active.
+ */
+ void setOrigRoute(boolean isOriginActive, AudioRoute origRoute, boolean isDestActive) {
+ mActive = isDestActive;
+ origRoute.onOrigRouteAsPendingRoute(isOriginActive, this, mAudioManager,
+ mBluetoothRouteManager);
mOrigRoute = origRoute;
}
@@ -130,6 +145,14 @@
mPendingMessages.remove(message);
}
+ public Set<Pair<Integer, String>> getPendingMessages() {
+ return mPendingMessages;
+ }
+
+ /**
+ * Whether the destination {@link #getDestRoute()} will be active or not.
+ * @return {@code true} if destination will be active, {@code false} otherwise.
+ */
public boolean isActive() {
return mActive;
}
@@ -146,4 +169,18 @@
public void overrideDestRoute(AudioRoute route) {
mDestRoute = route;
}
+
+ public FeatureFlags getFeatureFlags() {
+ return mFeatureFlags;
+ }
+
+ @Override
+ public String toString() {
+ return "PendingAudioRoute{" +
+ ", mOrigRoute=" + mOrigRoute +
+ ", mDestRoute=" + mDestRoute +
+ ", mActive=" + mActive +
+ ", mCommunicationDeviceType=" + mCommunicationDeviceType +
+ '}';
+ }
}
diff --git a/src/com/android/server/telecom/PhoneAccountRegistrar.java b/src/com/android/server/telecom/PhoneAccountRegistrar.java
index f0423c3..1a1af92 100644
--- a/src/com/android/server/telecom/PhoneAccountRegistrar.java
+++ b/src/com/android/server/telecom/PhoneAccountRegistrar.java
@@ -1284,12 +1284,15 @@
boolean isNewAccount;
// add self-managed capability for transactional accounts that are missing it
- if (hasTransactionalCallCapabilities(account) &&
- !account.hasCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED)) {
+ if (hasTransactionalCallCapabilities(account)
+ && !account.hasCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED)) {
account = account.toBuilder()
.setCapabilities(account.getCapabilities()
| PhoneAccount.CAPABILITY_SELF_MANAGED)
.build();
+ // Note: below we will automatically remove CAPABILITY_CONNECTION_MANAGER,
+ // CAPABILITY_CALL_PROVIDER, and CAPABILITY_SIM_SUBSCRIPTION if this magically becomes
+ // a self-managed phone account here.
}
PhoneAccount oldAccount = getPhoneAccountUnchecked(account.getAccountHandle());
@@ -1310,6 +1313,12 @@
if (account.hasCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED)) {
// Turn off bits we don't want to be able to set (TelecomServiceImpl protects against
// this but we'll also prevent it from happening here, just to be safe).
+ if ((account.getCapabilities() & (PhoneAccount.CAPABILITY_CALL_PROVIDER
+ | PhoneAccount.CAPABILITY_CONNECTION_MANAGER
+ | PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)) > 0) {
+ Log.w(this, "addOrReplacePhoneAccount: attempt to register a "
+ + "VoIP phone account with call provider/cm/sim sub capabilities.");
+ }
int newCapabilities = account.getCapabilities() &
~(PhoneAccount.CAPABILITY_CALL_PROVIDER |
PhoneAccount.CAPABILITY_CONNECTION_MANAGER |
@@ -1317,7 +1326,10 @@
// Ensure name is correct.
CharSequence newLabel = mAppLabelProxy.getAppLabel(
- account.getAccountHandle().getComponentName().getPackageName());
+ account.getAccountHandle().getComponentName().getPackageName(),
+ UserUtil.getAssociatedUserForCall(
+ mTelecomFeatureFlags.associatedUserRefactorForWorkProfile(),
+ this, UserHandle.CURRENT, account.getAccountHandle()));
account = account.toBuilder()
.setLabel(newLabel)
diff --git a/src/com/android/server/telecom/Ringer.java b/src/com/android/server/telecom/Ringer.java
index c309dd5..bfaadf0 100644
--- a/src/com/android/server/telecom/Ringer.java
+++ b/src/com/android/server/telecom/Ringer.java
@@ -59,6 +59,7 @@
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
+import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
@@ -176,6 +177,11 @@
private static VolumeShaper.Configuration mVolumeShaperConfig;
+ public static final UUID GET_RINGER_MODE_ANOMALY_UUID =
+ UUID.fromString("eb10505b-4d7b-4fab-b4a1-a18186799065");
+ public static final String GET_RINGER_MODE_ANOMALY_MSG = "AM#GetRingerMode() and"
+ + " AM#GetRingerModeInternal() are returning diff values when DoNotDisturb is OFF!";
+
/**
* Used to keep ordering of unanswered incoming calls. There can easily exist multiple incoming
* calls and explicit ordering is useful for maintaining the proper state of the ringer.
@@ -191,6 +197,8 @@
private final boolean mIsHapticPlaybackSupportedByDevice;
private final FeatureFlags mFlags;
private final boolean mRingtoneVibrationSupported;
+ private final AnomalyReporterAdapter mAnomalyReporter;
+
/**
* For unit testing purposes only; when set, {@link #startRinging(Call, boolean)} will complete
* the future provided by the test using {@link #setBlockOnRingingFuture(CompletableFuture)}.
@@ -237,7 +245,8 @@
InCallController inCallController,
NotificationManager notificationManager,
AccessibilityManagerAdapter accessibilityManagerAdapter,
- FeatureFlags featureFlags) {
+ FeatureFlags featureFlags,
+ AnomalyReporterAdapter anomalyReporter) {
mLock = new Object();
mSystemSettingsUtil = systemSettingsUtil;
@@ -252,6 +261,7 @@
mVibrationEffectProxy = vibrationEffectProxy;
mNotificationManager = notificationManager;
mAccessibilityManagerAdapter = accessibilityManagerAdapter;
+ mAnomalyReporter = anomalyReporter;
mDefaultVibrationEffect =
loadDefaultRingVibrationEffect(
@@ -358,6 +368,12 @@
mVolumeShaperConfig = null;
+ String vibratorAttrs = String.format("hasVibrator=%b, userRequestsVibrate=%b, "
+ + "ringerMode=%d, isVibratorEnabled=%b",
+ mVibrator.hasVibrator(),
+ mSystemSettingsUtil.isRingVibrationEnabled(mContext),
+ mAudioManager.getRingerMode(), isVibratorEnabled);
+
if (attributes.isRingerAudible()) {
mRingingCall = foregroundCall;
Log.addEvent(foregroundCall, LogUtils.Events.START_RINGER);
@@ -399,11 +415,12 @@
// If ringer is not audible for this call, then the phone is in "Vibrate" mode.
// Use haptic-only ringtone or do not play anything.
isHapticOnly = true;
- if (DEBUG_RINGER) {
- Log.i(this, "Set ringtone as haptic only: " + isHapticOnly);
- }
+ Log.i(this, "Set ringtone as haptic only: " + isHapticOnly);
} else {
+ Log.i(this, "ringer & haptics are off, user missed alerts for call");
foregroundCall.setUserMissed(USER_MISSED_NO_VIBRATE);
+ Log.addEvent(foregroundCall, LogUtils.Events.SKIP_VIBRATION,
+ vibratorAttrs);
return attributes.shouldAcquireAudioFocus(); // ringer not audible
}
}
@@ -429,18 +446,14 @@
ringtoneInfoSupplier = () -> mRingtoneFactory.getRingtone(
foregroundCall, null, false);
}
-
+ Log.i(this, "isRingtoneInfoSupplierNull=[%b]", ringtoneInfoSupplier == null);
// If vibration will be done, reserve the vibrator.
boolean vibratorReserved = isVibratorEnabled && attributes.shouldRingForContact()
&& tryReserveVibration(foregroundCall);
if (!vibratorReserved) {
foregroundCall.setUserMissed(USER_MISSED_NO_VIBRATE);
Log.addEvent(foregroundCall, LogUtils.Events.SKIP_VIBRATION,
- "hasVibrator=%b, userRequestsVibrate=%b, ringerMode=%d, "
- + "isVibratorEnabled=%b",
- mVibrator.hasVibrator(),
- mSystemSettingsUtil.isRingVibrationEnabled(mContext),
- mAudioManager.getRingerMode(), isVibratorEnabled);
+ vibratorAttrs);
}
// The vibration logic depends on the loaded ringtone, but we need to defer the ringtone
@@ -556,6 +569,11 @@
mIsVibrating = true;
mVibrator.vibrate(effect, VIBRATION_ATTRIBUTES);
Log.i(this, "start vibration.");
+ } else {
+ Log.i(this, "vibrateIfNeeded: skip; isVibrating=%b, fgCallId=%s, vibratingCall=%s",
+ mIsVibrating,
+ (foregroundCall == null ? "null" : foregroundCall.getId()),
+ (mVibratingCall == null ? "null" : mVibratingCall.getId()));
}
// else stopped already: this isn't started unless a reservation was made.
}
@@ -697,12 +715,43 @@
// AudioManager#getRingerModeInternal which only useful for volume controllers
boolean zenModeOn = mNotificationManager != null
&& mNotificationManager.getZenMode() != ZEN_MODE_OFF;
+ maybeGenAnomReportForGetRingerMode(zenModeOn, audioManager);
return mVibrator.hasVibrator()
&& mSystemSettingsUtil.isRingVibrationEnabled(context)
&& (audioManager.getRingerMode() != AudioManager.RINGER_MODE_SILENT
|| (zenModeOn && shouldRingForContact));
}
+ /**
+ * There are 3 settings for haptics:
+ * - AudioManager.RINGER_MODE_SILENT
+ * - AudioManager.RINGER_MODE_VIBRATE
+ * - AudioManager.RINGER_MODE_NORMAL
+ * If the user does not have {@link AudioManager#RINGER_MODE_SILENT} set, the user should
+ * have haptic feeback
+ *
+ * Note: If DND/ZEN_MODE is on, {@link AudioManager#getRingerMode()} will return
+ * {@link AudioManager#RINGER_MODE_SILENT}, regardless of the user setting. Therefore,
+ * getRingerModeInternal is the source of truth instead of {@link AudioManager#getRingerMode()}.
+ * However, if DND/ZEN_MOD is off, the APIs should return the same value. Generate an anomaly
+ * report if they diverge.
+ */
+ private void maybeGenAnomReportForGetRingerMode(boolean isZenModeOn, AudioManager am) {
+ if (!mFlags.getRingerModeAnomReport()) {
+ return;
+ }
+ if (!isZenModeOn) {
+ int ringerMode = am.getRingerMode();
+ int ringerModeInternal = am.getRingerModeInternal();
+ if (ringerMode != ringerModeInternal) {
+ Log.i(this, "getRingerMode=[%d], getRingerModeInternal=[%d]",
+ ringerMode, ringerModeInternal);
+ mAnomalyReporter.reportAnomaly(GET_RINGER_MODE_ANOMALY_UUID,
+ GET_RINGER_MODE_ANOMALY_MSG);
+ }
+ }
+ }
+
private RingerAttributes getRingerAttributes(Call call, boolean isHfpDeviceAttached) {
mAudioManager = mContext.getSystemService(AudioManager.class);
RingerAttributes.Builder builder = new RingerAttributes.Builder();
diff --git a/src/com/android/server/telecom/TelecomServiceImpl.java b/src/com/android/server/telecom/TelecomServiceImpl.java
index b8141bf..88adf14 100644
--- a/src/com/android/server/telecom/TelecomServiceImpl.java
+++ b/src/com/android/server/telecom/TelecomServiceImpl.java
@@ -52,12 +52,12 @@
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
import android.os.OutcomeReceiver;
import android.os.ParcelFileDescriptor;
import android.os.Process;
import android.os.RemoteException;
-import android.os.ResultReceiver;
-import android.os.ShellCallback;
import android.os.UserHandle;
import android.provider.BlockedNumberContract;
import android.provider.BlockedNumbersManager;
@@ -77,22 +77,23 @@
import android.util.EventLog;
import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telecom.ICallControl;
import com.android.internal.telecom.ICallEventCallback;
import com.android.internal.telecom.ITelecomService;
import com.android.internal.util.IndentingPrintWriter;
-import com.android.modules.utils.BasicShellCommandHandler;
+import com.android.server.telecom.callsequencing.voip.OutgoingCallTransactionSequencing;
import com.android.server.telecom.components.UserCallIntentProcessorFactory;
import com.android.server.telecom.flags.FeatureFlags;
+import com.android.server.telecom.metrics.ApiStats;
+import com.android.server.telecom.metrics.TelecomMetricsController;
import com.android.server.telecom.settings.BlockedNumbersActivity;
-import com.android.server.telecom.voip.IncomingCallTransaction;
-import com.android.server.telecom.voip.OutgoingCallTransaction;
-import com.android.server.telecom.voip.TransactionManager;
-import com.android.server.telecom.voip.VoipCallTransaction;
-import com.android.server.telecom.voip.VoipCallTransactionResult;
+import com.android.server.telecom.callsequencing.voip.IncomingCallTransaction;
+import com.android.server.telecom.callsequencing.voip.OutgoingCallTransaction;
+import com.android.server.telecom.callsequencing.TransactionManager;
+import com.android.server.telecom.callsequencing.CallTransaction;
+import com.android.server.telecom.callsequencing.CallTransactionResult;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -104,6 +105,7 @@
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
// TODO: Needed for move to system service: import com.android.internal.R;
@@ -112,45 +114,6 @@
*/
public class TelecomServiceImpl {
- public interface SubscriptionManagerAdapter {
- int getDefaultVoiceSubId();
- }
-
- static class SubscriptionManagerAdapterImpl implements SubscriptionManagerAdapter {
- @Override
- public int getDefaultVoiceSubId() {
- return SubscriptionManager.getDefaultVoiceSubscriptionId();
- }
- }
-
- public interface SettingsSecureAdapter {
- void putStringForUser(ContentResolver resolver, String name, String value, int userHandle);
-
- String getStringForUser(ContentResolver resolver, String name, int userHandle);
- }
-
- static class SettingsSecureAdapterImpl implements SettingsSecureAdapter {
- @Override
- public void putStringForUser(ContentResolver resolver, String name, String value,
- int userHandle) {
- Settings.Secure.putStringForUser(resolver, name, value, userHandle);
- }
-
- @Override
- public String getStringForUser(ContentResolver resolver, String name, int userHandle) {
- return Settings.Secure.getStringForUser(resolver, name, userHandle);
- }
- }
-
- private static final String TAG = "TelecomServiceImpl";
- private static final String TIME_LINE_ARG = "timeline";
- private static final int DEFAULT_VIDEO_STATE = -1;
- private static final String PERMISSION_HANDLE_CALL_INTENT =
- "android.permission.HANDLE_CALL_INTENT";
- private static final String ADD_CALL_ERR_MSG = "Call could not be created or found. "
- + "Retry operation.";
- private AnomalyReporterAdapter mAnomalyReporter = new AnomalyReporterAdapterImpl();
-
/**
* Anomaly Report UUIDs and corresponding error descriptions specific to TelecomServiceImpl.
*/
@@ -182,17 +145,39 @@
UUID.fromString("4edf6c8d-1e43-4c94-b0fc-a40c8d80cfe8");
public static final String PLACE_CALL_SECURITY_EXCEPTION_ERROR_MSG =
"Security exception thrown while placing an outgoing call.";
-
- @VisibleForTesting
- public void setAnomalyReporterAdapter(AnomalyReporterAdapter mAnomalyReporterAdapter){
- mAnomalyReporter = mAnomalyReporterAdapter;
- }
-
+ private static final String TAG = "TelecomServiceImpl";
+ private static final String TIME_LINE_ARG = "timeline";
+ private static final int DEFAULT_VIDEO_STATE = -1;
+ private static final String PERMISSION_HANDLE_CALL_INTENT =
+ "android.permission.HANDLE_CALL_INTENT";
+ private static final String ADD_CALL_ERR_MSG = "Call could not be created or found. "
+ + "Retry operation.";
+ private final PhoneAccountRegistrar mPhoneAccountRegistrar;
+ private final CallIntentProcessor.Adapter mCallIntentProcessorAdapter;
+ private final UserCallIntentProcessorFactory mUserCallIntentProcessorFactory;
+ private final DefaultDialerCache mDefaultDialerCache;
+ private final SubscriptionManagerAdapter mSubscriptionManagerAdapter;
+ private final SettingsSecureAdapter mSettingsSecureAdapter;
+ private final TelecomSystem.SyncRoot mLock;
+ private final TransactionalServiceRepository mTransactionalServiceRepository;
+ private final BlockedNumbersManager mBlockedNumbersManager;
+ private final FeatureFlags mFeatureFlags;
+ private final com.android.internal.telephony.flags.FeatureFlags mTelephonyFeatureFlags;
+ private final TelecomMetricsController mMetricsController;
+ private final String mSystemUiPackageName;
+ private AnomalyReporterAdapter mAnomalyReporter = new AnomalyReporterAdapterImpl();
+ private final Context mContext;
+ private final AppOpsManager mAppOpsManager;
+ private final PackageManager mPackageManager;
+ private final CallsManager mCallsManager;
+ private TransactionManager mTransactionManager;
private final ITelecomService.Stub mBinderImpl = new ITelecomService.Stub() {
@Override
public void addCall(CallAttributes callAttributes, ICallEventCallback callEventCallback,
String callId, String callingPackage) {
+ ApiStats.ApiEvent event = new ApiStats.ApiEvent(ApiStats.API_ADDCALL,
+ Binder.getCallingUid(), ApiStats.RESULT_PERMISSION);
try {
Log.startSession("TSI.aC", Log.getPackageAbbreviation(callingPackage));
Log.i(TAG, "addCall: id=[%s], attributes=[%s]", callId, callAttributes);
@@ -205,70 +190,73 @@
enforcePhoneAccountIsRegisteredEnabled(handle, handle.getUserHandle());
enforceCallingPackage(callingPackage, "addCall");
+ event.setResult(ApiStats.RESULT_EXCEPTION);
+
// add extras about info used for FGS delegation
Bundle extras = new Bundle();
extras.putInt(CallAttributes.CALLER_UID_KEY, Binder.getCallingUid());
extras.putInt(CallAttributes.CALLER_PID_KEY, Binder.getCallingPid());
- VoipCallTransaction transaction = null;
- // create transaction based on the call direction
- switch (callAttributes.getDirection()) {
- case DIRECTION_OUTGOING:
- transaction = new OutgoingCallTransaction(callId, mContext, callAttributes,
- mCallsManager, extras, mFeatureFlags);
- break;
- case DIRECTION_INCOMING:
- transaction = new IncomingCallTransaction(callId, callAttributes,
- mCallsManager, extras, mFeatureFlags);
- break;
- default:
- throw new IllegalArgumentException(String.format("Invalid Call Direction. "
- + "Was [%d] but should be within [%d,%d]",
- callAttributes.getDirection(), DIRECTION_INCOMING,
- DIRECTION_OUTGOING));
+
+ CompletableFuture<CallTransaction> transactionFuture;
+ long token = Binder.clearCallingIdentity();
+ try {
+ transactionFuture = mCallsManager.createTransactionalCall(callId,
+ callAttributes, extras, callingPackage);
+ } finally {
+ Binder.restoreCallingIdentity(token);
}
- mTransactionManager.addTransaction(transaction, new OutcomeReceiver<>() {
- @Override
- public void onResult(VoipCallTransactionResult result) {
- Log.d(TAG, "addCall: onResult");
- Call call = result.getCall();
+ transactionFuture.thenCompose((transaction) -> {
+ if (transaction != null) {
+ mTransactionManager.addTransaction(transaction, new OutcomeReceiver<>() {
+ @Override
+ public void onResult(CallTransactionResult result) {
+ Log.d(TAG, "addCall: onResult");
+ Call call = result.getCall();
- if (call == null || !call.getId().equals(callId)) {
- Log.i(TAG, "addCall: onResult: call is null or id mismatch");
- onAddCallControl(callId, callEventCallback, null,
- new CallException(ADD_CALL_ERR_MSG, CODE_ERROR_UNKNOWN));
- return;
- }
+ if (call == null || !call.getId().equals(callId)) {
+ Log.i(TAG, "addCall: onResult: call is null or id mismatch");
+ onAddCallControl(callId, callEventCallback, null,
+ new CallException(ADD_CALL_ERR_MSG,
+ CODE_ERROR_UNKNOWN));
+ return;
+ }
- TransactionalServiceWrapper serviceWrapper =
- mTransactionalServiceRepository
- .addNewCallForTransactionalServiceWrapper(handle,
- callEventCallback, mCallsManager, call);
+ TransactionalServiceWrapper serviceWrapper =
+ mTransactionalServiceRepository
+ .addNewCallForTransactionalServiceWrapper(handle,
+ callEventCallback, mCallsManager, call);
- call.setTransactionServiceWrapper(serviceWrapper);
+ call.setTransactionServiceWrapper(serviceWrapper);
- if (mFeatureFlags.transactionalVideoState()) {
- call.setTransactionalCallSupportsVideoCalling(callAttributes);
- }
- ICallControl clientCallControl = serviceWrapper.getICallControl();
+ if (mFeatureFlags.transactionalVideoState()) {
+ call.setTransactionalCallSupportsVideoCalling(callAttributes);
+ }
+ ICallControl clientCallControl = serviceWrapper.getICallControl();
- if (clientCallControl == null) {
- throw new IllegalStateException("TransactionalServiceWrapper"
- + "#ICallControl is null.");
- }
+ if (clientCallControl == null) {
+ throw new IllegalStateException("TransactionalServiceWrapper"
+ + "#ICallControl is null.");
+ }
- // finally, send objects back to the client
- onAddCallControl(callId, callEventCallback, clientCallControl, null);
+ // finally, send objects back to the client
+ onAddCallControl(callId, callEventCallback, clientCallControl,
+ null);
+ }
+
+ @Override
+ public void onError(@NonNull CallException exception) {
+ Log.d(TAG, "addCall: onError: e=[%s]", exception.toString());
+ onAddCallControl(callId, callEventCallback, null, exception);
+ }
+ });
}
-
- @Override
- public void onError(@NonNull CallException exception) {
- Log.d(TAG, "addCall: onError: e=[%s]", exception.toString());
- onAddCallControl(callId, callEventCallback, null, exception);
- }
+ event.setResult(ApiStats.RESULT_NORMAL);
+ return CompletableFuture.completedFuture(transaction);
});
} finally {
+ logEvent(event);
Log.endSession();
}
}
@@ -292,12 +280,17 @@
@Override
public PhoneAccountHandle getDefaultOutgoingPhoneAccount(String uriScheme,
String callingPackage, String callingFeatureId) {
+ ApiStats.ApiEvent event = new ApiStats.ApiEvent(
+ ApiStats.API_GETDEFAULTOUTGOINGPHONEACCOUNT,
+ Binder.getCallingUid(), ApiStats.RESULT_PERMISSION);
try {
Log.startSession("TSI.gDOPA", Log.getPackageAbbreviation(callingPackage));
synchronized (mLock) {
PhoneAccountHandle phoneAccountHandle = null;
final UserHandle callingUserHandle = Binder.getCallingUserHandle();
long token = Binder.clearCallingIdentity();
+
+ event.setResult(ApiStats.RESULT_EXCEPTION);
try {
phoneAccountHandle = mPhoneAccountRegistrar
.getOutgoingPhoneAccountForScheme(uriScheme, callingUserHandle);
@@ -307,6 +300,8 @@
} finally {
Binder.restoreCallingIdentity(token);
}
+
+ event.setResult(ApiStats.RESULT_NORMAL);
if (isCallerSimCallManager(phoneAccountHandle)
|| canReadPhoneState(
callingPackage,
@@ -317,12 +312,16 @@
return null;
}
} finally {
+ logEvent(event);
Log.endSession();
}
}
@Override
public PhoneAccountHandle getUserSelectedOutgoingPhoneAccount(String callingPackage) {
+ ApiStats.ApiEvent event = new ApiStats.ApiEvent(
+ ApiStats.API_GETUSERSELECTEDOUTGOINGPHONEACCOUNT,
+ Binder.getCallingUid(), ApiStats.RESULT_PERMISSION);
synchronized (mLock) {
try {
Log.startSession("TSI.gUSOPA", Log.getPackageAbbreviation(callingPackage));
@@ -330,6 +329,7 @@
throw new SecurityException("Only the default dialer, or caller with "
+ "READ_PRIVILEGED_PHONE_STATE can call this method.");
}
+ event.setResult(ApiStats.RESULT_NORMAL);
final UserHandle callingUserHandle = Binder.getCallingUserHandle();
return mPhoneAccountRegistrar.getUserSelectedOutgoingPhoneAccount(
callingUserHandle);
@@ -337,6 +337,7 @@
Log.e(this, e, "getUserSelectedOutgoingPhoneAccount");
throw e;
} finally {
+ logEvent(event);
Log.endSession();
}
}
@@ -344,6 +345,9 @@
@Override
public void setUserSelectedOutgoingPhoneAccount(PhoneAccountHandle accountHandle) {
+ ApiStats.ApiEvent event = new ApiStats.ApiEvent(
+ ApiStats.API_SETUSERSELECTEDOUTGOINGPHONEACCOUNT,
+ Binder.getCallingUid(), ApiStats.RESULT_PERMISSION);
try {
Log.startSession("TSI.sUSOPA");
synchronized (mLock) {
@@ -353,6 +357,7 @@
try {
mPhoneAccountRegistrar.setUserSelectedOutgoingPhoneAccount(
accountHandle, callingUserHandle);
+ event.setResult(ApiStats.RESULT_NORMAL);
} catch (Exception e) {
Log.e(this, e, "setUserSelectedOutgoingPhoneAccount");
mAnomalyReporter.reportAnomaly(SET_USER_PHONE_ACCOUNT_ERROR_UUID,
@@ -363,6 +368,7 @@
}
}
} finally {
+ logEvent(event);
Log.endSession();
}
}
@@ -371,6 +377,9 @@
public ParceledListSlice<PhoneAccountHandle> getCallCapablePhoneAccounts(
boolean includeDisabledAccounts, String callingPackage,
String callingFeatureId, boolean acrossProfiles) {
+ ApiStats.ApiEvent event = new ApiStats.ApiEvent(
+ ApiStats.API_GETCALLCAPABLEPHONEACCOUNTS,
+ Binder.getCallingUid(), ApiStats.RESULT_PERMISSION);
try {
Log.startSession("TSI.gCCPA", Log.getPackageAbbreviation(callingPackage));
@@ -400,13 +409,13 @@
"getCallCapablePhoneAccounts")) {
return ParceledListSlice.emptyList();
}
+ event.setResult(ApiStats.RESULT_NORMAL);
synchronized (mLock) {
final UserHandle callingUserHandle = Binder.getCallingUserHandle();
- boolean crossUserAccess = mTelephonyFeatureFlags.workProfileApiSplit()
- && !acrossProfiles ? false
- : (mTelephonyFeatureFlags.workProfileApiSplit()
- ? hasInAppCrossProfilePermission()
- : hasInAppCrossUserPermission());
+ boolean crossUserAccess = (!mTelephonyFeatureFlags.workProfileApiSplit()
+ || acrossProfiles) && (mTelephonyFeatureFlags.workProfileApiSplit()
+ ? hasInAppCrossProfilePermission()
+ : hasInAppCrossUserPermission());
long token = Binder.clearCallingIdentity();
try {
return new ParceledListSlice<>(
@@ -414,6 +423,7 @@
includeDisabledAccounts, callingUserHandle,
crossUserAccess));
} catch (Exception e) {
+ event.setResult(ApiStats.RESULT_EXCEPTION);
Log.e(this, e, "getCallCapablePhoneAccounts");
mAnomalyReporter.reportAnomaly(GET_CALL_CAPABLE_ACCOUNTS_ERROR_UUID,
GET_CALL_CAPABLE_ACCOUNTS_ERROR_MSG);
@@ -423,6 +433,7 @@
}
}
} finally {
+ logEvent(event);
Log.endSession();
}
}
@@ -430,6 +441,9 @@
@Override
public ParceledListSlice<PhoneAccountHandle> getSelfManagedPhoneAccounts(
String callingPackage, String callingFeatureId) {
+ ApiStats.ApiEvent event = new ApiStats.ApiEvent(
+ ApiStats.API_GETSELFMANAGEDPHONEACCOUNTS,
+ Binder.getCallingUid(), ApiStats.RESULT_PERMISSION);
try {
Log.startSession("TSI.gSMPA", Log.getPackageAbbreviation(callingPackage));
if (!canReadPhoneState(callingPackage, callingFeatureId,
@@ -439,10 +453,12 @@
synchronized (mLock) {
final UserHandle callingUserHandle = Binder.getCallingUserHandle();
long token = Binder.clearCallingIdentity();
+ event.setResult(ApiStats.RESULT_NORMAL);
try {
return new ParceledListSlice<>(mPhoneAccountRegistrar
.getSelfManagedPhoneAccounts(callingUserHandle));
} catch (Exception e) {
+ event.setResult(ApiStats.RESULT_EXCEPTION);
Log.e(this, e, "getSelfManagedPhoneAccounts");
throw e;
} finally {
@@ -450,6 +466,7 @@
}
}
} finally {
+ logEvent(event);
Log.endSession();
}
}
@@ -457,6 +474,9 @@
@Override
public ParceledListSlice<PhoneAccountHandle> getOwnSelfManagedPhoneAccounts(
String callingPackage, String callingFeatureId) {
+ ApiStats.ApiEvent event = new ApiStats.ApiEvent(
+ ApiStats.API_GETOWNSELFMANAGEDPHONEACCOUNTS,
+ Binder.getCallingUid(), ApiStats.RESULT_PERMISSION);
try {
Log.startSession("TSI.gOSMPA", Log.getPackageAbbreviation(callingPackage));
try {
@@ -472,11 +492,13 @@
synchronized (mLock) {
final UserHandle callingUserHandle = Binder.getCallingUserHandle();
long token = Binder.clearCallingIdentity();
+ event.setResult(ApiStats.RESULT_NORMAL);
try {
return new ParceledListSlice<>(mPhoneAccountRegistrar
.getSelfManagedPhoneAccountsForPackage(callingPackage,
callingUserHandle));
} catch (Exception e) {
+ event.setResult(ApiStats.RESULT_EXCEPTION);
Log.e(this, e,
"getSelfManagedPhoneAccountsForPackage");
throw e;
@@ -485,6 +507,7 @@
}
}
} finally {
+ logEvent(event);
Log.endSession();
}
}
@@ -492,6 +515,9 @@
@Override
public ParceledListSlice<PhoneAccountHandle> getPhoneAccountsSupportingScheme(
String uriScheme, String callingPackage) {
+ ApiStats.ApiEvent event = new ApiStats.ApiEvent(
+ ApiStats.API_GETPHONEACCOUNTSSUPPORTINGSCHEME,
+ Binder.getCallingUid(), ApiStats.RESULT_PERMISSION);
try {
Log.startSession("TSI.gPASS", Log.getPackageAbbreviation(callingPackage));
try {
@@ -506,11 +532,13 @@
synchronized (mLock) {
final UserHandle callingUserHandle = Binder.getCallingUserHandle();
long token = Binder.clearCallingIdentity();
+ event.setResult(ApiStats.RESULT_NORMAL);
try {
return new ParceledListSlice<>(mPhoneAccountRegistrar
- .getCallCapablePhoneAccounts(uriScheme, false,
- callingUserHandle, false));
+ .getCallCapablePhoneAccounts(uriScheme, false,
+ callingUserHandle, false));
} catch (Exception e) {
+ event.setResult(ApiStats.RESULT_EXCEPTION);
Log.e(this, e, "getPhoneAccountsSupportingScheme %s", uriScheme);
throw e;
} finally {
@@ -518,6 +546,7 @@
}
}
} finally {
+ logEvent(event);
Log.endSession();
}
}
@@ -526,42 +555,53 @@
public ParceledListSlice<PhoneAccountHandle> getPhoneAccountsForPackage(
String packageName) {
//TODO: Deprecate this in S
+ ApiStats.ApiEvent event = new ApiStats.ApiEvent(ApiStats.API_GETPHONEACCOUNTSFORPACKAGE,
+ Binder.getCallingUid(), ApiStats.RESULT_PERMISSION);
try {
- enforceCallingPackage(packageName, "getPhoneAccountsForPackage");
- } catch (SecurityException se1) {
- EventLog.writeEvent(0x534e4554, "153995334", Binder.getCallingUid(),
- "getPhoneAccountsForPackage: invalid calling package");
- throw se1;
- }
-
- try {
- enforcePermission(READ_PRIVILEGED_PHONE_STATE);
- } catch (SecurityException se2) {
- EventLog.writeEvent(0x534e4554, "153995334", Binder.getCallingUid(),
- "getPhoneAccountsForPackage: no permission");
- throw se2;
- }
-
- synchronized (mLock) {
- final UserHandle callingUserHandle = Binder.getCallingUserHandle();
- long token = Binder.clearCallingIdentity();
try {
- Log.startSession("TSI.gPAFP");
- return new ParceledListSlice<>(mPhoneAccountRegistrar
- .getAllPhoneAccountHandlesForPackage(callingUserHandle, packageName));
- } catch (Exception e) {
- Log.e(this, e, "getPhoneAccountsForPackage %s", packageName);
- throw e;
- } finally {
- Binder.restoreCallingIdentity(token);
- Log.endSession();
+ enforceCallingPackage(packageName, "getPhoneAccountsForPackage");
+ } catch (SecurityException se1) {
+ EventLog.writeEvent(0x534e4554, "153995334", Binder.getCallingUid(),
+ "getPhoneAccountsForPackage: invalid calling package");
+ throw se1;
}
+
+ try {
+ enforcePermission(READ_PRIVILEGED_PHONE_STATE);
+ } catch (SecurityException se2) {
+ EventLog.writeEvent(0x534e4554, "153995334", Binder.getCallingUid(),
+ "getPhoneAccountsForPackage: no permission");
+ throw se2;
+ }
+
+ synchronized (mLock) {
+ final UserHandle callingUserHandle = Binder.getCallingUserHandle();
+ long token = Binder.clearCallingIdentity();
+ event.setResult(ApiStats.RESULT_NORMAL);
+ try {
+ Log.startSession("TSI.gPAFP");
+ return new ParceledListSlice<>(mPhoneAccountRegistrar
+ .getAllPhoneAccountHandlesForPackage(
+ callingUserHandle, packageName));
+ } catch (Exception e) {
+ event.setResult(ApiStats.RESULT_EXCEPTION);
+ Log.e(this, e, "getPhoneAccountsForPackage %s", packageName);
+ throw e;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ Log.endSession();
+ }
+ }
+ } finally {
+ logEvent(event);
}
}
@Override
public PhoneAccount getPhoneAccount(PhoneAccountHandle accountHandle,
String callingPackage) {
+ ApiStats.ApiEvent event = new ApiStats.ApiEvent(ApiStats.API_GETPHONEACCOUNT,
+ Binder.getCallingUid(), ApiStats.RESULT_PERMISSION);
try {
Log.startSession("TSI.gPA", Log.getPackageAbbreviation(callingPackage));
try {
@@ -588,6 +628,7 @@
Set<String> permissions = computePermissionsForBoundPackage(
Set.of(MODIFY_PHONE_STATE), null);
long token = Binder.clearCallingIdentity();
+ event.setResult(ApiStats.RESULT_NORMAL);
try {
// In ideal case, we should not resolve the handle across profiles. But
// given the fact that profile's call is handled by its parent user's
@@ -598,6 +639,7 @@
/* acrossProfiles */ true);
return maybeCleansePhoneAccount(account, permissions);
} catch (Exception e) {
+ event.setResult(ApiStats.RESULT_EXCEPTION);
Log.e(this, e, "getPhoneAccount %s", accountHandle);
mAnomalyReporter.reportAnomaly(GET_PHONE_ACCOUNT_ERROR_UUID,
GET_PHONE_ACCOUNT_ERROR_MSG);
@@ -607,6 +649,7 @@
}
}
} finally {
+ logEvent(event);
Log.endSession();
}
}
@@ -614,6 +657,8 @@
@Override
public ParceledListSlice<PhoneAccount> getRegisteredPhoneAccounts(String callingPackage,
String callingFeatureId) {
+ ApiStats.ApiEvent event = new ApiStats.ApiEvent(ApiStats.API_GETREGISTEREDPHONEACCOUNTS,
+ Binder.getCallingUid(), ApiStats.RESULT_PERMISSION);
try {
Log.startSession("TSI.gRPA", Log.getPackageAbbreviation(callingPackage));
try {
@@ -635,6 +680,7 @@
synchronized (mLock) {
final UserHandle callingUserHandle = Binder.getCallingUserHandle();
long token = Binder.clearCallingIdentity();
+ event.setResult(ApiStats.RESULT_NORMAL);
try {
return new ParceledListSlice<>(
mPhoneAccountRegistrar.getPhoneAccounts(
@@ -647,6 +693,7 @@
hasCrossUserAccess /* crossUserAccess */,
false /* includeAll */));
} catch (Exception e) {
+ event.setResult(ApiStats.RESULT_EXCEPTION);
Log.e(this, e, "getRegisteredPhoneAccounts");
throw e;
} finally {
@@ -654,14 +701,18 @@
}
}
} finally {
+ logEvent(event);
Log.endSession();
}
}
@Override
public int getAllPhoneAccountsCount() {
+ ApiStats.ApiEvent event = new ApiStats.ApiEvent(ApiStats.API_GETALLPHONEACCOUNTSCOUNT,
+ Binder.getCallingUid(), ApiStats.RESULT_PERMISSION);
try {
Log.startSession("TSI.gAPAC");
+ event.setCallerUid(Binder.getCallingUid());
try {
enforceModifyPermission(
"getAllPhoneAccountsCount requires MODIFY_PHONE_STATE permission.");
@@ -672,22 +723,27 @@
}
synchronized (mLock) {
+ event.setResult(ApiStats.RESULT_NORMAL);
try {
// This list is pre-filtered for the calling user.
return getAllPhoneAccounts().getList().size();
} catch (Exception e) {
+ event.setResult(ApiStats.RESULT_EXCEPTION);
Log.e(this, e, "getAllPhoneAccountsCount");
throw e;
}
}
} finally {
+ logEvent(event);
Log.endSession();
}
}
@Override
public ParceledListSlice<PhoneAccount> getAllPhoneAccounts() {
+ ApiStats.ApiEvent event = new ApiStats.ApiEvent(ApiStats.API_GETALLPHONEACCOUNTS,
+ Binder.getCallingUid(), ApiStats.RESULT_PERMISSION);
synchronized (mLock) {
try {
Log.startSession("TSI.gAPA");
@@ -702,16 +758,19 @@
final UserHandle callingUserHandle = Binder.getCallingUserHandle();
long token = Binder.clearCallingIdentity();
+ event.setResult(ApiStats.RESULT_NORMAL);
try {
return new ParceledListSlice<>(mPhoneAccountRegistrar
.getAllPhoneAccounts(callingUserHandle, false));
} catch (Exception e) {
+ event.setResult(ApiStats.RESULT_EXCEPTION);
Log.e(this, e, "getAllPhoneAccounts");
throw e;
} finally {
Binder.restoreCallingIdentity(token);
}
} finally {
+ logEvent(event);
Log.endSession();
}
}
@@ -719,6 +778,8 @@
@Override
public ParceledListSlice<PhoneAccountHandle> getAllPhoneAccountHandles() {
+ ApiStats.ApiEvent event = new ApiStats.ApiEvent(ApiStats.API_GETALLPHONEACCOUNTHANDLES,
+ Binder.getCallingUid(), ApiStats.RESULT_PERMISSION);
try {
Log.startSession("TSI.gAPAH");
@@ -735,11 +796,13 @@
final UserHandle callingUserHandle = Binder.getCallingUserHandle();
boolean crossUserAccess = hasInAppCrossUserPermission();
long token = Binder.clearCallingIdentity();
+ event.setResult(ApiStats.RESULT_NORMAL);
try {
return new ParceledListSlice<>(mPhoneAccountRegistrar
.getAllPhoneAccountHandles(callingUserHandle,
crossUserAccess));
} catch (Exception e) {
+ event.setResult(ApiStats.RESULT_EXCEPTION);
Log.e(this, e, "getAllPhoneAccountsHandles");
throw e;
} finally {
@@ -747,12 +810,15 @@
}
}
} finally {
+ logEvent(event);
Log.endSession();
}
}
@Override
public PhoneAccountHandle getSimCallManager(int subId, String callingPackage) {
+ ApiStats.ApiEvent event = new ApiStats.ApiEvent(ApiStats.API_GETSIMCALLMANAGER,
+ Binder.getCallingUid(), ApiStats.RESULT_PERMISSION);
synchronized (mLock) {
try {
Log.startSession("TSI.gSCM", Log.getPackageAbbreviation(callingPackage));
@@ -763,6 +829,7 @@
if (user != ActivityManager.getCurrentUser()) {
enforceCrossUserPermission(callingUid);
}
+ event.setResult(ApiStats.RESULT_NORMAL);
return mPhoneAccountRegistrar.getSimCallManager(subId, UserHandle.of(user));
} finally {
Binder.restoreCallingIdentity(token);
@@ -773,6 +840,7 @@
GET_SIM_MANAGER_ERROR_MSG);
throw e;
} finally {
+ logEvent(event);
Log.endSession();
}
}
@@ -780,6 +848,8 @@
@Override
public PhoneAccountHandle getSimCallManagerForUser(int user, String callingPackage) {
+ ApiStats.ApiEvent event = new ApiStats.ApiEvent(ApiStats.API_GETSIMCALLMANAGERFORUSER,
+ Binder.getCallingUid(), ApiStats.RESULT_PERMISSION);
synchronized (mLock) {
try {
Log.startSession("TSI.gSCMFU", Log.getPackageAbbreviation(callingPackage));
@@ -788,6 +858,7 @@
enforceCrossUserPermission(callingUid);
}
long token = Binder.clearCallingIdentity();
+ event.setResult(ApiStats.RESULT_NORMAL);
try {
return mPhoneAccountRegistrar.getSimCallManager(UserHandle.of(user));
} finally {
@@ -799,6 +870,7 @@
GET_SIM_MANAGER_FOR_USER_ERROR_MSG);
throw e;
} finally {
+ logEvent(event);
Log.endSession();
}
}
@@ -806,22 +878,27 @@
@Override
public void registerPhoneAccount(PhoneAccount account, String callingPackage) {
+ ApiStats.ApiEvent event = new ApiStats.ApiEvent(ApiStats.API_REGISTERPHONEACCOUNT,
+ Binder.getCallingUid(), ApiStats.RESULT_PERMISSION);
try {
Log.startSession("TSI.rPA", Log.getPackageAbbreviation(callingPackage));
synchronized (mLock) {
try {
enforcePhoneAccountModificationForPackage(
account.getAccountHandle().getComponentName().getPackageName());
- if (account.hasCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED)) {
+ if (account.hasCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED)
+ || (mFeatureFlags.enforceTransactionalExclusivity()
+ && account.hasCapabilities(
+ PhoneAccount.CAPABILITY_SUPPORTS_TRANSACTIONAL_OPERATIONS))) {
enforceRegisterSelfManaged();
if (account.hasCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER) ||
account.hasCapabilities(
PhoneAccount.CAPABILITY_CONNECTION_MANAGER) ||
account.hasCapabilities(
PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)) {
- throw new SecurityException("Self-managed ConnectionServices " +
- "cannot also be call capable, connection managers, or " +
- "SIM accounts.");
+ throw new SecurityException("Self-managed ConnectionServices "
+ + "cannot also be call capable, connection managers, or "
+ + "SIM accounts.");
}
// For self-managed CS, the phone account registrar will override the
@@ -878,6 +955,7 @@
}
final long token = Binder.clearCallingIdentity();
+ event.setResult(ApiStats.RESULT_NORMAL);
try {
Log.i(this, "registerPhoneAccount: account=%s",
account);
@@ -893,6 +971,7 @@
}
}
} finally {
+ logEvent(event);
Log.endSession();
}
}
@@ -900,6 +979,8 @@
@Override
public void unregisterPhoneAccount(PhoneAccountHandle accountHandle,
String callingPackage) {
+ ApiStats.ApiEvent event = new ApiStats.ApiEvent(ApiStats.API_UNREGISTERPHONEACCOUNT,
+ Binder.getCallingUid(), ApiStats.RESULT_PERMISSION);
synchronized (mLock) {
try {
Log.startSession("TSI.uPA", Log.getPackageAbbreviation(callingPackage));
@@ -907,6 +988,7 @@
accountHandle.getComponentName().getPackageName());
enforceUserHandleMatchesCaller(accountHandle);
final long token = Binder.clearCallingIdentity();
+ event.setResult(ApiStats.RESULT_NORMAL);
try {
mPhoneAccountRegistrar.unregisterPhoneAccount(accountHandle);
} finally {
@@ -916,6 +998,7 @@
Log.e(this, e, "unregisterPhoneAccount %s", accountHandle);
throw e;
} finally {
+ logEvent(event);
Log.endSession();
}
}
@@ -923,16 +1006,20 @@
@Override
public void clearAccounts(String packageName) {
+ ApiStats.ApiEvent event = new ApiStats.ApiEvent(ApiStats.API_CLEARACCOUNTS,
+ Binder.getCallingUid(), ApiStats.RESULT_PERMISSION);
synchronized (mLock) {
try {
Log.startSession("TSI.cA");
enforcePhoneAccountModificationForPackage(packageName);
+ event.setResult(ApiStats.RESULT_NORMAL);
mPhoneAccountRegistrar
.clearAccounts(packageName, Binder.getCallingUserHandle());
} catch (Exception e) {
Log.e(this, e, "clearAccounts %s", packageName);
throw e;
} finally {
+ logEvent(event);
Log.endSession();
}
}
@@ -944,6 +1031,8 @@
@Override
public boolean isVoiceMailNumber(PhoneAccountHandle accountHandle, String number,
String callingPackage, String callingFeatureId) {
+ ApiStats.ApiEvent event = new ApiStats.ApiEvent(ApiStats.API_ISVOICEMAILNUMBER,
+ Binder.getCallingUid(), ApiStats.RESULT_PERMISSION);
try {
Log.startSession("TSI.iVMN", Log.getPackageAbbreviation(callingPackage));
synchronized (mLock) {
@@ -957,9 +1046,11 @@
return false;
}
long token = Binder.clearCallingIdentity();
+ event.setResult(ApiStats.RESULT_NORMAL);
try {
return mPhoneAccountRegistrar.isVoiceMailNumber(accountHandle, number);
} catch (Exception e) {
+ event.setResult(ApiStats.RESULT_EXCEPTION);
Log.e(this, e, "getSubscriptionIdForPhoneAccount");
throw e;
} finally {
@@ -967,6 +1058,7 @@
}
}
} finally {
+ logEvent(event);
Log.endSession();
}
}
@@ -977,6 +1069,8 @@
@Override
public String getVoiceMailNumber(PhoneAccountHandle accountHandle, String callingPackage,
String callingFeatureId) {
+ ApiStats.ApiEvent event = new ApiStats.ApiEvent(ApiStats.API_GETVOICEMAILNUMBER,
+ Binder.getCallingUid(), ApiStats.RESULT_PERMISSION);
try {
Log.startSession("TSI.gVMN", Log.getPackageAbbreviation(callingPackage));
if (!canReadPhoneState(callingPackage, callingFeatureId, "getVoiceMailNumber")) {
@@ -997,8 +1091,10 @@
.getSubscriptionIdForPhoneAccount(accountHandle);
}
}
+ event.setResult(ApiStats.RESULT_NORMAL);
return getTelephonyManager(subId).getVoiceMailNumber();
} catch (UnsupportedOperationException ignored) {
+ event.setResult(ApiStats.RESULT_EXCEPTION);
Log.w(this, "getVoiceMailNumber: no Telephony");
return null;
} catch (Exception e) {
@@ -1006,6 +1102,7 @@
throw e;
}
} finally {
+ logEvent(event);
Log.endSession();
}
}
@@ -1016,6 +1113,8 @@
@Override
public String getLine1Number(PhoneAccountHandle accountHandle, String callingPackage,
String callingFeatureId) {
+ ApiStats.ApiEvent event = new ApiStats.ApiEvent(ApiStats.API_GETLINE1NUMBER,
+ Binder.getCallingUid(), ApiStats.RESULT_PERMISSION);
try {
Log.startSession("getL1N", Log.getPackageAbbreviation(callingPackage));
if (!canReadPhoneNumbers(callingPackage, callingFeatureId, "getLine1Number")) {
@@ -1036,8 +1135,10 @@
subId = mPhoneAccountRegistrar.getSubscriptionIdForPhoneAccount(
accountHandle);
}
+ event.setResult(ApiStats.RESULT_NORMAL);
return getTelephonyManager(subId).getLine1Number();
} catch (UnsupportedOperationException ignored) {
+ event.setResult(ApiStats.RESULT_EXCEPTION);
Log.w(this, "getLine1Number: no telephony");
return null;
} catch (Exception e) {
@@ -1047,6 +1148,7 @@
Binder.restoreCallingIdentity(token);
}
} finally {
+ logEvent(event);
Log.endSession();
}
}
@@ -1056,6 +1158,8 @@
*/
@Override
public void silenceRinger(String callingPackage) {
+ ApiStats.ApiEvent event = new ApiStats.ApiEvent(ApiStats.API_SILENCERINGER,
+ Binder.getCallingUid(), ApiStats.RESULT_PERMISSION);
try {
Log.startSession("TSI.sR", Log.getPackageAbbreviation(callingPackage));
synchronized (mLock) {
@@ -1063,17 +1167,20 @@
UserHandle callingUserHandle = Binder.getCallingUserHandle();
boolean crossUserAccess = hasInAppCrossUserPermission();
long token = Binder.clearCallingIdentity();
+ event.setResult(ApiStats.RESULT_EXCEPTION);
try {
Log.i(this, "Silence Ringer requested by %s", callingPackage);
Set<UserHandle> userHandles = mCallsManager.getCallAudioManager().
silenceRingers(mContext, callingUserHandle,
crossUserAccess);
+ event.setResult(ApiStats.RESULT_NORMAL);
mCallsManager.getInCallController().silenceRinger(userHandles);
} finally {
Binder.restoreCallingIdentity(token);
}
}
} finally {
+ logEvent(event);
Log.endSession();
}
}
@@ -1085,10 +1192,13 @@
*/
@Override
public ComponentName getDefaultPhoneApp() {
+ ApiStats.ApiEvent event = new ApiStats.ApiEvent(ApiStats.API_GETDEFAULTPHONEAPP,
+ Binder.getCallingUid(), ApiStats.RESULT_NORMAL);
try {
Log.startSession("TSI.gDPA");
return mDefaultDialerCache.getDialtactsSystemDialerComponent();
} finally {
+ logEvent(event);
Log.endSession();
}
}
@@ -1101,6 +1211,8 @@
*/
@Override
public String getDefaultDialerPackage(String callingPackage) {
+ ApiStats.ApiEvent event = new ApiStats.ApiEvent(ApiStats.API_GETDEFAULTDIALERPACKAGE,
+ Binder.getCallingUid(), ApiStats.RESULT_NORMAL);
try {
Log.startSession("TSI.gDDP", Log.getPackageAbbreviation(callingPackage));
int callerUserId = UserHandle.getCallingUserId();
@@ -1112,6 +1224,7 @@
Binder.restoreCallingIdentity(token);
}
} finally {
+ logEvent(event);
Log.endSession();
}
}
@@ -1125,18 +1238,23 @@
*/
@Override
public String getDefaultDialerPackageForUser(int userId) {
+ ApiStats.ApiEvent event = new ApiStats.ApiEvent(
+ ApiStats.API_GETDEFAULTDIALERPACKAGEFORUSER,
+ Binder.getCallingUid(), ApiStats.RESULT_PERMISSION);
try {
Log.startSession("TSI.gDDPU");
mContext.enforceCallingOrSelfPermission(READ_PRIVILEGED_PHONE_STATE,
"READ_PRIVILEGED_PHONE_STATE permission required.");
final long token = Binder.clearCallingIdentity();
+ event.setResult(ApiStats.RESULT_NORMAL);
try {
return mDefaultDialerCache.getDefaultDialerApplication(userId);
} finally {
Binder.restoreCallingIdentity(token);
}
} finally {
+ logEvent(event);
Log.endSession();
}
}
@@ -1146,10 +1264,13 @@
*/
@Override
public String getSystemDialerPackage(String callingPackage) {
+ ApiStats.ApiEvent event = new ApiStats.ApiEvent(ApiStats.API_GETSYSTEMDIALERPACKAGE,
+ Binder.getCallingUid(), ApiStats.RESULT_NORMAL);
try {
Log.startSession("TSI.gSDP", Log.getPackageAbbreviation(callingPackage));
return mDefaultDialerCache.getSystemDialerApplication();
} finally {
+ logEvent(event);
Log.endSession();
}
}
@@ -1177,17 +1298,20 @@
*/
@Override
public boolean isInCall(String callingPackage, String callingFeatureId) {
+ ApiStats.ApiEvent event = new ApiStats.ApiEvent(ApiStats.API_ISINCALL,
+ Binder.getCallingUid(), ApiStats.RESULT_PERMISSION);
try {
Log.startSession("TSI.iIC", Log.getPackageAbbreviation(callingPackage));
if (!canReadPhoneState(callingPackage, callingFeatureId, "isInCall")) {
return false;
}
-
+ event.setResult(ApiStats.RESULT_NORMAL);
synchronized (mLock) {
return mCallsManager.hasOngoingCalls(Binder.getCallingUserHandle(),
hasInAppCrossUserPermission());
}
} finally {
+ logEvent(event);
Log.endSession();
}
}
@@ -1197,9 +1321,13 @@
*/
@Override
public boolean hasManageOngoingCallsPermission(String callingPackage) {
+ ApiStats.ApiEvent event = new ApiStats.ApiEvent(
+ ApiStats.API_HASMANAGEONGOINGCALLSPERMISSION,
+ Binder.getCallingUid(), ApiStats.RESULT_PERMISSION);
try {
Log.startSession("TSI.hMOCP", Log.getPackageAbbreviation(callingPackage));
enforceCallingPackage(callingPackage, "hasManageOngoingCallsPermission");
+ event.setResult(ApiStats.RESULT_NORMAL);
return PermissionChecker.checkPermissionForDataDeliveryFromDataSource(
mContext, Manifest.permission.MANAGE_ONGOING_CALLS,
Binder.getCallingPid(),
@@ -1209,6 +1337,7 @@
"Checking whether the caller has MANAGE_ONGOING_CALLS permission")
== PermissionChecker.PERMISSION_GRANTED;
} finally {
+ logEvent(event);
Log.endSession();
}
}
@@ -1218,18 +1347,21 @@
*/
@Override
public boolean isInManagedCall(String callingPackage, String callingFeatureId) {
+ ApiStats.ApiEvent event = new ApiStats.ApiEvent(ApiStats.API_ISINMANAGEDCALL,
+ Binder.getCallingUid(), ApiStats.RESULT_PERMISSION);
try {
Log.startSession("TSI.iIMC", Log.getPackageAbbreviation(callingPackage));
if (!canReadPhoneState(callingPackage, callingFeatureId, "isInManagedCall")) {
throw new SecurityException("Only the default dialer or caller with " +
"READ_PHONE_STATE permission can use this method.");
}
-
+ event.setResult(ApiStats.RESULT_NORMAL);
synchronized (mLock) {
return mCallsManager.hasOngoingManagedCalls(Binder.getCallingUserHandle(),
hasInAppCrossUserPermission());
}
} finally {
+ logEvent(event);
Log.endSession();
}
}
@@ -1239,6 +1371,8 @@
*/
@Override
public boolean isRinging(String callingPackage) {
+ ApiStats.ApiEvent event = new ApiStats.ApiEvent(ApiStats.API_ISRINGING,
+ Binder.getCallingUid(), ApiStats.RESULT_PERMISSION);
try {
Log.startSession("TSI.iR");
if (!isPrivilegedDialerCalling(callingPackage)) {
@@ -1251,6 +1385,7 @@
}
}
+ event.setResult(ApiStats.RESULT_NORMAL);
synchronized (mLock) {
// Note: We are explicitly checking the calls telecom is tracking rather than
// relying on mCallsManager#getCallState(). Since getCallState() relies on the
@@ -1260,6 +1395,7 @@
return mCallsManager.hasRingingOrSimulatedRingingCall();
}
} finally {
+ logEvent(event);
Log.endSession();
}
}
@@ -1272,6 +1408,8 @@
@Deprecated
@Override
public int getCallState() {
+ ApiStats.ApiEvent event = new ApiStats.ApiEvent(ApiStats.API_GETCALLSTATE,
+ Binder.getCallingUid(), ApiStats.RESULT_PERMISSION);
try {
Log.startSession("TSI.getCallState(DEPRECATED)");
if (CompatChanges.isChangeEnabled(
@@ -1282,6 +1420,7 @@
throw new SecurityException("This method can only be used for applications "
+ "targeting API version 30 or less.");
}
+ event.setResult(ApiStats.RESULT_NORMAL);
synchronized (mLock) {
return mCallsManager.getCallState();
}
@@ -1295,12 +1434,14 @@
*/
@Override
public int getCallStateUsingPackage(String callingPackage, String callingFeatureId) {
+ ApiStats.ApiEvent event = new ApiStats.ApiEvent(ApiStats.API_GETCALLSTATEUSINGPACKAGE,
+ Binder.getCallingUid(), ApiStats.RESULT_PERMISSION);
try {
Log.startSession("TSI.getCallStateUsingPackage");
// ensure the callingPackage is not spoofed
// skip check for privileged UIDs and throw SE if package does not match records
- if (!isPrivilegedUid(callingPackage)
+ if (!isPrivilegedUid()
&& !callingUidMatchesPackageManagerRecords(callingPackage)) {
EventLog.writeEvent(0x534e4554, "236813210", Binder.getCallingUid(),
"getCallStateUsingPackage");
@@ -1323,25 +1464,46 @@
+ " for API version 31+");
}
}
+ event.setResult(ApiStats.RESULT_NORMAL);
synchronized (mLock) {
return mCallsManager.getCallState();
}
} finally {
+ logEvent(event);
Log.endSession();
}
}
- private boolean isPrivilegedUid(String callingPackage) {
+ private boolean isPrivilegedUid() {
int callingUid = Binder.getCallingUid();
- boolean isPrivileged = false;
- switch (callingUid) {
- case Process.ROOT_UID:
- case Process.SYSTEM_UID:
- case Process.SHELL_UID:
- isPrivileged = true;
- break;
+ return mFeatureFlags.allowSystemAppsResolveVoipCalls()
+ ? (UserHandle.isSameApp(callingUid, Process.ROOT_UID)
+ || UserHandle.isSameApp(callingUid, Process.SYSTEM_UID)
+ || UserHandle.isSameApp(callingUid, Process.SHELL_UID))
+ : (callingUid == Process.ROOT_UID
+ || callingUid == Process.SYSTEM_UID
+ || callingUid == Process.SHELL_UID);
+ }
+
+ private boolean isSysUiUid() {
+ int callingUid = Binder.getCallingUid();
+ int systemUiUid;
+ if (mPackageManager != null && mSystemUiPackageName != null) {
+ try {
+ systemUiUid = mPackageManager.getPackageUid(mSystemUiPackageName, 0);
+ Log.i(TAG, "isSysUiUid: callingUid = " + callingUid + "; systemUiUid = "
+ + systemUiUid);
+ return UserHandle.isSameApp(callingUid, systemUiUid);
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.w(TAG, "isSysUiUid: caught PackageManager NameNotFoundException = " + e);
+ return false;
+ }
+ } else {
+ Log.w(TAG, "isSysUiUid: caught null check and returned false; "
+ + "mPackageManager = " + mPackageManager + "; mSystemUiPackageName = "
+ + mSystemUiPackageName);
}
- return isPrivileged;
+ return false;
}
/**
@@ -1349,21 +1511,32 @@
*/
@Override
public boolean endCall(String callingPackage) {
+ ApiStats.ApiEvent event = new ApiStats.ApiEvent(ApiStats.API_ENDCALL,
+ Binder.getCallingUid(), ApiStats.RESULT_PERMISSION);
try {
Log.startSession("TSI.eC", Log.getPackageAbbreviation(callingPackage));
synchronized (mLock) {
if (!enforceAnswerCallPermission(callingPackage, Binder.getCallingUid())) {
throw new SecurityException("requires ANSWER_PHONE_CALLS permission");
}
-
+ // Legacy behavior is to ignore whether the invocation is from a system app:
+ boolean isCallerPrivileged = false;
+ if (mFeatureFlags.allowSystemAppsResolveVoipCalls()) {
+ isCallerPrivileged = isPrivilegedUid() || isSysUiUid();
+ Log.i(TAG, "endCall: Binder.getCallingUid = [" +
+ Binder.getCallingUid() + "] isCallerPrivileged = " +
+ isCallerPrivileged);
+ }
long token = Binder.clearCallingIdentity();
+ event.setResult(ApiStats.RESULT_NORMAL);
try {
- return endCallInternal(callingPackage);
+ return endCallInternal(callingPackage, isCallerPrivileged);
} finally {
Binder.restoreCallingIdentity(token);
}
}
} finally {
+ logEvent(event);
Log.endSession();
}
}
@@ -1373,19 +1546,31 @@
*/
@Override
public void acceptRingingCall(String packageName) {
+ ApiStats.ApiEvent event = new ApiStats.ApiEvent(ApiStats.API_ACCEPTRINGINGCALL,
+ Binder.getCallingUid(), ApiStats.RESULT_PERMISSION);
try {
Log.startSession("TSI.aRC", Log.getPackageAbbreviation(packageName));
synchronized (mLock) {
if (!enforceAnswerCallPermission(packageName, Binder.getCallingUid())) return;
-
+ // Legacy behavior is to ignore whether the invocation is from a system app:
+ boolean isCallerPrivileged = false;
+ if (mFeatureFlags.allowSystemAppsResolveVoipCalls()) {
+ isCallerPrivileged = isPrivilegedUid() || isSysUiUid();
+ Log.i(TAG, "acceptRingingCall: Binder.getCallingUid = [" +
+ Binder.getCallingUid() + "] isCallerPrivileged = " +
+ isCallerPrivileged);
+ }
long token = Binder.clearCallingIdentity();
+ event.setResult(ApiStats.RESULT_NORMAL);
try {
- acceptRingingCallInternal(DEFAULT_VIDEO_STATE, packageName);
+ acceptRingingCallInternal(DEFAULT_VIDEO_STATE, packageName,
+ isCallerPrivileged);
} finally {
Binder.restoreCallingIdentity(token);
}
}
} finally {
+ logEvent(event);
Log.endSession();
}
}
@@ -1395,19 +1580,31 @@
*/
@Override
public void acceptRingingCallWithVideoState(String packageName, int videoState) {
+ ApiStats.ApiEvent event = new ApiStats.ApiEvent(
+ ApiStats.API_ACCEPTRINGINGCALLWITHVIDEOSTATE,
+ Binder.getCallingUid(), ApiStats.RESULT_PERMISSION);
try {
Log.startSession("TSI.aRCWVS", Log.getPackageAbbreviation(packageName));
synchronized (mLock) {
if (!enforceAnswerCallPermission(packageName, Binder.getCallingUid())) return;
-
+ // Legacy behavior is to ignore whether the invocation is from a system app:
+ boolean isCallerPrivileged = false;
+ if (mFeatureFlags.allowSystemAppsResolveVoipCalls()) {
+ isCallerPrivileged = isPrivilegedUid() || isSysUiUid();
+ Log.i(TAG, "acceptRingingCallWithVideoState: Binder.getCallingUid = "
+ + "[" + Binder.getCallingUid() + "] isCallerPrivileged = " +
+ isCallerPrivileged);
+ }
long token = Binder.clearCallingIdentity();
+ event.setResult(ApiStats.RESULT_NORMAL);
try {
- acceptRingingCallInternal(videoState, packageName);
+ acceptRingingCallInternal(videoState, packageName, isCallerPrivileged);
} finally {
Binder.restoreCallingIdentity(token);
}
}
} finally {
+ logEvent(event);
Log.endSession();
}
}
@@ -1418,6 +1615,8 @@
@Override
public void showInCallScreen(boolean showDialpad, String callingPackage,
String callingFeatureId) {
+ ApiStats.ApiEvent event = new ApiStats.ApiEvent(ApiStats.API_SHOWINCALLSCREEN,
+ Binder.getCallingUid(), ApiStats.RESULT_PERMISSION);
try {
Log.startSession("TSI.sICS", Log.getPackageAbbreviation(callingPackage));
if (!canReadPhoneState(callingPackage, callingFeatureId, "showInCallScreen")) {
@@ -1425,16 +1624,18 @@
}
synchronized (mLock) {
-
UserHandle callingUser = Binder.getCallingUserHandle();
long token = Binder.clearCallingIdentity();
+ event.setResult(ApiStats.RESULT_NORMAL);
try {
- mCallsManager.getInCallController().bringToForeground(showDialpad, callingUser);
+ mCallsManager.getInCallController().bringToForeground(
+ showDialpad, callingUser);
} finally {
Binder.restoreCallingIdentity(token);
}
}
} finally {
+ logEvent(event);
Log.endSession();
}
}
@@ -1444,12 +1645,16 @@
*/
@Override
public void cancelMissedCallsNotification(String callingPackage) {
+ ApiStats.ApiEvent event = new ApiStats.ApiEvent(
+ ApiStats.API_CANCELMISSEDCALLSNOTIFICATION,
+ Binder.getCallingUid(), ApiStats.RESULT_PERMISSION);
try {
Log.startSession("TSI.cMCN", Log.getPackageAbbreviation(callingPackage));
synchronized (mLock) {
enforcePermissionOrPrivilegedDialer(MODIFY_PHONE_STATE, callingPackage);
UserHandle userHandle = Binder.getCallingUserHandle();
long token = Binder.clearCallingIdentity();
+ event.setResult(ApiStats.RESULT_NORMAL);
try {
mCallsManager.getMissedCallNotifier().clearMissedCalls(userHandle);
} finally {
@@ -1457,6 +1662,7 @@
}
}
} finally {
+ logEvent(event);
Log.endSession();
}
}
@@ -1466,6 +1672,8 @@
*/
@Override
public boolean handlePinMmi(String dialString, String callingPackage) {
+ ApiStats.ApiEvent event = new ApiStats.ApiEvent(ApiStats.API_HANDLEPINMMI,
+ Binder.getCallingUid(), ApiStats.RESULT_PERMISSION);
try {
Log.startSession("TSI.hPM", Log.getPackageAbbreviation(callingPackage));
enforcePermissionOrPrivilegedDialer(MODIFY_PHONE_STATE, callingPackage);
@@ -1473,6 +1681,7 @@
// Switch identity so that TelephonyManager checks Telecom's permissions
// instead.
long token = Binder.clearCallingIdentity();
+ event.setResult(ApiStats.RESULT_NORMAL);
boolean retval = false;
try {
retval = getTelephonyManager(
@@ -1484,6 +1693,7 @@
return retval;
} finally {
+ logEvent(event);
Log.endSession();
}
}
@@ -1494,9 +1704,11 @@
@Override
public boolean handlePinMmiForPhoneAccount(PhoneAccountHandle accountHandle,
String dialString, String callingPackage) {
+ ApiStats.ApiEvent event = new ApiStats.ApiEvent(
+ ApiStats.API_HANDLEPINMMIFORPHONEACCOUNT,
+ Binder.getCallingUid(), ApiStats.RESULT_PERMISSION);
try {
Log.startSession("TSI.hPMFPA", Log.getPackageAbbreviation(callingPackage));
-
enforcePermissionOrPrivilegedDialer(MODIFY_PHONE_STATE, callingPackage);
UserHandle callingUserHandle = Binder.getCallingUserHandle();
synchronized (mLock) {
@@ -1511,6 +1723,7 @@
// Switch identity so that TelephonyManager checks Telecom's permissions
// instead.
long token = Binder.clearCallingIdentity();
+ event.setResult(ApiStats.RESULT_NORMAL);
boolean retval = false;
int subId;
try {
@@ -1522,6 +1735,7 @@
retval = getTelephonyManager(subId)
.handlePinMmiForSubscriber(subId, dialString);
} catch (UnsupportedOperationException uoe) {
+ event.setResult(ApiStats.RESULT_EXCEPTION);
Log.w(this, "handlePinMmiForPhoneAccount: no telephony");
retval = false;
}
@@ -1530,6 +1744,7 @@
}
return retval;
} finally {
+ logEvent(event);
Log.endSession();
}
}
@@ -1540,6 +1755,8 @@
@Override
public Uri getAdnUriForPhoneAccount(PhoneAccountHandle accountHandle,
String callingPackage) {
+ ApiStats.ApiEvent event = new ApiStats.ApiEvent(ApiStats.API_GETADNURIFORPHONEACCOUNT,
+ Binder.getCallingUid(), ApiStats.RESULT_PERMISSION);
try {
Log.startSession("TSI.aAUFPA", Log.getPackageAbbreviation(callingPackage));
enforcePermissionOrPrivilegedDialer(MODIFY_PHONE_STATE, callingPackage);
@@ -1554,6 +1771,7 @@
// Switch identity so that TelephonyManager checks Telecom's permissions
// instead.
long token = Binder.clearCallingIdentity();
+ event.setResult(ApiStats.RESULT_NORMAL);
String retval = "content://icc/adn/";
try {
long subId = mPhoneAccountRegistrar
@@ -1565,6 +1783,7 @@
return Uri.parse(retval);
} finally {
+ logEvent(event);
Log.endSession();
}
}
@@ -1574,6 +1793,8 @@
*/
@Override
public boolean isTtySupported(String callingPackage, String callingFeatureId) {
+ ApiStats.ApiEvent event = new ApiStats.ApiEvent(ApiStats.API_ISTTYSUPPORTED,
+ Binder.getCallingUid(), ApiStats.RESULT_PERMISSION);
try {
Log.startSession("TSI.iTS", Log.getPackageAbbreviation(callingPackage));
if (!canReadPhoneState(callingPackage, callingFeatureId, "isTtySupported")) {
@@ -1581,10 +1802,12 @@
"READ_PRIVILEGED_PHONE_STATE or READ_PHONE_STATE can call this api");
}
+ event.setResult(ApiStats.RESULT_NORMAL);
synchronized (mLock) {
return mCallsManager.isTtySupported();
}
} finally {
+ logEvent(event);
Log.endSession();
}
}
@@ -1594,16 +1817,20 @@
*/
@Override
public int getCurrentTtyMode(String callingPackage, String callingFeatureId) {
+ ApiStats.ApiEvent event = new ApiStats.ApiEvent(ApiStats.API_GETCURRENTTTYMODE,
+ Binder.getCallingUid(), ApiStats.RESULT_PERMISSION);
try {
Log.startSession("TSI.gCTM", Log.getPackageAbbreviation(callingPackage));
if (!canReadPhoneState(callingPackage, callingFeatureId, "getCurrentTtyMode")) {
return TelecomManager.TTY_MODE_OFF;
}
+ event.setResult(ApiStats.RESULT_NORMAL);
synchronized (mLock) {
return mCallsManager.getCurrentTtyMode();
}
} finally {
+ logEvent(event);
Log.endSession();
}
}
@@ -1614,6 +1841,8 @@
@Override
public void addNewIncomingCall(PhoneAccountHandle phoneAccountHandle, Bundle extras,
String callingPackage) {
+ ApiStats.ApiEvent event = new ApiStats.ApiEvent(ApiStats.API_ADDNEWINCOMINGCALL,
+ Binder.getCallingUid(), ApiStats.RESULT_PERMISSION);
try {
Log.startSession("TSI.aNIC", Log.getPackageAbbreviation(callingPackage));
synchronized (mLock) {
@@ -1647,6 +1876,7 @@
}
}
long token = Binder.clearCallingIdentity();
+ event.setResult(ApiStats.RESULT_NORMAL);
try {
Intent intent = new Intent(TelecomManager.ACTION_INCOMING_CALL);
intent.putExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE,
@@ -1685,11 +1915,14 @@
Binder.restoreCallingIdentity(token);
}
} else {
+ // Invalid parameters are considered as an exception
+ event.setResult(ApiStats.RESULT_EXCEPTION);
Log.w(this, "Null phoneAccountHandle. Ignoring request to add new" +
" incoming call");
}
}
} finally {
+ logEvent(event);
Log.endSession();
}
}
@@ -1700,6 +1933,8 @@
@Override
public void addNewIncomingConference(PhoneAccountHandle phoneAccountHandle, Bundle extras,
String callingPackage) {
+ ApiStats.ApiEvent event = new ApiStats.ApiEvent(ApiStats.API_ADDNEWINCOMINGCONFERENCE,
+ Binder.getCallingUid(), ApiStats.RESULT_PERMISSION);
try {
Log.startSession("TSI.aNIC", Log.getPackageAbbreviation(callingPackage));
synchronized (mLock) {
@@ -1727,6 +1962,7 @@
}
}
long token = Binder.clearCallingIdentity();
+ event.setResult(ApiStats.RESULT_NORMAL);
try {
mCallsManager.processIncomingConference(
phoneAccountHandle, extras);
@@ -1734,22 +1970,26 @@
Binder.restoreCallingIdentity(token);
}
} else {
+ // Invalid parameters are considered as an exception
+ event.setResult(ApiStats.RESULT_EXCEPTION);
Log.w(this, "Null phoneAccountHandle. Ignoring request to add new" +
" incoming conference");
}
}
} finally {
+ logEvent(event);
Log.endSession();
}
}
-
/**
* @see android.telecom.TelecomManager#acceptHandover
*/
@Override
public void acceptHandover(Uri srcAddr, int videoState, PhoneAccountHandle destAcct,
String callingPackage) {
+ ApiStats.ApiEvent event = new ApiStats.ApiEvent(ApiStats.API_ACCEPTHANDOVER,
+ Binder.getCallingUid(), ApiStats.RESULT_PERMISSION);
try {
Log.startSession("TSI.aHO", Log.getPackageAbbreviation(callingPackage));
synchronized (mLock) {
@@ -1779,17 +2019,22 @@
}
long token = Binder.clearCallingIdentity();
+ event.setResult(ApiStats.RESULT_EXCEPTION);
try {
mCallsManager.acceptHandover(srcAddr, videoState, destAcct);
+ event.setResult(ApiStats.RESULT_NORMAL);
} finally {
Binder.restoreCallingIdentity(token);
}
} else {
+ // Invalid parameters are considered as an exception
+ event.setResult(ApiStats.RESULT_EXCEPTION);
Log.w(this, "Null phoneAccountHandle. Ignoring request " +
"to handover the call");
}
}
} finally {
+ logEvent(event);
Log.endSession();
}
}
@@ -1799,6 +2044,8 @@
*/
@Override
public void addNewUnknownCall(PhoneAccountHandle phoneAccountHandle, Bundle extras) {
+ ApiStats.ApiEvent event = new ApiStats.ApiEvent(ApiStats.API_ADDNEWUNKNOWNCALL,
+ Binder.getCallingUid(), ApiStats.RESULT_PERMISSION);
try {
Log.startSession("TSI.aNUC");
try {
@@ -1822,7 +2069,7 @@
enforcePhoneAccountIsRegisteredEnabled(phoneAccountHandle,
Binder.getCallingUserHandle());
long token = Binder.clearCallingIdentity();
-
+ event.setResult(ApiStats.RESULT_NORMAL);
try {
Intent intent = new Intent(TelecomManager.ACTION_NEW_UNKNOWN_CALL);
if (extras != null) {
@@ -1838,12 +2085,15 @@
Binder.restoreCallingIdentity(token);
}
} else {
+ // Invalid parameters are considered as an exception
+ event.setResult(ApiStats.RESULT_EXCEPTION);
Log.i(this,
"Null phoneAccountHandle or not initiated by Telephony. " +
"Ignoring request to add new unknown call.");
}
}
} finally {
+ logEvent(event);
Log.endSession();
}
}
@@ -1854,6 +2104,8 @@
@Override
public void startConference(List<Uri> participants, Bundle extras,
String callingPackage) {
+ ApiStats.ApiEvent event = new ApiStats.ApiEvent(ApiStats.API_STARTCONFERENCE,
+ Binder.getCallingUid(), ApiStats.RESULT_PERMISSION);
try {
Log.startSession("TSI.sC", Log.getPackageAbbreviation(callingPackage));
if (!canCallPhone(callingPackage, "startConference")) {
@@ -1863,6 +2115,7 @@
// Binder is clearing the identity, so we need to keep the store the handle
UserHandle currentUserHandle = Binder.getCallingUserHandle();
long token = Binder.clearCallingIdentity();
+ event.setResult(ApiStats.RESULT_NORMAL);
try {
mCallsManager.startConference(participants, extras, callingPackage,
currentUserHandle);
@@ -1870,6 +2123,7 @@
Binder.restoreCallingIdentity(token);
}
} finally {
+ logEvent(event);
Log.endSession();
}
}
@@ -1880,6 +2134,8 @@
@Override
public void placeCall(Uri handle, Bundle extras, String callingPackage,
String callingFeatureId) {
+ ApiStats.ApiEvent event = new ApiStats.ApiEvent(ApiStats.API_PLACECALL,
+ Binder.getCallingUid(), ApiStats.RESULT_PERMISSION);
try {
Log.startSession("TSI.pC", Log.getPackageAbbreviation(callingPackage));
enforceCallingPackage(callingPackage, "placeCall");
@@ -1955,6 +2211,7 @@
synchronized (mLock) {
final UserHandle userHandle = Binder.getCallingUserHandle();
long token = Binder.clearCallingIdentity();
+ event.setResult(ApiStats.RESULT_NORMAL);
try {
final Intent intent = new Intent(hasCallPrivilegedPermission ?
Intent.ACTION_CALL_PRIVILEGED : Intent.ACTION_CALL, handle);
@@ -1972,6 +2229,7 @@
}
}
} finally {
+ logEvent(event);
Log.endSession();
}
}
@@ -1981,11 +2239,14 @@
*/
@Override
public boolean enablePhoneAccount(PhoneAccountHandle accountHandle, boolean isEnabled) {
+ ApiStats.ApiEvent event = new ApiStats.ApiEvent(ApiStats.API_ENABLEPHONEACCOUNT,
+ Binder.getCallingUid(), ApiStats.RESULT_PERMISSION);
try {
Log.startSession("TSI.ePA");
enforceModifyPermission();
synchronized (mLock) {
long token = Binder.clearCallingIdentity();
+ event.setResult(ApiStats.RESULT_NORMAL);
try {
// enable/disable phone account
return mPhoneAccountRegistrar.enablePhoneAccount(accountHandle, isEnabled);
@@ -1994,12 +2255,15 @@
}
}
} finally {
+ logEvent(event);
Log.endSession();
}
}
@Override
public boolean setDefaultDialer(String packageName) {
+ ApiStats.ApiEvent event = new ApiStats.ApiEvent(ApiStats.API_SETDEFAULTDIALER,
+ Binder.getCallingUid(), ApiStats.RESULT_PERMISSION);
try {
Log.startSession("TSI.sDD");
enforcePermission(MODIFY_PHONE_STATE);
@@ -2007,6 +2271,7 @@
synchronized (mLock) {
int callerUserId = UserHandle.getCallingUserId();
long token = Binder.clearCallingIdentity();
+ event.setResult(ApiStats.RESULT_NORMAL);
try {
return mDefaultDialerCache.setDefaultDialer(packageName,
callerUserId);
@@ -2015,6 +2280,7 @@
}
}
} finally {
+ logEvent(event);
Log.endSession();
}
}
@@ -2047,11 +2313,15 @@
@Override
public TelecomAnalytics dumpCallAnalytics() {
+ ApiStats.ApiEvent event = new ApiStats.ApiEvent(ApiStats.API_DUMPCALLANALYTICS,
+ Binder.getCallingUid(), ApiStats.RESULT_PERMISSION);
try {
Log.startSession("TSI.dCA");
enforcePermission(DUMP);
+ event.setResult(ApiStats.RESULT_NORMAL);
return Analytics.dumpToParcelableAnalytics();
} finally {
+ logEvent(event);
Log.endSession();
}
}
@@ -2066,6 +2336,8 @@
*/
@Override
protected void dump(FileDescriptor fd, final PrintWriter writer, String[] args) {
+ ApiStats.ApiEvent event = new ApiStats.ApiEvent(ApiStats.API_DUMP,
+ Binder.getCallingUid(), ApiStats.RESULT_PERMISSION);
if (mContext.checkCallingOrSelfPermission(
android.Manifest.permission.DUMP)
!= PackageManager.PERMISSION_GRANTED) {
@@ -2075,6 +2347,8 @@
return;
}
+ event.setResult(ApiStats.RESULT_NORMAL);
+ logEvent(event);
if (args != null && args.length > 0 && Analytics.ANALYTICS_DUMPSYS_ARG.equals(
args[0])) {
@@ -2154,7 +2428,7 @@
}
for (Method m : methods) {
- String flagEnabled = (Boolean) m.invoke(mFeatureFlags) ? "[✅]": "[❌]";
+ String flagEnabled = (Boolean) m.invoke(mFeatureFlags) ? "[✅]" : "[❌]";
String methodName = m.getName();
String camelCaseName = methodName.replaceAll("([a-z])([A-Z]+)", "$1_$2")
.toLowerCase(Locale.US);
@@ -2171,17 +2445,23 @@
*/
@Override
public Intent createManageBlockedNumbersIntent(String callingPackage) {
+ ApiStats.ApiEvent event = new ApiStats.ApiEvent(
+ ApiStats.API_CREATEMANAGEBLOCKEDNUMBERSINTENT,
+ Binder.getCallingUid(), ApiStats.RESULT_NORMAL);
try {
Log.startSession("TSI.cMBNI", Log.getPackageAbbreviation(callingPackage));
return BlockedNumbersActivity.getIntentForStartingActivity();
} finally {
+ logEvent(event);
Log.endSession();
}
}
-
@Override
public Intent createLaunchEmergencyDialerIntent(String number) {
+ ApiStats.ApiEvent event = new ApiStats.ApiEvent(
+ ApiStats.API_CREATELAUNCHEMERGENCYDIALERINTENT,
+ Binder.getCallingUid(), ApiStats.RESULT_NORMAL);
String packageName = mContext.getApplicationContext().getString(
com.android.internal.R.string.config_emergency_dialer_package);
Intent intent = new Intent(Intent.ACTION_DIAL_EMERGENCY)
@@ -2194,6 +2474,7 @@
if (!TextUtils.isEmpty(number) && TextUtils.isDigitsOnly(number)) {
intent.setData(Uri.parse("tel:" + number));
}
+ logEvent(event);
return intent;
}
@@ -2203,6 +2484,8 @@
@Override
public boolean isIncomingCallPermitted(PhoneAccountHandle phoneAccountHandle,
String callingPackage) {
+ ApiStats.ApiEvent event = new ApiStats.ApiEvent(ApiStats.API_ISINCOMINGCALLPERMITTED,
+ Binder.getCallingUid(), ApiStats.RESULT_PERMISSION);
Log.startSession("TSI.iICP", Log.getPackageAbbreviation(callingPackage));
try {
enforceCallingPackage(callingPackage, "isIncomingCallPermitted");
@@ -2211,6 +2494,7 @@
enforceUserHandleMatchesCaller(phoneAccountHandle);
synchronized (mLock) {
long token = Binder.clearCallingIdentity();
+ event.setResult(ApiStats.RESULT_NORMAL);
try {
return mCallsManager.isIncomingCallPermitted(phoneAccountHandle);
} finally {
@@ -2218,6 +2502,7 @@
}
}
} finally {
+ logEvent(event);
Log.endSession();
}
}
@@ -2228,6 +2513,8 @@
@Override
public boolean isOutgoingCallPermitted(PhoneAccountHandle phoneAccountHandle,
String callingPackage) {
+ ApiStats.ApiEvent event = new ApiStats.ApiEvent(ApiStats.API_ISOUTGOINGCALLPERMITTED,
+ Binder.getCallingUid(), ApiStats.RESULT_PERMISSION);
Log.startSession("TSI.iOCP", Log.getPackageAbbreviation(callingPackage));
try {
enforceCallingPackage(callingPackage, "isOutgoingCallPermitted");
@@ -2236,6 +2523,7 @@
enforceUserHandleMatchesCaller(phoneAccountHandle);
synchronized (mLock) {
long token = Binder.clearCallingIdentity();
+ event.setResult(ApiStats.RESULT_NORMAL);
try {
return mCallsManager.isOutgoingCallPermitted(phoneAccountHandle);
} finally {
@@ -2243,6 +2531,7 @@
}
}
} finally {
+ logEvent(event);
Log.endSession();
}
}
@@ -2296,11 +2585,14 @@
*/
@Override
public boolean isInEmergencyCall() {
+ ApiStats.ApiEvent event = new ApiStats.ApiEvent(ApiStats.API_ISINEMERGENCYCALL,
+ Binder.getCallingUid(), ApiStats.RESULT_PERMISSION);
try {
Log.startSession("TSI.iIEC");
enforceModifyPermission();
synchronized (mLock) {
long token = Binder.clearCallingIdentity();
+ event.setResult(ApiStats.RESULT_NORMAL);
try {
boolean isInEmergencyCall = mCallsManager.isInEmergencyCall();
Log.i(this, "isInEmergencyCall: %b", isInEmergencyCall);
@@ -2310,6 +2602,7 @@
}
}
} finally {
+ logEvent(event);
Log.endSession();
}
}
@@ -2383,7 +2676,7 @@
}
}
- private boolean isDisconnectingOrDisconnected(Call call){
+ private boolean isDisconnectingOrDisconnected(Call call) {
return call.getState() == CallState.DISCONNECTED
|| call.getState() == CallState.DISCONNECTING;
}
@@ -2631,6 +2924,8 @@
@Override
public boolean isInSelfManagedCall(String packageName, UserHandle userHandle,
String callingPackage) {
+ ApiStats.ApiEvent event = new ApiStats.ApiEvent(ApiStats.API_ISINSELFMANAGEDCALL,
+ Binder.getCallingUid(), ApiStats.RESULT_PERMISSION);
try {
mContext.enforceCallingOrSelfPermission(READ_PRIVILEGED_PHONE_STATE,
"READ_PRIVILEGED_PHONE_STATE required.");
@@ -2643,6 +2938,7 @@
Log.startSession("TSI.iISMC", Log.getPackageAbbreviation(callingPackage));
synchronized (mLock) {
long token = Binder.clearCallingIdentity();
+ event.setResult(ApiStats.RESULT_NORMAL);
try {
return mCallsManager.isInSelfManagedCall(
packageName, userHandle);
@@ -2651,10 +2947,70 @@
}
}
} finally {
+ logEvent(event);
Log.endSession();
}
}
};
+ public TelecomServiceImpl(
+ Context context,
+ CallsManager callsManager,
+ PhoneAccountRegistrar phoneAccountRegistrar,
+ CallIntentProcessor.Adapter callIntentProcessorAdapter,
+ UserCallIntentProcessorFactory userCallIntentProcessorFactory,
+ DefaultDialerCache defaultDialerCache,
+ SubscriptionManagerAdapter subscriptionManagerAdapter,
+ SettingsSecureAdapter settingsSecureAdapter,
+ FeatureFlags featureFlags,
+ com.android.internal.telephony.flags.FeatureFlags telephonyFeatureFlags,
+ TelecomSystem.SyncRoot lock, TelecomMetricsController metricsController,
+ String sysUiPackageName) {
+ mContext = context;
+ mAppOpsManager = mContext.getSystemService(AppOpsManager.class);
+
+ mPackageManager = mContext.getPackageManager();
+
+ mCallsManager = callsManager;
+ mFeatureFlags = featureFlags;
+ if (telephonyFeatureFlags != null) {
+ mTelephonyFeatureFlags = telephonyFeatureFlags;
+ } else {
+ mTelephonyFeatureFlags =
+ new com.android.internal.telephony.flags.FeatureFlagsImpl();
+ }
+ mLock = lock;
+ mPhoneAccountRegistrar = phoneAccountRegistrar;
+ mUserCallIntentProcessorFactory = userCallIntentProcessorFactory;
+ mDefaultDialerCache = defaultDialerCache;
+ mCallIntentProcessorAdapter = callIntentProcessorAdapter;
+ mSubscriptionManagerAdapter = subscriptionManagerAdapter;
+ mSettingsSecureAdapter = settingsSecureAdapter;
+ mMetricsController = metricsController;
+ mSystemUiPackageName = sysUiPackageName;
+
+ mDefaultDialerCache.observeDefaultDialerApplication(mContext.getMainExecutor(), userId -> {
+ String defaultDialer = mDefaultDialerCache.getDefaultDialerApplication(userId);
+ if (defaultDialer == null) {
+ // We are replacing the dialer, just wait for the upcoming callback.
+ return;
+ }
+ final Intent intent = new Intent(TelecomManager.ACTION_DEFAULT_DIALER_CHANGED)
+ .putExtra(TelecomManager.EXTRA_CHANGE_DEFAULT_DIALER_PACKAGE_NAME,
+ defaultDialer);
+ mContext.sendBroadcastAsUser(intent, UserHandle.of(userId));
+ });
+
+ mTransactionManager = TransactionManager.getInstance();
+ mTransactionalServiceRepository = new TransactionalServiceRepository(mFeatureFlags);
+ mBlockedNumbersManager = mFeatureFlags.telecomMainlineBlockedNumbersManager()
+ ? mContext.getSystemService(BlockedNumbersManager.class)
+ : null;
+ }
+
+ @VisibleForTesting
+ public void setAnomalyReporterAdapter(AnomalyReporterAdapter mAnomalyReporterAdapter) {
+ mAnomalyReporter = mAnomalyReporterAdapter;
+ }
private boolean enforceCallStreamingPermission(String packageName, PhoneAccountHandle handle,
int uid) {
@@ -2684,7 +3040,7 @@
final int opCode = AppOpsManager.permissionToOpCode(permission);
if (opCode != AppOpsManager.OP_NONE
&& mAppOpsManager.checkOp(opCode, uid, packageName)
- != AppOpsManager.MODE_ALLOWED) {
+ != AppOpsManager.MODE_ALLOWED) {
return false;
}
}
@@ -2701,85 +3057,12 @@
"App requires ACCEPT_HANDOVER permission to accept handovers.");
final int opCode = AppOpsManager.permissionToOpCode(Manifest.permission.ACCEPT_HANDOVER);
- if (opCode != AppOpsManager.OP_ACCEPT_HANDOVER || (
- mAppOpsManager.checkOp(opCode, uid, packageName)
- != AppOpsManager.MODE_ALLOWED)) {
- return false;
- }
- return true;
- }
-
- private Context mContext;
- private AppOpsManager mAppOpsManager;
- private PackageManager mPackageManager;
- private CallsManager mCallsManager;
- private final PhoneAccountRegistrar mPhoneAccountRegistrar;
- private final CallIntentProcessor.Adapter mCallIntentProcessorAdapter;
- private final UserCallIntentProcessorFactory mUserCallIntentProcessorFactory;
- private final DefaultDialerCache mDefaultDialerCache;
- private final SubscriptionManagerAdapter mSubscriptionManagerAdapter;
- private final SettingsSecureAdapter mSettingsSecureAdapter;
- private final TelecomSystem.SyncRoot mLock;
- private TransactionManager mTransactionManager;
- private final TransactionalServiceRepository mTransactionalServiceRepository;
- private final BlockedNumbersManager mBlockedNumbersManager;
- private final FeatureFlags mFeatureFlags;
- private final com.android.internal.telephony.flags.FeatureFlags mTelephonyFeatureFlags;
-
- public TelecomServiceImpl(
- Context context,
- CallsManager callsManager,
- PhoneAccountRegistrar phoneAccountRegistrar,
- CallIntentProcessor.Adapter callIntentProcessorAdapter,
- UserCallIntentProcessorFactory userCallIntentProcessorFactory,
- DefaultDialerCache defaultDialerCache,
- SubscriptionManagerAdapter subscriptionManagerAdapter,
- SettingsSecureAdapter settingsSecureAdapter,
- FeatureFlags featureFlags,
- com.android.internal.telephony.flags.FeatureFlags telephonyFeatureFlags,
- TelecomSystem.SyncRoot lock) {
- mContext = context;
- mAppOpsManager = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
-
- mPackageManager = mContext.getPackageManager();
-
- mCallsManager = callsManager;
- mFeatureFlags = featureFlags;
- if (telephonyFeatureFlags != null) {
- mTelephonyFeatureFlags = telephonyFeatureFlags;
- } else {
- mTelephonyFeatureFlags =
- new com.android.internal.telephony.flags.FeatureFlagsImpl();
- }
- mLock = lock;
- mPhoneAccountRegistrar = phoneAccountRegistrar;
- mUserCallIntentProcessorFactory = userCallIntentProcessorFactory;
- mDefaultDialerCache = defaultDialerCache;
- mCallIntentProcessorAdapter = callIntentProcessorAdapter;
- mSubscriptionManagerAdapter = subscriptionManagerAdapter;
- mSettingsSecureAdapter = settingsSecureAdapter;
-
- mDefaultDialerCache.observeDefaultDialerApplication(mContext.getMainExecutor(), userId -> {
- String defaultDialer = mDefaultDialerCache.getDefaultDialerApplication(userId);
- if (defaultDialer == null) {
- // We are replacing the dialer, just wait for the upcoming callback.
- return;
- }
- final Intent intent = new Intent(TelecomManager.ACTION_DEFAULT_DIALER_CHANGED)
- .putExtra(TelecomManager.EXTRA_CHANGE_DEFAULT_DIALER_PACKAGE_NAME,
- defaultDialer);
- mContext.sendBroadcastAsUser(intent, UserHandle.of(userId));
- });
-
- mTransactionManager = TransactionManager.getInstance();
- mTransactionalServiceRepository = new TransactionalServiceRepository();
- mBlockedNumbersManager = mFeatureFlags.telecomMainlineBlockedNumbersManager()
- ? mContext.getSystemService(BlockedNumbersManager.class)
- : null;
+ return opCode == AppOpsManager.OP_ACCEPT_HANDOVER
+ && (mAppOpsManager.checkOp(opCode, uid, packageName) == AppOpsManager.MODE_ALLOWED);
}
@VisibleForTesting
- public void setTransactionManager(TransactionManager transactionManager){
+ public void setTransactionManager(TransactionManager transactionManager) {
mTransactionManager = transactionManager;
}
@@ -2787,10 +3070,6 @@
return mBinderImpl;
}
- //
- // Supporting methods for the ITelecomService interface implementation.
- //
-
private boolean isPhoneAccountHandleVisibleToCallingUser(
PhoneAccountHandle phoneAccountUserHandle, UserHandle callingUser) {
synchronized (mLock) {
@@ -2822,13 +3101,14 @@
return false;
}
- private void acceptRingingCallInternal(int videoState, String packageName) {
+ private void acceptRingingCallInternal(int videoState, String packageName,
+ boolean isCallerPrivileged) {
Call call = mCallsManager.getFirstCallWithState(CallState.RINGING,
CallState.SIMULATED_RINGING);
if (call != null) {
- if (call.isSelfManaged()) {
+ if (call.isSelfManaged() && !isCallerPrivileged) {
Log.addEvent(call, LogUtils.Events.REQUEST_ACCEPT,
- "self-mgd accept ignored from " + packageName);
+ "self-mgd accept ignored from non-privileged app " + packageName);
return;
}
@@ -2839,7 +3119,11 @@
}
}
- private boolean endCallInternal(String callingPackage) {
+ //
+ // Supporting methods for the ITelecomService interface implementation.
+ //
+
+ private boolean endCallInternal(String callingPackage, boolean isCallerPrivileged) {
// Always operate on the foreground call if one exists, otherwise get the first call in
// priority order by call-state.
Call call = mCallsManager.getForegroundCall();
@@ -2859,9 +3143,10 @@
return false;
}
- if (call.isSelfManaged()) {
+ if (call.isSelfManaged() && !isCallerPrivileged) {
Log.addEvent(call, LogUtils.Events.REQUEST_DISCONNECT,
- "self-mgd disconnect ignored from " + callingPackage);
+ "self-mgd disconnect ignored from non-privileged app " +
+ callingPackage);
return false;
}
@@ -2880,14 +3165,14 @@
// Enforce that the PhoneAccountHandle being passed in is both registered to the current user
// and enabled.
private void enforcePhoneAccountIsRegisteredEnabled(PhoneAccountHandle phoneAccountHandle,
- UserHandle callingUserHandle) {
+ UserHandle callingUserHandle) {
PhoneAccount phoneAccount = mPhoneAccountRegistrar.getPhoneAccount(phoneAccountHandle,
callingUserHandle);
- if(phoneAccount == null) {
+ if (phoneAccount == null) {
EventLog.writeEvent(0x534e4554, "26864502", Binder.getCallingUid(), "R");
throw new SecurityException("This PhoneAccountHandle is not registered for this user!");
}
- if(!phoneAccount.isEnabled()) {
+ if (!phoneAccount.isEnabled()) {
EventLog.writeEvent(0x534e4554, "26864502", Binder.getCallingUid(), "E");
throw new SecurityException("This PhoneAccountHandle is not enabled for this user!");
}
@@ -2959,7 +3244,7 @@
/**
* helper method that compares the binder_uid to what the packageManager_uid reports for the
* passed in packageName.
- *
+ * <p>
* returns true if the binder_uid matches the packageManager_uid records
*/
private boolean callingUidMatchesPackageManagerRecords(String packageName) {
@@ -2967,13 +3252,12 @@
int callingUid = Binder.getCallingUid();
PackageManager pm;
long token = Binder.clearCallingIdentity();
- try{
+ try {
pm = mContext.createContextAsUser(
UserHandle.getUserHandleForUid(callingUid), 0).getPackageManager();
- }
- catch (Exception e){
+ } catch (Exception e) {
Log.i(this, "callingUidMatchesPackageManagerRecords:"
- + " createContextAsUser hit exception=[%s]", e.toString());
+ + " createContextAsUser hit exception=[%s]", e.toString());
return false;
} finally {
Binder.restoreCallingIdentity(token);
@@ -2988,7 +3272,7 @@
if (packageUid != callingUid) {
Log.i(this, "callingUidMatchesPackageManagerRecords: uid mismatch found for"
- + "packageName=[%s]. packageManager reports packageUid=[%d] but "
+ + "packageName=[%s]. packageManager reports packageUid=[%d] but "
+ "binder reports callingUid=[%d]", packageName, packageUid, callingUid);
}
@@ -3079,7 +3363,7 @@
boolean permissionsOk =
isCallerSimCallManagerForAnySim(account.getAccountHandle())
|| mContext.checkCallingOrSelfPermission(REGISTER_SIM_SUBSCRIPTION)
- == PackageManager.PERMISSION_GRANTED;
+ == PackageManager.PERMISSION_GRANTED;
if (!prerequisiteCapabilitiesOk || !permissionsOk) {
throw new SecurityException(
"Only SIM subscriptions and connection managers are allowed to declare "
@@ -3091,7 +3375,7 @@
private void enforceRegisterSkipCallFiltering() {
if (!isCallerSystemApp()) {
throw new SecurityException(
- "EXTRA_SKIP_CALL_FILTERING is only available to system apps.");
+ "EXTRA_SKIP_CALL_FILTERING is only available to system apps.");
}
}
@@ -3261,9 +3545,9 @@
private boolean isSelfManagedConnectionService(PhoneAccountHandle phoneAccountHandle) {
if (phoneAccountHandle != null) {
- PhoneAccount phoneAccount = mPhoneAccountRegistrar.getPhoneAccountUnchecked(
- phoneAccountHandle);
- return phoneAccount != null && phoneAccount.isSelfManaged();
+ PhoneAccount phoneAccount = mPhoneAccountRegistrar.getPhoneAccountUnchecked(
+ phoneAccountHandle);
+ return phoneAccount != null && phoneAccount.isSelfManaged();
}
return false;
}
@@ -3365,10 +3649,11 @@
// Note: Important to clear the calling identity since the code below calls into RoleManager
// to check who holds the dialer role, and that requires MANAGE_ROLE_HOLDERS permission
// which is a system permission.
+ int callingUserId = Binder.getCallingUserHandle().getIdentifier();
long token = Binder.clearCallingIdentity();
try {
return mDefaultDialerCache.isDefaultOrSystemDialer(
- callingPackage, Binder.getCallingUserHandle().getIdentifier());
+ callingPackage, callingUserId);
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -3399,7 +3684,7 @@
}
private void broadcastCallScreeningAppChangedIntent(String componentName,
- boolean isDefault) {
+ boolean isDefault) {
if (TextUtils.isEmpty(componentName)) {
return;
}
@@ -3408,11 +3693,11 @@
if (broadcastComponentName != null) {
Intent intent = new Intent(TelecomManager
- .ACTION_DEFAULT_CALL_SCREENING_APP_CHANGED);
+ .ACTION_DEFAULT_CALL_SCREENING_APP_CHANGED);
intent.putExtra(TelecomManager
- .EXTRA_IS_DEFAULT_CALL_SCREENING_APP, isDefault);
+ .EXTRA_IS_DEFAULT_CALL_SCREENING_APP, isDefault);
intent.putExtra(TelecomManager
- .EXTRA_DEFAULT_CALL_SCREENING_APP_COMPONENT_NAME, componentName);
+ .EXTRA_DEFAULT_CALL_SCREENING_APP_COMPONENT_NAME, componentName);
intent.setPackage(broadcastComponentName.getPackageName());
mContext.sendBroadcast(intent);
}
@@ -3451,4 +3736,40 @@
}
}
}
+
+ private void logEvent(ApiStats.ApiEvent event) {
+ if (mFeatureFlags.telecomMetricsSupport()) {
+ mMetricsController.getApiStats().log(event);
+ }
+ }
+
+ public interface SubscriptionManagerAdapter {
+ int getDefaultVoiceSubId();
+ }
+
+ public interface SettingsSecureAdapter {
+ void putStringForUser(ContentResolver resolver, String name, String value, int userHandle);
+
+ String getStringForUser(ContentResolver resolver, String name, int userHandle);
+ }
+
+ static class SubscriptionManagerAdapterImpl implements SubscriptionManagerAdapter {
+ @Override
+ public int getDefaultVoiceSubId() {
+ return SubscriptionManager.getDefaultVoiceSubscriptionId();
+ }
+ }
+
+ static class SettingsSecureAdapterImpl implements SettingsSecureAdapter {
+ @Override
+ public void putStringForUser(ContentResolver resolver, String name, String value,
+ int userHandle) {
+ Settings.Secure.putStringForUser(resolver, name, value, userHandle);
+ }
+
+ @Override
+ public String getStringForUser(ContentResolver resolver, String name, int userHandle) {
+ return Settings.Secure.getStringForUser(resolver, name, userHandle);
+ }
+ }
}
diff --git a/src/com/android/server/telecom/TelecomSystem.java b/src/com/android/server/telecom/TelecomSystem.java
index fd1053f..7020885 100644
--- a/src/com/android/server/telecom/TelecomSystem.java
+++ b/src/com/android/server/telecom/TelecomSystem.java
@@ -55,7 +55,7 @@
import com.android.server.telecom.ui.IncomingCallNotifier;
import com.android.server.telecom.ui.MissedCallNotifierImpl.MissedCallNotifierImplFactory;
import com.android.server.telecom.ui.ToastFactory;
-import com.android.server.telecom.voip.TransactionManager;
+import com.android.server.telecom.callsequencing.TransactionManager;
import java.io.FileNotFoundException;
import java.io.InputStream;
@@ -224,6 +224,7 @@
RoleManagerAdapter roleManagerAdapter,
ContactsAsyncHelper.Factory contactsAsyncHelperFactory,
DeviceIdleControllerAdapter deviceIdleControllerAdapter,
+ String sysUiPackageName,
Ringer.AccessibilityManagerAdapter accessibilityManagerAdapter,
Executor asyncTaskExecutor,
Executor asyncCallAudioTaskExecutor,
@@ -245,8 +246,8 @@
// Wrap this in a try block to ensure session cleanup occurs in the case of error.
try {
mPhoneAccountRegistrar = new PhoneAccountRegistrar(mContext, mLock, defaultDialerCache,
- packageName -> AppLabelProxy.Util.getAppLabel(
- mContext.getPackageManager(), packageName), null, mFeatureFlags);
+ (packageName, userHandle) -> AppLabelProxy.Util.getAppLabel(mContext,
+ userHandle, packageName, mFeatureFlags), null, mFeatureFlags);
mContactsAsyncHelper = contactsAsyncHelperFactory.create(
new ContactsAsyncHelper.ContentResolverAdapter() {
@@ -386,8 +387,8 @@
CallStreamingNotification callStreamingNotification =
new CallStreamingNotification(mContext,
- packageName -> AppLabelProxy.Util.getAppLabel(
- mContext.getPackageManager(), packageName), asyncTaskExecutor);
+ (packageName, userHandle) -> AppLabelProxy.Util.getAppLabel(mContext,
+ userHandle, packageName, mFeatureFlags), asyncTaskExecutor);
mCallsManager = new CallsManager(
mContext,
@@ -502,7 +503,9 @@
new TelecomServiceImpl.SettingsSecureAdapterImpl(),
featureFlags,
null,
- mLock);
+ mLock,
+ metricsController,
+ sysUiPackageName);
} finally {
Log.endSession();
}
diff --git a/src/com/android/server/telecom/Timeouts.java b/src/com/android/server/telecom/Timeouts.java
index 0ed71df..ee18250 100644
--- a/src/com/android/server/telecom/Timeouts.java
+++ b/src/com/android/server/telecom/Timeouts.java
@@ -447,12 +447,12 @@
/**
* Returns the duration of time a VoIP call can be in an intermediate state before Telecom will
- * try to clean up the call.
+ * try to clean up the call. The default is 2 minutes.
* @return the state timeout in millis.
*/
public static long getVoipCallIntermediateStateTimeoutMillis() {
return DeviceConfig.getLong(DeviceConfig.NAMESPACE_TELEPHONY,
- INTERMEDIATE_STATE_VOIP_NORMAL_TIMEOUT_MILLIS, 60000L);
+ INTERMEDIATE_STATE_VOIP_NORMAL_TIMEOUT_MILLIS, 120000L);
}
/**
diff --git a/src/com/android/server/telecom/TransactionalServiceRepository.java b/src/com/android/server/telecom/TransactionalServiceRepository.java
index 793840e..5ae459e 100644
--- a/src/com/android/server/telecom/TransactionalServiceRepository.java
+++ b/src/com/android/server/telecom/TransactionalServiceRepository.java
@@ -20,6 +20,8 @@
import android.telecom.PhoneAccountHandle;
import com.android.internal.telecom.ICallEventCallback;
+import com.android.server.telecom.flags.FeatureFlags;
+import com.android.server.telecom.callsequencing.TransactionManager;
import java.util.HashMap;
import java.util.Map;
@@ -32,8 +34,10 @@
private static final String TAG = TransactionalServiceRepository.class.getSimpleName();
private static final Map<PhoneAccountHandle, TransactionalServiceWrapper> mServiceLookupTable =
new HashMap<>();
+ private final FeatureFlags mFlags;
- public TransactionalServiceRepository() {
+ public TransactionalServiceRepository(FeatureFlags flags) {
+ mFlags = flags;
}
public TransactionalServiceWrapper addNewCallForTransactionalServiceWrapper
@@ -45,7 +49,8 @@
if (!hasExistingServiceWrapper(phoneAccountHandle)) {
Log.d(TAG, "creating a new TSW; handle=[%s]", phoneAccountHandle);
service = new TransactionalServiceWrapper(callEventCallback,
- callsManager, phoneAccountHandle, call, this);
+ callsManager, phoneAccountHandle, call, this,
+ TransactionManager.getInstance(), mFlags.enableCallSequencing());
} else {
Log.d(TAG, "add a new call to an existing TSW; handle=[%s]", phoneAccountHandle);
service = getTransactionalServiceWrapper(phoneAccountHandle);
diff --git a/src/com/android/server/telecom/TransactionalServiceWrapper.java b/src/com/android/server/telecom/TransactionalServiceWrapper.java
index b73de23..d63a0bd 100644
--- a/src/com/android/server/telecom/TransactionalServiceWrapper.java
+++ b/src/com/android/server/telecom/TransactionalServiceWrapper.java
@@ -39,23 +39,18 @@
import com.android.internal.telecom.ICallControl;
import com.android.internal.telecom.ICallEventCallback;
-import com.android.server.telecom.voip.CallEventCallbackAckTransaction;
-import com.android.server.telecom.voip.EndpointChangeTransaction;
-import com.android.server.telecom.voip.HoldCallTransaction;
-import com.android.server.telecom.voip.EndCallTransaction;
-import com.android.server.telecom.voip.MaybeHoldCallForNewCallTransaction;
-import com.android.server.telecom.voip.RequestNewActiveCallTransaction;
-import com.android.server.telecom.voip.SerialTransaction;
-import com.android.server.telecom.voip.SetMuteStateTransaction;
-import com.android.server.telecom.voip.RequestVideoStateTransaction;
-import com.android.server.telecom.voip.TransactionManager;
-import com.android.server.telecom.voip.VoipCallTransaction;
-import com.android.server.telecom.voip.VoipCallTransactionResult;
+import com.android.server.telecom.callsequencing.TransactionalCallSequencingAdapter;
+import com.android.server.telecom.callsequencing.voip.CallEventCallbackAckTransaction;
+import com.android.server.telecom.callsequencing.voip.EndpointChangeTransaction;
+import com.android.server.telecom.callsequencing.voip.SetMuteStateTransaction;
+import com.android.server.telecom.callsequencing.voip.RequestVideoStateTransaction;
+import com.android.server.telecom.callsequencing.TransactionManager;
+import com.android.server.telecom.callsequencing.CallTransaction;
+import com.android.server.telecom.callsequencing.CallTransactionResult;
-import java.util.ArrayList;
-import java.util.List;
import java.util.Locale;
import java.util.Set;
+import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
/**
@@ -73,6 +68,8 @@
public static final String DISCONNECT = "Disconnect";
public static final String START_STREAMING = "StartStreaming";
public static final String REQUEST_VIDEO_STATE = "RequestVideoState";
+ public static final String SET_MUTE_STATE = "SetMuteState";
+ public static final String CALL_ENDPOINT_CHANGE = "CallEndpointChange";
// CallEventCallback : Telecom --> Client (ex. voip app)
public static final String ON_SET_ACTIVE = "onSetActive";
@@ -80,6 +77,7 @@
public static final String ON_ANSWER = "onAnswer";
public static final String ON_DISCONNECT = "onDisconnect";
public static final String ON_STREAMING_STARTED = "onStreamingStarted";
+ public static final String STOP_STREAMING = "stopStreaming";
private final CallsManager mCallsManager;
private final ICallEventCallback mICallEventCallback;
@@ -93,6 +91,7 @@
// needs to be non-final for testing
private TransactionManager mTransactionManager;
private CallStreamingController mStreamingController;
+ private final TransactionalCallSequencingAdapter mCallSequencingAdapter;
// Each TransactionalServiceWrapper should have their own Binder.DeathRecipient to clean up
@@ -108,26 +107,24 @@
public TransactionalServiceWrapper(ICallEventCallback callEventCallback,
CallsManager callsManager, PhoneAccountHandle phoneAccountHandle, Call call,
- TransactionalServiceRepository repo) {
+ TransactionalServiceRepository repo, TransactionManager transactionManager,
+ boolean isCallSequencingEnabled) {
// passed args
mICallEventCallback = callEventCallback;
mCallsManager = callsManager;
mPhoneAccountHandle = phoneAccountHandle;
mTrackedCalls.put(call.getId(), call); // service is now tracking its first call
mRepository = repo;
+ mTransactionManager = transactionManager;
// init instance vars
mPackageName = phoneAccountHandle.getComponentName().getPackageName();
- mTransactionManager = TransactionManager.getInstance();
mStreamingController = mCallsManager.getCallStreamingController();
mLock = mCallsManager.getLock();
+ mCallSequencingAdapter = new TransactionalCallSequencingAdapter(mTransactionManager,
+ mCallsManager, isCallSequencingEnabled);
setDeathRecipient(callEventCallback);
}
- @VisibleForTesting
- public void setTransactionManager(TransactionManager transactionManager) {
- mTransactionManager = transactionManager;
- }
-
public TransactionManager getTransactionManager() {
return mTransactionManager;
}
@@ -170,11 +167,7 @@
}
private void cleanupTransactionalServiceWrapper() {
- for (Call call : mTrackedCalls.values()) {
- mCallsManager.markCallAsDisconnected(call,
- new DisconnectCause(DisconnectCause.ERROR, "process died"));
- mCallsManager.removeCall(call); // This will clear mTrackedCalls && ClientTWS
- }
+ mCallSequencingAdapter.cleanup(mTrackedCalls.values());
}
/***
@@ -184,8 +177,7 @@
*/
private final ICallControl mICallControl = new ICallControl.Stub() {
@Override
- public void setActive(String callId, android.os.ResultReceiver callback)
- throws RemoteException {
+ public void setActive(String callId, android.os.ResultReceiver callback) {
long token = Binder.clearCallingIdentity();
try {
Log.startSession("TSW.sA");
@@ -197,8 +189,8 @@
}
@Override
- public void answer(int videoState, String callId, android.os.ResultReceiver callback)
- throws RemoteException {
+
+ public void answer(int videoState, String callId, android.os.ResultReceiver callback) {
long token = Binder.clearCallingIdentity();
try {
Log.startSession("TSW.a");
@@ -210,8 +202,7 @@
}
@Override
- public void setInactive(String callId, android.os.ResultReceiver callback)
- throws RemoteException {
+ public void setInactive(String callId, android.os.ResultReceiver callback) {
long token = Binder.clearCallingIdentity();
try {
Log.startSession("TSW.sI");
@@ -224,8 +215,7 @@
@Override
public void disconnect(String callId, DisconnectCause disconnectCause,
- android.os.ResultReceiver callback)
- throws RemoteException {
+ android.os.ResultReceiver callback) {
long token = Binder.clearCallingIdentity();
try {
Log.startSession("TSW.d");
@@ -237,12 +227,11 @@
}
@Override
- public void setMuteState(boolean isMuted, android.os.ResultReceiver callback)
- throws RemoteException {
+ public void setMuteState(boolean isMuted, android.os.ResultReceiver callback) {
long token = Binder.clearCallingIdentity();
try {
Log.startSession("TSW.sMS");
- addTransactionsToManager(
+ addTransactionsToManager(SET_MUTE_STATE,
new SetMuteStateTransaction(mCallsManager, isMuted), callback);
} finally {
Binder.restoreCallingIdentity(token);
@@ -251,8 +240,7 @@
}
@Override
- public void startCallStreaming(String callId, android.os.ResultReceiver callback)
- throws RemoteException {
+ public void startCallStreaming(String callId, android.os.ResultReceiver callback) {
long token = Binder.clearCallingIdentity();
try {
Log.startSession("TSW.sCS");
@@ -264,8 +252,7 @@
}
@Override
- public void requestVideoState(int videoState, String callId, ResultReceiver callback)
- throws RemoteException {
+ public void requestVideoState(int videoState, String callId, ResultReceiver callback) {
long token = Binder.clearCallingIdentity();
try {
Log.startSession("TSW.rVS");
@@ -283,27 +270,29 @@
if (call != null) {
switch (action) {
case SET_ACTIVE:
- handleCallControlNewCallFocusTransactions(call, SET_ACTIVE,
- false /* isAnswer */, 0/*VideoState (ignored)*/, callback);
+ mCallSequencingAdapter.setActive(call,
+ getCompleteReceiver(action, callback));
break;
case ANSWER:
- handleCallControlNewCallFocusTransactions(call, ANSWER,
- true /* isAnswer */, (int) objects[0] /*VideoState*/, callback);
+ mCallSequencingAdapter.setAnswered(call, (int) objects[0] /*VideoState*/,
+ getCompleteReceiver(action, callback));
break;
case DISCONNECT:
- addTransactionsToManager(new EndCallTransaction(mCallsManager,
- (DisconnectCause) objects[0], call), callback);
+ DisconnectCause dc = (DisconnectCause) objects[0];
+ mCallSequencingAdapter.setDisconnected(call, dc,
+ getCompleteReceiver(action, callback));
break;
case SET_INACTIVE:
- addTransactionsToManager(
- new HoldCallTransaction(mCallsManager, call), callback);
+ mCallSequencingAdapter.setInactive(call,
+ getCompleteReceiver(action,callback));
break;
case START_STREAMING:
- addTransactionsToManager(mStreamingController.getStartStreamingTransaction(mCallsManager,
- TransactionalServiceWrapper.this, call, mLock), callback);
+ addTransactionsToManager(action,
+ mStreamingController.getStartStreamingTransaction(mCallsManager,
+ TransactionalServiceWrapper.this, call, mLock), callback);
break;
case REQUEST_VIDEO_STATE:
- addTransactionsToManager(
+ addTransactionsToManager(action,
new RequestVideoStateTransaction(mCallsManager, call,
(int) objects[0]), callback);
break;
@@ -321,40 +310,13 @@
}
}
- // The client is request their VoIP call state go ACTIVE/ANSWERED.
- // This request is originating from the VoIP application.
- private void handleCallControlNewCallFocusTransactions(Call call, String action,
- boolean isAnswer, int potentiallyNewVideoState, ResultReceiver callback) {
- mTransactionManager.addTransaction(
- createSetActiveTransactions(call, true /* isCallControlRequest */),
- new OutcomeReceiver<>() {
- @Override
- public void onResult(VoipCallTransactionResult result) {
- Log.i(TAG, String.format(Locale.US,
- "%s: onResult: callId=[%s]", action, call.getId()));
- if (isAnswer) {
- call.setVideoState(potentiallyNewVideoState);
- }
- callback.send(TELECOM_TRANSACTION_SUCCESS, new Bundle());
- }
-
- @Override
- public void onError(CallException exception) {
- Bundle extras = new Bundle();
- extras.putParcelable(TRANSACTION_EXCEPTION_KEY, exception);
- callback.send(exception == null ? CallException.CODE_ERROR_UNKNOWN :
- exception.getCode(), extras);
- }
- });
- }
-
@Override
public void requestCallEndpointChange(CallEndpoint endpoint, ResultReceiver callback) {
long token = Binder.clearCallingIdentity();
try {
Log.startSession("TSW.rCEC");
- addTransactionsToManager(new EndpointChangeTransaction(endpoint, mCallsManager),
- callback);
+ addTransactionsToManager(CALL_ENDPOINT_CHANGE,
+ new EndpointChangeTransaction(endpoint, mCallsManager), callback);
} finally {
Binder.restoreCallingIdentity(token);
Log.endSession();
@@ -384,26 +346,31 @@
}
};
- private void addTransactionsToManager(VoipCallTransaction transaction,
+ private void addTransactionsToManager(String action, CallTransaction transaction,
ResultReceiver callback) {
Log.d(TAG, "addTransactionsToManager");
+ CompletableFuture<Boolean> transactionResult = mTransactionManager
+ .addTransaction(transaction, getCompleteReceiver(action, callback));
+ }
- mTransactionManager.addTransaction(transaction, new OutcomeReceiver<>() {
+ private OutcomeReceiver<CallTransactionResult, CallException> getCompleteReceiver(
+ String action, ResultReceiver callback) {
+ return new OutcomeReceiver<>() {
@Override
- public void onResult(VoipCallTransactionResult result) {
- Log.d(TAG, "addTransactionsToManager: onResult:");
+ public void onResult(CallTransactionResult result) {
+ Log.d(TAG, "completeReceiver: onResult[" + action + "]:" + result);
callback.send(TELECOM_TRANSACTION_SUCCESS, new Bundle());
}
@Override
public void onError(CallException exception) {
- Log.d(TAG, "addTransactionsToManager: onError");
+ Log.d(TAG, "completeReceiver: onError[" + action + "]" + exception);
Bundle extras = new Bundle();
extras.putParcelable(TRANSACTION_EXCEPTION_KEY, exception);
callback.send(exception == null ? CallException.CODE_ERROR_UNKNOWN :
exception.getCode(), extras);
}
- });
+ };
}
public ICallControl getICallControl() {
@@ -416,89 +383,55 @@
**********************************************************************************************
*/
- public void onSetActive(Call call) {
+ public CompletableFuture<Boolean> onSetActive(Call call) {
+ CallTransaction callTransaction = new CallEventCallbackAckTransaction(
+ mICallEventCallback, ON_SET_ACTIVE, call.getId(), mLock);
+ CompletableFuture<Boolean> onSetActiveFuture;
try {
Log.startSession("TSW.oSA");
Log.d(TAG, String.format(Locale.US, "onSetActive: callId=[%s]", call.getId()));
- handleCallEventCallbackNewFocus(call, ON_SET_ACTIVE, false /*isAnswerRequest*/,
- 0 /*VideoState*/);
+ onSetActiveFuture = mCallSequencingAdapter.onSetActive(call,
+ callTransaction, result ->
+ Log.i(TAG, String.format(Locale.US,
+ "%s: onResult: callId=[%s], result=[%s]", ON_SET_ACTIVE,
+ call.getId(), result)));
} finally {
Log.endSession();
}
+ return onSetActiveFuture;
}
- public void onAnswer(Call call, int videoState) {
+ public CompletableFuture<Boolean> onAnswer(Call call, int videoState) {
+ CompletableFuture<Boolean> onAnswerFuture;
try {
Log.startSession("TSW.oA");
Log.d(TAG, String.format(Locale.US, "onAnswer: callId=[%s]", call.getId()));
- handleCallEventCallbackNewFocus(call, ON_ANSWER, true /*isAnswerRequest*/,
- videoState /*VideoState*/);
+ onAnswerFuture = mCallSequencingAdapter.onSetAnswered(call, videoState,
+ new CallEventCallbackAckTransaction(mICallEventCallback,
+ ON_ANSWER, call.getId(), videoState, mLock),
+ result -> Log.i(TAG, String.format(Locale.US,
+ "%s: onResult: callId=[%s], result=[%s]",
+ ON_ANSWER, call.getId(), result)));
} finally {
Log.endSession();
}
+ return onAnswerFuture;
}
- // handle a CallEventCallback to set a call ACTIVE/ANSWERED. Must get ack from client since the
- // request has come from another source (ex. Android Auto is requesting a call to go active)
- private void handleCallEventCallbackNewFocus(Call call, String action, boolean isAnswerRequest,
- int potentiallyNewVideoState) {
- // save CallsManager state before sending client state changes
- Call foregroundCallBeforeSwap = mCallsManager.getForegroundCall();
- boolean wasActive = foregroundCallBeforeSwap != null && foregroundCallBeforeSwap.isActive();
-
- SerialTransaction serialTransactions = createSetActiveTransactions(call,
- false /* isCallControlRequest */);
- // 3. get ack from client (that the requested call can go active)
- if (isAnswerRequest) {
- serialTransactions.appendTransaction(
- new CallEventCallbackAckTransaction(mICallEventCallback,
- action, call.getId(), potentiallyNewVideoState, mLock));
- } else {
- serialTransactions.appendTransaction(
- new CallEventCallbackAckTransaction(mICallEventCallback,
- action, call.getId(), mLock));
- }
-
- // do CallsManager workload before asking client and
- // reset CallsManager state if client does NOT ack
- mTransactionManager.addTransaction(serialTransactions,
- new OutcomeReceiver<>() {
- @Override
- public void onResult(VoipCallTransactionResult result) {
- Log.i(TAG, String.format(Locale.US,
- "%s: onResult: callId=[%s]", action, call.getId()));
- if (isAnswerRequest) {
- call.setVideoState(potentiallyNewVideoState);
- }
- }
-
- @Override
- public void onError(CallException exception) {
- if (isAnswerRequest) {
- // This also sends the signal to untrack from TSW and the client_TSW
- removeCallFromCallsManager(call,
- new DisconnectCause(DisconnectCause.REJECTED,
- "client rejected to answer the call;"
- + " force disconnecting"));
- } else {
- mCallsManager.markCallAsOnHold(call);
- }
- maybeResetForegroundCall(foregroundCallBeforeSwap, wasActive);
- }
- });
- }
-
-
- public void onSetInactive(Call call) {
+ public CompletableFuture<Boolean> onSetInactive(Call call) {
+ CallTransaction callTransaction = new CallEventCallbackAckTransaction(
+ mICallEventCallback, ON_SET_INACTIVE, call.getId(), mLock);
+ CompletableFuture<Boolean> onSetInactiveFuture;
try {
Log.startSession("TSW.oSI");
Log.i(TAG, String.format(Locale.US, "onSetInactive: callId=[%s]", call.getId()));
- mTransactionManager.addTransaction(
- new CallEventCallbackAckTransaction(mICallEventCallback,
- ON_SET_INACTIVE, call.getId(), mLock), new OutcomeReceiver<>() {
+ onSetInactiveFuture = mCallSequencingAdapter.onSetInactive(call,
+ callTransaction, new OutcomeReceiver<>() {
@Override
- public void onResult(VoipCallTransactionResult result) {
- mCallsManager.markCallAsOnHold(call);
+ public void onResult(CallTransactionResult result) {
+ Log.i(TAG, String.format(Locale.US, "onSetInactive: callId=[%s]"
+ + ", result=[%s]",
+ call.getId(), result));
}
@Override
@@ -510,30 +443,26 @@
} finally {
Log.endSession();
}
+ return onSetInactiveFuture;
}
- public void onDisconnect(Call call, DisconnectCause cause) {
+ public CompletableFuture<Boolean> onDisconnect(Call call,
+ DisconnectCause cause) {
+ CallTransaction callTransaction = new CallEventCallbackAckTransaction(
+ mICallEventCallback, ON_DISCONNECT, call.getId(), cause, mLock);
+ CompletableFuture<Boolean> onDisconnectFuture;
try {
Log.startSession("TSW.oD");
Log.d(TAG, String.format(Locale.US, "onDisconnect: callId=[%s]", call.getId()));
-
- mTransactionManager.addTransaction(
- new CallEventCallbackAckTransaction(mICallEventCallback, ON_DISCONNECT,
- call.getId(), cause, mLock), new OutcomeReceiver<>() {
- @Override
- public void onResult(VoipCallTransactionResult result) {
- removeCallFromCallsManager(call, cause);
- }
-
- @Override
- public void onError(CallException exception) {
- removeCallFromCallsManager(call, cause);
- }
- }
- );
+ onDisconnectFuture = mCallSequencingAdapter.onSetDisconnected(call, cause,
+ callTransaction,
+ result -> Log.i(TAG, String.format(Locale.US,
+ "%s: onResult: callId=[%s], result=[%s]",
+ ON_DISCONNECT, call.getId(), result)));
} finally {
Log.endSession();
}
+ return onDisconnectFuture;
}
public void onCallStreamingStarted(Call call) {
@@ -546,7 +475,7 @@
new CallEventCallbackAckTransaction(mICallEventCallback, ON_STREAMING_STARTED,
call.getId(), mLock), new OutcomeReceiver<>() {
@Override
- public void onResult(VoipCallTransactionResult result) {
+ public void onResult(CallTransactionResult result) {
}
@Override
@@ -641,35 +570,6 @@
** Helpers **
**********************************************************************************************
*/
- private void maybeResetForegroundCall(Call foregroundCallBeforeSwap, boolean wasActive) {
- if (foregroundCallBeforeSwap == null) {
- return;
- }
- if (wasActive && !foregroundCallBeforeSwap.isActive()) {
- mCallsManager.markCallAsActive(foregroundCallBeforeSwap);
- }
- }
-
- private void removeCallFromCallsManager(Call call, DisconnectCause cause) {
- if (cause.getCode() != DisconnectCause.REJECTED) {
- mCallsManager.markCallAsDisconnected(call, cause);
- }
- mCallsManager.removeCall(call);
- }
-
- private SerialTransaction createSetActiveTransactions(Call call, boolean isCallControlRequest) {
- // create list for multiple transactions
- List<VoipCallTransaction> transactions = new ArrayList<>();
-
- // potentially hold the current active call in order to set a new call (active/answered)
- transactions.add(
- new MaybeHoldCallForNewCallTransaction(mCallsManager, call, isCallControlRequest));
- // And request a new focus call update
- transactions.add(new RequestNewActiveCallTransaction(mCallsManager, call));
-
- return new SerialTransaction(transactions, mLock);
- }
-
private void setDeathRecipient(ICallEventCallback callEventCallback) {
try {
callEventCallback.asBinder().linkToDeath(mAppDeathListener, 0);
@@ -720,9 +620,10 @@
public void stopCallStreaming(Call call) {
Log.i(this, "stopCallStreaming; callid=%s", call.getId());
if (call != null && call.isStreaming()) {
- VoipCallTransaction stopStreamingTransaction = mStreamingController
+ CallTransaction stopStreamingTransaction = mStreamingController
.getStopStreamingTransaction(call, mLock);
- addTransactionsToManager(stopStreamingTransaction, new ResultReceiver(null));
+ addTransactionsToManager(STOP_STREAMING, stopStreamingTransaction,
+ new ResultReceiver(null));
}
}
}
diff --git a/src/com/android/server/telecom/bluetooth/BluetoothDeviceManager.java b/src/com/android/server/telecom/bluetooth/BluetoothDeviceManager.java
index 0f27dad..550a815 100644
--- a/src/com/android/server/telecom/bluetooth/BluetoothDeviceManager.java
+++ b/src/com/android/server/telecom/bluetooth/BluetoothDeviceManager.java
@@ -34,6 +34,7 @@
import android.media.AudioDeviceInfo;
import android.media.AudioManager;
import android.os.Bundle;
+import android.telecom.CallAudioState;
import android.telecom.Log;
import android.util.ArraySet;
import android.util.LocalLog;
@@ -233,7 +234,8 @@
}
};
- private void handleAudioRefactoringServiceDisconnected(int profile) {
+ @VisibleForTesting
+ public void handleAudioRefactoringServiceDisconnected(int profile) {
CallAudioRouteController controller = (CallAudioRouteController)
mCallAudioRouteAdapter;
Map<AudioRoute, BluetoothDevice> btRoutes = controller
@@ -257,8 +259,23 @@
mCallAudioRouteAdapter.sendMessageWithSessionInfo(
BT_DEVICE_REMOVED, route.getType(), device);
}
- mCallAudioRouteAdapter.sendMessageWithSessionInfo(
- SWITCH_BASELINE_ROUTE, INCLUDE_BLUETOOTH_IN_BASELINE, (String) null);
+
+ if (mFeatureFlags.skipBaselineSwitchWhenRouteNotBluetooth()) {
+ CallAudioState currentAudioState = controller.getCurrentCallAudioState();
+ int currentRoute = currentAudioState.getRoute();
+ if (currentRoute == CallAudioState.ROUTE_BLUETOOTH) {
+ Log.d(this, "handleAudioRefactoringServiceDisconnected: call audio "
+ + "is currently routed to BT so switching back to baseline");
+ mCallAudioRouteAdapter.sendMessageWithSessionInfo(
+ SWITCH_BASELINE_ROUTE, INCLUDE_BLUETOOTH_IN_BASELINE, (String) null);
+ } else {
+ Log.d(this, "handleAudioRefactoringServiceDisconnected: call audio "
+ + "is not currently routed to BT so skipping switch to baseline");
+ }
+ } else {
+ mCallAudioRouteAdapter.sendMessageWithSessionInfo(
+ SWITCH_BASELINE_ROUTE, INCLUDE_BLUETOOTH_IN_BASELINE, (String) null);
+ }
}
private final LinkedHashMap<String, BluetoothDevice> mHfpDevicesByAddress =
@@ -308,19 +325,19 @@
mFeatureFlags = featureFlags;
if (bluetoothAdapter != null) {
mBluetoothAdapter = bluetoothAdapter;
- if (mFeatureFlags.useRefactoredAudioRouteSwitching()) {
- mBluetoothHeadsetFuture = new CompletableFuture<>();
- }
bluetoothAdapter.getProfileProxy(context, mBluetoothProfileServiceListener,
BluetoothProfile.HEADSET);
bluetoothAdapter.getProfileProxy(context, mBluetoothProfileServiceListener,
BluetoothProfile.HEARING_AID);
bluetoothAdapter.getProfileProxy(context, mBluetoothProfileServiceListener,
BluetoothProfile.LE_AUDIO);
- mAudioManager = context.getSystemService(AudioManager.class);
- mExecutor = context.getMainExecutor();
- mCommunicationDeviceTracker = communicationDeviceTracker;
}
+ if (mFeatureFlags.useRefactoredAudioRouteSwitching()) {
+ mBluetoothHeadsetFuture = new CompletableFuture<>();
+ }
+ mAudioManager = context.getSystemService(AudioManager.class);
+ mExecutor = context.getMainExecutor();
+ mCommunicationDeviceTracker = communicationDeviceTracker;
}
public void setBluetoothRouteManager(BluetoothRouteManager brm) {
@@ -519,7 +536,10 @@
Log.i(this, "onDeviceConnected: Adding device with address: %s and devicetype=%s",
device, getDeviceTypeString(deviceType));
targetDeviceMap.put(device.getAddress(), device);
- mBluetoothRouteManager.onDeviceAdded(device.getAddress());
+ if (!mFeatureFlags.keepBluetoothDevicesCacheUpdated()
+ || !mFeatureFlags.useRefactoredAudioRouteSwitching()) {
+ mBluetoothRouteManager.onDeviceAdded(device.getAddress());
+ }
}
}
}
@@ -551,7 +571,10 @@
Log.i(this, "onDeviceDisconnected: Removing device with address: %s, devicetype=%s",
device, getDeviceTypeString(deviceType));
targetDeviceMap.remove(device.getAddress());
- mBluetoothRouteManager.onDeviceLost(device.getAddress());
+ if (!mFeatureFlags.keepBluetoothDevicesCacheUpdated()
+ || !mFeatureFlags.useRefactoredAudioRouteSwitching()) {
+ mBluetoothRouteManager.onDeviceLost(device.getAddress());
+ }
}
}
}
@@ -759,7 +782,7 @@
Log.w(this, "setCommunicationDeviceForAddress: Device %s not found.", address);
return false;
}
- if (mAudioManager.getCommunicationDevice().equals(deviceInfo)) {
+ if (deviceInfo.equals(mAudioManager.getCommunicationDevice())) {
Log.i(this, "setCommunicationDeviceForAddress: Device %s already active.", address);
return true;
}
diff --git a/src/com/android/server/telecom/bluetooth/BluetoothStateReceiver.java b/src/com/android/server/telecom/bluetooth/BluetoothStateReceiver.java
index cd52889..1cea531 100644
--- a/src/com/android/server/telecom/bluetooth/BluetoothStateReceiver.java
+++ b/src/com/android/server/telecom/bluetooth/BluetoothStateReceiver.java
@@ -211,6 +211,9 @@
if (mFeatureFlags.useRefactoredAudioRouteSwitching()) {
mCallAudioRouteAdapter.sendMessageWithSessionInfo(BT_DEVICE_ADDED,
audioRouteType, device);
+ if (mFeatureFlags.keepBluetoothDevicesCacheUpdated()) {
+ mBluetoothDeviceManager.onDeviceConnected(device, deviceType);
+ }
} else {
mBluetoothDeviceManager.onDeviceConnected(device, deviceType);
}
@@ -219,6 +222,9 @@
if (mFeatureFlags.useRefactoredAudioRouteSwitching()) {
mCallAudioRouteAdapter.sendMessageWithSessionInfo(BT_DEVICE_REMOVED,
audioRouteType, device);
+ if (mFeatureFlags.keepBluetoothDevicesCacheUpdated()) {
+ mBluetoothDeviceManager.onDeviceDisconnected(device, deviceType);
+ }
} else {
mBluetoothDeviceManager.onDeviceDisconnected(device, deviceType);
}
@@ -252,10 +258,12 @@
CallAudioRouteController audioRouteController = (CallAudioRouteController)
mCallAudioRouteAdapter;
if (device == null) {
+ // Update the active device cache immediately.
audioRouteController.updateActiveBluetoothDevice(new Pair(audioRouteType, null));
mCallAudioRouteAdapter.sendMessageWithSessionInfo(BT_ACTIVE_DEVICE_GONE,
audioRouteType);
} else {
+ // Update the active device cache immediately.
audioRouteController.updateActiveBluetoothDevice(
new Pair(audioRouteType, device.getAddress()));
mCallAudioRouteAdapter.sendMessageWithSessionInfo(BT_ACTIVE_DEVICE_PRESENT,
@@ -265,11 +273,17 @@
if (!mBluetoothDeviceManager.setCommunicationDeviceForAddress(
device.getAddress())) {
Log.i(this, "handleActiveDeviceChanged: Failed to set "
- + "communication device for %s. Sending PENDING_ROUTE_FAILED to "
- + "pending audio route.", device);
- mCallAudioRouteAdapter.getPendingAudioRoute()
- .onMessageReceived(new Pair<>(PENDING_ROUTE_FAILED,
- device.getAddress()), device.getAddress());
+ + "communication device for %s.", device);
+ if (!mFeatureFlags.resolveActiveBtRoutingAndBtTimingIssue()) {
+ Log.i(this, "Sending PENDING_ROUTE_FAILED "
+ + "to pending audio route.");
+ mCallAudioRouteAdapter.getPendingAudioRoute()
+ .onMessageReceived(new Pair<>(PENDING_ROUTE_FAILED,
+ device.getAddress()), device.getAddress());
+ } else {
+ Log.i(this, "Refrain from sending PENDING_ROUTE_FAILED"
+ + " to pending audio route.");
+ }
} else {
// Track the currently set communication device.
int routeType = deviceType == BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO
diff --git a/src/com/android/server/telecom/callfiltering/BlockCheckerFilter.java b/src/com/android/server/telecom/callfiltering/BlockCheckerFilter.java
index 5beb5f0..7e3837d 100644
--- a/src/com/android/server/telecom/callfiltering/BlockCheckerFilter.java
+++ b/src/com/android/server/telecom/callfiltering/BlockCheckerFilter.java
@@ -21,6 +21,7 @@
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
+import android.os.UserManager;
import android.provider.BlockedNumberContract;
import android.provider.CallLog;
import android.telecom.CallerInfo;
@@ -29,6 +30,7 @@
import com.android.server.telecom.Call;
import com.android.server.telecom.CallerInfoLookupHelper;
+import com.android.server.telecom.flags.FeatureFlags;
import com.android.server.telecom.LogUtils;
import com.android.server.telecom.LoggedHandlerExecutor;
import com.android.server.telecom.settings.BlockedNumbersUtil;
@@ -45,6 +47,7 @@
private boolean mContactExists;
private HandlerThread mHandlerThread;
private Handler mHandler;
+ private FeatureFlags mFeatureFlags;
public static final long CALLER_INFO_QUERY_TIMEOUT = 5000;
@@ -105,7 +108,7 @@
public BlockCheckerFilter(Context context, Call call,
CallerInfoLookupHelper callerInfoLookupHelper,
- BlockCheckerAdapter blockCheckerAdapter) {
+ BlockCheckerAdapter blockCheckerAdapter, FeatureFlags featureFlags) {
mCall = call;
mContext = context;
mCallerInfoLookupHelper = callerInfoLookupHelper;
@@ -114,6 +117,7 @@
mHandlerThread = new HandlerThread(TAG);
mHandlerThread.start();
mHandler = new Handler(mHandlerThread.getLooper());
+ mFeatureFlags = featureFlags;
}
@Override
@@ -121,7 +125,13 @@
Log.addEvent(mCall, LogUtils.Events.BLOCK_CHECK_INITIATED);
CompletableFuture<CallFilteringResult> resultFuture = new CompletableFuture<>();
Bundle extras = new Bundle();
- if (BlockedNumbersUtil.isEnhancedCallBlockingEnabledByPlatform(mContext)) {
+ final Context userContext;
+ if (mFeatureFlags.telecomMainUserInBlockCheck()) {
+ userContext = mContext.createContextAsUser(mCall.getAssociatedUser(), 0);
+ } else {
+ userContext = mContext;
+ }
+ if (BlockedNumbersUtil.isEnhancedCallBlockingEnabledByPlatform(userContext)) {
int presentation = mCall.getHandlePresentation();
extras.putInt(BlockedNumberContract.EXTRA_CALL_PRESENTATION, presentation);
if (presentation == TelecomManager.PRESENTATION_ALLOWED) {
@@ -132,7 +142,7 @@
if (info != null && info.contactExists) {
mContactExists = true;
}
- getBlockStatus(resultFuture);
+ getBlockStatus(resultFuture, userContext);
}
@Override
@@ -141,22 +151,22 @@
}
});
} else {
- getBlockStatus(resultFuture);
+ getBlockStatus(resultFuture, userContext);
}
} else {
- getBlockStatus(resultFuture);
+ getBlockStatus(resultFuture, userContext);
}
return resultFuture;
}
private void getBlockStatus(
- CompletableFuture<CallFilteringResult> resultFuture) {
+ CompletableFuture<CallFilteringResult> resultFuture, Context userContext) {
// Set presentation and if contact exists. Used in determining if the system should block
// the passed in number. Use default values as they would be returned if the keys didn't
// exist in the extras to maintain existing behavior.
int presentation;
boolean isNumberInContacts;
- if (BlockedNumbersUtil.isEnhancedCallBlockingEnabledByPlatform(mContext)) {
+ if (BlockedNumbersUtil.isEnhancedCallBlockingEnabledByPlatform(userContext)) {
presentation = mCall.getHandlePresentation();
} else {
presentation = 0;
@@ -173,7 +183,7 @@
mCall.getHandle().getSchemeSpecificPart();
CompletableFuture.supplyAsync(
- () -> mBlockCheckerAdapter.getBlockStatus(mContext, number,
+ () -> mBlockCheckerAdapter.getBlockStatus(userContext, number,
presentation, isNumberInContacts),
new LoggedHandlerExecutor(mHandler, "BCF.gBS", null))
.thenApplyAsync((x) -> completeResult(resultFuture, x),
diff --git a/src/com/android/server/telecom/callfiltering/CallScreeningServiceFilter.java b/src/com/android/server/telecom/callfiltering/CallScreeningServiceFilter.java
index f07c0aa..efac87d 100644
--- a/src/com/android/server/telecom/callfiltering/CallScreeningServiceFilter.java
+++ b/src/com/android/server/telecom/callfiltering/CallScreeningServiceFilter.java
@@ -269,7 +269,8 @@
mContext = context;
mPackageManager = mContext.getPackageManager();
mCallsManager = callsManager;
- mAppName = appLabelProxy.getAppLabel(mPackageName);
+ mAppName = appLabelProxy.getAppLabel(mPackageName,
+ mCall.getAssociatedUser());
mParcelableCallUtilsConverter = parcelableCallUtilsConverter;
}
diff --git a/src/com/android/server/telecom/callsequencing/CallSequencingController.java b/src/com/android/server/telecom/callsequencing/CallSequencingController.java
new file mode 100644
index 0000000..f0aa8ef
--- /dev/null
+++ b/src/com/android/server/telecom/callsequencing/CallSequencingController.java
@@ -0,0 +1,824 @@
+/*
+ * Copyright (C) 2024 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.callsequencing;
+
+import static android.Manifest.permission.CALL_PRIVILEGED;
+
+import static com.android.server.telecom.CallsManager.LIVE_CALL_STUCK_CONNECTING_EMERGENCY_ERROR_MSG;
+import static com.android.server.telecom.CallsManager.LIVE_CALL_STUCK_CONNECTING_EMERGENCY_ERROR_UUID;
+import static com.android.server.telecom.CallsManager.OUTGOING_CALL_STATES;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.OutcomeReceiver;
+import android.telecom.CallAttributes;
+import android.telecom.CallException;
+import android.telecom.DisconnectCause;
+import android.telecom.Log;
+import android.telecom.PhoneAccount;
+import android.telecom.PhoneAccountHandle;
+import android.telephony.AnomalyReporter;
+
+import com.android.server.telecom.Call;
+import com.android.server.telecom.CallState;
+import com.android.server.telecom.CallsManager;
+import com.android.server.telecom.LogUtils;
+import com.android.server.telecom.LoggedHandlerExecutor;
+import com.android.server.telecom.callsequencing.voip.OutgoingCallTransaction;
+import com.android.server.telecom.callsequencing.voip.OutgoingCallTransactionSequencing;
+import com.android.server.telecom.flags.FeatureFlags;
+import com.android.server.telecom.stats.CallFailureCause;
+
+import java.util.concurrent.CompletableFuture;
+
+/**
+ * Controls the sequencing between calls when moving between the user ACTIVE (RINGING/ACTIVE) and
+ * user INACTIVE (INCOMING/HOLD/DISCONNECTED) states. This controller is gated by the
+ * {@link FeatureFlags#enableCallSequencing()} flag. Call state changes are verified on a
+ * transactional basis where each operation is verified step by step for cross-phone account calls
+ * or just for the focus call in the case of processing calls on the same phone account.
+ */
+public class CallSequencingController {
+ private final CallsManager mCallsManager;
+ private final Handler mHandler;
+ private final Context mContext;
+ private final FeatureFlags mFeatureFlags;
+ private boolean mProcessingCallSequencing;
+
+ public CallSequencingController(CallsManager callsManager, Context context,
+ FeatureFlags featureFlags) {
+ mCallsManager = callsManager;
+ HandlerThread handlerThread = new HandlerThread(this.toString());
+ handlerThread.start();
+ mHandler = new Handler(handlerThread.getLooper());
+ mProcessingCallSequencing = false;
+ mFeatureFlags = featureFlags;
+ mContext = context;
+ }
+
+ /**
+ * Creates the outgoing call transaction given that call sequencing is enabled. Two separate
+ * transactions are being tracked here; one is if room needs to be made for the outgoing call
+ * and another to verify that the new call was placed. We need to ensure that the transaction
+ * to make room for the outgoing call is processed beforehand (i.e. see
+ * {@link OutgoingCallTransaction}.
+ * @param callAttributes The call attributes associated with the call.
+ * @param extras The extras that are associated with the call.
+ * @param callingPackage The calling package representing where the request was invoked from.
+ * @return The {@link CompletableFuture<CallTransaction>} that encompasses the request to
+ * place/receive the transactional call.
+ */
+ public CompletableFuture<CallTransaction> createTransactionalOutgoingCall(String callId,
+ CallAttributes callAttributes, Bundle extras, String callingPackage) {
+ PhoneAccountHandle requestedAccountHandle = callAttributes.getPhoneAccountHandle();
+ Uri address = callAttributes.getAddress();
+ if (mCallsManager.isOutgoingCallPermitted(requestedAccountHandle)) {
+ Log.d(this, "createTransactionalOutgoingCall: outgoing call permitted");
+ final boolean hasCallPrivilegedPermission = mContext.checkCallingPermission(
+ CALL_PRIVILEGED) == PackageManager.PERMISSION_GRANTED;
+
+ final Intent intent = new Intent(hasCallPrivilegedPermission ?
+ Intent.ACTION_CALL_PRIVILEGED : Intent.ACTION_CALL, address);
+ Bundle updatedExtras = OutgoingCallTransaction.generateExtras(callId, extras,
+ callAttributes, mFeatureFlags);
+ // Note that this may start a potential transaction to make room for the outgoing call
+ // so we want to ensure that transaction is queued up first and then create another
+ // transaction to complete the call future.
+ CompletableFuture<Call> callFuture = mCallsManager.startOutgoingCall(address,
+ requestedAccountHandle, updatedExtras, requestedAccountHandle.getUserHandle(),
+ intent, callingPackage);
+ // The second transaction is represented below which will contain the result of whether
+ // the new outgoing call was placed or not. To simplify the logic, we will wait on the
+ // result of the outgoing call future before adding the transaction so that we can wait
+ // for the make room future to complete first.
+ if (callFuture == null) {
+ Log.d(this, "createTransactionalOutgoingCall: Outgoing call not permitted at the "
+ + "current time.");
+ return CompletableFuture.completedFuture(null);
+ }
+ return callFuture.thenComposeAsync((call) -> CompletableFuture.completedFuture(
+ new OutgoingCallTransactionSequencing(mCallsManager, callFuture,
+ mFeatureFlags)),
+ new LoggedHandlerExecutor(mHandler, "CSC.aC", mCallsManager.getLock()));
+ } else {
+ Log.d(this, "createTransactionalOutgoingCall: outgoing call not permitted at the "
+ + "current time.");
+ return CompletableFuture.completedFuture(null);
+ }
+ }
+
+ /**
+ * Processes the answer call request from the app and verifies the call state changes with
+ * sequencing provided that the calls that are being manipulated are across phone accounts.
+ * @param incomingCall The incoming call to be answered.
+ * @param videoState The video state configuration for the provided call.
+ */
+ public void answerCall(Call incomingCall, int videoState) {
+ Log.i(this, "answerCall: Beginning call sequencing transaction for answering "
+ + "incoming call.");
+ // Retrieve the CompletableFuture which processes the steps to make room to answer the
+ // incoming call.
+ CompletableFuture<Boolean> holdActiveForNewCallFutureHandler =
+ holdActiveCallForNewCallWithSequencing(incomingCall);
+ // If we're performing call sequencing across phone accounts, then ensure that we only
+ // proceed if the future above has completed successfully.
+ if (isProcessingCallSequencing()) {
+ holdActiveForNewCallFutureHandler.thenComposeAsync((result) -> {
+ if (result) {
+ mCallsManager.requestFocusActionAnswerCall(incomingCall, videoState);
+ } else {
+ Log.i(this, "answerCall: Hold active call transaction failed. Aborting "
+ + "request to answer the incoming call.");
+ }
+ return CompletableFuture.completedFuture(result);
+ }, new LoggedHandlerExecutor(mHandler, "CSC.aC",
+ mCallsManager.getLock()));
+ } else {
+ mCallsManager.requestFocusActionAnswerCall(incomingCall, videoState);
+ }
+ resetProcessingCallSequencing();
+ }
+
+ /**
+ * Handles the case of setting a self-managed call active with call sequencing support.
+ * @param call The self-managed call that's waiting to go active.
+ */
+ public void handleSetSelfManagedCallActive(Call call) {
+ CompletableFuture<Boolean> holdActiveCallFuture =
+ holdActiveCallForNewCallWithSequencing(call);
+ if (isProcessingCallSequencing()) {
+ holdActiveCallFuture.thenComposeAsync((result) -> {
+ if (result) {
+ Log.i(this, "markCallAsActive: requesting focus for self managed call "
+ + "before setting active.");
+ mCallsManager.requestActionSetActiveCall(call,
+ "active set explicitly for self-managed");
+ } else {
+ Log.i(this, "markCallAsActive: Unable to hold active call. "
+ + "Aborting transaction to set self managed call active.");
+ }
+ return CompletableFuture.completedFuture(result);
+ }, new LoggedHandlerExecutor(mHandler,
+ "CM.mCAA", mCallsManager.getLock()));
+ } else {
+ mCallsManager.requestActionSetActiveCall(call,
+ "active set explicitly for self-managed");
+ }
+ resetProcessingCallSequencing();
+ }
+
+ /**
+ * This applies to transactional calls which request to hold the active call with call
+ * sequencing support. The resulting future is an indication of whether the hold request
+ * succeeded which is then used to create additional transactions to request call focus for the
+ * new call.
+ * @param newCall The new transactional call that's waiting to go active.
+ * @param callback The callback used to report the result of holding the active call and if
+ * the new call can go active.
+ * @return The {@code CompletableFuture} indicating the result of holding the active call
+ * (if applicable).
+ */
+ public void transactionHoldPotentialActiveCallForNewCallSequencing(
+ Call newCall, OutcomeReceiver<Boolean, CallException> callback) {
+ CompletableFuture<Boolean> holdActiveCallFuture =
+ holdActiveCallForNewCallWithSequencing(newCall).thenComposeAsync((result) -> {
+ if (result) {
+ // Either we were able to hold the active call or the active call was
+ // disconnected in favor of the new call.
+ callback.onResult(true);
+ } else {
+ Log.i(this, "transactionHoldPotentialActiveCallForNewCallSequencing: "
+ + "active call could not be held or disconnected");
+ callback.onError(
+ new CallException("activeCall could not be held or disconnected",
+ CallException.CODE_CANNOT_HOLD_CURRENT_ACTIVE_CALL));
+ }
+ return CompletableFuture.completedFuture(result);
+ }, new LoggedHandlerExecutor(mHandler, "CM.mCAA", mCallsManager.getLock()));
+ resetProcessingCallSequencing();
+ }
+
+ /**
+ * Attempts to hold the active call so that the provided call can go active. This is done via
+ * call sequencing and the resulting future is an indication of whether that request
+ * has succeeded.
+ * @param call The call that's waiting to go active.
+ * @return The {@code CompletableFuture} indicating the result of whether the active call was
+ * able to be held (if applicable).
+ */
+ CompletableFuture<Boolean> holdActiveCallForNewCallWithSequencing(Call call) {
+ Call activeCall = (Call) mCallsManager.getConnectionServiceFocusManager()
+ .getCurrentFocusCall();
+ Log.i(this, "holdActiveCallForNewCallWithSequencing, newCall: %s, "
+ + "activeCall: %s", call.getId(),
+ (activeCall == null ? "<none>" : activeCall.getId()));
+ if (activeCall != null && activeCall != call) {
+ processCallSequencing(call, activeCall);
+ if (mCallsManager.canHold(activeCall)) {
+ return activeCall.hold("swap to " + call.getId());
+ } else if (mCallsManager.supportsHold(activeCall)) {
+ // Handle the case where active call supports hold but can't currently be held.
+ // In this case, we'll look for the currently held call to disconnect prior to
+ // holding the active call.
+ // E.g.
+ // Call A - Held (Supports hold, can't hold)
+ // Call B - Active (Supports hold, can't hold)
+ // Call C - Incoming
+ // Here we need to disconnect A prior to holding B so that C can be answered.
+ // This case is driven by telephony requirements ultimately.
+ //
+ // These cases can further be broken down at the phone account level:
+ // E.g. All cases not outlined below...
+ // (1) (2)
+ // Call A (Held) - PA1 Call A (Held) - PA1
+ // Call B (Active) - PA2 Call B (Active) - PA2
+ // Call C (Incoming) - PA1 Call C (Incoming) - PA2
+ // We should ensure that only operations across phone accounts require sequencing.
+ // Otherwise, we can send the requests up til the focus call state in question.
+ Call heldCall = mCallsManager.getFirstCallWithState(CallState.ON_HOLD);
+ CompletableFuture<Boolean> disconnectFutureHandler = null;
+ // Assume default case (no sequencing required).
+ boolean areIncomingHeldFromSameSource;
+
+ if (heldCall != null) {
+ processCallSequencing(heldCall, activeCall);
+ processCallSequencing(call, heldCall);
+ areIncomingHeldFromSameSource = CallsManager.areFromSameSource(call, heldCall);
+
+ // If the calls are from the same source or the incoming call isn't a VOIP call
+ // and the held call is a carrier call, then disconnect the held call. The
+ // idea is that if we have a held carrier call and the incoming call is a
+ // VOIP call, we don't want to force the carrier call to auto-disconnect).
+ if (areIncomingHeldFromSameSource || !(call.isSelfManaged()
+ && !heldCall.isSelfManaged())) {
+ disconnectFutureHandler = heldCall.disconnect();
+ Log.i(this, "holdActiveCallForNewCallWithSequencing: "
+ + "Disconnect held call %s before holding active call %s.",
+ heldCall.getId(), activeCall.getId());
+ } else {
+ // Otherwise, fail the transaction.
+ return CompletableFuture.completedFuture(false);
+ }
+ }
+ Log.i(this, "holdActiveCallForNewCallWithSequencing: Holding active "
+ + "%s before making %s active.", activeCall.getId(), call.getId());
+
+ CompletableFuture<Boolean> holdFutureHandler;
+ if (isProcessingCallSequencing() && disconnectFutureHandler != null) {
+ holdFutureHandler = disconnectFutureHandler
+ .thenComposeAsync((result) -> {
+ if (result) {
+ return activeCall.hold();
+ }
+ return CompletableFuture.completedFuture(false);
+ }, new LoggedHandlerExecutor(mHandler,
+ "CSC.hACFNCWS", mCallsManager.getLock()));
+ } else {
+ holdFutureHandler = activeCall.hold();
+ }
+ call.increaseHeldByThisCallCount();
+ return holdFutureHandler;
+ } else {
+ // This call does not support hold. If it is from a different connection
+ // service or connection manager, then disconnect it, otherwise allow the connection
+ // service or connection manager to figure out the right states.
+ if (isProcessingCallSequencing()) {
+ Log.i(this, "holdActiveCallForNewCallWithSequencing: disconnecting %s "
+ + "so that %s can be made active.", activeCall.getId(), call.getId());
+ if (!activeCall.isEmergencyCall()) {
+ // We don't want to allow VOIP apps to disconnect carrier calls. We are
+ // purposely completing the future with false so that the call isn't
+ // answered.
+ if (call.isSelfManaged() && !activeCall.isSelfManaged()) {
+ Log.w(this, "holdActiveCallForNewCallWithSequencing: ignore "
+ + "disconnecting carrier call for making VOIP call active");
+ return CompletableFuture.completedFuture(false);
+ } else {
+ return activeCall.disconnect();
+ }
+ } else {
+ // It's not possible to hold the active call, and it's an emergency call so
+ // we will silently reject the incoming call instead of answering it.
+ Log.w(this, "holdActiveCallForNewCallWithSequencing: rejecting incoming "
+ + "call %s as the active call is an emergency call and "
+ + "it cannot be held.", call.getId());
+ return call.reject(false /* rejectWithMessage */, "" /* message */,
+ "active emergency call can't be held");
+ }
+ } else {
+ // Same source case: if the active call cannot be held, then the user has
+ // willingly chosen to accept the incoming call knowing that the active call
+ // will be disconnected.
+ return activeCall.disconnect("Active call disconnected in favor of accepting "
+ + "incoming call.");
+ }
+ }
+ }
+ return CompletableFuture.completedFuture(true);
+ }
+
+ /**
+ * Processes the unhold call request sent by the app with call sequencing support.
+ * @param call The call to be unheld.
+ */
+ public void unholdCall(Call call) {
+ // Cases: set active call on hold and then set this call to active
+ // Calls could be made on different phone accounts, in which case, we need to verify state
+ // change for each call.
+ CompletableFuture<Boolean> unholdCallFutureHandler = null;
+ Call activeCall = (Call) mCallsManager.getConnectionServiceFocusManager()
+ .getCurrentFocusCall();
+ String activeCallId = null;
+ if (activeCall != null && !activeCall.isLocallyDisconnecting()) {
+ activeCallId = activeCall.getId();
+ // Determine whether the calls are placed on different phone accounts.
+ boolean areFromSameSource = CallsManager.areFromSameSource(activeCall, call);
+ processCallSequencing(activeCall, call);
+ boolean canHoldActiveCall = mCallsManager.canHold(activeCall);
+
+ // If the active + held call are from different phone accounts, ensure that the call
+ // sequencing states are verified at each step.
+ if (canHoldActiveCall) {
+ unholdCallFutureHandler = activeCall.hold("Swap to " + call.getId());
+ Log.addEvent(activeCall, LogUtils.Events.SWAP, "To " + call.getId());
+ Log.addEvent(call, LogUtils.Events.SWAP, "From " + activeCallId);
+ } else {
+ if (!areFromSameSource) {
+ // Don't unhold the call as requested if the active and held call are on
+ // different phone accounts - consider the WhatsApp (held) and PSTN (active)
+ // case. We also don't want to drop an emergency call.
+ if (!activeCall.isEmergencyCall()) {
+ Log.w(this, "unholdCall: % and %s are using different phone accounts. "
+ + "Aborting swap to %s", activeCallId, call.getId(),
+ call.getId());
+ } else {
+ Log.w(this, "unholdCall: % is an emergency call, aborting swap to %s",
+ activeCallId, call.getId());
+ }
+ return;
+ } else {
+ activeCall.hold("Swap to " + call.getId());
+ }
+ }
+ }
+
+ // Verify call state was changed to ACTIVE state
+ if (isProcessingCallSequencing() && unholdCallFutureHandler != null) {
+ String fixedActiveCallId = activeCallId;
+ // Only attempt to unhold call if previous request to hold/disconnect call (on different
+ // phone account) succeeded.
+ unholdCallFutureHandler.thenComposeAsync((result) -> {
+ if (result) {
+ Log.i(this, "unholdCall: Request to hold active call transaction succeeded.");
+ mCallsManager.requestActionUnholdCall(call, fixedActiveCallId);
+ } else {
+ Log.i(this, "unholdCall: Request to hold active call transaction failed. "
+ + "Aborting unhold transaction.");
+ }
+ return CompletableFuture.completedFuture(result);
+ }, new LoggedHandlerExecutor(mHandler, "CSC.uC",
+ mCallsManager.getLock()));
+ } else {
+ // Otherwise, we should verify call unhold succeeded for focus call.
+ mCallsManager.requestActionUnholdCall(call, activeCallId);
+ }
+ resetProcessingCallSequencing();
+ }
+
+ public CompletableFuture<Boolean> makeRoomForOutgoingCall(boolean isEmergency, Call call) {
+ CompletableFuture<Boolean> makeRoomForOutgoingCallFuture = isEmergency
+ ? makeRoomForOutgoingEmergencyCall(call)
+ : makeRoomForOutgoingCall(call);
+ resetProcessingCallSequencing();
+ return makeRoomForOutgoingCallFuture;
+ }
+
+ /**
+ * This function tries to make room for the new emergency outgoing call via call sequencing.
+ * The resulting future is an indication of whether room was able to be made for the emergency
+ * call if needed.
+ * @param emergencyCall The outgoing emergency call to be placed.
+ * @return The {@code CompletableFuture} indicating the result of whether room was able to be
+ * made for the emergency call.
+ */
+ private CompletableFuture<Boolean> makeRoomForOutgoingEmergencyCall(Call emergencyCall) {
+ // Always disconnect any ringing/incoming calls when an emergency call is placed to minimize
+ // distraction. This does not affect live call count.
+ CompletableFuture<Boolean> ringingCallFuture = null;
+ Call ringingCall = null;
+ if (mCallsManager.hasRingingOrSimulatedRingingCall()) {
+ ringingCall = mCallsManager.getRingingOrSimulatedRingingCall();
+ processCallSequencing(ringingCall, emergencyCall);
+ ringingCall.getAnalytics().setCallIsAdditional(true);
+ ringingCall.getAnalytics().setCallIsInterrupted(true);
+ if (ringingCall.getState() == CallState.SIMULATED_RINGING) {
+ if (!ringingCall.hasGoneActiveBefore()) {
+ // If this is an incoming call that is currently in SIMULATED_RINGING only
+ // after a call screen, disconnect to make room and mark as missed, since
+ // the user didn't get a chance to accept/reject.
+ ringingCallFuture = ringingCall.disconnect("emergency call dialed during "
+ + "simulated ringing after screen.");
+ } else {
+ // If this is a simulated ringing call after being active and put in
+ // AUDIO_PROCESSING state again, disconnect normally.
+ ringingCallFuture = ringingCall.reject(false, null, "emergency call dialed "
+ + "during simulated ringing.");
+ }
+ } else { // normal incoming ringing call.
+ // Hang up the ringing call to make room for the emergency call and mark as missed,
+ // since the user did not reject.
+ ringingCall.setOverrideDisconnectCauseCode(
+ new DisconnectCause(DisconnectCause.MISSED));
+ ringingCallFuture = ringingCall.reject(false, null, "emergency call dialed "
+ + "during ringing.");
+ }
+ }
+
+ // There is already room!
+ if (!mCallsManager.hasMaximumLiveCalls(emergencyCall)) {
+ return CompletableFuture.completedFuture(true);
+ }
+
+ Call liveCall = mCallsManager.getFirstCallWithLiveState();
+ Log.i(this, "makeRoomForOutgoingEmergencyCall: call = " + emergencyCall
+ + " livecall = " + liveCall);
+
+ if (emergencyCall == liveCall) {
+ // Not likely, but a good correctness check.
+ return CompletableFuture.completedFuture(true);
+ }
+
+ if (mCallsManager.hasMaximumOutgoingCalls(emergencyCall)) {
+ Call outgoingCall = mCallsManager.getFirstCallWithState(OUTGOING_CALL_STATES);
+ String disconnectReason = null;
+ if (!outgoingCall.isEmergencyCall()) {
+ emergencyCall.getAnalytics().setCallIsAdditional(true);
+ outgoingCall.getAnalytics().setCallIsInterrupted(true);
+ disconnectReason = "Disconnecting dialing call in favor of new dialing"
+ + " emergency call.";
+ }
+ if (outgoingCall.getState() == CallState.SELECT_PHONE_ACCOUNT) {
+ // Correctness check: if there is an orphaned emergency call in the
+ // {@link CallState#SELECT_PHONE_ACCOUNT} state, just disconnect it since the user
+ // has explicitly started a new call.
+ emergencyCall.getAnalytics().setCallIsAdditional(true);
+ outgoingCall.getAnalytics().setCallIsInterrupted(true);
+ disconnectReason = "Disconnecting call in SELECT_PHONE_ACCOUNT in favor"
+ + " of new outgoing call.";
+ }
+ if (disconnectReason != null) {
+ processCallSequencing(outgoingCall, emergencyCall);
+ if (ringingCallFuture != null && isProcessingCallSequencing()) {
+ String finalDisconnectReason = disconnectReason;
+ return ringingCallFuture.thenComposeAsync((result) -> {
+ if (result) {
+ Log.i(this, "makeRoomForOutgoingEmergencyCall: Request to disconnect"
+ + " ringing call succeeded. Attempting to disconnect "
+ + "outgoing call.");
+ return outgoingCall.disconnect(finalDisconnectReason);
+ } else {
+ Log.i(this, "makeRoomForOutgoingEmergencyCall: Request to disconnect"
+ + "ringing call failed. Aborting attempt to disconnect "
+ + "outgoing call");
+ return CompletableFuture.completedFuture(false);
+ }
+ }, new LoggedHandlerExecutor(mHandler, "CSC.mRFOEC",
+ mCallsManager.getLock()));
+ } else {
+ return outgoingCall.disconnect(disconnectReason);
+ }
+ }
+ // If the user tries to make two outgoing calls to different emergency call numbers,
+ // we will try to connect the first outgoing call and reject the second.
+ emergencyCall.setStartFailCause(CallFailureCause.IN_EMERGENCY_CALL);
+ return CompletableFuture.completedFuture(false);
+ }
+
+ processCallSequencing(liveCall, emergencyCall);
+ if (ringingCall != null) {
+ processCallSequencing(ringingCall, liveCall);
+ }
+ if (liveCall.getState() == CallState.AUDIO_PROCESSING) {
+ emergencyCall.getAnalytics().setCallIsAdditional(true);
+ liveCall.getAnalytics().setCallIsInterrupted(true);
+ final String disconnectReason = "disconnecting audio processing call for emergency";
+ if (ringingCallFuture != null && isProcessingCallSequencing()) {
+ return ringingCallFuture.thenComposeAsync((result) -> {
+ if (result) {
+ Log.i(this, "makeRoomForOutgoingEmergencyCall: Request to disconnect "
+ + "ringing call succeeded. Attempting to disconnect live call.");
+ return liveCall.disconnect(disconnectReason);
+ } else {
+ Log.i(this, "makeRoomForOutgoingEmergencyCall: Request to disconnect "
+ + "ringing call failed. Aborting attempt to disconnect live call.");
+ return CompletableFuture.completedFuture(false);
+ }
+ }, new LoggedHandlerExecutor(mHandler, "CSC.mRFOEC",
+ mCallsManager.getLock()));
+ } else {
+ return liveCall.disconnect(disconnectReason);
+ }
+ }
+
+ // If the live call is stuck in a connecting state, prompt the user to generate a bugreport.
+ if (liveCall.getState() == CallState.CONNECTING) {
+ AnomalyReporter.reportAnomaly(LIVE_CALL_STUCK_CONNECTING_EMERGENCY_ERROR_UUID,
+ LIVE_CALL_STUCK_CONNECTING_EMERGENCY_ERROR_MSG);
+ }
+
+ // If we have the max number of held managed calls and we're placing an emergency call,
+ // we'll disconnect the ongoing call if it cannot be held.
+ if (mCallsManager.hasMaximumManagedHoldingCalls(emergencyCall)
+ && !mCallsManager.canHold(liveCall)) {
+ emergencyCall.getAnalytics().setCallIsAdditional(true);
+ liveCall.getAnalytics().setCallIsInterrupted(true);
+ // Disconnect the active call instead of the holding call because it is historically
+ // easier to do, rather than disconnect a held call.
+ final String disconnectReason = "disconnecting to make room for emergency call "
+ + emergencyCall.getId();
+ if (ringingCallFuture != null && isProcessingCallSequencing()) {
+ return ringingCallFuture.thenComposeAsync((result) -> {
+ if (result) {
+ Log.i(this, "makeRoomForOutgoingEmergencyCall: Request to disconnect "
+ + "ringing call succeeded. Attempting to disconnect live call.");
+ return liveCall.disconnect(disconnectReason);
+ } else {
+ Log.i(this, "makeRoomForOutgoingEmergencyCall: Request to disconnect "
+ + "ringing call failed. Aborting attempt to disconnect live call.");
+ return CompletableFuture.completedFuture(false);
+ }
+ }, new LoggedHandlerExecutor(mHandler, "CSC.mRFOEC",
+ mCallsManager.getLock()));
+ } else {
+ return liveCall.disconnect(disconnectReason);
+ }
+ }
+
+ // TODO: Remove once b/23035408 has been corrected.
+ // If the live call is a conference, it will not have a target phone account set. This
+ // means the check to see if the live call has the same target phone account as the new
+ // call will not cause us to bail early. As a result, we'll end up holding the
+ // ongoing conference call. However, the ConnectionService is already doing that. This
+ // has caused problems with some carriers. As a workaround until b/23035408 is
+ // corrected, we will try and get the target phone account for one of the conference's
+ // children and use that instead.
+ PhoneAccountHandle liveCallPhoneAccount = liveCall.getTargetPhoneAccount();
+ if (liveCallPhoneAccount == null && liveCall.isConference() &&
+ !liveCall.getChildCalls().isEmpty()) {
+ liveCallPhoneAccount = mCallsManager.getFirstChildPhoneAccount(liveCall);
+ Log.i(this, "makeRoomForOutgoingEmergencyCall: using child call PhoneAccount = " +
+ liveCallPhoneAccount);
+ }
+
+ // We may not know which PhoneAccount the emergency call will be placed on yet, but if
+ // the liveCall PhoneAccount does not support placing emergency calls, then we know it
+ // will not be that one and we do not want multiple PhoneAccounts active during an
+ // emergency call if possible. Disconnect the active call in favor of the emergency call
+ // instead of trying to hold.
+ if (liveCall.getTargetPhoneAccount() != null) {
+ PhoneAccount pa = mCallsManager.getPhoneAccountRegistrar().getPhoneAccountUnchecked(
+ liveCall.getTargetPhoneAccount());
+ if((pa.getCapabilities() & PhoneAccount.CAPABILITY_PLACE_EMERGENCY_CALLS) == 0) {
+ liveCall.setOverrideDisconnectCauseCode(new DisconnectCause(
+ DisconnectCause.LOCAL, DisconnectCause.REASON_EMERGENCY_CALL_PLACED));
+ final String disconnectReason = "outgoing call does not support emergency calls, "
+ + "disconnecting.";
+ if (ringingCallFuture != null && isProcessingCallSequencing()) {
+ return ringingCallFuture.thenComposeAsync((result) -> {
+ if (result) {
+ Log.i(this, "makeRoomForOutgoingEmergencyCall: Request to disconnect "
+ + "ringing call succeeded. "
+ + "Attempting to disconnect live call.");
+ return liveCall.disconnect(disconnectReason);
+ } else {
+ Log.i(this, "makeRoomForOutgoingEmergencyCall: Request to disconnect "
+ + "ringing call failed. "
+ + "Aborting attempt to disconnect live call.");
+ return CompletableFuture.completedFuture(false);
+ }
+ }, new LoggedHandlerExecutor(mHandler, "CSC.mRFOEC",
+ mCallsManager.getLock()));
+ } else {
+ return liveCall.disconnect(disconnectReason);
+ }
+ } else {
+ return CompletableFuture.completedFuture(true);
+ }
+ }
+
+ // First thing, if we are trying to make an emergency call with the same package name as
+ // the live call, then allow it so that the connection service can make its own decision
+ // about how to handle the new call relative to the current one.
+ // By default, for telephony, it will try to hold the existing call before placing the new
+ // emergency call except for if the carrier does not support holding calls for emergency.
+ // In this case, telephony will disconnect the call.
+ if (PhoneAccountHandle.areFromSamePackage(liveCallPhoneAccount,
+ emergencyCall.getTargetPhoneAccount())) {
+ Log.i(this, "makeRoomForOutgoingEmergencyCall: phoneAccount matches.");
+ emergencyCall.getAnalytics().setCallIsAdditional(true);
+ liveCall.getAnalytics().setCallIsInterrupted(true);
+ return CompletableFuture.completedFuture(true);
+ } else if (emergencyCall.getTargetPhoneAccount() == null) {
+ // Without a phone account, we can't say reliably that the call will fail.
+ // If the user chooses the same phone account as the live call, then it's
+ // still possible that the call can be made (like with CDMA calls not supporting
+ // hold but they still support adding a call by going immediately into conference
+ // mode). Return true here and we'll run this code again after user chooses an
+ // account.
+ return CompletableFuture.completedFuture(true);
+ }
+
+ // Hold the live call if possible before attempting the new outgoing emergency call.
+ if (mCallsManager.canHold(liveCall)) {
+ Log.i(this, "makeRoomForOutgoingEmergencyCall: holding live call.");
+ emergencyCall.getAnalytics().setCallIsAdditional(true);
+ emergencyCall.increaseHeldByThisCallCount();
+ liveCall.getAnalytics().setCallIsInterrupted(true);
+ final String holdReason = "calling " + emergencyCall.getId();
+ if (ringingCallFuture != null && isProcessingCallSequencing()) {
+ return ringingCallFuture.thenComposeAsync((result) -> {
+ if (result) {
+ Log.i(this, "makeRoomForOutgoingEmergencyCall: Request to disconnect "
+ + "ringing call succeeded. Attempting to hold live call.");
+ return liveCall.hold(holdReason);
+ } else {
+ Log.i(this, "makeRoomForOutgoingEmergencyCall: Request to disconnect "
+ + "ringing call failed. Aborting attempt to hold live call.");
+ return CompletableFuture.completedFuture(false);
+ }
+ }, new LoggedHandlerExecutor(mHandler, "CSC.mRFOEC",
+ mCallsManager.getLock()));
+ } else {
+ return liveCall.hold(holdReason);
+ }
+ }
+
+ // The live call cannot be held so we're out of luck here. There's no room.
+ emergencyCall.setStartFailCause(CallFailureCause.CANNOT_HOLD_CALL);
+ return CompletableFuture.completedFuture(false);
+ }
+
+ /**
+ * This function tries to make room for the new outgoing call via call sequencing. The
+ * resulting future is an indication of whether room was able to be made for the call if
+ * needed.
+ * @param call The outgoing call to make room for.
+ * @return The {@code CompletableFuture} indicating the result of whether room was able to be
+ * made for the outgoing call.
+ */
+ private CompletableFuture<Boolean> makeRoomForOutgoingCall(Call call) {
+ // Already room!
+ if (!mCallsManager.hasMaximumLiveCalls(call)) {
+ return CompletableFuture.completedFuture(true);
+ }
+
+ // NOTE: If the amount of live calls changes beyond 1, this logic will probably
+ // have to change.
+ Call liveCall = mCallsManager.getFirstCallWithLiveState();
+ Log.i(this, "makeRoomForOutgoingCall call = " + call + " livecall = " +
+ liveCall);
+
+ if (call == liveCall) {
+ // If the call is already the foreground call, then we are golden.
+ // This can happen after the user selects an account in the SELECT_PHONE_ACCOUNT
+ // state since the call was already populated into the list.
+ return CompletableFuture.completedFuture(true);
+ }
+
+ CompletableFuture<Boolean> disconnectFuture = mCallsManager
+ .maybeDisconnectExistingCallForNewOutgoingCall(call, liveCall);
+ if (disconnectFuture != null) {
+ return disconnectFuture;
+ }
+
+ // TODO: Remove once b/23035408 has been corrected.
+ // If the live call is a conference, it will not have a target phone account set. This
+ // means the check to see if the live call has the same target phone account as the new
+ // call will not cause us to bail early. As a result, we'll end up holding the
+ // ongoing conference call. However, the ConnectionService is already doing that. This
+ // has caused problems with some carriers. As a workaround until b/23035408 is
+ // corrected, we will try and get the target phone account for one of the conference's
+ // children and use that instead.
+ PhoneAccountHandle liveCallPhoneAccount = liveCall.getTargetPhoneAccount();
+ if (liveCallPhoneAccount == null && liveCall.isConference() &&
+ !liveCall.getChildCalls().isEmpty()) {
+ liveCallPhoneAccount = mCallsManager.getFirstChildPhoneAccount(liveCall);
+ Log.i(this, "makeRoomForOutgoingCall: using child call PhoneAccount = " +
+ liveCallPhoneAccount);
+ }
+
+ // First thing, for managed calls, if we are trying to make a call with the same phone
+ // account as the live call, then allow it so that the connection service can make its own
+ // decision about how to handle the new call relative to the current one.
+ // Note: This behavior is primarily in place because Telephony historically manages the
+ // state of the calls it tracks by itself, holding and unholding as needed. Self-managed
+ // calls, even though from the same package are normally held/unheld automatically by
+ // Telecom. Calls within a single ConnectionService get held/unheld automatically during
+ // "swap" operations by CallsManager#holdActiveCallForNewCall. There is, however, a quirk
+ // in that if an app declares TWO different ConnectionServices, holdActiveCallForNewCall
+ // would not work correctly because focus switches between ConnectionServices, yet we
+ // tended to assume that if the calls are from the same package that the hold/unhold should
+ // be done by the app. That was a bad assumption as it meant that we could have two active
+ // calls.
+ // TODO(b/280826075): We need to come back and revisit all this logic in a holistic manner.
+ if (PhoneAccountHandle.areFromSamePackage(liveCallPhoneAccount,
+ call.getTargetPhoneAccount())
+ && !call.isSelfManaged()
+ && !liveCall.isSelfManaged()) {
+ Log.i(this, "makeRoomForOutgoingCall: managed phoneAccount matches");
+ call.getAnalytics().setCallIsAdditional(true);
+ liveCall.getAnalytics().setCallIsInterrupted(true);
+ return CompletableFuture.completedFuture(true);
+ } else if (call.getTargetPhoneAccount() == null) {
+ // Without a phone account, we can't say reliably that the call will fail.
+ // If the user chooses the same phone account as the live call, then it's
+ // still possible that the call can be made (like with CDMA calls not supporting
+ // hold but they still support adding a call by going immediately into conference
+ // mode). Return true here and we'll run this code again after user chooses an
+ // account.
+ return CompletableFuture.completedFuture(true);
+ }
+
+ // Try to hold the live call before attempting the new outgoing call.
+ if (mCallsManager.canHold(liveCall)) {
+ Log.i(this, "makeRoomForOutgoingCall: holding live call.");
+ call.getAnalytics().setCallIsAdditional(true);
+ liveCall.getAnalytics().setCallIsInterrupted(true);
+ return liveCall.hold("calling " + call.getId());
+ }
+
+ // The live call cannot be held so we're out of luck here. There's no room.
+ call.setStartFailCause(CallFailureCause.CANNOT_HOLD_CALL);
+ return CompletableFuture.completedFuture(false);
+ }
+
+ /**
+ * Processes the request from the app to disconnect a call. This is done via call sequencing
+ * so that Telecom properly cleans up the call locally provided that the call has been
+ * properly disconnected on the connection side.
+ * @param call The call to disconnect.
+ * @param previousState The previous state of the call before disconnecting.
+ */
+ public void disconnectCall(Call call, int previousState) {
+ CompletableFuture<Boolean> disconnectFuture = call.disconnect();
+ disconnectFuture.thenComposeAsync((result) -> {
+ if (result) {
+ Log.i(this, "disconnectCall: Disconnect call transaction succeeded. "
+ + "Processing associated cleanup.");
+ mCallsManager.processDisconnectCallAndCleanup(call, previousState);
+ } else {
+ Log.i(this, "disconnectCall: Disconnect call transaction failed. "
+ + "Aborting associated cleanup.");
+ }
+ return CompletableFuture.completedFuture(false);
+ }, new LoggedHandlerExecutor(mHandler, "CSC.dC",
+ mCallsManager.getLock()));
+ }
+
+ private void resetProcessingCallSequencing() {
+ setProcessingCallSequencing(false);
+ }
+
+ private void setProcessingCallSequencing(boolean processingCallSequencing) {
+ mProcessingCallSequencing = processingCallSequencing;
+ }
+
+ /**
+ * Checks if the 2 calls provided are from the same source and sets the
+ * mProcessingCallSequencing field if they aren't in order to signal that sequencing is
+ * required to verify the call state changes.
+ */
+ private void processCallSequencing(Call call1, Call call2) {
+ boolean areCallsFromSameSource = CallsManager.areFromSameSource(call1, call2);
+ if (!areCallsFromSameSource) {
+ setProcessingCallSequencing(true);
+ }
+ }
+
+ public boolean isProcessingCallSequencing() {
+ return mProcessingCallSequencing;
+ }
+
+ public Handler getHandler() {
+ return mHandler;
+ }
+}
diff --git a/src/com/android/server/telecom/voip/VoipCallTransaction.java b/src/com/android/server/telecom/callsequencing/CallTransaction.java
similarity index 85%
rename from src/com/android/server/telecom/voip/VoipCallTransaction.java
rename to src/com/android/server/telecom/callsequencing/CallTransaction.java
index a589a6d..8d7da7c 100644
--- a/src/com/android/server/telecom/voip/VoipCallTransaction.java
+++ b/src/com/android/server/telecom/callsequencing/CallTransaction.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2024 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.telecom.voip;
+package com.android.server.telecom.callsequencing;
import android.os.Handler;
import android.os.HandlerThread;
@@ -34,7 +34,7 @@
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Function;
-public class VoipCallTransaction {
+public class CallTransaction {
//TODO: add log events
private static final long DEFAULT_TRANSACTION_TIMEOUT_MS = 5000L;
@@ -52,7 +52,7 @@
private long mFinishedTimeNs = -1L;
// If finished, did this transaction finish because it timed out?
private boolean mIsTimedOut = false;
- private VoipCallTransactionResult mTransactionResult = null;
+ private CallTransactionResult mTransactionResult = null;
public Stats() {
addedTimeStamp = LocalDateTime.now();
@@ -70,7 +70,7 @@
/**
* Mark the transaction as completed and record the time.
*/
- public void markComplete(boolean isTimedOut, VoipCallTransactionResult result) {
+ public void markComplete(boolean isTimedOut, CallTransactionResult result) {
if (mFinishedTimeNs > -1) return;
mFinishedTimeNs = System.nanoTime();
mIsTimedOut = isTimedOut;
@@ -124,7 +124,7 @@
* @return the result if the transaction completed, null if it timed out or hasn't completed
* yet.
*/
- public VoipCallTransactionResult getTransactionResult() {
+ public CallTransactionResult getTransactionResult() {
return mTransactionResult;
}
}
@@ -134,13 +134,13 @@
private final HandlerThread mHandlerThread;
protected final Handler mHandler;
protected TransactionManager.TransactionCompleteListener mCompleteListener;
- protected final List<VoipCallTransaction> mSubTransactions;
+ protected final List<CallTransaction> mSubTransactions;
protected final TelecomSystem.SyncRoot mLock;
protected final long mTransactionTimeoutMs;
protected final Stats mStats;
- public VoipCallTransaction(
- List<VoipCallTransaction> subTransactions, TelecomSystem.SyncRoot lock,
+ public CallTransaction(
+ List<CallTransaction> subTransactions, TelecomSystem.SyncRoot lock,
long timeoutMs) {
mSubTransactions = subTransactions;
mHandlerThread = new HandlerThread(this.toString());
@@ -151,15 +151,15 @@
mStats = Flags.enableCallSequencing() ? new Stats() : null;
}
- public VoipCallTransaction(List<VoipCallTransaction> subTransactions,
+ public CallTransaction(List<CallTransaction> subTransactions,
TelecomSystem.SyncRoot lock) {
this(subTransactions, lock, DEFAULT_TRANSACTION_TIMEOUT_MS);
}
- public VoipCallTransaction(TelecomSystem.SyncRoot lock, long timeoutMs) {
+ public CallTransaction(TelecomSystem.SyncRoot lock, long timeoutMs) {
this(null /* mSubTransactions */, lock, timeoutMs);
}
- public VoipCallTransaction(TelecomSystem.SyncRoot lock) {
+ public CallTransaction(TelecomSystem.SyncRoot lock) {
this(null /* mSubTransactions */, lock);
}
@@ -178,7 +178,7 @@
}
/**
- * By default, this processes this transaction. For VoipCallTransactions with sub-transactions,
+ * By default, this processes this transaction. For CallTransaction with sub-transactions,
* this implementation should be overwritten to handle also processing sub-transactions.
*/
protected void processTransactions() {
@@ -187,7 +187,7 @@
/**
* This method is called when the transaction has finished either successfully or exceptionally.
- * VoipCallTransactions that are extending this class should override this method to clean up
+ * CallTransaction that are extending this class should override this method to clean up
* any leftover state.
*/
protected void finishTransaction() {
@@ -199,7 +199,7 @@
mTransactionName + "@" + hashCode() + ".sT", mLock);
CompletableFuture<Void> future = CompletableFuture.completedFuture(null);
future.thenComposeAsync(this::processTransaction, executor)
- .thenApplyAsync((Function<VoipCallTransactionResult, Void>) result -> {
+ .thenApplyAsync((Function<CallTransactionResult, Void>) result -> {
notifyListenersOfResult(result);
return null;
}, executor)
@@ -208,14 +208,14 @@
// Instead, propagate the failure to the other transactions immediately!
String errorMessage = throwable != null ? throwable.getMessage() :
"encountered an exception while processing " + mTransactionName;
- notifyListenersOfResult(new VoipCallTransactionResult(
+ notifyListenersOfResult(new CallTransactionResult(
CallException.CODE_ERROR_UNKNOWN, errorMessage));
Log.e(this, throwable, "Error while executing transaction.");
return null;
}));
}
- protected void notifyListenersOfResult(VoipCallTransactionResult result){
+ protected void notifyListenersOfResult(CallTransactionResult result){
mCompleted.set(true);
finish(result);
if (mCompleteListener != null) {
@@ -223,9 +223,9 @@
}
}
- protected CompletionStage<VoipCallTransactionResult> processTransaction(Void v) {
+ protected CompletionStage<CallTransactionResult> processTransaction(Void v) {
return CompletableFuture.completedFuture(
- new VoipCallTransactionResult(VoipCallTransactionResult.RESULT_SUCCEED, null));
+ new CallTransactionResult(CallTransactionResult.RESULT_SUCCEED, null));
}
public final void setCompleteListener(TransactionManager.TransactionCompleteListener listener) {
@@ -248,11 +248,11 @@
return mHandler;
}
- public final void finish(VoipCallTransactionResult result) {
+ public final void finish(CallTransactionResult result) {
finish(false, result);
}
- private void finish(boolean isTimedOut, VoipCallTransactionResult result) {
+ private void finish(boolean isTimedOut, CallTransactionResult result) {
if (mStats != null) mStats.markComplete(isTimedOut, result);
finishTransaction();
// finish all sub transactions
diff --git a/src/com/android/server/telecom/voip/VoipCallTransactionResult.java b/src/com/android/server/telecom/callsequencing/CallTransactionResult.java
similarity index 66%
rename from src/com/android/server/telecom/voip/VoipCallTransactionResult.java
rename to src/com/android/server/telecom/callsequencing/CallTransactionResult.java
index 50871f2..8b5f5bf 100644
--- a/src/com/android/server/telecom/voip/VoipCallTransactionResult.java
+++ b/src/com/android/server/telecom/callsequencing/CallTransactionResult.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2024 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.
@@ -14,33 +14,38 @@
* limitations under the License.
*/
-package com.android.server.telecom.voip;
+package com.android.server.telecom.callsequencing;
import com.android.server.telecom.Call;
import java.util.Objects;
-public class VoipCallTransactionResult {
+public class CallTransactionResult {
public static final int RESULT_SUCCEED = 0;
+ private static final String VOIP_TRANSACTION_TAG = "VoipCallTransactionResult";
+ private static final String PSTN_TRANSACTION_TAG = "PstnTransactionResult";
- // NOTE: if the VoipCallTransactionResult should not use the RESULT_SUCCEED to represent a
+ // NOTE: if the CallTransactionResult should not use the RESULT_SUCCEED to represent a
// successful transaction, use an error code defined in the
// {@link android.telecom.CallException} class
private final int mResult;
private final String mMessage;
private final Call mCall;
+ private final String mCallType;
- public VoipCallTransactionResult(int result, String message) {
+ public CallTransactionResult(int result, String message) {
mResult = result;
mMessage = message;
mCall = null;
+ mCallType = "";
}
- public VoipCallTransactionResult(int result, Call call, String message) {
+ public CallTransactionResult(int result, Call call, String message, boolean isVoip) {
mResult = result;
mCall = call;
mMessage = message;
+ mCallType = isVoip ? VOIP_TRANSACTION_TAG : PSTN_TRANSACTION_TAG;
}
public int getResult() {
@@ -58,8 +63,8 @@
@Override
public boolean equals(Object o) {
if (this == o) return true;
- if (!(o instanceof VoipCallTransactionResult)) return false;
- VoipCallTransactionResult that = (VoipCallTransactionResult) o;
+ if (!(o instanceof CallTransactionResult)) return false;
+ CallTransactionResult that = (CallTransactionResult) o;
return mResult == that.mResult && Objects.equals(mMessage, that.mMessage);
}
@@ -71,7 +76,9 @@
@Override
public String toString() {
return new StringBuilder().
- append("{ VoipCallTransactionResult: [mResult: ").
+ append("{ ").
+ append(mCallType).
+ append(": [mResult: ").
append(mResult).
append("], [mCall: ").
append((mCall != null) ? mCall : "null").
diff --git a/src/com/android/server/telecom/callsequencing/CallsManagerCallSequencingAdapter.java b/src/com/android/server/telecom/callsequencing/CallsManagerCallSequencingAdapter.java
new file mode 100644
index 0000000..df0837d
--- /dev/null
+++ b/src/com/android/server/telecom/callsequencing/CallsManagerCallSequencingAdapter.java
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 2024 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.callsequencing;
+
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.OutcomeReceiver;
+import android.telecom.CallAttributes;
+import android.telecom.CallException;
+import android.telecom.Log;
+
+import com.android.server.telecom.Call;
+import com.android.server.telecom.CallsManager;
+import com.android.server.telecom.LoggedHandlerExecutor;
+import com.android.server.telecom.callsequencing.voip.OutgoingCallTransaction;
+import com.android.server.telecom.flags.FeatureFlags;
+
+import java.util.concurrent.CompletableFuture;
+
+/**
+ * Abstraction layer for CallsManager to perform call sequencing operations through CallsManager
+ * or CallSequencingController, which is controlled by {@link FeatureFlags#enableCallSequencing()}.
+ */
+public class CallsManagerCallSequencingAdapter {
+
+ private final CallsManager mCallsManager;
+ private final CallSequencingController mSequencingController;
+ private final Handler mHandler;
+ private final FeatureFlags mFeatureFlags;
+ private final boolean mIsCallSequencingEnabled;
+
+ public CallsManagerCallSequencingAdapter(CallsManager callsManager,
+ CallSequencingController sequencingController,
+ FeatureFlags featureFlags) {
+ mCallsManager = callsManager;
+ mSequencingController = sequencingController;
+ mHandler = sequencingController.getHandler();
+ mFeatureFlags = featureFlags;
+ mIsCallSequencingEnabled = featureFlags.enableCallSequencing();
+ }
+
+ /**
+ * Helps create the transaction representing the outgoing transactional call. For outgoing
+ * calls, there can be more than one transaction that will need to complete when
+ * mIsCallSequencingEnabled is true. Otherwise, rely on the old behavior of creating an
+ * {@link OutgoingCallTransaction}.
+ * @param callAttributes The call attributes associated with the call.
+ * @param extras The extras that are associated with the call.
+ * @param callingPackage The calling package representing where the request was invoked from.
+ * @return The {@link CompletableFuture<CallTransaction>} that encompasses the request to
+ * place/receive the transactional call.
+ */
+ public CompletableFuture<CallTransaction> createTransactionalOutgoingCall(String callId,
+ CallAttributes callAttributes, Bundle extras, String callingPackage) {
+ return mIsCallSequencingEnabled
+ ? mSequencingController.createTransactionalOutgoingCall(callId,
+ callAttributes, extras, callingPackage)
+ : CompletableFuture.completedFuture(new OutgoingCallTransaction(callId,
+ mCallsManager.getContext(), callAttributes, mCallsManager, extras,
+ mFeatureFlags));
+ }
+
+ /**
+ * Conditionally try to answer the call depending on whether call sequencing
+ * (mIsCallSequencingEnabled) is enabled.
+ * @param incomingCall The incoming call that should be answered.
+ * @param videoState The video state configuration associated with the call.
+ */
+ public void answerCall(Call incomingCall, int videoState) {
+ if (mIsCallSequencingEnabled && !incomingCall.isTransactionalCall()) {
+ mSequencingController.answerCall(incomingCall, videoState);
+ } else {
+ mCallsManager.answerCallOld(incomingCall, videoState);
+ }
+ }
+
+ /**
+ * Conditionally attempt to unhold the provided call depending on whether call sequencing
+ * (mIsCallSequencingEnabled) is enabled.
+ * @param call The call to unhold.
+ */
+ public void unholdCall(Call call) {
+ if (mIsCallSequencingEnabled) {
+ mSequencingController.unholdCall(call);
+ } else {
+ mCallsManager.unholdCallOld(call);
+ }
+ }
+
+ /**
+ * Conditionally attempt to hold the provided call depending on whether call sequencing
+ * (mIsCallSequencingEnabled) is enabled.
+ * @param call The call to hold.
+ */
+ public void holdCall(Call call) {
+ // Sequencing already taken care of for CSW/TSW in Call class.
+ CompletableFuture<Boolean> holdFuture = call.hold();
+ if (mIsCallSequencingEnabled) {
+ logFutureResultTransaction(holdFuture, "holdCall", "CMCSA.hC",
+ "hold call transaction succeeded.", "hold call transaction failed.");
+ }
+ }
+
+ /**
+ * Conditionally disconnect the provided call depending on whether call sequencing
+ * (mIsCallSequencingEnabled) is enabled. The sequencing functionality ensures that we wait for
+ * the call to be disconnected as signalled by CSW/TSW as to ensure that subsequent call
+ * operations don't overlap with this one.
+ * @param call The call to disconnect.
+ */
+ public void disconnectCall(Call call) {
+ int previousState = call.getState();
+ if (mIsCallSequencingEnabled) {
+ mSequencingController.disconnectCall(call, previousState);
+ } else {
+ mCallsManager.disconnectCallOld(call, previousState);
+ }
+ }
+
+ /**
+ * Conditionally make room for the outgoing call depending on whether call sequencing
+ * (mIsCallSequencingEnabled) is enabled.
+ * @param isEmergency Indicator of whether the call is an emergency call.
+ * @param call The call to potentially make room for.
+ * @return {@link CompletableFuture} which will contain the result of the transaction if room
+ * was able to made for the call.
+ */
+ public CompletableFuture<Boolean> makeRoomForOutgoingCall(boolean isEmergency, Call call) {
+ if (mIsCallSequencingEnabled) {
+ return mSequencingController.makeRoomForOutgoingCall(isEmergency, call);
+ } else {
+ return isEmergency
+ ? CompletableFuture.completedFuture(
+ mCallsManager.makeRoomForOutgoingEmergencyCall(call))
+ : CompletableFuture.completedFuture(
+ mCallsManager.makeRoomForOutgoingCall(call));
+ }
+ }
+
+ /**
+ * Attempts to mark the self-managed call as active by first holding the active call and then
+ * requesting call focus for the self-managed call.
+ * @param call The self-managed call to set active
+ */
+ public void markCallAsActiveSelfManagedCall(Call call) {
+ if (mIsCallSequencingEnabled) {
+ mSequencingController.handleSetSelfManagedCallActive(call);
+ } else {
+ mCallsManager.holdActiveCallForNewCall(call);
+ mCallsManager.requestActionSetActiveCall(call,
+ "active set explicitly for self-managed");
+ }
+ }
+
+ /**
+ * Attempts to hold the active call for transactional call cases with call sequencing support
+ * if mIsCallSequencingEnabled is true.
+ * @param newCall The new (transactional) call that's waiting to go active.
+ * @param activeCall The currently active call.
+ * @param callback The callback to report the result of the aforementioned hold transaction.
+ * @return {@code CompletableFuture} indicating the result of holding the active call.
+ */
+ public void transactionHoldPotentialActiveCallForNewCall(Call newCall,
+ Call activeCall, OutcomeReceiver<Boolean, CallException> callback) {
+ if (mIsCallSequencingEnabled) {
+ mSequencingController.transactionHoldPotentialActiveCallForNewCallSequencing(
+ newCall, callback);
+ } else {
+ mCallsManager.transactionHoldPotentialActiveCallForNewCallOld(newCall,
+ activeCall, callback);
+ }
+ }
+
+ /**
+ * Generic helper to log the result of the {@link CompletableFuture} containing the transactions
+ * that are being processed in the context of call sequencing.
+ * @param future The {@link CompletableFuture} encompassing the transaction that's being
+ * computed.
+ * @param methodName The method name to describe the type of transaction being processed.
+ * @param sessionName The session name to identify the log.
+ * @param successMsg The message to be logged if the transaction succeeds.
+ * @param failureMsg The message to be logged if the transaction fails.
+ */
+ public void logFutureResultTransaction(CompletableFuture<Boolean> future, String methodName,
+ String sessionName, String successMsg, String failureMsg) {
+ future.thenApplyAsync((result) -> {
+ StringBuilder msg = new StringBuilder(methodName).append(": ");
+ msg.append(result ? successMsg : failureMsg);
+ Log.i(this, String.valueOf(msg));
+ return CompletableFuture.completedFuture(result);
+ }, new LoggedHandlerExecutor(mHandler, sessionName, mCallsManager.getLock()));
+ }
+
+ public Handler getHandler() {
+ return mHandler;
+ }
+}
diff --git a/src/com/android/server/telecom/voip/TransactionManager.java b/src/com/android/server/telecom/callsequencing/TransactionManager.java
similarity index 78%
rename from src/com/android/server/telecom/voip/TransactionManager.java
rename to src/com/android/server/telecom/callsequencing/TransactionManager.java
index 0086d07..2a6431b 100644
--- a/src/com/android/server/telecom/voip/TransactionManager.java
+++ b/src/com/android/server/telecom/callsequencing/TransactionManager.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.telecom.voip;
+package com.android.server.telecom.callsequencing;
import static android.telecom.CallException.CODE_OPERATION_TIMED_OUT;
@@ -32,18 +32,20 @@
import java.util.List;
import java.util.Locale;
import java.util.Queue;
+import java.util.concurrent.CompletableFuture;
public class TransactionManager {
- private static final String TAG = "VoipCallTransactionManager";
+ private static final String TAG = "CallTransactionManager";
private static final int TRANSACTION_HISTORY_SIZE = 20;
private static TransactionManager INSTANCE = null;
private static final Object sLock = new Object();
- private final Queue<VoipCallTransaction> mTransactions;
- private final Deque<VoipCallTransaction> mCompletedTransactions;
- private VoipCallTransaction mCurrentTransaction;
+ private final Queue<CallTransaction> mTransactions;
+ private final Deque<CallTransaction> mCompletedTransactions;
+ private CallTransaction mCurrentTransaction;
+ private boolean mProcessingCallSequencing;
public interface TransactionCompleteListener {
- void onTransactionCompleted(VoipCallTransactionResult result, String transactionName);
+ void onTransactionCompleted(CallTransactionResult result, String transactionName);
void onTransactionTimeout(String transactionName);
}
@@ -70,28 +72,32 @@
return new TransactionManager();
}
- public void addTransaction(VoipCallTransaction transaction,
- OutcomeReceiver<VoipCallTransactionResult, CallException> receiver) {
+ public CompletableFuture<Boolean> addTransaction(CallTransaction transaction,
+ OutcomeReceiver<CallTransactionResult, CallException> receiver) {
+ CompletableFuture<Boolean> transactionCompleteFuture = new CompletableFuture<>();
synchronized (sLock) {
mTransactions.add(transaction);
}
transaction.setCompleteListener(new TransactionCompleteListener() {
@Override
- public void onTransactionCompleted(VoipCallTransactionResult result,
+ public void onTransactionCompleted(CallTransactionResult result,
String transactionName) {
Log.i(TAG, String.format("transaction %s completed: with result=[%d]",
transactionName, result.getResult()));
try {
if (result.getResult() == TelecomManager.TELECOM_TRANSACTION_SUCCESS) {
receiver.onResult(result);
+ transactionCompleteFuture.complete(true);
} else {
receiver.onError(
new CallException(result.getMessage(),
result.getResult()));
+ transactionCompleteFuture.complete(false);
}
} catch (Exception e) {
Log.e(TAG, String.format("onTransactionCompleted: Notifying transaction result"
+ " %s resulted in an Exception.", result), e);
+ transactionCompleteFuture.complete(false);
}
finishTransaction();
}
@@ -102,15 +108,18 @@
try {
receiver.onError(new CallException(transactionName + " timeout",
CODE_OPERATION_TIMED_OUT));
+ transactionCompleteFuture.complete(false);
} catch (Exception e) {
Log.e(TAG, String.format("onTransactionTimeout: Notifying transaction "
+ " %s resulted in an Exception.", transactionName), e);
+ transactionCompleteFuture.complete(false);
}
finishTransaction();
}
});
startTransactions();
+ return transactionCompleteFuture;
}
private void startTransactions() {
@@ -141,17 +150,17 @@
@VisibleForTesting
public void clear() {
- List<VoipCallTransaction> pendingTransactions;
+ List<CallTransaction> pendingTransactions;
synchronized (sLock) {
pendingTransactions = new ArrayList<>(mTransactions);
}
- for (VoipCallTransaction t : pendingTransactions) {
- t.finish(new VoipCallTransactionResult(CallException.CODE_ERROR_UNKNOWN
+ for (CallTransaction t : pendingTransactions) {
+ t.finish(new CallTransactionResult(CallException.CODE_ERROR_UNKNOWN
/* TODO:: define error b/335703584 */, "clear called"));
}
}
- private void addTransactionToHistory(VoipCallTransaction t) {
+ private void addTransactionToHistory(CallTransaction t) {
if (!Flags.enableCallSequencing()) return;
mCompletedTransactions.add(t);
@@ -171,7 +180,7 @@
synchronized (sLock) {
pw.println("Pending Transactions:");
pw.increaseIndent();
- for (VoipCallTransaction t : mTransactions) {
+ for (CallTransaction t : mTransactions) {
printPendingTransactionStats(t, pw);
}
pw.decreaseIndent();
@@ -185,7 +194,7 @@
pw.println("Completed Transactions:");
pw.increaseIndent();
- for (VoipCallTransaction t : mCompletedTransactions) {
+ for (CallTransaction t : mCompletedTransactions) {
printCompleteTransactionStats(t, pw);
}
pw.decreaseIndent();
@@ -193,12 +202,12 @@
}
/**
- * Recursively print the pending {@link VoipCallTransaction} stats for logging purposes.
+ * Recursively print the pending {@link CallTransaction} stats for logging purposes.
* @param t The transaction that stats should be printed for
* @param pw The IndentingPrintWriter to print the result to
*/
- private void printPendingTransactionStats(VoipCallTransaction t, IndentingPrintWriter pw) {
- VoipCallTransaction.Stats s = t.getStats();
+ private void printPendingTransactionStats(CallTransaction t, IndentingPrintWriter pw) {
+ CallTransaction.Stats s = t.getStats();
if (s == null) {
pw.println(String.format(Locale.getDefault(), "%s: <NO STATS>", t.mTransactionName));
return;
@@ -215,7 +224,7 @@
return;
}
pw.increaseIndent();
- for (VoipCallTransaction subTransaction : t.mSubTransactions) {
+ for (CallTransaction subTransaction : t.mSubTransactions) {
printPendingTransactionStats(subTransaction, pw);
}
pw.decreaseIndent();
@@ -226,8 +235,8 @@
* @param t The transaction that stats should be printed for
* @param pw The IndentingPrintWriter to print the result to
*/
- private void printCompleteTransactionStats(VoipCallTransaction t, IndentingPrintWriter pw) {
- VoipCallTransaction.Stats s = t.getStats();
+ private void printCompleteTransactionStats(CallTransaction t, IndentingPrintWriter pw) {
+ CallTransaction.Stats s = t.getStats();
if (s == null) {
pw.println(String.format(Locale.getDefault(), "%s: <NO STATS>", t.mTransactionName));
return;
@@ -242,16 +251,16 @@
return;
}
pw.increaseIndent();
- for (VoipCallTransaction subTransaction : t.mSubTransactions) {
+ for (CallTransaction subTransaction : t.mSubTransactions) {
printCompleteTransactionStats(subTransaction, pw);
}
pw.decreaseIndent();
}
- private String parseTransactionResult(VoipCallTransaction.Stats s) {
+ private String parseTransactionResult(CallTransaction.Stats s) {
if (s.isTimedOut()) return "TIMED OUT";
if (s.getTransactionResult() == null) return "PENDING";
- if (s.getTransactionResult().getResult() == VoipCallTransactionResult.RESULT_SUCCEED) {
+ if (s.getTransactionResult().getResult() == CallTransactionResult.RESULT_SUCCEED) {
return "SUCCESS";
}
return s.getTransactionResult().toString();
diff --git a/src/com/android/server/telecom/callsequencing/TransactionalCallSequencingAdapter.java b/src/com/android/server/telecom/callsequencing/TransactionalCallSequencingAdapter.java
new file mode 100644
index 0000000..570c2cc
--- /dev/null
+++ b/src/com/android/server/telecom/callsequencing/TransactionalCallSequencingAdapter.java
@@ -0,0 +1,329 @@
+/*
+ * Copyright (C) 2024 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.callsequencing;
+
+import android.os.OutcomeReceiver;
+import android.telecom.CallException;
+import android.telecom.DisconnectCause;
+
+import com.android.server.telecom.Call;
+import com.android.server.telecom.CallsManager;
+import com.android.server.telecom.callsequencing.voip.EndCallTransaction;
+import com.android.server.telecom.callsequencing.voip.HoldCallTransaction;
+import com.android.server.telecom.callsequencing.voip.MaybeHoldCallForNewCallTransaction;
+import com.android.server.telecom.callsequencing.voip.RequestNewActiveCallTransaction;
+import com.android.server.telecom.callsequencing.voip.SerialTransaction;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+
+/**
+ * Helper adapter class used to centralized code that will be affected by toggling the
+ * {@link Flags#enableCallSequencing()} flag.
+ */
+public class TransactionalCallSequencingAdapter {
+ private final TransactionManager mTransactionManager;
+ private final CallsManager mCallsManager;
+ private final boolean mIsCallSequencingEnabled;
+
+ public TransactionalCallSequencingAdapter(TransactionManager transactionManager,
+ CallsManager callsManager, boolean isCallSequencingEnabled) {
+ mTransactionManager = transactionManager;
+ mCallsManager = callsManager;
+ mIsCallSequencingEnabled = isCallSequencingEnabled;
+ }
+
+ /**
+ * Client -> Server request to set a call active
+ */
+ public void setActive(Call call,
+ OutcomeReceiver<CallTransactionResult, CallException> receiver) {
+ if (mIsCallSequencingEnabled) {
+ createSetActiveTransactionSequencing(call, true /* callControlRequest */, null,
+ receiver, receiver);
+ } else {
+ mTransactionManager.addTransaction(createSetActiveTransactions(call,
+ true /* callControlRequest */), receiver);
+ }
+ }
+
+ /**
+ * Client -> Server request to answer a call
+ */
+ public void setAnswered(Call call, int newVideoState,
+ OutcomeReceiver<CallTransactionResult, CallException> receiver) {
+ boolean isCallControlRequest = true;
+ OutcomeReceiver<CallTransactionResult, CallException> receiverForTransaction =
+ getSetAnswerReceiver(call, null /* foregroundCallBeforeSwap */,
+ false /* wasForegroundActive */, newVideoState, receiver,
+ isCallControlRequest);
+ if (mIsCallSequencingEnabled) {
+ createSetActiveTransactionSequencing(call, isCallControlRequest, null,
+ receiver, receiverForTransaction /* receiverForTransaction */);
+ } else {
+ mTransactionManager.addTransaction(createSetActiveTransactions(call,
+ isCallControlRequest), receiverForTransaction);
+ }
+ }
+
+ /**
+ * Client -> Server request to set a call to disconnected
+ */
+ public void setDisconnected(Call call, DisconnectCause dc,
+ OutcomeReceiver<CallTransactionResult, CallException> receiver) {
+ mTransactionManager.addTransaction(
+ new EndCallTransaction(mCallsManager, dc, call), receiver);
+ }
+
+ /**
+ * Client -> Server request to set a call to inactive
+ */
+ public void setInactive(Call call,
+ OutcomeReceiver<CallTransactionResult, CallException> receiver) {
+ mTransactionManager.addTransaction(new HoldCallTransaction(mCallsManager,call), receiver);
+ }
+
+ /**
+ * Server -> Client command to set the call active, which if it fails, will try to reset the
+ * state to what it was before the call was set to active.
+ */
+ public CompletableFuture<Boolean> onSetActive(Call call,
+ CallTransaction clientCbT,
+ OutcomeReceiver<CallTransactionResult, CallException> receiver) {
+ // save CallsManager state before sending client state changes
+ Call foregroundCallBeforeSwap = mCallsManager.getForegroundCall();
+ boolean wasActive = foregroundCallBeforeSwap != null && foregroundCallBeforeSwap.isActive();
+ OutcomeReceiver<CallTransactionResult, CallException> receiverForTransaction =
+ getOnSetActiveReceiver(call, foregroundCallBeforeSwap, wasActive, receiver);
+
+ if (mIsCallSequencingEnabled) {
+ return createSetActiveTransactionSequencing(call, false /* callControlRequest */,
+ clientCbT, receiver, receiverForTransaction);
+ } else {
+ SerialTransaction serialTransactions = createSetActiveTransactions(call,
+ false /* callControlRequest */);
+ serialTransactions.appendTransaction(clientCbT);
+ // do CallsManager workload before asking client and
+ // reset CallsManager state if client does NOT ack
+ return mTransactionManager.addTransaction(
+ serialTransactions, receiverForTransaction);
+ }
+ }
+
+ /**
+ * Server -> Client command to answer an incoming call, which if it fails, will trigger the
+ * disconnect of the call and then reset the state of the other call back to what it was before.
+ */
+ public CompletableFuture<Boolean> onSetAnswered(Call call, int videoState,
+ CallTransaction clientCbT, OutcomeReceiver<CallTransactionResult,
+ CallException> receiver) {
+ boolean isCallControlRequest = false;
+ // save CallsManager state before sending client state changes
+ Call foregroundCallBeforeSwap = mCallsManager.getForegroundCall();
+ boolean wasActive = foregroundCallBeforeSwap != null && foregroundCallBeforeSwap.isActive();
+ OutcomeReceiver<CallTransactionResult, CallException> receiverForTransaction =
+ getSetAnswerReceiver(call, foregroundCallBeforeSwap, wasActive,
+ videoState, receiver, isCallControlRequest);
+
+ if (mIsCallSequencingEnabled) {
+ return createSetActiveTransactionSequencing(call, false /* callControlRequest */,
+ clientCbT, receiver, receiverForTransaction);
+ } else {
+ SerialTransaction serialTransactions = createSetActiveTransactions(call,
+ isCallControlRequest);
+ serialTransactions.appendTransaction(clientCbT);
+ // do CallsManager workload before asking client and
+ // reset CallsManager state if client does NOT ack
+ return mTransactionManager.addTransaction(serialTransactions, receiverForTransaction);
+ }
+ }
+
+ /**
+ * Server -> Client command to set the call as inactive
+ */
+ public CompletableFuture<Boolean> onSetInactive(Call call,
+ CallTransaction clientCbT,
+ OutcomeReceiver<CallTransactionResult, CallException> receiver) {
+ return mTransactionManager.addTransaction(clientCbT,
+ new OutcomeReceiver<>() {
+ @Override
+ public void onResult(CallTransactionResult callTransactionResult) {
+ mCallsManager.markCallAsOnHold(call);
+ receiver.onResult(callTransactionResult);
+ }
+
+ @Override
+ public void onError(CallException error) {
+ receiver.onError(error);
+ }
+ });
+ }
+
+ /**
+ * Server -> Client command to disconnect the call
+ */
+ public CompletableFuture<Boolean> onSetDisconnected(Call call,
+ DisconnectCause dc, CallTransaction clientCbT, OutcomeReceiver<CallTransactionResult,
+ CallException> receiver) {
+ return mTransactionManager.addTransaction(clientCbT,
+ new OutcomeReceiver<>() {
+ @Override
+ public void onResult(CallTransactionResult result) {
+ removeCallFromCallsManager(call, dc);
+ receiver.onResult(result);
+ }
+
+ @Override
+ public void onError(CallException exception) {
+ removeCallFromCallsManager(call, dc);
+ receiver.onError(exception);
+ }
+ });
+ }
+
+ /**
+ * Clean up the calls that have been passed in from CallsManager
+ */
+ public void cleanup(Collection<Call> calls) {
+ cleanupFlagOff(calls);
+ }
+
+ private SerialTransaction createSetActiveTransactions(Call call, boolean isCallControlRequest) {
+ // create list for multiple transactions
+ List<CallTransaction> transactions = new ArrayList<>();
+
+ // potentially hold the current active call in order to set a new call (active/answered)
+ transactions.add(new MaybeHoldCallForNewCallTransaction(mCallsManager, call,
+ isCallControlRequest));
+ // And request a new focus call update
+ transactions.add(new RequestNewActiveCallTransaction(mCallsManager, call));
+
+ return new SerialTransaction(transactions, mCallsManager.getLock());
+ }
+
+ /**
+ * This code path is invoked when mIsCallSequencingEnabled is true. We will first try to hold
+ * the active call before adding the transactions to request call focus for the new call as well
+ * as verify the client ack for the transaction (if applicable). If the hold transaction
+ * succeeds, we will continue processing the rest of the transactions via a SerialTransaction.
+ */
+ private CompletableFuture<Boolean> createSetActiveTransactionSequencing(
+ Call call, boolean isCallControlRequest, CallTransaction clientCbT,
+ OutcomeReceiver<CallTransactionResult, CallException> receiver,
+ OutcomeReceiver<CallTransactionResult, CallException> receiverForTransaction) {
+ final CompletableFuture<Boolean>[] createSetActiveFuture =
+ new CompletableFuture[]{new CompletableFuture<>()};
+ OutcomeReceiver<Boolean, CallException> maybePerformHoldCallback = new OutcomeReceiver<>() {
+ @Override
+ public void onResult(Boolean result) {
+ // Transaction not yet completed. Still need to request focus for active call and
+ // process client callback transaction if applicable.
+ // create list for multiple transactions
+ List<CallTransaction> transactions = new ArrayList<>();
+ // And request a new focus call update
+ transactions.add(new RequestNewActiveCallTransaction(mCallsManager, call));
+ if (clientCbT != null){
+ transactions.add(clientCbT);
+ }
+ SerialTransaction serialTransactions = new SerialTransaction(
+ transactions, mCallsManager.getLock());
+ createSetActiveFuture[0] = mTransactionManager.addTransaction(serialTransactions,
+ receiverForTransaction);
+ }
+
+ @Override
+ public void onError(CallException exception) {
+ createSetActiveFuture[0] = CompletableFuture.completedFuture(false);
+ receiver.onError(exception);
+ }
+ };
+
+ mCallsManager.transactionHoldPotentialActiveCallForNewCall(call,
+ isCallControlRequest, maybePerformHoldCallback);
+ return createSetActiveFuture[0];
+ }
+
+ private void removeCallFromCallsManager(Call call, DisconnectCause cause) {
+ if (cause.getCode() != DisconnectCause.REJECTED) {
+ mCallsManager.markCallAsDisconnected(call, cause);
+ }
+ mCallsManager.removeCall(call);
+ }
+
+ private void maybeResetForegroundCall(Call foregroundCallBeforeSwap, boolean wasActive) {
+ if (foregroundCallBeforeSwap == null) {
+ return;
+ }
+ if (wasActive && !foregroundCallBeforeSwap.isActive()) {
+ mCallsManager.markCallAsActive(foregroundCallBeforeSwap);
+ }
+ }
+ private void cleanupFlagOff(Collection<Call> calls) {
+ for (Call call : calls) {
+ mCallsManager.markCallAsDisconnected(call,
+ new DisconnectCause(DisconnectCause.ERROR, "process died"));
+ mCallsManager.removeCall(call); // This will clear mTrackedCalls && ClientTWS
+ }
+ }
+
+ private OutcomeReceiver<CallTransactionResult, CallException> getOnSetActiveReceiver(
+ Call call, Call foregroundCallBeforeSwap, boolean wasForegroundActive,
+ OutcomeReceiver<CallTransactionResult, CallException> receiver) {
+ return new OutcomeReceiver<>() {
+ @Override
+ public void onResult(CallTransactionResult result) {
+ receiver.onResult(result);
+ }
+
+ @Override
+ public void onError(CallException exception) {
+ mCallsManager.markCallAsOnHold(call);
+ maybeResetForegroundCall(foregroundCallBeforeSwap, wasForegroundActive);
+ receiver.onError(exception);
+ }
+ };
+ }
+
+ private OutcomeReceiver<CallTransactionResult, CallException> getSetAnswerReceiver(
+ Call call, Call foregroundCallBeforeSwap, boolean wasForegroundActive, int videoState,
+ OutcomeReceiver<CallTransactionResult, CallException> receiver,
+ boolean isCallControlRequest) {
+ return new OutcomeReceiver<>() {
+ @Override
+ public void onResult(CallTransactionResult result) {
+ call.setVideoState(videoState);
+ receiver.onResult(result);
+ }
+
+ @Override
+ public void onError(CallException exception) {
+ if (!isCallControlRequest) {
+ // This also sends the signal to untrack from TSW and the
+ // client_TSW
+ removeCallFromCallsManager(call,
+ new DisconnectCause(DisconnectCause.REJECTED,
+ "client rejected to answer the call;"
+ + " force disconnecting"));
+ maybeResetForegroundCall(foregroundCallBeforeSwap, wasForegroundActive);
+ }
+ receiver.onError(exception);
+ }
+ };
+ }
+}
diff --git a/src/com/android/server/telecom/voip/VerifyCallStateChangeTransaction.java b/src/com/android/server/telecom/callsequencing/VerifyCallStateChangeTransaction.java
similarity index 69%
rename from src/com/android/server/telecom/voip/VerifyCallStateChangeTransaction.java
rename to src/com/android/server/telecom/callsequencing/VerifyCallStateChangeTransaction.java
index 5de4b1d..82b32fb 100644
--- a/src/com/android/server/telecom/voip/VerifyCallStateChangeTransaction.java
+++ b/src/com/android/server/telecom/callsequencing/VerifyCallStateChangeTransaction.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.telecom.voip;
+package com.android.server.telecom.callsequencing;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.telecom.Call;
@@ -22,8 +22,11 @@
import android.telecom.Log;
+import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
/**
* VerifyCallStateChangeTransaction is a transaction that verifies a CallState change and has
@@ -31,21 +34,22 @@
* <p>
* Note: This transaction has a timeout of 2 seconds.
*/
-public class VerifyCallStateChangeTransaction extends VoipCallTransaction {
+public class VerifyCallStateChangeTransaction extends CallTransaction {
private static final String TAG = VerifyCallStateChangeTransaction.class.getSimpleName();
private static final long CALL_STATE_TIMEOUT_MILLISECONDS = 2000L;
private final Call mCall;
- private final int mTargetCallState;
- private final CompletableFuture<VoipCallTransactionResult> mTransactionResult =
+ private final Set<Integer> mTargetCallStates;
+ private final CompletableFuture<CallTransactionResult> mTransactionResult =
new CompletableFuture<>();
private final Call.CallStateListener mCallStateListenerImpl = new Call.CallStateListener() {
@Override
public void onCallStateChanged(int newCallState) {
- Log.d(TAG, "newState=[%d], expectedState=[%d]", newCallState, mTargetCallState);
- if (newCallState == mTargetCallState) {
- mTransactionResult.complete(new VoipCallTransactionResult(
- VoipCallTransactionResult.RESULT_SUCCEED, TAG));
+ Log.d(TAG, "newState=[%d], possible expected state(s)=[%s]", newCallState,
+ mTargetCallStates);
+ if (mTargetCallStates.contains(newCallState)) {
+ mTransactionResult.complete(new CallTransactionResult(
+ CallTransactionResult.RESULT_SUCCEED, TAG));
}
// NOTE:: keep listening to the call state until the timeout is reached. It's possible
// another call state is reached in between...
@@ -53,19 +57,19 @@
};
public VerifyCallStateChangeTransaction(TelecomSystem.SyncRoot lock, Call call,
- int targetCallState) {
+ int... targetCallStates) {
super(lock, CALL_STATE_TIMEOUT_MILLISECONDS);
mCall = call;
- mTargetCallState = targetCallState;
+ mTargetCallStates = IntStream.of(targetCallStates).boxed().collect(Collectors.toSet());;
}
@Override
- public CompletionStage<VoipCallTransactionResult> processTransaction(Void v) {
+ public CompletionStage<CallTransactionResult> processTransaction(Void v) {
Log.d(TAG, "processTransaction:");
// It's possible the Call is already in the expected call state
if (isNewCallStateTargetCallState()) {
- mTransactionResult.complete(new VoipCallTransactionResult(
- VoipCallTransactionResult.RESULT_SUCCEED, TAG));
+ mTransactionResult.complete(new CallTransactionResult(
+ CallTransactionResult.RESULT_SUCCEED, TAG));
return mTransactionResult;
}
mCall.addCallStateListener(mCallStateListenerImpl);
@@ -78,11 +82,11 @@
}
private boolean isNewCallStateTargetCallState() {
- return mCall.getState() == mTargetCallState;
+ return mTargetCallStates.contains(mCall.getState());
}
@VisibleForTesting
- public CompletableFuture<VoipCallTransactionResult> getTransactionResult() {
+ public CompletableFuture<CallTransactionResult> getTransactionResult() {
return mTransactionResult;
}
diff --git a/src/com/android/server/telecom/voip/CallEventCallbackAckTransaction.java b/src/com/android/server/telecom/callsequencing/voip/CallEventCallbackAckTransaction.java
similarity index 90%
rename from src/com/android/server/telecom/voip/CallEventCallbackAckTransaction.java
rename to src/com/android/server/telecom/callsequencing/voip/CallEventCallbackAckTransaction.java
index 9e140a7..802ea7e 100644
--- a/src/com/android/server/telecom/voip/CallEventCallbackAckTransaction.java
+++ b/src/com/android/server/telecom/callsequencing/voip/CallEventCallbackAckTransaction.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.telecom.voip;
+package com.android.server.telecom.callsequencing.voip;
import static android.telecom.TelecomManager.TELECOM_TRANSACTION_SUCCESS;
import static android.telecom.CallException.CODE_OPERATION_TIMED_OUT;
@@ -29,6 +29,8 @@
import com.android.internal.telecom.ICallEventCallback;
import com.android.server.telecom.TelecomSystem;
import com.android.server.telecom.TransactionalServiceWrapper;
+import com.android.server.telecom.callsequencing.CallTransaction;
+import com.android.server.telecom.callsequencing.CallTransactionResult;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
@@ -39,7 +41,7 @@
* SRP: using the ICallEventCallback binder, reach out to the client for the pending call event and
* get an acknowledgement that the call event can be completed.
*/
-public class CallEventCallbackAckTransaction extends VoipCallTransaction {
+public class CallEventCallbackAckTransaction extends CallTransaction {
private static final String TAG = CallEventCallbackAckTransaction.class.getSimpleName();
private final ICallEventCallback mICallEventCallback;
private final String mAction;
@@ -48,7 +50,7 @@
private int mVideoState = CallAttributes.AUDIO_CALL;
private DisconnectCause mDisconnectCause = null;
- private final VoipCallTransactionResult TRANSACTION_FAILED = new VoipCallTransactionResult(
+ private final CallTransactionResult TRANSACTION_FAILED = new CallTransactionResult(
CODE_OPERATION_TIMED_OUT, "failed to complete the operation before timeout");
private static class AckResultReceiver extends ResultReceiver {
@@ -96,7 +98,7 @@
@Override
- public CompletionStage<VoipCallTransactionResult> processTransaction(Void v) {
+ public CompletionStage<CallTransactionResult> processTransaction(Void v) {
Log.d(TAG, "processTransaction");
CountDownLatch latch = new CountDownLatch(1);
ResultReceiver receiver = new AckResultReceiver(latch);
@@ -134,7 +136,7 @@
} else {
// success
return CompletableFuture.completedFuture(
- new VoipCallTransactionResult(VoipCallTransactionResult.RESULT_SUCCEED,
+ new CallTransactionResult(CallTransactionResult.RESULT_SUCCEED,
"success"));
}
} catch (InterruptedException ie) {
diff --git a/src/com/android/server/telecom/voip/EndCallTransaction.java b/src/com/android/server/telecom/callsequencing/voip/EndCallTransaction.java
similarity index 82%
rename from src/com/android/server/telecom/voip/EndCallTransaction.java
rename to src/com/android/server/telecom/callsequencing/voip/EndCallTransaction.java
index 0cb7458..b4c92fe 100644
--- a/src/com/android/server/telecom/voip/EndCallTransaction.java
+++ b/src/com/android/server/telecom/callsequencing/voip/EndCallTransaction.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.telecom.voip;
+package com.android.server.telecom.callsequencing.voip;
import android.telecom.DisconnectCause;
import android.util.Log;
@@ -22,6 +22,8 @@
import com.android.server.telecom.Call;
import com.android.server.telecom.CallState;
import com.android.server.telecom.CallsManager;
+import com.android.server.telecom.callsequencing.CallTransaction;
+import com.android.server.telecom.callsequencing.CallTransactionResult;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
@@ -29,7 +31,7 @@
/**
* This transaction should only be created for a CallControl action.
*/
-public class EndCallTransaction extends VoipCallTransaction {
+public class EndCallTransaction extends CallTransaction {
private static final String TAG = EndCallTransaction.class.getSimpleName();
private final CallsManager mCallsManager;
private final Call mCall;
@@ -43,7 +45,7 @@
}
@Override
- public CompletionStage<VoipCallTransactionResult> processTransaction(Void v) {
+ public CompletionStage<CallTransactionResult> processTransaction(Void v) {
int code = mCause.getCode();
Log.d(TAG, String.format("processTransaction: mCode=[%d], mCall=[%s]", code, mCall));
@@ -56,7 +58,7 @@
mCallsManager.markCallAsRemoved(mCall);
return CompletableFuture.completedFuture(
- new VoipCallTransactionResult(VoipCallTransactionResult.RESULT_SUCCEED,
+ new CallTransactionResult(CallTransactionResult.RESULT_SUCCEED,
"EndCallTransaction: RESULT_SUCCEED"));
}
}
diff --git a/src/com/android/server/telecom/voip/EndpointChangeTransaction.java b/src/com/android/server/telecom/callsequencing/voip/EndpointChangeTransaction.java
similarity index 75%
rename from src/com/android/server/telecom/voip/EndpointChangeTransaction.java
rename to src/com/android/server/telecom/callsequencing/voip/EndpointChangeTransaction.java
index 6841fcf..46678da 100644
--- a/src/com/android/server/telecom/voip/EndpointChangeTransaction.java
+++ b/src/com/android/server/telecom/callsequencing/voip/EndpointChangeTransaction.java
@@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.server.telecom.voip;
+package com.android.server.telecom.callsequencing.voip;
import android.os.Bundle;
import android.os.ResultReceiver;
@@ -23,11 +23,13 @@
import android.util.Log;
import com.android.server.telecom.CallsManager;
+import com.android.server.telecom.callsequencing.CallTransaction;
+import com.android.server.telecom.callsequencing.CallTransactionResult;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
-public class EndpointChangeTransaction extends VoipCallTransaction {
+public class EndpointChangeTransaction extends CallTransaction {
private static final String TAG = EndpointChangeTransaction.class.getSimpleName();
private final CallEndpoint mCallEndpoint;
private final CallsManager mCallsManager;
@@ -39,19 +41,19 @@
}
@Override
- public CompletionStage<VoipCallTransactionResult> processTransaction(Void v) {
+ public CompletionStage<CallTransactionResult> processTransaction(Void v) {
Log.i(TAG, "processTransaction");
- CompletableFuture<VoipCallTransactionResult> future = new CompletableFuture<>();
+ CompletableFuture<CallTransactionResult> future = new CompletableFuture<>();
mCallsManager.requestCallEndpointChange(mCallEndpoint, new ResultReceiver(null) {
@Override
protected void onReceiveResult(int resultCode, Bundle resultData) {
Log.i(TAG, "processTransaction: code=" + resultCode);
if (resultCode == CallEndpoint.ENDPOINT_OPERATION_SUCCESS) {
- future.complete(new VoipCallTransactionResult(
- VoipCallTransactionResult.RESULT_SUCCEED, null));
+ future.complete(new CallTransactionResult(
+ CallTransactionResult.RESULT_SUCCEED, null));
} else {
// TODO:: define errors in CallException class. b/335703584
- future.complete(new VoipCallTransactionResult(
+ future.complete(new CallTransactionResult(
CallException.CODE_ERROR_UNKNOWN, null));
}
}
diff --git a/src/com/android/server/telecom/voip/HoldCallTransaction.java b/src/com/android/server/telecom/callsequencing/voip/HoldCallTransaction.java
similarity index 72%
rename from src/com/android/server/telecom/voip/HoldCallTransaction.java
rename to src/com/android/server/telecom/callsequencing/voip/HoldCallTransaction.java
index 6c4e8b7..2fa7ff7 100644
--- a/src/com/android/server/telecom/voip/HoldCallTransaction.java
+++ b/src/com/android/server/telecom/callsequencing/voip/HoldCallTransaction.java
@@ -14,18 +14,20 @@
* limitations under the License.
*/
-package com.android.server.telecom.voip;
+package com.android.server.telecom.callsequencing.voip;
import android.telecom.CallException;
import android.util.Log;
import com.android.server.telecom.Call;
import com.android.server.telecom.CallsManager;
+import com.android.server.telecom.callsequencing.CallTransaction;
+import com.android.server.telecom.callsequencing.CallTransactionResult;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
-public class HoldCallTransaction extends VoipCallTransaction {
+public class HoldCallTransaction extends CallTransaction {
private static final String TAG = HoldCallTransaction.class.getSimpleName();
private final CallsManager mCallsManager;
@@ -38,17 +40,17 @@
}
@Override
- public CompletionStage<VoipCallTransactionResult> processTransaction(Void v) {
+ public CompletionStage<CallTransactionResult> processTransaction(Void v) {
Log.d(TAG, "processTransaction");
- CompletableFuture<VoipCallTransactionResult> future = new CompletableFuture<>();
+ CompletableFuture<CallTransactionResult> future = new CompletableFuture<>();
if (mCallsManager.canHold(mCall)) {
mCallsManager.markCallAsOnHold(mCall);
- future.complete(new VoipCallTransactionResult(
- VoipCallTransactionResult.RESULT_SUCCEED, null));
+ future.complete(new CallTransactionResult(
+ CallTransactionResult.RESULT_SUCCEED, null));
} else {
Log.d(TAG, "processTransaction: onError");
- future.complete(new VoipCallTransactionResult(
+ future.complete(new CallTransactionResult(
CallException.CODE_CANNOT_HOLD_CURRENT_ACTIVE_CALL, "cannot hold call"));
}
return future;
diff --git a/src/com/android/server/telecom/voip/IncomingCallTransaction.java b/src/com/android/server/telecom/callsequencing/voip/IncomingCallTransaction.java
similarity index 85%
rename from src/com/android/server/telecom/voip/IncomingCallTransaction.java
rename to src/com/android/server/telecom/callsequencing/voip/IncomingCallTransaction.java
index ed0c7d6..31ce303 100644
--- a/src/com/android/server/telecom/voip/IncomingCallTransaction.java
+++ b/src/com/android/server/telecom/callsequencing/voip/IncomingCallTransaction.java
@@ -14,12 +14,13 @@
* limitations under the License.
*/
-package com.android.server.telecom.voip;
+package com.android.server.telecom.callsequencing.voip;
import static android.telecom.CallAttributes.CALL_CAPABILITIES_KEY;
import static android.telecom.CallAttributes.DISPLAY_NAME_KEY;
-import static com.android.server.telecom.voip.VideoStateTranslation.TransactionalVideoStateToVideoProfileState;
+import static com.android.server.telecom.callsequencing.voip.VideoStateTranslation
+ .TransactionalVideoStateToVideoProfileState;
import android.os.Bundle;
import android.telecom.CallAttributes;
@@ -30,12 +31,14 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.telecom.Call;
import com.android.server.telecom.CallsManager;
+import com.android.server.telecom.callsequencing.CallTransaction;
+import com.android.server.telecom.callsequencing.CallTransactionResult;
import com.android.server.telecom.flags.FeatureFlags;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
-public class IncomingCallTransaction extends VoipCallTransaction {
+public class IncomingCallTransaction extends CallTransaction {
private static final String TAG = IncomingCallTransaction.class.getSimpleName();
private final String mCallId;
@@ -64,7 +67,7 @@
}
@Override
- public CompletionStage<VoipCallTransactionResult> processTransaction(Void v) {
+ public CompletionStage<CallTransactionResult> processTransaction(Void v) {
Log.d(TAG, "processTransaction");
if (mCallsManager.isIncomingCallPermitted(mCallAttributes.getPhoneAccountHandle())) {
@@ -75,13 +78,13 @@
generateExtras(mCallAttributes), false);
return CompletableFuture.completedFuture(
- new VoipCallTransactionResult(
- VoipCallTransactionResult.RESULT_SUCCEED, call, "success"));
+ new CallTransactionResult(
+ CallTransactionResult.RESULT_SUCCEED, call, "success", true));
} else {
Log.d(TAG, "processTransaction: incoming call is not permitted at this time");
return CompletableFuture.completedFuture(
- new VoipCallTransactionResult(
+ new CallTransactionResult(
CallException.CODE_CALL_NOT_PERMITTED_AT_PRESENT_TIME,
"incoming call not permitted at the current time"));
}
diff --git a/src/com/android/server/telecom/voip/MaybeHoldCallForNewCallTransaction.java b/src/com/android/server/telecom/callsequencing/voip/MaybeHoldCallForNewCallTransaction.java
similarity index 74%
rename from src/com/android/server/telecom/voip/MaybeHoldCallForNewCallTransaction.java
rename to src/com/android/server/telecom/callsequencing/voip/MaybeHoldCallForNewCallTransaction.java
index 3bed088..32062b5 100644
--- a/src/com/android/server/telecom/voip/MaybeHoldCallForNewCallTransaction.java
+++ b/src/com/android/server/telecom/callsequencing/voip/MaybeHoldCallForNewCallTransaction.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.telecom.voip;
+package com.android.server.telecom.callsequencing.voip;
import android.os.OutcomeReceiver;
import android.telecom.CallException;
@@ -22,15 +22,17 @@
import com.android.server.telecom.Call;
import com.android.server.telecom.CallsManager;
+import com.android.server.telecom.callsequencing.CallTransaction;
+import com.android.server.telecom.callsequencing.CallTransactionResult;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
/**
- * This VoipCallTransaction is responsible for holding any active call in favor of a new call
+ * This VOIP CallTransaction is responsible for holding any active call in favor of a new call
* request. If the active call cannot be held or disconnected, the transaction will fail.
*/
-public class MaybeHoldCallForNewCallTransaction extends VoipCallTransaction {
+public class MaybeHoldCallForNewCallTransaction extends CallTransaction {
private static final String TAG = MaybeHoldCallForNewCallTransaction.class.getSimpleName();
private final CallsManager mCallsManager;
@@ -46,23 +48,23 @@
}
@Override
- public CompletionStage<VoipCallTransactionResult> processTransaction(Void v) {
+ public CompletionStage<CallTransactionResult> processTransaction(Void v) {
Log.d(TAG, "processTransaction");
- CompletableFuture<VoipCallTransactionResult> future = new CompletableFuture<>();
+ CompletableFuture<CallTransactionResult> future = new CompletableFuture<>();
mCallsManager.transactionHoldPotentialActiveCallForNewCall(mCall, mIsCallControlRequest,
new OutcomeReceiver<>() {
@Override
public void onResult(Boolean result) {
Log.d(TAG, "processTransaction: onResult");
- future.complete(new VoipCallTransactionResult(
- VoipCallTransactionResult.RESULT_SUCCEED, null));
+ future.complete(new CallTransactionResult(
+ CallTransactionResult.RESULT_SUCCEED, null));
}
@Override
public void onError(CallException exception) {
Log.d(TAG, "processTransaction: onError");
- future.complete(new VoipCallTransactionResult(
+ future.complete(new CallTransactionResult(
exception.getCode(), exception.getMessage()));
}
});
diff --git a/src/com/android/server/telecom/voip/OutgoingCallTransaction.java b/src/com/android/server/telecom/callsequencing/voip/OutgoingCallTransaction.java
similarity index 63%
rename from src/com/android/server/telecom/voip/OutgoingCallTransaction.java
rename to src/com/android/server/telecom/callsequencing/voip/OutgoingCallTransaction.java
index 68ffecf..b221579 100644
--- a/src/com/android/server/telecom/voip/OutgoingCallTransaction.java
+++ b/src/com/android/server/telecom/callsequencing/voip/OutgoingCallTransaction.java
@@ -14,14 +14,15 @@
* limitations under the License.
*/
-package com.android.server.telecom.voip;
+package com.android.server.telecom.callsequencing.voip;
import static android.Manifest.permission.CALL_PRIVILEGED;
import static android.telecom.CallAttributes.CALL_CAPABILITIES_KEY;
import static android.telecom.CallAttributes.DISPLAY_NAME_KEY;
import static android.telecom.CallException.CODE_CALL_NOT_PERMITTED_AT_PRESENT_TIME;
-import static com.android.server.telecom.voip.VideoStateTranslation.TransactionalVideoStateToVideoProfileState;
+import static com.android.server.telecom.callsequencing.voip.VideoStateTranslation
+ .TransactionalVideoStateToVideoProfileState;
import android.content.Context;
import android.content.Intent;
@@ -35,12 +36,14 @@
import com.android.server.telecom.Call;
import com.android.server.telecom.CallsManager;
import com.android.server.telecom.LoggedHandlerExecutor;
+import com.android.server.telecom.callsequencing.CallTransaction;
+import com.android.server.telecom.callsequencing.CallTransactionResult;
import com.android.server.telecom.flags.FeatureFlags;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
-public class OutgoingCallTransaction extends VoipCallTransaction {
+public class OutgoingCallTransaction extends CallTransaction {
private static final String TAG = OutgoingCallTransaction.class.getSimpleName();
private final String mCallId;
@@ -73,7 +76,7 @@
}
@Override
- public CompletionStage<VoipCallTransactionResult> processTransaction(Void v) {
+ public CompletionStage<CallTransactionResult> processTransaction(Void v) {
Log.d(TAG, "processTransaction");
final boolean hasCallPrivilegedPermission = mContext.checkCallingPermission(
@@ -88,49 +91,25 @@
CompletableFuture<Call> callFuture =
mCallsManager.startOutgoingCall(mCallAttributes.getAddress(),
mCallAttributes.getPhoneAccountHandle(),
- generateExtras(mCallAttributes),
+ generateExtras(mCallId, mExtras, mCallAttributes, mFeatureFlags),
mCallAttributes.getPhoneAccountHandle().getUserHandle(),
intent,
mCallingPackage);
if (callFuture == null) {
return CompletableFuture.completedFuture(
- new VoipCallTransactionResult(
+ new CallTransactionResult(
CODE_CALL_NOT_PERMITTED_AT_PRESENT_TIME,
"incoming call not permitted at the current time"));
}
- CompletionStage<VoipCallTransactionResult> result = callFuture.thenComposeAsync(
- (call) -> {
- Log.d(TAG, "processTransaction: completing future");
-
- if (call == null) {
- Log.d(TAG, "processTransaction: call is null");
- return CompletableFuture.completedFuture(
- new VoipCallTransactionResult(
- CODE_CALL_NOT_PERMITTED_AT_PRESENT_TIME,
- "call could not be created at this time"));
- } else {
- Log.d(TAG, "processTransaction: call done. id=" + call.getId());
- }
-
- if (mFeatureFlags.disconnectSelfManagedStuckStartupCalls()) {
- // set to dialing so the CallAnomalyWatchdog gives the VoIP calls 1
- // minute to timeout rather than 5 seconds.
- mCallsManager.markCallAsDialing(call);
- }
-
- return CompletableFuture.completedFuture(
- new VoipCallTransactionResult(
- VoipCallTransactionResult.RESULT_SUCCEED,
- call, null));
- }
+ return callFuture.thenComposeAsync(
+ (call) -> processOutgoingCallTransactionHelper(call, TAG,
+ mCallsManager, mFeatureFlags)
, new LoggedHandlerExecutor(mHandler, "OCT.pT", null));
-
- return result;
} else {
return CompletableFuture.completedFuture(
- new VoipCallTransactionResult(
+ new CallTransactionResult(
CODE_CALL_NOT_PERMITTED_AT_PRESENT_TIME,
"incoming call not permitted at the current time"));
@@ -138,20 +117,47 @@
}
@VisibleForTesting
- public Bundle generateExtras(CallAttributes callAttributes) {
- mExtras.setDefusable(true);
- mExtras.putString(TelecomManager.TRANSACTION_CALL_ID_KEY, mCallId);
- mExtras.putInt(CALL_CAPABILITIES_KEY, callAttributes.getCallCapabilities());
- if (mFeatureFlags.transactionalVideoState()) {
+ public static Bundle generateExtras(String callId, Bundle extras,
+ CallAttributes callAttributes, FeatureFlags featureFlags) {
+ extras.setDefusable(true);
+ extras.putString(TelecomManager.TRANSACTION_CALL_ID_KEY, callId);
+ extras.putInt(CALL_CAPABILITIES_KEY, callAttributes.getCallCapabilities());
+ if (featureFlags.transactionalVideoState()) {
// Transactional calls need to remap the CallAttributes video state to the existing
// VideoProfile for consistency.
- mExtras.putInt(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE,
+ extras.putInt(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE,
TransactionalVideoStateToVideoProfileState(callAttributes.getCallType()));
} else {
- mExtras.putInt(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE,
+ extras.putInt(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE,
callAttributes.getCallType());
}
- mExtras.putCharSequence(DISPLAY_NAME_KEY, callAttributes.getDisplayName());
- return mExtras;
+ extras.putCharSequence(DISPLAY_NAME_KEY, callAttributes.getDisplayName());
+ return extras;
+ }
+
+ public static CompletableFuture<CallTransactionResult> processOutgoingCallTransactionHelper(
+ Call call, String tag, CallsManager callsManager, FeatureFlags featureFlags) {
+ Log.d(tag, "processTransaction: completing future");
+
+ if (call == null) {
+ Log.d(tag, "processTransaction: call is null");
+ return CompletableFuture.completedFuture(
+ new CallTransactionResult(
+ CODE_CALL_NOT_PERMITTED_AT_PRESENT_TIME,
+ "call could not be created at this time"));
+ } else {
+ Log.d(tag, "processTransaction: call done. id=" + call.getId());
+ }
+
+ if (featureFlags.disconnectSelfManagedStuckStartupCalls()) {
+ // set to dialing so the CallAnomalyWatchdog gives the VoIP calls 1
+ // minute to timeout rather than 5 seconds.
+ callsManager.markCallAsDialing(call);
+ }
+
+ return CompletableFuture.completedFuture(
+ new CallTransactionResult(
+ CallTransactionResult.RESULT_SUCCEED,
+ call, null, true));
}
}
diff --git a/src/com/android/server/telecom/callsequencing/voip/OutgoingCallTransactionSequencing.java b/src/com/android/server/telecom/callsequencing/voip/OutgoingCallTransactionSequencing.java
new file mode 100644
index 0000000..c38b55d
--- /dev/null
+++ b/src/com/android/server/telecom/callsequencing/voip/OutgoingCallTransactionSequencing.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2024 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.callsequencing.voip;
+
+import static android.telecom.CallException.CODE_CALL_NOT_PERMITTED_AT_PRESENT_TIME;
+
+import android.util.Log;
+
+import com.android.server.telecom.Call;
+import com.android.server.telecom.CallsManager;
+import com.android.server.telecom.LoggedHandlerExecutor;
+import com.android.server.telecom.callsequencing.CallTransaction;
+import com.android.server.telecom.callsequencing.CallTransactionResult;
+import com.android.server.telecom.flags.FeatureFlags;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
+
+public class OutgoingCallTransactionSequencing extends CallTransaction {
+
+ private static final String TAG = OutgoingCallTransactionSequencing.class.getSimpleName();
+ private final CompletableFuture<Call> mCallFuture;
+ private final CallsManager mCallsManager;
+ private FeatureFlags mFeatureFlags;
+
+ public OutgoingCallTransactionSequencing(CallsManager callsManager,
+ CompletableFuture<Call> callFuture, FeatureFlags featureFlags) {
+ super(callsManager.getLock());
+ mCallsManager = callsManager;
+ mCallFuture = callFuture;
+ mFeatureFlags = featureFlags;
+ }
+
+ @Override
+ public CompletionStage<CallTransactionResult> processTransaction(Void v) {
+ Log.d(TAG, "processTransaction");
+ if (mCallFuture == null) {
+ return CompletableFuture.completedFuture(
+ new CallTransactionResult(
+ CODE_CALL_NOT_PERMITTED_AT_PRESENT_TIME,
+ "outgoing call not permitted at the current time"));
+ }
+
+ return mCallFuture.thenComposeAsync(
+ (call) -> OutgoingCallTransaction.processOutgoingCallTransactionHelper(call, TAG,
+ mCallsManager, mFeatureFlags)
+ , new LoggedHandlerExecutor(mHandler, "OCT.pT", null));
+ }
+}
diff --git a/src/com/android/server/telecom/voip/ParallelTransaction.java b/src/com/android/server/telecom/callsequencing/voip/ParallelTransaction.java
similarity index 79%
rename from src/com/android/server/telecom/voip/ParallelTransaction.java
rename to src/com/android/server/telecom/callsequencing/voip/ParallelTransaction.java
index e235ead..77e93f9 100644
--- a/src/com/android/server/telecom/voip/ParallelTransaction.java
+++ b/src/com/android/server/telecom/callsequencing/voip/ParallelTransaction.java
@@ -14,22 +14,25 @@
* limitations under the License.
*/
-package com.android.server.telecom.voip;
+package com.android.server.telecom.callsequencing.voip;
import android.telecom.CallException;
import com.android.server.telecom.LoggedHandlerExecutor;
import com.android.server.telecom.TelecomSystem;
+import com.android.server.telecom.callsequencing.CallTransaction;
+import com.android.server.telecom.callsequencing.CallTransactionResult;
+import com.android.server.telecom.callsequencing.TransactionManager;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicInteger;
/**
- * A VoipCallTransaction implementation that its sub transactions will be executed in parallel
+ * A CallTransaction implementation that its sub transactions will be executed in parallel
*/
-public class ParallelTransaction extends VoipCallTransaction {
- public ParallelTransaction(List<VoipCallTransaction> subTransactions,
+public class ParallelTransaction extends CallTransaction {
+ public ParallelTransaction(List<CallTransaction> subTransactions,
TelecomSystem.SyncRoot lock) {
super(subTransactions, lock);
}
@@ -45,9 +48,9 @@
private final AtomicInteger mCount = new AtomicInteger(mSubTransactions.size());
@Override
- public void onTransactionCompleted(VoipCallTransactionResult result,
+ public void onTransactionCompleted(CallTransactionResult result,
String transactionName) {
- if (result.getResult() != VoipCallTransactionResult.RESULT_SUCCEED) {
+ if (result.getResult() != CallTransactionResult.RESULT_SUCCEED) {
CompletableFuture.completedFuture(null).thenApplyAsync(
(x) -> {
finish(result);
@@ -68,8 +71,8 @@
public void onTransactionTimeout(String transactionName) {
CompletableFuture.completedFuture(null).thenApplyAsync(
(x) -> {
- VoipCallTransactionResult mainResult =
- new VoipCallTransactionResult(
+ CallTransactionResult mainResult =
+ new CallTransactionResult(
CallException.CODE_OPERATION_TIMED_OUT,
String.format("sub transaction %s timed out",
transactionName));
@@ -82,7 +85,7 @@
+ ".oTT", mLock));
}
};
- for (VoipCallTransaction transaction : mSubTransactions) {
+ for (CallTransaction transaction : mSubTransactions) {
transaction.setCompleteListener(subTransactionListener);
transaction.start();
}
diff --git a/src/com/android/server/telecom/voip/RequestNewActiveCallTransaction.java b/src/com/android/server/telecom/callsequencing/voip/RequestNewActiveCallTransaction.java
similarity index 83%
rename from src/com/android/server/telecom/voip/RequestNewActiveCallTransaction.java
rename to src/com/android/server/telecom/callsequencing/voip/RequestNewActiveCallTransaction.java
index e3aed8e..8e6e354 100644
--- a/src/com/android/server/telecom/voip/RequestNewActiveCallTransaction.java
+++ b/src/com/android/server/telecom/callsequencing/voip/RequestNewActiveCallTransaction.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.telecom.voip;
+package com.android.server.telecom.callsequencing.voip;
import android.os.OutcomeReceiver;
import android.telecom.CallException;
@@ -24,6 +24,8 @@
import com.android.server.telecom.CallState;
import com.android.server.telecom.CallsManager;
import com.android.server.telecom.ConnectionServiceFocusManager;
+import com.android.server.telecom.callsequencing.CallTransaction;
+import com.android.server.telecom.callsequencing.CallTransactionResult;
import com.android.server.telecom.flags.Flags;
import java.util.concurrent.CompletableFuture;
@@ -42,7 +44,7 @@
* - MaybeHoldCallForNewCallTransaction was performed before this so any potential active calls
* should be held now.
*/
-public class RequestNewActiveCallTransaction extends VoipCallTransaction {
+public class RequestNewActiveCallTransaction extends CallTransaction {
private static final String TAG = RequestNewActiveCallTransaction.class.getSimpleName();
private final CallsManager mCallsManager;
@@ -55,14 +57,14 @@
}
@Override
- public CompletionStage<VoipCallTransactionResult> processTransaction(Void v) {
+ public CompletionStage<CallTransactionResult> processTransaction(Void v) {
Log.d(TAG, "processTransaction");
- CompletableFuture<VoipCallTransactionResult> future = new CompletableFuture<>();
+ CompletableFuture<CallTransactionResult> future = new CompletableFuture<>();
int currentCallState = mCall.getState();
// certain calls cannot go active/answered (ex. disconnect calls, etc.)
if (!canBecomeNewCallFocus(currentCallState)) {
- future.complete(new VoipCallTransactionResult(
+ future.complete(new CallTransactionResult(
CallException.CODE_CALL_CANNOT_BE_SET_TO_ACTIVE,
"CallState cannot be set to active or answered due to current call"
+ " state being in invalid state"));
@@ -71,7 +73,7 @@
if (!Flags.transactionalHoldDisconnectsUnholdable() &&
mCallsManager.getActiveCall() != null) {
- future.complete(new VoipCallTransactionResult(
+ future.complete(new CallTransactionResult(
CallException.CODE_CALL_CANNOT_BE_SET_TO_ACTIVE,
"Already an active call. Request hold on current active call."));
return future;
@@ -81,14 +83,14 @@
@Override
public void onResult(Boolean result) {
Log.d(TAG, "processTransaction: onResult");
- future.complete(new VoipCallTransactionResult(
- VoipCallTransactionResult.RESULT_SUCCEED, null));
+ future.complete(new CallTransactionResult(
+ CallTransactionResult.RESULT_SUCCEED, null));
}
@Override
public void onError(CallException exception) {
Log.d(TAG, "processTransaction: onError");
- future.complete(new VoipCallTransactionResult(
+ future.complete(new CallTransactionResult(
exception.getCode(), exception.getMessage()));
}
});
diff --git a/src/com/android/server/telecom/voip/RequestVideoStateTransaction.java b/src/com/android/server/telecom/callsequencing/voip/RequestVideoStateTransaction.java
similarity index 73%
rename from src/com/android/server/telecom/voip/RequestVideoStateTransaction.java
rename to src/com/android/server/telecom/callsequencing/voip/RequestVideoStateTransaction.java
index c1bc343..6fb1836 100644
--- a/src/com/android/server/telecom/voip/RequestVideoStateTransaction.java
+++ b/src/com/android/server/telecom/callsequencing/voip/RequestVideoStateTransaction.java
@@ -14,9 +14,10 @@
* limitations under the License.
*/
-package com.android.server.telecom.voip;
+package com.android.server.telecom.callsequencing.voip;
-import static com.android.server.telecom.voip.VideoStateTranslation.TransactionalVideoStateToVideoProfileState;
+import static com.android.server.telecom.callsequencing.voip.VideoStateTranslation
+ .TransactionalVideoStateToVideoProfileState;
import android.telecom.CallException;
import android.telecom.VideoProfile;
@@ -24,11 +25,13 @@
import com.android.server.telecom.CallsManager;
import com.android.server.telecom.Call;
+import com.android.server.telecom.callsequencing.CallTransaction;
+import com.android.server.telecom.callsequencing.CallTransactionResult;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
-public class RequestVideoStateTransaction extends VoipCallTransaction {
+public class RequestVideoStateTransaction extends CallTransaction {
private static final String TAG = RequestVideoStateTransaction.class.getSimpleName();
private final Call mCall;
@@ -42,19 +45,19 @@
}
@Override
- public CompletionStage<VoipCallTransactionResult> processTransaction(Void v) {
+ public CompletionStage<CallTransactionResult> processTransaction(Void v) {
Log.d(TAG, "processTransaction");
- CompletableFuture<VoipCallTransactionResult> future = new CompletableFuture<>();
+ CompletableFuture<CallTransactionResult> future = new CompletableFuture<>();
if (isRequestingVideoTransmission(mVideoProfileState) &&
!mCall.isVideoCallingSupportedByPhoneAccount()) {
- future.complete(new VoipCallTransactionResult(
+ future.complete(new CallTransactionResult(
CallException.CODE_ERROR_UNKNOWN /*TODO:: define error code. b/335703584 */,
"Video calling is not supported by the target account"));
} else {
mCall.setVideoState(mVideoProfileState);
- future.complete(new VoipCallTransactionResult(
- VoipCallTransactionResult.RESULT_SUCCEED,
+ future.complete(new CallTransactionResult(
+ CallTransactionResult.RESULT_SUCCEED,
"The Video State was changed successfully"));
}
return future;
diff --git a/src/com/android/server/telecom/voip/SerialTransaction.java b/src/com/android/server/telecom/callsequencing/voip/SerialTransaction.java
similarity index 79%
rename from src/com/android/server/telecom/voip/SerialTransaction.java
rename to src/com/android/server/telecom/callsequencing/voip/SerialTransaction.java
index 748f285..d5d75d0 100644
--- a/src/com/android/server/telecom/voip/SerialTransaction.java
+++ b/src/com/android/server/telecom/callsequencing/voip/SerialTransaction.java
@@ -14,27 +14,30 @@
* limitations under the License.
*/
-package com.android.server.telecom.voip;
+package com.android.server.telecom.callsequencing.voip;
import android.telecom.CallException;
import com.android.server.telecom.LoggedHandlerExecutor;
import com.android.server.telecom.TelecomSystem;
+import com.android.server.telecom.callsequencing.CallTransaction;
+import com.android.server.telecom.callsequencing.CallTransactionResult;
+import com.android.server.telecom.callsequencing.TransactionManager;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicInteger;
/**
- * A VoipCallTransaction implementation that its sub transactions will be executed in serial
+ * A CallTransaction implementation that its sub transactions will be executed in serial
*/
-public class SerialTransaction extends VoipCallTransaction {
- public SerialTransaction(List<VoipCallTransaction> subTransactions,
+public class SerialTransaction extends CallTransaction {
+ public SerialTransaction(List<CallTransaction> subTransactions,
TelecomSystem.SyncRoot lock) {
super(subTransactions, lock);
}
- public void appendTransaction(VoipCallTransaction transaction){
+ public void appendTransaction(CallTransaction transaction){
mSubTransactions.add(transaction);
}
@@ -49,9 +52,9 @@
private final AtomicInteger mTransactionIndex = new AtomicInteger(0);
@Override
- public void onTransactionCompleted(VoipCallTransactionResult result,
+ public void onTransactionCompleted(CallTransactionResult result,
String transactionName) {
- if (result.getResult() != VoipCallTransactionResult.RESULT_SUCCEED) {
+ if (result.getResult() != CallTransactionResult.RESULT_SUCCEED) {
handleTransactionFailure();
CompletableFuture.completedFuture(null).thenApplyAsync(
(x) -> {
@@ -65,7 +68,7 @@
} else {
int currTransactionIndex = mTransactionIndex.incrementAndGet();
if (currTransactionIndex < mSubTransactions.size()) {
- VoipCallTransaction transaction = mSubTransactions.get(
+ CallTransaction transaction = mSubTransactions.get(
currTransactionIndex);
transaction.setCompleteListener(this);
transaction.start();
@@ -80,8 +83,8 @@
handleTransactionFailure();
CompletableFuture.completedFuture(null).thenApplyAsync(
(x) -> {
- VoipCallTransactionResult mainResult =
- new VoipCallTransactionResult(
+ CallTransactionResult mainResult =
+ new CallTransactionResult(
CallException.CODE_OPERATION_TIMED_OUT,
String.format("sub transaction %s timed out",
transactionName));
@@ -94,7 +97,7 @@
+ ".oTT", mLock));
}
};
- VoipCallTransaction transaction = mSubTransactions.get(0);
+ CallTransaction transaction = mSubTransactions.get(0);
transaction.setCompleteListener(subTransactionListener);
transaction.start();
diff --git a/src/com/android/server/telecom/voip/SetMuteStateTransaction.java b/src/com/android/server/telecom/callsequencing/voip/SetMuteStateTransaction.java
similarity index 73%
rename from src/com/android/server/telecom/voip/SetMuteStateTransaction.java
rename to src/com/android/server/telecom/callsequencing/voip/SetMuteStateTransaction.java
index d9f7329..14f8945 100644
--- a/src/com/android/server/telecom/voip/SetMuteStateTransaction.java
+++ b/src/com/android/server/telecom/callsequencing/voip/SetMuteStateTransaction.java
@@ -14,11 +14,13 @@
* limitations under the License.
*/
-package com.android.server.telecom.voip;
+package com.android.server.telecom.callsequencing.voip;
import android.util.Log;
import com.android.server.telecom.CallsManager;
+import com.android.server.telecom.callsequencing.CallTransaction;
+import com.android.server.telecom.callsequencing.CallTransactionResult;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
@@ -27,7 +29,7 @@
* This transaction should be used to change the global mute state for transactional
* calls. There is currently no way for this transaction to fail.
*/
-public class SetMuteStateTransaction extends VoipCallTransaction {
+public class SetMuteStateTransaction extends CallTransaction {
private static final String TAG = SetMuteStateTransaction.class.getSimpleName();
private final CallsManager mCallsManager;
@@ -40,14 +42,14 @@
}
@Override
- public CompletionStage<VoipCallTransactionResult> processTransaction(Void v) {
+ public CompletionStage<CallTransactionResult> processTransaction(Void v) {
Log.d(TAG, "processTransaction");
- CompletableFuture<VoipCallTransactionResult> future = new CompletableFuture<>();
+ CompletableFuture<CallTransactionResult> future = new CompletableFuture<>();
mCallsManager.mute(mIsMuted);
- future.complete(new VoipCallTransactionResult(
- VoipCallTransactionResult.RESULT_SUCCEED,
+ future.complete(new CallTransactionResult(
+ CallTransactionResult.RESULT_SUCCEED,
"The Mute State was changed successfully"));
return future;
diff --git a/src/com/android/server/telecom/voip/VideoStateTranslation.java b/src/com/android/server/telecom/callsequencing/voip/VideoStateTranslation.java
similarity index 95%
rename from src/com/android/server/telecom/voip/VideoStateTranslation.java
rename to src/com/android/server/telecom/callsequencing/voip/VideoStateTranslation.java
index 3812d15..4610f96 100644
--- a/src/com/android/server/telecom/voip/VideoStateTranslation.java
+++ b/src/com/android/server/telecom/callsequencing/voip/VideoStateTranslation.java
@@ -14,17 +14,12 @@
* limitations under the License.
*/
-package com.android.server.telecom.voip;
+package com.android.server.telecom.callsequencing.voip;
import android.telecom.CallAttributes;
import android.telecom.Log;
import android.telecom.VideoProfile;
-import com.android.server.telecom.AnomalyReporterAdapter;
-import com.android.server.telecom.AnomalyReporterAdapterImpl;
-
-import java.util.UUID;
-
/**
* This remapping class is needed because {@link VideoProfile} has more fine grain levels of video
* states as apposed to Transactional video states (defined in {@link CallAttributes.CallType}.
diff --git a/src/com/android/server/telecom/voip/VoipCallMonitor.java b/src/com/android/server/telecom/callsequencing/voip/VoipCallMonitor.java
similarity index 99%
rename from src/com/android/server/telecom/voip/VoipCallMonitor.java
rename to src/com/android/server/telecom/callsequencing/voip/VoipCallMonitor.java
index 8f6ad51..1d1a1a6 100644
--- a/src/com/android/server/telecom/voip/VoipCallMonitor.java
+++ b/src/com/android/server/telecom/callsequencing/voip/VoipCallMonitor.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.telecom.voip;
+package com.android.server.telecom.callsequencing.voip;
import static android.app.ForegroundServiceDelegationOptions.DELEGATION_SERVICE_PHONE_CALL;
import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_CAMERA;
diff --git a/src/com/android/server/telecom/components/TelecomService.java b/src/com/android/server/telecom/components/TelecomService.java
index 2d8c78e..4db3e14 100644
--- a/src/com/android/server/telecom/components/TelecomService.java
+++ b/src/com/android/server/telecom/components/TelecomService.java
@@ -81,10 +81,11 @@
Log.d(this, "onBind");
return new ITelecomLoader.Stub() {
@Override
- public ITelecomService createTelecomService(IInternalServiceRetriever retriever) {
+ public ITelecomService createTelecomService(IInternalServiceRetriever retriever,
+ String sysUiPackageName) {
InternalServiceRetrieverAdapter adapter =
new InternalServiceRetrieverAdapter(retriever);
- initializeTelecomSystem(TelecomService.this, adapter);
+ initializeTelecomSystem(TelecomService.this, adapter, sysUiPackageName);
synchronized (getTelecomSystem().getLock()) {
return getTelecomSystem().getTelecomServiceImpl().getBinder();
}
@@ -103,7 +104,7 @@
* @param context
*/
static void initializeTelecomSystem(Context context,
- InternalServiceRetrieverAdapter internalServiceRetriever) {
+ InternalServiceRetrieverAdapter internalServiceRetriever, String sysUiPackageName) {
if (TelecomSystem.getInstance() == null) {
FeatureFlags featureFlags = new FeatureFlagsImpl();
NotificationChannelManager notificationChannelManager =
@@ -204,6 +205,7 @@
(RoleManager) context.getSystemService(Context.ROLE_SERVICE)),
new ContactsAsyncHelper.Factory(),
internalServiceRetriever.getDeviceIdleController(),
+ sysUiPackageName,
new Ringer.AccessibilityManagerAdapter() {
@Override
public boolean startFlashNotificationSequence(
diff --git a/src/com/android/server/telecom/metrics/ApiStats.java b/src/com/android/server/telecom/metrics/ApiStats.java
index b37569f..4b23e47 100644
--- a/src/com/android/server/telecom/metrics/ApiStats.java
+++ b/src/com/android/server/telecom/metrics/ApiStats.java
@@ -18,10 +18,12 @@
import static com.android.server.telecom.TelecomStatsLog.TELECOM_API_STATS;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.app.StatsManager;
import android.content.Context;
import android.os.Looper;
+import android.telecom.Log;
import android.util.StatsEvent;
import androidx.annotation.VisibleForTesting;
@@ -29,6 +31,8 @@
import com.android.server.telecom.TelecomStatsLog;
import com.android.server.telecom.nano.PulledAtomsClass;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
@@ -36,9 +40,134 @@
import java.util.Objects;
public class ApiStats extends TelecomPulledAtom {
-
+ public static final int API_UNSPECIFIC = TelecomStatsLog
+ .TELECOM_API_STATS__API_NAME__API_UNSPECIFIED;
+ public static final int API_ACCEPTHANDOVER = TelecomStatsLog
+ .TELECOM_API_STATS__API_NAME__API_ACCEPT_HANDOVER;
+ public static final int API_ACCEPTRINGINGCALL = TelecomStatsLog
+ .TELECOM_API_STATS__API_NAME__API_ACCEPT_RINGING_CALL;
+ public static final int API_ACCEPTRINGINGCALLWITHVIDEOSTATE = TelecomStatsLog
+ .TELECOM_API_STATS__API_NAME__API_ACCEPT_RINGING_CALL_WITH_VIDEO_STATE;
+ public static final int API_ADDCALL = TelecomStatsLog.TELECOM_API_STATS__API_NAME__API_ADD_CALL;
+ public static final int API_ADDNEWINCOMINGCALL = TelecomStatsLog
+ .TELECOM_API_STATS__API_NAME__API_ADD_NEW_INCOMING_CALL;
+ public static final int API_ADDNEWINCOMINGCONFERENCE = TelecomStatsLog
+ .TELECOM_API_STATS__API_NAME__API_ADD_NEW_INCOMING_CONFERENCE;
+ public static final int API_ADDNEWUNKNOWNCALL = TelecomStatsLog
+ .TELECOM_API_STATS__API_NAME__API_ADD_NEW_UNKNOWN_CALL;
+ public static final int API_CANCELMISSEDCALLSNOTIFICATION = TelecomStatsLog
+ .TELECOM_API_STATS__API_NAME__API_CANCEL_MISSED_CALLS_NOTIFICATION;
+ public static final int API_CLEARACCOUNTS = TelecomStatsLog
+ .TELECOM_API_STATS__API_NAME__API_CLEAR_ACCOUNTS;
+ public static final int API_CREATELAUNCHEMERGENCYDIALERINTENT = TelecomStatsLog
+ .TELECOM_API_STATS__API_NAME__API_CREATE_LAUNCH_EMERGENCY_DIALER_INTENT;
+ public static final int API_CREATEMANAGEBLOCKEDNUMBERSINTENT = TelecomStatsLog
+ .TELECOM_API_STATS__API_NAME__API_CREATE_MANAGE_BLOCKED_NUMBERS_INTENT;
+ public static final int API_DUMP = TelecomStatsLog.TELECOM_API_STATS__API_NAME__API_DUMP;
+ public static final int API_DUMPCALLANALYTICS = TelecomStatsLog
+ .TELECOM_API_STATS__API_NAME__API_DUMP_CALL_ANALYTICS;
+ public static final int API_ENABLEPHONEACCOUNT = TelecomStatsLog
+ .TELECOM_API_STATS__API_NAME__API_ENABLE_PHONE_ACCOUNT;
+ public static final int API_ENDCALL = TelecomStatsLog.TELECOM_API_STATS__API_NAME__API_END_CALL;
+ public static final int API_GETADNURIFORPHONEACCOUNT = TelecomStatsLog
+ .TELECOM_API_STATS__API_NAME__API_GET_ADN_URI_FOR_PHONE_ACCOUNT;
+ public static final int API_GETALLPHONEACCOUNTHANDLES = TelecomStatsLog
+ .TELECOM_API_STATS__API_NAME__API_GET_ALL_PHONE_ACCOUNT_HANDLES;
+ public static final int API_GETALLPHONEACCOUNTS = TelecomStatsLog
+ .TELECOM_API_STATS__API_NAME__API_GET_ALL_PHONE_ACCOUNTS;
+ public static final int API_GETALLPHONEACCOUNTSCOUNT = TelecomStatsLog
+ .TELECOM_API_STATS__API_NAME__API_GET_ALL_PHONE_ACCOUNTS_COUNT;
+ public static final int API_GETCALLCAPABLEPHONEACCOUNTS = TelecomStatsLog
+ .TELECOM_API_STATS__API_NAME__API_GET_CALL_CAPABLE_PHONE_ACCOUNTS;
+ public static final int API_GETCALLSTATE = TelecomStatsLog
+ .TELECOM_API_STATS__API_NAME__API_GET_CALL_STATE;
+ public static final int API_GETCALLSTATEUSINGPACKAGE = TelecomStatsLog
+ .TELECOM_API_STATS__API_NAME__API_GET_CALL_STATE_USING_PACKAGE;
+ public static final int API_GETCURRENTTTYMODE = TelecomStatsLog
+ .TELECOM_API_STATS__API_NAME__API_GET_CURRENT_TTY_MODE;
+ public static final int API_GETDEFAULTDIALERPACKAGE = TelecomStatsLog
+ .TELECOM_API_STATS__API_NAME__API_GET_DEFAULT_DIALER_PACKAGE;
+ public static final int API_GETDEFAULTDIALERPACKAGEFORUSER = TelecomStatsLog
+ .TELECOM_API_STATS__API_NAME__API_GET_DEFAULT_DIALER_PACKAGE_FOR_USER;
+ public static final int API_GETDEFAULTOUTGOINGPHONEACCOUNT = TelecomStatsLog
+ .TELECOM_API_STATS__API_NAME__API_GET_DEFAULT_OUTGOING_PHONE_ACCOUNT;
+ public static final int API_GETDEFAULTPHONEAPP = TelecomStatsLog
+ .TELECOM_API_STATS__API_NAME__API_GET_DEFAULT_PHONE_APP;
+ public static final int API_GETLINE1NUMBER = TelecomStatsLog
+ .TELECOM_API_STATS__API_NAME__API_GET_LINE1_NUMBER;
+ public static final int API_GETOWNSELFMANAGEDPHONEACCOUNTS = TelecomStatsLog
+ .TELECOM_API_STATS__API_NAME__API_GET_OWN_SELF_MANAGED_PHONE_ACCOUNTS;
+ public static final int API_GETPHONEACCOUNT = TelecomStatsLog
+ .TELECOM_API_STATS__API_NAME__API_GET_PHONE_ACCOUNT;
+ public static final int API_GETPHONEACCOUNTSFORPACKAGE = TelecomStatsLog
+ .TELECOM_API_STATS__API_NAME__API_GET_PHONE_ACCOUNTS_FOR_PACKAGE;
+ public static final int API_GETPHONEACCOUNTSSUPPORTINGSCHEME = TelecomStatsLog
+ .TELECOM_API_STATS__API_NAME__API_GET_PHONE_ACCOUNTS_SUPPORTING_SCHEME;
+ public static final int API_GETREGISTEREDPHONEACCOUNTS = TelecomStatsLog
+ .TELECOM_API_STATS__API_NAME__API_GET_REGISTERED_PHONE_ACCOUNTS;
+ public static final int API_GETSELFMANAGEDPHONEACCOUNTS = TelecomStatsLog
+ .TELECOM_API_STATS__API_NAME__API_GET_SELF_MANAGED_PHONE_ACCOUNTS;
+ public static final int API_GETSIMCALLMANAGER = TelecomStatsLog
+ .TELECOM_API_STATS__API_NAME__API_GET_SIM_CALL_MANAGER;
+ public static final int API_GETSIMCALLMANAGERFORUSER = TelecomStatsLog
+ .TELECOM_API_STATS__API_NAME__API_GET_SIM_CALL_MANAGER_FOR_USER;
+ public static final int API_GETSYSTEMDIALERPACKAGE = TelecomStatsLog
+ .TELECOM_API_STATS__API_NAME__API_GET_SYSTEM_DIALER_PACKAGE;
+ public static final int API_GETUSERSELECTEDOUTGOINGPHONEACCOUNT = TelecomStatsLog
+ .TELECOM_API_STATS__API_NAME__API_GET_USER_SELECTED_OUTGOING_PHONE_ACCOUNT;
+ public static final int API_GETVOICEMAILNUMBER = TelecomStatsLog
+ .TELECOM_API_STATS__API_NAME__API_GET_VOICE_MAIL_NUMBER;
+ public static final int API_HANDLEPINMMI = TelecomStatsLog
+ .TELECOM_API_STATS__API_NAME__API_HANDLE_PIN_MMI;
+ public static final int API_HANDLEPINMMIFORPHONEACCOUNT = TelecomStatsLog
+ .TELECOM_API_STATS__API_NAME__API_HANDLE_PIN_MMI_FOR_PHONE_ACCOUNT;
+ public static final int API_HASMANAGEONGOINGCALLSPERMISSION = TelecomStatsLog
+ .TELECOM_API_STATS__API_NAME__API_HAS_MANAGE_ONGOING_CALLS_PERMISSION;
+ public static final int API_ISINCALL = TelecomStatsLog
+ .TELECOM_API_STATS__API_NAME__API_IS_IN_CALL;
+ public static final int API_ISINCOMINGCALLPERMITTED = TelecomStatsLog
+ .TELECOM_API_STATS__API_NAME__API_IS_IN_EMERGENCY_CALL;
+ public static final int API_ISINEMERGENCYCALL = TelecomStatsLog
+ .TELECOM_API_STATS__API_NAME__API_IS_IN_MANAGED_CALL;
+ public static final int API_ISINMANAGEDCALL = TelecomStatsLog
+ .TELECOM_API_STATS__API_NAME__API_IS_IN_SELF_MANAGED_CALL;
+ public static final int API_ISINSELFMANAGEDCALL = TelecomStatsLog
+ .TELECOM_API_STATS__API_NAME__API_IS_INCOMING_CALL_PERMITTED;
+ public static final int API_ISOUTGOINGCALLPERMITTED = TelecomStatsLog
+ .TELECOM_API_STATS__API_NAME__API_IS_OUTGOING_CALL_PERMITTED;
+ public static final int API_ISRINGING = TelecomStatsLog
+ .TELECOM_API_STATS__API_NAME__API_IS_RINGING;
+ public static final int API_ISTTYSUPPORTED = TelecomStatsLog
+ .TELECOM_API_STATS__API_NAME__API_IS_TTY_SUPPORTED;
+ public static final int API_ISVOICEMAILNUMBER = TelecomStatsLog
+ .TELECOM_API_STATS__API_NAME__API_IS_VOICE_MAIL_NUMBER;
+ public static final int API_PLACECALL = TelecomStatsLog
+ .TELECOM_API_STATS__API_NAME__API_PLACE_CALL;
+ public static final int API_REGISTERPHONEACCOUNT = TelecomStatsLog
+ .TELECOM_API_STATS__API_NAME__API_REGISTER_PHONE_ACCOUNT;
+ public static final int API_SETDEFAULTDIALER = TelecomStatsLog
+ .TELECOM_API_STATS__API_NAME__API_SET_DEFAULT_DIALER;
+ public static final int API_SETUSERSELECTEDOUTGOINGPHONEACCOUNT = TelecomStatsLog
+ .TELECOM_API_STATS__API_NAME__API_SET_USER_SELECTED_OUTGOING_PHONE_ACCOUNT;
+ public static final int API_SHOWINCALLSCREEN = TelecomStatsLog
+ .TELECOM_API_STATS__API_NAME__API_SHOW_IN_CALL_SCREEN;
+ public static final int API_SILENCERINGER = TelecomStatsLog
+ .TELECOM_API_STATS__API_NAME__API_SILENCE_RINGER;
+ public static final int API_STARTCONFERENCE = TelecomStatsLog
+ .TELECOM_API_STATS__API_NAME__API_START_CONFERENCE;
+ public static final int API_UNREGISTERPHONEACCOUNT = TelecomStatsLog
+ .TELECOM_API_STATS__API_NAME__API_UNREGISTER_PHONE_ACCOUNT;
+ public static final int RESULT_UNKNOWN = TelecomStatsLog
+ .TELECOM_API_STATS__API_RESULT__RESULT_UNKNOWN;
+ public static final int RESULT_NORMAL = TelecomStatsLog
+ .TELECOM_API_STATS__API_RESULT__RESULT_SUCCESS;
+ public static final int RESULT_PERMISSION = TelecomStatsLog
+ .TELECOM_API_STATS__API_RESULT__RESULT_PERMISSION;
+ public static final int RESULT_EXCEPTION = TelecomStatsLog
+ .TELECOM_API_STATS__API_RESULT__RESULT_EXCEPTION;
+ private static final String TAG = ApiStats.class.getSimpleName();
private static final String FILE_NAME = "api_stats";
- private Map<ApiStatsKey, Integer> mApiStatsMap;
+ private Map<ApiEvent, Integer> mApiStatsMap;
public ApiStats(@NonNull Context context, @NonNull Looper looper) {
super(context, looper);
@@ -62,6 +191,8 @@
Arrays.stream(mPulledAtoms.telecomApiStats).forEach(v -> data.add(
TelecomStatsLog.buildStatsEvent(getTag(),
v.getApiName(), v.getUid(), v.getApiResult(), v.getCount())));
+ mApiStatsMap.clear();
+ onAggregate();
return StatsManager.PULL_SUCCESS;
} else {
return StatsManager.PULL_SKIP;
@@ -73,7 +204,7 @@
if (mPulledAtoms.telecomApiStats != null) {
mApiStatsMap = new HashMap<>();
for (PulledAtomsClass.TelecomApiStats v : mPulledAtoms.telecomApiStats) {
- mApiStatsMap.put(new ApiStatsKey(v.getApiName(), v.getUid(), v.getApiResult()),
+ mApiStatsMap.put(new ApiEvent(v.getApiName(), v.getUid(), v.getApiResult()),
v.getCount());
}
mLastPulledTimestamps = mPulledAtoms.getTelecomApiStatsPullTimestampMillis();
@@ -83,6 +214,7 @@
@VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
@Override
public synchronized void onAggregate() {
+ Log.d(TAG, "onAggregate: %s", mApiStatsMap);
clearAtoms();
if (mApiStatsMap.isEmpty()) {
return;
@@ -93,7 +225,7 @@
int[] index = new int[1];
mApiStatsMap.forEach((k, v) -> {
mPulledAtoms.telecomApiStats[index[0]] = new PulledAtomsClass.TelecomApiStats();
- mPulledAtoms.telecomApiStats[index[0]].setApiName(k.mApiId);
+ mPulledAtoms.telecomApiStats[index[0]].setApiName(k.mId);
mPulledAtoms.telecomApiStats[index[0]].setUid(k.mCallerUid);
mPulledAtoms.telecomApiStats[index[0]].setApiResult(k.mResult);
mPulledAtoms.telecomApiStats[index[0]].setCount(v);
@@ -102,46 +234,131 @@
save(DELAY_FOR_PERSISTENT_MILLIS);
}
- public void log(int apiId, int callerUid, int result) {
+ public void log(@NonNull ApiEvent event) {
post(() -> {
- ApiStatsKey key = new ApiStatsKey(apiId, callerUid, result);
- mApiStatsMap.put(key, mApiStatsMap.getOrDefault(key, 0) + 1);
+ mApiStatsMap.put(event, mApiStatsMap.getOrDefault(event, 0) + 1);
onAggregate();
});
}
- static class ApiStatsKey {
+ @IntDef(prefix = "API", value = {
+ API_UNSPECIFIC,
+ API_ACCEPTHANDOVER,
+ API_ACCEPTRINGINGCALL,
+ API_ACCEPTRINGINGCALLWITHVIDEOSTATE,
+ API_ADDCALL,
+ API_ADDNEWINCOMINGCALL,
+ API_ADDNEWINCOMINGCONFERENCE,
+ API_ADDNEWUNKNOWNCALL,
+ API_CANCELMISSEDCALLSNOTIFICATION,
+ API_CLEARACCOUNTS,
+ API_CREATELAUNCHEMERGENCYDIALERINTENT,
+ API_CREATEMANAGEBLOCKEDNUMBERSINTENT,
+ API_DUMP,
+ API_DUMPCALLANALYTICS,
+ API_ENABLEPHONEACCOUNT,
+ API_ENDCALL,
+ API_GETADNURIFORPHONEACCOUNT,
+ API_GETALLPHONEACCOUNTHANDLES,
+ API_GETALLPHONEACCOUNTS,
+ API_GETALLPHONEACCOUNTSCOUNT,
+ API_GETCALLCAPABLEPHONEACCOUNTS,
+ API_GETCALLSTATE,
+ API_GETCALLSTATEUSINGPACKAGE,
+ API_GETCURRENTTTYMODE,
+ API_GETDEFAULTDIALERPACKAGE,
+ API_GETDEFAULTDIALERPACKAGEFORUSER,
+ API_GETDEFAULTOUTGOINGPHONEACCOUNT,
+ API_GETDEFAULTPHONEAPP,
+ API_GETLINE1NUMBER,
+ API_GETOWNSELFMANAGEDPHONEACCOUNTS,
+ API_GETPHONEACCOUNT,
+ API_GETPHONEACCOUNTSFORPACKAGE,
+ API_GETPHONEACCOUNTSSUPPORTINGSCHEME,
+ API_GETREGISTEREDPHONEACCOUNTS,
+ API_GETSELFMANAGEDPHONEACCOUNTS,
+ API_GETSIMCALLMANAGER,
+ API_GETSIMCALLMANAGERFORUSER,
+ API_GETSYSTEMDIALERPACKAGE,
+ API_GETUSERSELECTEDOUTGOINGPHONEACCOUNT,
+ API_GETVOICEMAILNUMBER,
+ API_HANDLEPINMMI,
+ API_HANDLEPINMMIFORPHONEACCOUNT,
+ API_HASMANAGEONGOINGCALLSPERMISSION,
+ API_ISINCALL,
+ API_ISINCOMINGCALLPERMITTED,
+ API_ISINEMERGENCYCALL,
+ API_ISINMANAGEDCALL,
+ API_ISINSELFMANAGEDCALL,
+ API_ISOUTGOINGCALLPERMITTED,
+ API_ISRINGING,
+ API_ISTTYSUPPORTED,
+ API_ISVOICEMAILNUMBER,
+ API_PLACECALL,
+ API_REGISTERPHONEACCOUNT,
+ API_SETDEFAULTDIALER,
+ API_SETUSERSELECTEDOUTGOINGPHONEACCOUNT,
+ API_SHOWINCALLSCREEN,
+ API_SILENCERINGER,
+ API_STARTCONFERENCE,
+ API_UNREGISTERPHONEACCOUNT,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ApiId {
+ }
- int mApiId;
+ @IntDef(prefix = "RESULT", value = {
+ RESULT_UNKNOWN,
+ RESULT_NORMAL,
+ RESULT_PERMISSION,
+ RESULT_EXCEPTION,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ResultId {
+ }
+
+ public static class ApiEvent {
+
+ @ApiId
+ int mId;
int mCallerUid;
+ @ResultId
int mResult;
- ApiStatsKey(int apiId, int callerUid, int result) {
- mApiId = apiId;
+ public ApiEvent(@ApiId int id, int callerUid, @ResultId int result) {
+ mId = id;
mCallerUid = callerUid;
mResult = result;
}
+ public void setCallerUid(int uid) {
+ this.mCallerUid = uid;
+ }
+
+ public void setResult(@ResultId int result) {
+ this.mResult = result;
+ }
+
@Override
public boolean equals(Object other) {
if (this == other) {
return true;
}
- if (other == null || !(other instanceof ApiStatsKey obj)) {
+ if (!(other instanceof ApiEvent obj)) {
return false;
}
- return this.mApiId == obj.mApiId && this.mCallerUid == obj.mCallerUid
+ return this.mId == obj.mId && this.mCallerUid == obj.mCallerUid
&& this.mResult == obj.mResult;
}
@Override
public int hashCode() {
- return Objects.hash(mApiId, mCallerUid, mResult);
+ return Objects.hash(mId, mCallerUid, mResult);
}
@Override
public String toString() {
- return "[ApiStatsKey: mApiId=" + mApiId + ", mCallerUid=" + mCallerUid
+ return "[ApiEvent: mApiId=" + mId + ", mCallerUid=" + mCallerUid
+ ", mResult=" + mResult + "]";
}
}
diff --git a/src/com/android/server/telecom/metrics/AudioRouteStats.java b/src/com/android/server/telecom/metrics/AudioRouteStats.java
index 21624f1..4611b22 100644
--- a/src/com/android/server/telecom/metrics/AudioRouteStats.java
+++ b/src/com/android/server/telecom/metrics/AudioRouteStats.java
@@ -99,6 +99,8 @@
TelecomStatsLog.buildStatsEvent(getTag(),
v.getCallAudioRouteSource(), v.getCallAudioRouteDest(),
v.getSuccess(), v.getRevert(), v.getCount(), v.getAverageLatencyMs())));
+ mAudioRouteStatsMap.clear();
+ onAggregate();
return StatsManager.PULL_SUCCESS;
} else {
return StatsManager.PULL_SKIP;
diff --git a/src/com/android/server/telecom/metrics/CallStats.java b/src/com/android/server/telecom/metrics/CallStats.java
index 39b0e6d..8bdeffb 100644
--- a/src/com/android/server/telecom/metrics/CallStats.java
+++ b/src/com/android/server/telecom/metrics/CallStats.java
@@ -81,6 +81,8 @@
v.getCallDirection(), v.getExternalCall(), v.getEmergencyCall(),
v.getMultipleAudioAvailable(), v.getAccountType(), v.getUid(),
v.getCount(), v.getAverageDurationMs())));
+ mCallStatsMap.clear();
+ onAggregate();
return StatsManager.PULL_SUCCESS;
} else {
return StatsManager.PULL_SKIP;
@@ -129,7 +131,7 @@
}
public void log(int direction, boolean isExternal, boolean isEmergency,
- boolean isMultipleAudioAvailable, int accountType, int uid, int duration) {
+ boolean isMultipleAudioAvailable, int accountType, int uid, int duration) {
post(() -> {
CallStatsKey key = new CallStatsKey(direction, isExternal, isEmergency,
isMultipleAudioAvailable, accountType, uid);
@@ -158,13 +160,23 @@
: (call.isOutgoing() ? CALL_STATS__CALL_DIRECTION__DIR_OUTGOING
: CALL_STATS__CALL_DIRECTION__DIR_UNKNOWN);
final int accountType = getAccountType(call.getPhoneAccountFromHandle());
- final int uid = call.getAssociatedUser().getIdentifier();
+ int uid = call.getCallingPackageIdentity().mCallingPackageUid;
+ try {
+ uid = mContext.getPackageManager().getApplicationInfo(
+ call.getTargetPhoneAccount().getComponentName().getPackageName(), 0).uid;
+ } catch (Exception e) {
+ Log.i(TAG, "failed to get the uid for " + e);
+ }
+
log(direction, call.isExternalCall(), call.isEmergencyCall(), hasMultipleAudioDevices,
accountType, uid, duration);
});
}
private int getAccountType(PhoneAccount account) {
+ if (account == null) {
+ return CALL_STATS__ACCOUNT_TYPE__ACCOUNT_UNKNOWN;
+ }
if (account.hasCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED)) {
return account.hasCapabilities(
PhoneAccount.CAPABILITY_SUPPORTS_TRANSACTIONAL_OPERATIONS)
@@ -202,7 +214,7 @@
final int mUid;
CallStatsKey(int direction, boolean isExternal, boolean isEmergency,
- boolean isMultipleAudioAvailable, int accountType, int uid) {
+ boolean isMultipleAudioAvailable, int accountType, int uid) {
mDirection = direction;
mIsExternal = isExternal;
mIsEmergency = isEmergency;
diff --git a/src/com/android/server/telecom/metrics/ErrorStats.java b/src/com/android/server/telecom/metrics/ErrorStats.java
index e4d0a51..f334710 100644
--- a/src/com/android/server/telecom/metrics/ErrorStats.java
+++ b/src/com/android/server/telecom/metrics/ErrorStats.java
@@ -18,10 +18,12 @@
import static com.android.server.telecom.TelecomStatsLog.TELECOM_ERROR_STATS;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.app.StatsManager;
import android.content.Context;
import android.os.Looper;
+import android.telecom.Log;
import android.util.StatsEvent;
import androidx.annotation.VisibleForTesting;
@@ -29,6 +31,8 @@
import com.android.server.telecom.TelecomStatsLog;
import com.android.server.telecom.nano.PulledAtomsClass;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
@@ -36,9 +40,83 @@
import java.util.Objects;
public class ErrorStats extends TelecomPulledAtom {
-
+ public static final int SUB_UNKNOWN = TelecomStatsLog
+ .TELECOM_ERROR_STATS__SUBMODULE__SUB_UNKNOWN;
+ public static final int SUB_CALL_AUDIO = TelecomStatsLog
+ .TELECOM_ERROR_STATS__SUBMODULE__SUB_CALL_AUDIO;
+ public static final int SUB_CALL_LOGS = TelecomStatsLog
+ .TELECOM_ERROR_STATS__SUBMODULE__SUB_CALL_LOGS;
+ public static final int SUB_CALL_MANAGER = TelecomStatsLog
+ .TELECOM_ERROR_STATS__SUBMODULE__SUB_CALL_MANAGER;
+ public static final int SUB_CONNECTION_SERVICE = TelecomStatsLog
+ .TELECOM_ERROR_STATS__SUBMODULE__SUB_CONNECTION_SERVICE;
+ public static final int SUB_EMERGENCY_CALL = TelecomStatsLog
+ .TELECOM_ERROR_STATS__SUBMODULE__SUB_EMERGENCY_CALL;
+ public static final int SUB_IN_CALL_SERVICE = TelecomStatsLog
+ .TELECOM_ERROR_STATS__SUBMODULE__SUB_IN_CALL_SERVICE;
+ public static final int SUB_MISC = TelecomStatsLog.TELECOM_ERROR_STATS__SUBMODULE__SUB_MISC;
+ public static final int SUB_PHONE_ACCOUNT = TelecomStatsLog
+ .TELECOM_ERROR_STATS__SUBMODULE__SUB_PHONE_ACCOUNT;
+ public static final int SUB_SYSTEM_SERVICE = TelecomStatsLog
+ .TELECOM_ERROR_STATS__SUBMODULE__SUB_SYSTEM_SERVICE;
+ public static final int SUB_TELEPHONY = TelecomStatsLog
+ .TELECOM_ERROR_STATS__SUBMODULE__SUB_TELEPHONY;
+ public static final int SUB_UI = TelecomStatsLog.TELECOM_ERROR_STATS__SUBMODULE__SUB_UI;
+ public static final int SUB_VOIP_CALL = TelecomStatsLog
+ .TELECOM_ERROR_STATS__SUBMODULE__SUB_VOIP_CALL;
+ public static final int ERROR_UNKNOWN = TelecomStatsLog
+ .TELECOM_ERROR_STATS__ERROR__ERROR_UNKNOWN;
+ public static final int ERROR_EXTERNAL_EXCEPTION = TelecomStatsLog
+ .TELECOM_ERROR_STATS__ERROR__ERROR_EXTERNAL_EXCEPTION;
+ public static final int ERROR_INTERNAL_EXCEPTION = TelecomStatsLog
+ .TELECOM_ERROR_STATS__ERROR__ERROR_INTERNAL_EXCEPTION;
+ public static final int ERROR_AUDIO_ROUTE_RETRY_REJECTED = TelecomStatsLog
+ .TELECOM_ERROR_STATS__ERROR__ERROR_AUDIO_ROUTE_RETRY_REJECTED;
+ public static final int ERROR_BT_GET_SERVICE_FAILURE = TelecomStatsLog
+ .TELECOM_ERROR_STATS__ERROR__ERROR_BT_GET_SERVICE_FAILURE;
+ public static final int ERROR_BT_REGISTER_CALLBACK_FAILURE = TelecomStatsLog
+ .TELECOM_ERROR_STATS__ERROR__ERROR_BT_REGISTER_CALLBACK_FAILURE;
+ public static final int ERROR_AUDIO_ROUTE_UNAVAILABLE = TelecomStatsLog
+ .TELECOM_ERROR_STATS__ERROR__ERROR_AUDIO_ROUTE_UNAVAILABLE;
+ public static final int ERROR_EMERGENCY_NUMBER_DETERMINED_FAILURE = TelecomStatsLog
+ .TELECOM_ERROR_STATS__ERROR__ERROR_EMERGENCY_NUMBER_DETERMINED_FAILURE;
+ public static final int ERROR_NOTIFY_CALL_STREAM_START_FAILURE = TelecomStatsLog
+ .TELECOM_ERROR_STATS__ERROR__ERROR_NOTIFY_CALL_STREAM_START_FAILURE;
+ public static final int ERROR_NOTIFY_CALL_STREAM_STATE_CHANGED_FAILURE = TelecomStatsLog
+ .TELECOM_ERROR_STATS__ERROR__ERROR_NOTIFY_CALL_STREAM_STATE_CHANGED_FAILURE;
+ public static final int ERROR_NOTIFY_CALL_STREAM_STOP_FAILURE = TelecomStatsLog
+ .TELECOM_ERROR_STATS__ERROR__ERROR_NOTIFY_CALL_STREAM_STOP_FAILURE;
+ public static final int ERROR_RTT_STREAM_CLOSE_FAILURE = TelecomStatsLog
+ .TELECOM_ERROR_STATS__ERROR__ERROR_RTT_STREAM_CLOSE_FAILURE;
+ public static final int ERROR_RTT_STREAM_CREATE_FAILURE = TelecomStatsLog
+ .TELECOM_ERROR_STATS__ERROR__ERROR_RTT_STREAM_CREATE_FAILURE;
+ public static final int ERROR_SET_MUTED_FAILURE = TelecomStatsLog
+ .TELECOM_ERROR_STATS__ERROR__ERROR_SET_MUTED_FAILURE;
+ public static final int ERROR_VIDEO_PROVIDER_SET_FAILURE = TelecomStatsLog
+ .TELECOM_ERROR_STATS__ERROR__ERROR_VIDEO_PROVIDER_SET_FAILURE;
+ public static final int ERROR_WIRED_HEADSET_NOT_AVAILABLE = TelecomStatsLog
+ .TELECOM_ERROR_STATS__ERROR__ERROR_WIRED_HEADSET_NOT_AVAILABLE;
+ public static final int ERROR_LOG_CALL_FAILURE = TelecomStatsLog
+ .TELECOM_ERROR_STATS__ERROR__ERROR_LOG_CALL_FAILURE;
+ public static final int ERROR_RETRIEVING_ACCOUNT_EMERGENCY = TelecomStatsLog
+ .TELECOM_ERROR_STATS__ERROR__ERROR_RETRIEVING_ACCOUNT_EMERGENCY;
+ public static final int ERROR_RETRIEVING_ACCOUNT = TelecomStatsLog
+ .TELECOM_ERROR_STATS__ERROR__ERROR_RETRIEVING_ACCOUNT;
+ public static final int ERROR_EMERGENCY_CALL_ABORTED_NO_ACCOUNT = TelecomStatsLog
+ .TELECOM_ERROR_STATS__ERROR__ERROR_EMERGENCY_CALL_ABORTED_NO_ACCOUNT;
+ public static final int ERROR_DEFAULT_MO_ACCOUNT_MISMATCH = TelecomStatsLog
+ .TELECOM_ERROR_STATS__ERROR__ERROR_DEFAULT_MO_ACCOUNT_MISMATCH;
+ public static final int ERROR_ESTABLISHING_CONNECTION = TelecomStatsLog
+ .TELECOM_ERROR_STATS__ERROR__ERROR_ESTABLISHING_CONNECTION;
+ public static final int ERROR_REMOVING_CALL = TelecomStatsLog
+ .TELECOM_ERROR_STATS__ERROR__ERROR_REMOVING_CALL;
+ public static final int ERROR_STUCK_CONNECTING_EMERGENCY = TelecomStatsLog
+ .TELECOM_ERROR_STATS__ERROR__ERROR_STUCK_CONNECTING_EMERGENCY;
+ public static final int ERROR_STUCK_CONNECTING = TelecomStatsLog
+ .TELECOM_ERROR_STATS__ERROR__ERROR_STUCK_CONNECTING;
+ private static final String TAG = ErrorStats.class.getSimpleName();
private static final String FILE_NAME = "error_stats";
- private Map<ErrorStatsKey, Integer> mErrorStatsMap;
+ private Map<ErrorEvent, Integer> mErrorStatsMap;
public ErrorStats(@NonNull Context context, @NonNull Looper looper) {
super(context, looper);
@@ -61,7 +139,9 @@
if (mPulledAtoms.telecomErrorStats.length != 0) {
Arrays.stream(mPulledAtoms.telecomErrorStats).forEach(v -> data.add(
TelecomStatsLog.buildStatsEvent(getTag(),
- v.getSubmoduleName(), v.getErrorName(), v.getCount())));
+ v.getSubmodule(), v.getError(), v.getCount())));
+ mErrorStatsMap.clear();
+ onAggregate();
return StatsManager.PULL_SUCCESS;
} else {
return StatsManager.PULL_SKIP;
@@ -73,7 +153,7 @@
if (mPulledAtoms.telecomErrorStats != null) {
mErrorStatsMap = new HashMap<>();
for (PulledAtomsClass.TelecomErrorStats v : mPulledAtoms.telecomErrorStats) {
- mErrorStatsMap.put(new ErrorStatsKey(v.getSubmoduleName(), v.getErrorName()),
+ mErrorStatsMap.put(new ErrorEvent(v.getSubmodule(), v.getError()),
v.getCount());
}
mLastPulledTimestamps = mPulledAtoms.getTelecomErrorStatsPullTimestampMillis();
@@ -83,6 +163,7 @@
@VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
@Override
public synchronized void onAggregate() {
+ Log.d(TAG, "onAggregate: %s", mErrorStatsMap);
clearAtoms();
if (mErrorStatsMap.isEmpty()) {
return;
@@ -93,28 +174,78 @@
int[] index = new int[1];
mErrorStatsMap.forEach((k, v) -> {
mPulledAtoms.telecomErrorStats[index[0]] = new PulledAtomsClass.TelecomErrorStats();
- mPulledAtoms.telecomErrorStats[index[0]].setSubmoduleName(k.mModuleId);
- mPulledAtoms.telecomErrorStats[index[0]].setErrorName(k.mErrorId);
+ mPulledAtoms.telecomErrorStats[index[0]].setSubmodule(k.mModuleId);
+ mPulledAtoms.telecomErrorStats[index[0]].setError(k.mErrorId);
mPulledAtoms.telecomErrorStats[index[0]].setCount(v);
index[0]++;
});
save(DELAY_FOR_PERSISTENT_MILLIS);
}
- public void log(int moduleId, int errorId) {
+ public void log(@SubModuleId int moduleId, @ErrorId int errorId) {
post(() -> {
- ErrorStatsKey key = new ErrorStatsKey(moduleId, errorId);
+ ErrorEvent key = new ErrorEvent(moduleId, errorId);
mErrorStatsMap.put(key, mErrorStatsMap.getOrDefault(key, 0) + 1);
onAggregate();
});
}
- static class ErrorStatsKey {
+ @IntDef(prefix = "SUB", value = {
+ SUB_UNKNOWN,
+ SUB_CALL_AUDIO,
+ SUB_CALL_LOGS,
+ SUB_CALL_MANAGER,
+ SUB_CONNECTION_SERVICE,
+ SUB_EMERGENCY_CALL,
+ SUB_IN_CALL_SERVICE,
+ SUB_MISC,
+ SUB_PHONE_ACCOUNT,
+ SUB_SYSTEM_SERVICE,
+ SUB_TELEPHONY,
+ SUB_UI,
+ SUB_VOIP_CALL,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface SubModuleId {
+ }
- final int mModuleId;
- final int mErrorId;
+ @IntDef(prefix = "ERROR", value = {
+ ERROR_UNKNOWN,
+ ERROR_EXTERNAL_EXCEPTION,
+ ERROR_INTERNAL_EXCEPTION,
+ ERROR_AUDIO_ROUTE_RETRY_REJECTED,
+ ERROR_BT_GET_SERVICE_FAILURE,
+ ERROR_BT_REGISTER_CALLBACK_FAILURE,
+ ERROR_AUDIO_ROUTE_UNAVAILABLE,
+ ERROR_EMERGENCY_NUMBER_DETERMINED_FAILURE,
+ ERROR_NOTIFY_CALL_STREAM_START_FAILURE,
+ ERROR_NOTIFY_CALL_STREAM_STATE_CHANGED_FAILURE,
+ ERROR_NOTIFY_CALL_STREAM_STOP_FAILURE,
+ ERROR_RTT_STREAM_CLOSE_FAILURE,
+ ERROR_RTT_STREAM_CREATE_FAILURE,
+ ERROR_SET_MUTED_FAILURE,
+ ERROR_VIDEO_PROVIDER_SET_FAILURE,
+ ERROR_WIRED_HEADSET_NOT_AVAILABLE,
+ ERROR_LOG_CALL_FAILURE,
+ ERROR_RETRIEVING_ACCOUNT_EMERGENCY,
+ ERROR_RETRIEVING_ACCOUNT,
+ ERROR_EMERGENCY_CALL_ABORTED_NO_ACCOUNT,
+ ERROR_DEFAULT_MO_ACCOUNT_MISMATCH,
+ ERROR_ESTABLISHING_CONNECTION,
+ ERROR_REMOVING_CALL,
+ ERROR_STUCK_CONNECTING_EMERGENCY,
+ ERROR_STUCK_CONNECTING,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ErrorId {
+ }
- ErrorStatsKey(int moduleId, int errorId) {
+ static class ErrorEvent {
+
+ final @SubModuleId int mModuleId;
+ final @ErrorId int mErrorId;
+
+ ErrorEvent(@SubModuleId int moduleId, @ErrorId int errorId) {
mModuleId = moduleId;
mErrorId = errorId;
}
@@ -124,7 +255,7 @@
if (this == other) {
return true;
}
- if (!(other instanceof ErrorStatsKey obj)) {
+ if (!(other instanceof ErrorEvent obj)) {
return false;
}
return this.mModuleId == obj.mModuleId && this.mErrorId == obj.mErrorId;
@@ -137,7 +268,7 @@
@Override
public String toString() {
- return "[ErrorStatsKey: mModuleId=" + mModuleId + ", mErrorId=" + mErrorId + "]";
+ return "[ErrorEvent: mModuleId=" + mModuleId + ", mErrorId=" + mErrorId + "]";
}
}
}
diff --git a/src/com/android/server/telecom/metrics/TelecomMetricsController.java b/src/com/android/server/telecom/metrics/TelecomMetricsController.java
index 8903b02..c642303 100644
--- a/src/com/android/server/telecom/metrics/TelecomMetricsController.java
+++ b/src/com/android/server/telecom/metrics/TelecomMetricsController.java
@@ -24,12 +24,15 @@
import android.annotation.NonNull;
import android.app.StatsManager;
import android.content.Context;
+import android.os.Binder;
import android.os.HandlerThread;
import android.telecom.Log;
import android.util.StatsEvent;
import androidx.annotation.VisibleForTesting;
+import com.android.modules.utils.HandlerExecutor;
+
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -51,7 +54,7 @@
@NonNull
public static TelecomMetricsController make(@NonNull Context context) {
- Log.i(TAG, "TMC.iN1");
+ Log.i(TAG, "TMC.m1");
HandlerThread handlerThread = new HandlerThread(TAG);
handlerThread.start();
return make(context, handlerThread);
@@ -61,7 +64,7 @@
@NonNull
public static TelecomMetricsController make(@NonNull Context context,
@NonNull HandlerThread handlerThread) {
- Log.i(TAG, "TMC.iN2");
+ Log.i(TAG, "TMC.m2");
Objects.requireNonNull(context);
Objects.requireNonNull(handlerThread);
return new TelecomMetricsController(context, handlerThread);
@@ -71,8 +74,13 @@
public ApiStats getApiStats() {
ApiStats stats = (ApiStats) mStats.get(TELECOM_API_STATS);
if (stats == null) {
- stats = new ApiStats(mContext, mHandlerThread.getLooper());
- registerAtom(stats.getTag(), stats);
+ long token = Binder.clearCallingIdentity();
+ try {
+ stats = new ApiStats(mContext, mHandlerThread.getLooper());
+ registerAtom(stats.getTag(), stats);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}
return stats;
}
@@ -122,10 +130,23 @@
@VisibleForTesting
public void registerAtom(int tag, TelecomPulledAtom atom) {
- mStats.put(tag, atom);
+ final StatsManager statsManager = mContext.getSystemService(StatsManager.class);
+ if (statsManager != null) {
+ statsManager.setPullAtomCallback(tag, null, new HandlerExecutor(atom), this);
+ mStats.put(tag, atom);
+ } else {
+ Log.w(TAG, "Unable to register the pulled atom as StatsManager is null");
+ }
}
public void destroy() {
+ final StatsManager statsManager = mContext.getSystemService(StatsManager.class);
+ if (statsManager != null) {
+ mStats.forEach((tag, stat) -> statsManager.clearPullAtomCallback(tag));
+ } else {
+ Log.w(TAG, "Unable to clear pulled atoms as StatsManager is null");
+ }
+
mStats.clear();
mHandlerThread.quitSafely();
}
diff --git a/src/com/android/server/telecom/metrics/TelecomPulledAtom.java b/src/com/android/server/telecom/metrics/TelecomPulledAtom.java
index d6eb039..161eaa8 100644
--- a/src/com/android/server/telecom/metrics/TelecomPulledAtom.java
+++ b/src/com/android/server/telecom/metrics/TelecomPulledAtom.java
@@ -44,7 +44,7 @@
private static final String TAG = TelecomPulledAtom.class.getSimpleName();
private static final long MIN_PULL_INTERVAL_MILLIS = 23L * 60 * 60 * 1000;
private static final int EVENT_SAVE = 1;
- private final Context mContext;
+ protected final Context mContext;
@VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
public PulledAtoms mPulledAtoms;
protected long mLastPulledTimestamps;
diff --git a/src/com/android/server/telecom/ui/CallStreamingNotification.java b/src/com/android/server/telecom/ui/CallStreamingNotification.java
index 8414047..06da5e3 100644
--- a/src/com/android/server/telecom/ui/CallStreamingNotification.java
+++ b/src/com/android/server/telecom/ui/CallStreamingNotification.java
@@ -192,7 +192,7 @@
// Use the caller name for the label if available, default to app name if none.
if (TextUtils.isEmpty(callerName)) {
// App did not provide a caller name, so default to app's name.
- callerName = mAppLabelProxy.getAppLabel(appPackageName).toString();
+ callerName = mAppLabelProxy.getAppLabel(appPackageName, userHandle).toString();
}
// Action to hangup; this can use the default hangup action from the call style
diff --git a/tests/src/com/android/server/telecom/tests/BasicCallTests.java b/tests/src/com/android/server/telecom/tests/BasicCallTests.java
index 4bca30d..7646c2d 100644
--- a/tests/src/com/android/server/telecom/tests/BasicCallTests.java
+++ b/tests/src/com/android/server/telecom/tests/BasicCallTests.java
@@ -1036,6 +1036,7 @@
call.setTargetPhoneAccount(mPhoneAccountA1.getAccountHandle());
assert(call.isVideoCallingSupportedByPhoneAccount());
assertEquals(VideoProfile.STATE_BIDIRECTIONAL, call.getVideoState());
+ call.setIsCreateConnectionComplete(true);
}
/**
@@ -1059,6 +1060,7 @@
call.setTargetPhoneAccount(mPhoneAccountA2.getAccountHandle());
assert(!call.isVideoCallingSupportedByPhoneAccount());
assertEquals(VideoProfile.STATE_AUDIO_ONLY, call.getVideoState());
+ call.setIsCreateConnectionComplete(true);
}
/**
diff --git a/tests/src/com/android/server/telecom/tests/BlockCheckerFilterTest.java b/tests/src/com/android/server/telecom/tests/BlockCheckerFilterTest.java
index e76989c..a706f4b 100644
--- a/tests/src/com/android/server/telecom/tests/BlockCheckerFilterTest.java
+++ b/tests/src/com/android/server/telecom/tests/BlockCheckerFilterTest.java
@@ -88,7 +88,7 @@
super.setUp();
when(mCall.getHandle()).thenReturn(TEST_HANDLE);
mFilter = new BlockCheckerFilter(mContext, mCall, mCallerInfoLookupHelper,
- mBlockCheckerAdapter);
+ mBlockCheckerAdapter, mFeatureFlags);
}
@SmallTest
diff --git a/tests/src/com/android/server/telecom/tests/BluetoothDeviceManagerTest.java b/tests/src/com/android/server/telecom/tests/BluetoothDeviceManagerTest.java
index ac4a94e..9f97bbe 100644
--- a/tests/src/com/android/server/telecom/tests/BluetoothDeviceManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/BluetoothDeviceManagerTest.java
@@ -18,6 +18,9 @@
import static android.media.AudioDeviceInfo.TYPE_BUILTIN_SPEAKER;
+import static com.android.server.telecom.CallAudioRouteAdapter.SWITCH_BASELINE_ROUTE;
+import static com.android.server.telecom.CallAudioRouteController.INCLUDE_BLUETOOTH_IN_BASELINE;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
@@ -44,11 +47,14 @@
import android.media.AudioManager;
import android.os.Bundle;
import android.os.Parcel;
+import android.telecom.CallAudioState;
import androidx.test.filters.SmallTest;
+import com.android.server.telecom.AudioRoute;
import com.android.server.telecom.CallAudioCommunicationDeviceTracker;
import com.android.server.telecom.CallAudioRouteAdapter;
+import com.android.server.telecom.CallAudioRouteController;
import com.android.server.telecom.bluetooth.BluetoothDeviceManager;
import com.android.server.telecom.bluetooth.BluetoothRouteManager;
import com.android.server.telecom.bluetooth.BluetoothStateReceiver;
@@ -64,7 +70,9 @@
import static org.mockito.Mockito.reset;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import java.util.concurrent.Executor;
@RunWith(JUnit4.class)
@@ -79,6 +87,8 @@
@Mock AudioManager mockAudioManager;
@Mock AudioDeviceInfo mSpeakerInfo;
@Mock Executor mExecutor;
+ @Mock CallAudioRouteController mCallAudioRouteController;
+ @Mock CallAudioState mCallAudioState;
BluetoothDeviceManager mBluetoothDeviceManager;
BluetoothProfile.ServiceListener serviceListenerUnderTest;
@@ -115,6 +125,7 @@
mBluetoothDeviceManager = new BluetoothDeviceManager(mContext, mAdapter,
mCommunicationDeviceTracker, mFeatureFlags);
mBluetoothDeviceManager.setBluetoothRouteManager(mRouteManager);
+ mBluetoothDeviceManager.setCallAudioRouteAdapter(mCallAudioRouteController);
mCommunicationDeviceTracker.setBluetoothRouteManager(mRouteManager);
mockAudioManager = mContext.getSystemService(AudioManager.class);
@@ -299,6 +310,38 @@
@SmallTest
@Test
+ public void testHandleAudioRefactoringServiceDisconnectedWhileBluetooth() {
+ when(mFeatureFlags.skipBaselineSwitchWhenRouteNotBluetooth()).thenReturn(true);
+ Map<AudioRoute, BluetoothDevice> btRoutes = new HashMap<>();
+ when(mCallAudioRouteController.getBluetoothRoutes()).thenReturn(btRoutes);
+ when(mCallAudioRouteController.getCurrentCallAudioState()).thenReturn(mCallAudioState);
+ when(mCallAudioState.getRoute()).thenReturn(CallAudioState.ROUTE_BLUETOOTH);
+
+ mBluetoothDeviceManager
+ .handleAudioRefactoringServiceDisconnected(BluetoothProfile.LE_AUDIO);
+
+ verify(mCallAudioRouteController).sendMessageWithSessionInfo(SWITCH_BASELINE_ROUTE,
+ INCLUDE_BLUETOOTH_IN_BASELINE, (String) null);
+ }
+
+ @SmallTest
+ @Test
+ public void testHandleAudioRefactoringServiceDisconnectedWhileSpeaker() {
+ when(mFeatureFlags.skipBaselineSwitchWhenRouteNotBluetooth()).thenReturn(true);
+ Map<AudioRoute, BluetoothDevice> btRoutes = new HashMap<>();
+ when(mCallAudioRouteController.getBluetoothRoutes()).thenReturn(btRoutes);
+ when(mCallAudioRouteController.getCurrentCallAudioState()).thenReturn(mCallAudioState);
+ when(mCallAudioState.getRoute()).thenReturn(CallAudioState.ROUTE_SPEAKER);
+
+ mBluetoothDeviceManager
+ .handleAudioRefactoringServiceDisconnected(BluetoothProfile.LE_AUDIO);
+
+ verify(mCallAudioRouteController, never()).sendMessageWithSessionInfo(SWITCH_BASELINE_ROUTE,
+ INCLUDE_BLUETOOTH_IN_BASELINE, (String) null);
+ }
+
+ @SmallTest
+ @Test
public void testHeadsetServiceDisconnect() {
receiverUnderTest.onReceive(mContext,
buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device1,
diff --git a/tests/src/com/android/server/telecom/tests/CallAudioModeStateMachineTest.java b/tests/src/com/android/server/telecom/tests/CallAudioModeStateMachineTest.java
index 4513c65..9414e16 100644
--- a/tests/src/com/android/server/telecom/tests/CallAudioModeStateMachineTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallAudioModeStateMachineTest.java
@@ -16,15 +16,10 @@
package com.android.server.telecom.tests;
-import static com.android.server.telecom.CallAudioModeStateMachine.CALL_AUDIO_FOCUS_REQUEST;
-import static com.android.server.telecom.CallAudioModeStateMachine.RING_AUDIO_FOCUS_REQUEST;
-
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.nullable;
-import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
@@ -49,7 +44,6 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
-import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
@RunWith(JUnit4.class)
@@ -329,33 +323,6 @@
verify(mCallAudioManager, times(2)).startRinging();
}
- @SmallTest
- @Test
- public void testAudioFocusRequestWithResolveHiddenDependencies() {
- CallAudioModeStateMachine sm = new CallAudioModeStateMachine(mSystemStateHelper,
- mAudioManager, mTestThread.getLooper(), mFeatureFlags, mCommunicationDeviceTracker);
- when(mFeatureFlags.telecomResolveHiddenDependencies()).thenReturn(true);
- ArgumentCaptor<AudioFocusRequest> captor = ArgumentCaptor.forClass(AudioFocusRequest.class);
- sm.setCallAudioManager(mCallAudioManager);
-
- resetMocks();
- when(mCallAudioManager.startRinging()).thenReturn(true);
- when(mCallAudioManager.isRingtonePlaying()).thenReturn(false);
-
- sm.sendMessage(CallAudioModeStateMachine.ENTER_RING_FOCUS_FOR_TESTING);
- waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
- verify(mAudioManager).requestAudioFocus(captor.capture());
- assertTrue(areAudioFocusRequestsMatch(captor.getValue(), RING_AUDIO_FOCUS_REQUEST));
-
- sm.sendMessage(CallAudioModeStateMachine.ENTER_CALL_FOCUS_FOR_TESTING);
- waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
- verify(mAudioManager, atLeast(1)).requestAudioFocus(captor.capture());
- AudioFocusRequest request = captor.getValue();
- assertTrue(areAudioFocusRequestsMatch(request, CALL_AUDIO_FOCUS_REQUEST));
-
- sm.sendMessage(CallAudioModeStateMachine.ABANDON_FOCUS_FOR_TESTING);
- }
-
private void resetMocks() {
clearInvocations(mCallAudioManager, mAudioManager);
}
diff --git a/tests/src/com/android/server/telecom/tests/CallAudioRouteControllerTest.java b/tests/src/com/android/server/telecom/tests/CallAudioRouteControllerTest.java
index ade2a22..e234d9d 100644
--- a/tests/src/com/android/server/telecom/tests/CallAudioRouteControllerTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallAudioRouteControllerTest.java
@@ -20,6 +20,7 @@
import static com.android.server.telecom.CallAudioRouteAdapter.BT_ACTIVE_DEVICE_GONE;
import static com.android.server.telecom.CallAudioRouteAdapter.BT_ACTIVE_DEVICE_PRESENT;
import static com.android.server.telecom.CallAudioRouteAdapter.BT_AUDIO_CONNECTED;
+import static com.android.server.telecom.CallAudioRouteAdapter.BT_AUDIO_DISCONNECTED;
import static com.android.server.telecom.CallAudioRouteAdapter.BT_DEVICE_ADDED;
import static com.android.server.telecom.CallAudioRouteAdapter.BT_DEVICE_REMOVED;
import static com.android.server.telecom.CallAudioRouteAdapter.CONNECT_DOCK;
@@ -36,10 +37,14 @@
import static com.android.server.telecom.CallAudioRouteAdapter.STREAMING_FORCE_ENABLED;
import static com.android.server.telecom.CallAudioRouteAdapter.SWITCH_BASELINE_ROUTE;
import static com.android.server.telecom.CallAudioRouteAdapter.SWITCH_FOCUS;
+import static com.android.server.telecom.CallAudioRouteAdapter.TOGGLE_MUTE;
+import static com.android.server.telecom.CallAudioRouteAdapter.USER_SWITCH_BASELINE_ROUTE;
import static com.android.server.telecom.CallAudioRouteAdapter.USER_SWITCH_BLUETOOTH;
import static com.android.server.telecom.CallAudioRouteAdapter.USER_SWITCH_EARPIECE;
import static com.android.server.telecom.CallAudioRouteAdapter.USER_SWITCH_HEADSET;
import static com.android.server.telecom.CallAudioRouteAdapter.USER_SWITCH_SPEAKER;
+import static com.android.server.telecom.CallAudioRouteController.INCLUDE_BLUETOOTH_IN_BASELINE;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@@ -52,6 +57,7 @@
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -70,6 +76,7 @@
import android.os.UserHandle;
import android.telecom.CallAudioState;
import android.telecom.VideoProfile;
+import android.util.Pair;
import androidx.test.filters.SmallTest;
@@ -189,6 +196,9 @@
when(mCall.getSupportedAudioRoutes()).thenReturn(CallAudioState.ROUTE_ALL);
when(mFeatureFlags.ignoreAutoRouteToWatchDevice()).thenReturn(false);
when(mFeatureFlags.useRefactoredAudioRouteSwitching()).thenReturn(true);
+ when(mFeatureFlags.resolveActiveBtRoutingAndBtTimingIssue()).thenReturn(false);
+ when(mFeatureFlags.newAudioPathSpeakerBroadcastAndUnfocusedRouting()).thenReturn(false);
+ when(mFeatureFlags.fixUserRequestBaselineRouteVideoCall()).thenReturn(false);
}
@After
@@ -637,6 +647,17 @@
anyInt(), anyString());
verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged(
any(CallAudioState.class), eq(expectedState));
+
+ // Send TOGGLE_MUTE
+ when(mAudioManager.isMicrophoneMute()).thenReturn(false);
+ mController.sendMessageWithSessionInfo(TOGGLE_MUTE);
+ expectedState = new CallAudioState(true, CallAudioState.ROUTE_EARPIECE,
+ CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_SPEAKER, null,
+ new HashSet<>());
+ verify(mAudioService, timeout(TEST_TIMEOUT)).setMicrophoneMute(eq(true), anyString(),
+ anyInt(), anyString());
+ verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged(
+ any(CallAudioState.class), eq(expectedState));
}
@SmallTest
@@ -711,6 +732,14 @@
@SmallTest
@Test
+ public void testConnectDisconnectScoDuringCallNoClear() {
+ when(mFeatureFlags.onlyClearCommunicationDeviceOnInactive()).thenReturn(true);
+ verifyConnectBluetoothDevice(AudioRoute.TYPE_BLUETOOTH_SCO);
+ verifyDisconnectBluetoothDevice(AudioRoute.TYPE_BLUETOOTH_SCO);
+ }
+
+ @SmallTest
+ @Test
public void testConnectAndDisconnectLeDeviceDuringCall() {
when(mBluetoothLeAudio.getConnectedGroupLeadDevice(anyInt()))
.thenReturn(BLUETOOTH_DEVICE_1);
@@ -720,6 +749,16 @@
@SmallTest
@Test
+ public void testConnectAndDisconnectLeDeviceDuringCallNoClear() {
+ when(mFeatureFlags.onlyClearCommunicationDeviceOnInactive()).thenReturn(true);
+ when(mBluetoothLeAudio.getConnectedGroupLeadDevice(anyInt()))
+ .thenReturn(BLUETOOTH_DEVICE_1);
+ verifyConnectBluetoothDevice(AudioRoute.TYPE_BLUETOOTH_LE);
+ verifyDisconnectBluetoothDevice(AudioRoute.TYPE_BLUETOOTH_LE);
+ }
+
+ @SmallTest
+ @Test
public void testConnectAndDisconnectHearingAidDuringCall() {
verifyConnectBluetoothDevice(AudioRoute.TYPE_BLUETOOTH_HA);
verifyDisconnectBluetoothDevice(AudioRoute.TYPE_BLUETOOTH_HA);
@@ -727,6 +766,15 @@
@SmallTest
@Test
+ public void testConnectAndDisconnectHearingAidDuringCallNoClear() {
+ when(mFeatureFlags.onlyClearCommunicationDeviceOnInactive()).thenReturn(true);
+ verifyConnectBluetoothDevice(AudioRoute.TYPE_BLUETOOTH_HA);
+ verifyDisconnectBluetoothDevice(AudioRoute.TYPE_BLUETOOTH_HA);
+ }
+
+
+ @SmallTest
+ @Test
public void testSwitchBetweenLeAndScoDevices() {
when(mBluetoothLeAudio.getConnectedGroupLeadDevice(anyInt()))
.thenReturn(BLUETOOTH_DEVICE_1);
@@ -908,6 +956,279 @@
}
+ @SmallTest
+ @Test
+ public void testMimicVoiceDialWithBt() {
+ when(mFeatureFlags.resolveActiveBtRoutingAndBtTimingIssue()).thenReturn(true);
+ mController.initialize();
+ mController.setActive(true);
+
+ mController.sendMessageWithSessionInfo(BT_DEVICE_ADDED, AudioRoute.TYPE_BLUETOOTH_SCO,
+ BLUETOOTH_DEVICE_1);
+ CallAudioState expectedState = new CallAudioState(false, CallAudioState.ROUTE_EARPIECE,
+ CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH
+ | CallAudioState.ROUTE_SPEAKER, null, BLUETOOTH_DEVICES);
+ verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged(
+ any(CallAudioState.class), eq(expectedState));
+
+ mController.sendMessageWithSessionInfo(SWITCH_FOCUS, ACTIVE_FOCUS, 0);
+ // Mimic behavior of controller processing BT_AUDIO_DISCONNECTED
+ mController.sendMessageWithSessionInfo(SWITCH_BASELINE_ROUTE,
+ INCLUDE_BLUETOOTH_IN_BASELINE, BLUETOOTH_DEVICE_1.getAddress());
+ // Process BT_AUDIO_CONNECTED from connecting to BT device in active focus request.
+ mController.setIsScoAudioConnected(true);
+ mController.sendMessageWithSessionInfo(BT_AUDIO_CONNECTED, 0, BLUETOOTH_DEVICE_1);
+ // Verify SCO not disconnected and route stays on connected BT device.
+ verify(mBluetoothDeviceManager, timeout(TEST_TIMEOUT).times(0)).disconnectSco();
+ expectedState = new CallAudioState(false, CallAudioState.ROUTE_BLUETOOTH,
+ CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH
+ | CallAudioState.ROUTE_SPEAKER, BLUETOOTH_DEVICE_1, BLUETOOTH_DEVICES);
+ verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged(
+ any(CallAudioState.class), eq(expectedState));
+ }
+
+ @SmallTest
+ @Test
+ public void testTransactionalCallBtConnectingAndSwitchCallEndpoint() {
+ when(mFeatureFlags.resolveActiveBtRoutingAndBtTimingIssue()).thenReturn(true);
+ mController.initialize();
+ mController.setActive(true);
+
+ mController.sendMessageWithSessionInfo(BT_DEVICE_ADDED, AudioRoute.TYPE_BLUETOOTH_SCO,
+ BLUETOOTH_DEVICE_1);
+ CallAudioState expectedState = new CallAudioState(false, CallAudioState.ROUTE_EARPIECE,
+ CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH
+ | CallAudioState.ROUTE_SPEAKER, null, BLUETOOTH_DEVICES);
+ verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged(
+ any(CallAudioState.class), eq(expectedState));
+
+ mController.sendMessageWithSessionInfo(BT_ACTIVE_DEVICE_PRESENT,
+ AudioRoute.TYPE_BLUETOOTH_SCO, BT_ADDRESS_1);
+ // Omit sending BT_AUDIO_CONNECTED to mimic scenario where BT is still connecting and user
+ // switches to speaker.
+ mController.sendMessageWithSessionInfo(USER_SWITCH_SPEAKER);
+ mController.sendMessageWithSessionInfo(SPEAKER_ON);
+ mController.sendMessageWithSessionInfo(BT_AUDIO_DISCONNECTED, 0,
+ BLUETOOTH_DEVICE_1);
+
+ // Verify SCO disconnected
+ verify(mBluetoothDeviceManager, timeout(TEST_TIMEOUT)).disconnectSco();
+ // Verify audio properly routes into speaker.
+ expectedState = new CallAudioState(false, CallAudioState.ROUTE_SPEAKER,
+ CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH
+ | CallAudioState.ROUTE_SPEAKER, null, BLUETOOTH_DEVICES);
+ verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged(
+ any(CallAudioState.class), eq(expectedState));
+ }
+
+ @Test
+ @SmallTest
+ public void testBluetoothRouteToActiveDevice() {
+ when(mFeatureFlags.resolveActiveBtRoutingAndBtTimingIssue()).thenReturn(true);
+ // Connect first BT device.
+ verifyConnectBluetoothDevice(AudioRoute.TYPE_BLUETOOTH_SCO);
+ // Connect another BT device.
+ String scoDeviceAddress = "00:00:00:00:00:03";
+ BluetoothDevice scoDevice =
+ BluetoothRouteManagerTest.makeBluetoothDevice(scoDeviceAddress);
+ BLUETOOTH_DEVICES.add(scoDevice);
+ mController.sendMessageWithSessionInfo(BT_DEVICE_ADDED, AudioRoute.TYPE_BLUETOOTH_SCO,
+ scoDevice);
+ mController.sendMessageWithSessionInfo(BT_ACTIVE_DEVICE_PRESENT,
+ AudioRoute.TYPE_BLUETOOTH_SCO, scoDeviceAddress);
+ mController.sendMessageWithSessionInfo(BT_AUDIO_DISCONNECTED, 0,
+ BLUETOOTH_DEVICE_1);
+ mController.sendMessageWithSessionInfo(BT_AUDIO_CONNECTED, 0,
+ scoDevice);
+ CallAudioState expectedState = new CallAudioState(false, CallAudioState.ROUTE_BLUETOOTH,
+ CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH
+ | CallAudioState.ROUTE_SPEAKER, scoDevice, BLUETOOTH_DEVICES);
+ verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged(
+ any(CallAudioState.class), eq(expectedState));
+
+ // Mimic behavior when inactive headset is used to answer the call (i.e. tap headset). In
+ // this case, the inactive BT device will become the active device (reported to us from BT
+ // stack to controller via BT_ACTIVE_DEVICE_PRESENT).
+ mController.sendMessageWithSessionInfo(BT_ACTIVE_DEVICE_PRESENT,
+ AudioRoute.TYPE_BLUETOOTH_SCO, BLUETOOTH_DEVICE_1.getAddress());
+ mController.sendMessageWithSessionInfo(BT_AUDIO_DISCONNECTED, 0,
+ scoDevice);
+ mController.sendMessageWithSessionInfo(BT_AUDIO_CONNECTED, 0,
+ BLUETOOTH_DEVICE_1);
+ // Verify audio routed to BLUETOOTH_DEVICE_1
+ expectedState = new CallAudioState(false, CallAudioState.ROUTE_BLUETOOTH,
+ CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH
+ | CallAudioState.ROUTE_SPEAKER, BLUETOOTH_DEVICE_1, BLUETOOTH_DEVICES);
+ verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged(
+ any(CallAudioState.class), eq(expectedState));
+
+ // Now switch call to active focus so that base route can be recalculated.
+ mController.sendMessageWithSessionInfo(SWITCH_FOCUS, ACTIVE_FOCUS, 0);
+ expectedState = new CallAudioState(false, CallAudioState.ROUTE_BLUETOOTH,
+ CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH
+ | CallAudioState.ROUTE_SPEAKER, BLUETOOTH_DEVICE_1, BLUETOOTH_DEVICES);
+ // Verify that audio is still routed into BLUETOOTH_DEVICE_1 and not the 2nd BT device.
+ verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged(
+ any(CallAudioState.class), eq(expectedState));
+
+ // Clean up BLUETOOTH_DEVICES for subsequent tests.
+ BLUETOOTH_DEVICES.remove(scoDevice);
+ }
+
+ @Test
+ @SmallTest
+ public void verifyRouteReinitializedAfterCallEnd() {
+ when(mFeatureFlags.resolveActiveBtRoutingAndBtTimingIssue()).thenReturn(true);
+ mController.initialize();
+ mController.setActive(true);
+
+ // Switch to speaker
+ mController.sendMessageWithSessionInfo(SPEAKER_ON);
+ CallAudioState expectedState = new CallAudioState(false, CallAudioState.ROUTE_SPEAKER,
+ CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_SPEAKER, null,
+ new HashSet<>());
+ verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged(
+ any(CallAudioState.class), eq(expectedState));
+
+ // Verify that call audio route is reinitialized to default (in this case, earpiece) when
+ // call audio focus is lost.
+ mController.sendMessageWithSessionInfo(SWITCH_FOCUS, NO_FOCUS, 0);
+ mController.sendMessageWithSessionInfo(SPEAKER_OFF);
+ expectedState = new CallAudioState(false, CallAudioState.ROUTE_EARPIECE,
+ CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_SPEAKER, null,
+ new HashSet<>());
+ verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged(
+ any(CallAudioState.class), eq(expectedState));
+ }
+
+ @Test
+ @SmallTest
+ public void testUserSwitchBaselineRouteVideoCall() {
+ when(mFeatureFlags.fixUserRequestBaselineRouteVideoCall()).thenReturn(true);
+ mController.initialize();
+ mController.setActive(true);
+ // Set capabilities for video call.
+ when(mCall.getVideoState()).thenReturn(VideoProfile.STATE_BIDIRECTIONAL);
+
+ // Turn on speaker
+ mController.sendMessageWithSessionInfo(SPEAKER_ON);
+ CallAudioState expectedState = new CallAudioState(false, CallAudioState.ROUTE_SPEAKER,
+ CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_SPEAKER, null,
+ new HashSet<>());
+ verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged(
+ any(CallAudioState.class), eq(expectedState));
+
+ // USER_SWITCH_BASELINE_ROUTE (explicit user request). Verify that audio is routed back to
+ // earpiece.
+ mController.sendMessageWithSessionInfo(USER_SWITCH_BASELINE_ROUTE,
+ CallAudioRouteController.INCLUDE_BLUETOOTH_IN_BASELINE);
+ mController.sendMessageWithSessionInfo(SPEAKER_OFF);
+ expectedState = new CallAudioState(false, CallAudioState.ROUTE_EARPIECE,
+ CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_SPEAKER, null,
+ new HashSet<>());
+ verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged(
+ any(CallAudioState.class), eq(expectedState));
+
+ // SWITCH_BASELINE_ROUTE. Verify that audio is routed to speaker for non-user requests.
+ mController.sendMessageWithSessionInfo(SWITCH_BASELINE_ROUTE,
+ CallAudioRouteController.INCLUDE_BLUETOOTH_IN_BASELINE);
+ mController.sendMessageWithSessionInfo(SPEAKER_ON);
+ expectedState = new CallAudioState(false, CallAudioState.ROUTE_SPEAKER,
+ CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_SPEAKER, null,
+ new HashSet<>());
+ verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged(
+ any(CallAudioState.class), eq(expectedState));
+ }
+
+ @Test
+ @SmallTest
+ public void testRouteToWatchWhenCallAnsweredOnWatch_MultipleBtDevices() {
+ when(mFeatureFlags.resolveActiveBtRoutingAndBtTimingIssue()).thenReturn(true);
+ // Connect first BT device.
+ verifyConnectBluetoothDevice(AudioRoute.TYPE_BLUETOOTH_SCO);
+ // Connect another BT device.
+ String scoDeviceAddress = "00:00:00:00:00:03";
+ BluetoothDevice watchDevice =
+ BluetoothRouteManagerTest.makeBluetoothDevice(scoDeviceAddress);
+ when(mBluetoothRouteManager.isWatch(eq(watchDevice))).thenReturn(true);
+ BLUETOOTH_DEVICES.add(watchDevice);
+
+ mController.sendMessageWithSessionInfo(BT_DEVICE_ADDED, AudioRoute.TYPE_BLUETOOTH_SCO,
+ watchDevice);
+ CallAudioState expectedState = new CallAudioState(false, CallAudioState.ROUTE_BLUETOOTH,
+ CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_SPEAKER
+ | CallAudioState.ROUTE_BLUETOOTH, BLUETOOTH_DEVICE_1, BLUETOOTH_DEVICES);
+ verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged(
+ any(CallAudioState.class), eq(expectedState));
+
+ // Signal that watch is now the active device. This is done in BluetoothStateReceiver and
+ // then BT_ACTIVE_DEVICE_PRESENT will be sent to the controller to be processed.
+ mController.updateActiveBluetoothDevice(
+ new Pair<>(AudioRoute.TYPE_BLUETOOTH_SCO, watchDevice.getAddress()));
+ // Emulate scenario with call answered on watch. Ensure at this point that audio was routed
+ // into watch
+ mController.sendMessageWithSessionInfo(SWITCH_FOCUS, ACTIVE_FOCUS, 0);
+ mController.sendMessageWithSessionInfo(BT_AUDIO_CONNECTED,
+ 0, watchDevice);
+ mController.sendMessageWithSessionInfo(BT_AUDIO_DISCONNECTED,
+ 0, BLUETOOTH_DEVICE_1);
+ expectedState = new CallAudioState(false, CallAudioState.ROUTE_BLUETOOTH,
+ CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_SPEAKER
+ | CallAudioState.ROUTE_BLUETOOTH, watchDevice, BLUETOOTH_DEVICES);
+ verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged(
+ any(CallAudioState.class), eq(expectedState));
+
+ // Hardcode signal from BT stack signaling to Telecom that watch is now the active device.
+ // This should just be a no-op since audio was already routed when processing active focus.
+ mController.sendMessageWithSessionInfo(BT_ACTIVE_DEVICE_PRESENT,
+ AudioRoute.TYPE_BLUETOOTH_SCO, scoDeviceAddress);
+ verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged(
+ any(CallAudioState.class), eq(expectedState));
+
+ // Mimic behavior of controller processing BT_AUDIO_DISCONNECTED for BLUETOOTH_DEVICE_1 and
+ // verify that audio remains routed to the watch and not routed to earpiece (this should
+ // be taking into account what the BT active device is as reported to us by the BT stack).
+ mController.sendMessageWithSessionInfo(SWITCH_BASELINE_ROUTE,
+ INCLUDE_BLUETOOTH_IN_BASELINE, BLUETOOTH_DEVICE_1.getAddress());
+ verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged(
+ any(CallAudioState.class), eq(expectedState));
+
+ BLUETOOTH_DEVICES.remove(watchDevice);
+ }
+
+
+ @Test
+ @SmallTest
+ public void testAbandonCallAudioFocusAfterCallEnd() {
+ // Make sure in-band ringing is disabled so that route never becomes active
+ when(mBluetoothRouteManager.isInbandRingEnabled(eq(BLUETOOTH_DEVICE_1))).thenReturn(false);
+
+ mController.initialize();
+ mController.sendMessageWithSessionInfo(BT_DEVICE_ADDED, AudioRoute.TYPE_BLUETOOTH_SCO,
+ BLUETOOTH_DEVICE_1);
+
+ CallAudioState expectedState = new CallAudioState(false, CallAudioState.ROUTE_BLUETOOTH,
+ CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH
+ | CallAudioState.ROUTE_SPEAKER, BLUETOOTH_DEVICE_1, BLUETOOTH_DEVICES);
+ mController.sendMessageWithSessionInfo(BT_ACTIVE_DEVICE_PRESENT,
+ AudioRoute.TYPE_BLUETOOTH_SCO, BT_ADDRESS_1);
+ verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged(
+ any(CallAudioState.class), eq(expectedState));
+ assertFalse(mController.isActive());
+
+ // Verify route never went active due to in-band ringing being disabled.
+ mController.sendMessageWithSessionInfo(SWITCH_FOCUS, RINGING_FOCUS, 0);
+ assertFalse(mController.isActive());
+
+ // Emulate scenario of rejecting an incoming call so that call focus is lost and verify
+ // that we abandon the call audio focus that was gained from when the call went to
+ // ringing state.
+ mController.sendMessageWithSessionInfo(SWITCH_FOCUS, NO_FOCUS, 0);
+ // Ensure we tell the CallAudioManager that audio operations are done so that we can ensure
+ // audio focus is relinquished.
+ verify(mCallAudioManager, timeout(TEST_TIMEOUT)).notifyAudioOperationsComplete();
+ }
+
private void verifyConnectBluetoothDevice(int audioType) {
mController.initialize();
mController.setActive(true);
@@ -958,7 +1279,17 @@
if (audioType == AudioRoute.TYPE_BLUETOOTH_SCO) {
verify(mBluetoothDeviceManager, timeout(TEST_TIMEOUT)).disconnectSco();
} else {
- verify(mAudioManager, timeout(TEST_TIMEOUT)).clearCommunicationDevice();
+ if (mFeatureFlags.onlyClearCommunicationDeviceOnInactive()) {
+ verify(mAudioManager, timeout(TEST_TIMEOUT).times(2))
+ .setCommunicationDevice(any(AudioDeviceInfo.class));
+ // Don't use a timeout here because that will cause the test to pause for a long
+ // period of time to verify; the previous verify has a timeout on it, so it will
+ // have already waited for any AudioManager invocations to take place. Any
+ // potential clear would have happened by now.
+ verify(mAudioManager, never()).clearCommunicationDevice();
+ } else {
+ verify(mAudioManager, timeout(TEST_TIMEOUT)).clearCommunicationDevice();
+ }
}
verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged(
any(CallAudioState.class), eq(expectedState));
diff --git a/tests/src/com/android/server/telecom/tests/CallScreeningServiceFilterTest.java b/tests/src/com/android/server/telecom/tests/CallScreeningServiceFilterTest.java
index d1427db..d97263d 100644
--- a/tests/src/com/android/server/telecom/tests/CallScreeningServiceFilterTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallScreeningServiceFilterTest.java
@@ -17,6 +17,7 @@
package com.android.server.telecom.tests;
import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
@@ -136,7 +137,7 @@
when(mContext.getSystemService(TelecomManager.class))
.thenReturn(mTelecomManager);
when(mTelecomManager.getSystemDialerPackage()).thenReturn(PKG_NAME);
- when(mAppLabelProxy.getAppLabel(PKG_NAME)).thenReturn(APP_NAME);
+ when(mAppLabelProxy.getAppLabel(PKG_NAME, PA_HANDLE.getUserHandle())).thenReturn(APP_NAME);
when(mParcelableCallUtilsConverter.toParcelableCall(
eq(mCall), anyBoolean(), eq(mPhoneAccountRegistrar))).thenReturn(null);
when(mContext.bindServiceAsUser(nullable(Intent.class), nullable(ServiceConnection.class),
diff --git a/tests/src/com/android/server/telecom/tests/CallsManagerTest.java b/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
index 265d7b2..34d8830 100644
--- a/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
@@ -59,6 +59,7 @@
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
+import android.os.IBinder;
import android.os.Looper;
import android.os.OutcomeReceiver;
import android.os.Process;
@@ -87,6 +88,7 @@
import androidx.test.filters.MediumTest;
import androidx.test.filters.SmallTest;
+import com.android.internal.telecom.IConnectionService;
import com.android.server.telecom.AnomalyReporterAdapter;
import com.android.server.telecom.AsyncRingtonePlayer;
import com.android.server.telecom.Call;
@@ -105,6 +107,7 @@
import com.android.server.telecom.ConnectionServiceFocusManager;
import com.android.server.telecom.ConnectionServiceFocusManager.ConnectionServiceFocusManagerFactory;
import com.android.server.telecom.ConnectionServiceWrapper;
+import com.android.server.telecom.CreateConnectionResponse;
import com.android.server.telecom.DefaultDialerCache;
import com.android.server.telecom.EmergencyCallDiagnosticLogger;
import com.android.server.telecom.EmergencyCallHelper;
@@ -139,7 +142,7 @@
import com.android.server.telecom.ui.CallStreamingNotification;
import com.android.server.telecom.ui.DisconnectedCallNotifier;
import com.android.server.telecom.ui.ToastFactory;
-import com.android.server.telecom.voip.TransactionManager;
+import com.android.server.telecom.callsequencing.TransactionManager;
import com.google.common.base.Objects;
@@ -316,6 +319,7 @@
@Mock private IncomingCallFilterGraph mIncomingCallFilterGraph;
@Mock private Context mMockCreateContextAsUser;
@Mock private UserManager mMockCurrentUserManager;
+ @Mock private IConnectionService mIConnectionService;
@Mock private TelecomMetricsController mMockTelecomMetricsController;
private CallsManager mCallsManager;
@@ -416,11 +420,17 @@
.thenReturn(mMockCreateContextAsUser);
when(mMockCreateContextAsUser.getSystemService(UserManager.class))
.thenReturn(mMockCurrentUserManager);
+ when(mIConnectionService.asBinder()).thenReturn(mock(IBinder.class));
+
+ mComponentContextFixture.addConnectionService(
+ SIM_1_ACCOUNT.getAccountHandle().getComponentName(), mIConnectionService);
}
@Override
@After
public void tearDown() throws Exception {
+ mComponentContextFixture.removeConnectionService(
+ SIM_1_ACCOUNT.getAccountHandle().getComponentName(), mIConnectionService);
super.tearDown();
}
@@ -3241,6 +3251,35 @@
assertTrue(result.contains("onReceiveResult"));
}
+ @Test
+ public void testConnectionServiceCreateConnectionTimeout() throws Exception {
+ ConnectionServiceWrapper service = new ConnectionServiceWrapper(
+ SIM_1_ACCOUNT.getAccountHandle().getComponentName(), null,
+ mPhoneAccountRegistrar, mCallsManager, mContext, mLock, null, mFeatureFlags);
+ TestScheduledExecutorService scheduledExecutorService = new TestScheduledExecutorService();
+ service.setScheduledExecutorService(scheduledExecutorService);
+ Call call = addSpyCall();
+ service.addCall(call);
+ when(call.isCreateConnectionComplete()).thenReturn(false);
+ CreateConnectionResponse response = mock(CreateConnectionResponse.class);
+
+ service.createConnection(call, response);
+ waitUntilConditionIsTrueOrTimeout(new Condition() {
+ @Override
+ public Object expected() {
+ return true;
+ }
+
+ @Override
+ public Object actual() {
+ return scheduledExecutorService.isRunnableScheduledAtTime(15000L);
+ }
+ }, 5000L, "Expected job failed to schedule");
+ scheduledExecutorService.advanceTime(15000L);
+ verify(response).handleCreateConnectionFailure(
+ eq(new DisconnectCause(DisconnectCause.ERROR)));
+ }
+
@SmallTest
@Test
public void testOnFailedOutgoingCallUnholdsCallAfterLocallyDisconnect() {
@@ -3764,9 +3803,9 @@
Call callSpy = Mockito.spy(ongoingCall);
// Mocks some methods to not call the real method.
- doNothing().when(callSpy).unhold();
- doNothing().when(callSpy).hold();
- doNothing().when(callSpy).answer(ArgumentMatchers.anyInt());
+ doReturn(null).when(callSpy).unhold();
+ doReturn(null).when(callSpy).hold();
+ doReturn(null).when(callSpy).answer(ArgumentMatchers.anyInt());
doNothing().when(callSpy).setStartWithSpeakerphoneOn(ArgumentMatchers.anyBoolean());
mCallsManager.addCall(callSpy);
@@ -3778,10 +3817,10 @@
Call callSpy = Mockito.spy(ongoingCall);
// Mocks some methods to not call the real method.
- doNothing().when(callSpy).unhold();
- doNothing().when(callSpy).hold();
- doNothing().when(callSpy).disconnect();
- doNothing().when(callSpy).answer(ArgumentMatchers.anyInt());
+ doReturn(null).when(callSpy).unhold();
+ doReturn(null).when(callSpy).hold();
+ doReturn(null).when(callSpy).disconnect();
+ doReturn(null).when(callSpy).answer(ArgumentMatchers.anyInt());
doNothing().when(callSpy).setStartWithSpeakerphoneOn(ArgumentMatchers.anyBoolean());
return callSpy;
diff --git a/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java b/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java
index 25f94c6..1432834 100644
--- a/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java
+++ b/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java
@@ -32,6 +32,7 @@
import android.annotation.RequiresPermission;
import android.app.AppOpsManager;
import android.app.NotificationManager;
+import android.app.StatsManager;
import android.app.StatusBarManager;
import android.app.UiModeManager;
import android.app.role.RoleManager;
@@ -258,6 +259,8 @@
return mAccessibilityManager;
case Context.BLOCKED_NUMBERS_SERVICE:
return mBlockedNumbersManager;
+ case Context.STATS_MANAGER_SERVICE:
+ return mStatsManager;
default:
return null;
}
@@ -301,6 +304,10 @@
return Context.TELECOM_SERVICE;
} else if (svcClass == BlockedNumbersManager.class) {
return Context.BLOCKED_NUMBERS_SERVICE;
+ } else if (svcClass == AppOpsManager.class) {
+ return Context.APP_OPS_SERVICE;
+ } else if (svcClass == StatsManager.class) {
+ return Context.STATS_MANAGER_SERVICE;
}
throw new UnsupportedOperationException(svcClass.getName());
}
@@ -642,6 +649,7 @@
private final PermissionInfo mPermissionInfo = mock(PermissionInfo.class);
private final SensorPrivacyManager mSensorPrivacyManager = mock(SensorPrivacyManager.class);
private final List<BroadcastReceiver> mBroadcastReceivers = new ArrayList<>();
+ private final StatsManager mStatsManager = mock(StatsManager.class);
private TelecomManager mTelecomManager = mock(TelecomManager.class);
private BlockedNumbersManager mBlockedNumbersManager = mock(BlockedNumbersManager.class);
diff --git a/tests/src/com/android/server/telecom/tests/DefaultDialerCacheTest.java b/tests/src/com/android/server/telecom/tests/DefaultDialerCacheTest.java
index 18f2eb0..3da9284 100644
--- a/tests/src/com/android/server/telecom/tests/DefaultDialerCacheTest.java
+++ b/tests/src/com/android/server/telecom/tests/DefaultDialerCacheTest.java
@@ -56,14 +56,17 @@
private static final int USER0 = 0;
private static final int USER1 = 1;
private static final int USER2 = 2;
+ private static final int DELAY_TOLERANCE = 100;
private DefaultDialerCache mDefaultDialerCache;
private ContentObserver mDefaultDialerSettingObserver;
private BroadcastReceiver mPackageChangeReceiver;
private BroadcastReceiver mUserRemovedReceiver;
- @Mock private DefaultDialerCache.DefaultDialerManagerAdapter mMockDefaultDialerManager;
- @Mock private RoleManagerAdapter mRoleManagerAdapter;
+ @Mock
+ private DefaultDialerCache.DefaultDialerManagerAdapter mMockDefaultDialerManager;
+ @Mock
+ private RoleManagerAdapter mRoleManagerAdapter;
@Override
@Before
@@ -76,18 +79,19 @@
mDefaultDialerCache = new DefaultDialerCache(
mContext, mMockDefaultDialerManager, mRoleManagerAdapter,
- new TelecomSystem.SyncRoot() { });
+ new TelecomSystem.SyncRoot() {
+ });
verify(mContext, times(2)).registerReceiverAsUser(
- packageReceiverCaptor.capture(), eq(UserHandle.ALL), any(IntentFilter.class),
+ packageReceiverCaptor.capture(), eq(UserHandle.ALL), any(IntentFilter.class),
isNull(String.class), isNull(Handler.class));
// Receive the first receiver that was captured, the package change receiver.
mPackageChangeReceiver = packageReceiverCaptor.getAllValues().get(0);
ArgumentCaptor<BroadcastReceiver> userRemovedReceiverCaptor =
- ArgumentCaptor.forClass(BroadcastReceiver.class);
+ ArgumentCaptor.forClass(BroadcastReceiver.class);
verify(mContext).registerReceiver(
- userRemovedReceiverCaptor.capture(), any(IntentFilter.class));
+ userRemovedReceiverCaptor.capture(), any(IntentFilter.class));
mUserRemovedReceiver = userRemovedReceiverCaptor.getAllValues().get(0);
mDefaultDialerSettingObserver = mDefaultDialerCache.getContentObserver();
@@ -140,7 +144,10 @@
Intent packageChangeIntent = new Intent(Intent.ACTION_PACKAGE_CHANGED,
Uri.fromParts("package", DIALER1, null));
when(mRoleManagerAdapter.getDefaultDialerApp(eq(USER0))).thenReturn(DIALER2);
+
mPackageChangeReceiver.onReceive(mContext, packageChangeIntent);
+ waitForHandlerAction(mDefaultDialerCache.mHandler, DELAY_TOLERANCE);
+
verify(mRoleManagerAdapter, times(2)).getDefaultDialerApp(eq(USER0));
verify(mRoleManagerAdapter, times(2)).getDefaultDialerApp(eq(USER1));
verify(mRoleManagerAdapter, times(2)).getDefaultDialerApp(eq(USER2));
@@ -158,6 +165,8 @@
Intent packageChangeIntent = new Intent(Intent.ACTION_PACKAGE_CHANGED,
Uri.fromParts("package", "red.orange.blue", null));
mPackageChangeReceiver.onReceive(mContext, packageChangeIntent);
+ waitForHandlerAction(mDefaultDialerCache.mHandler, DELAY_TOLERANCE);
+
verify(mRoleManagerAdapter, times(2)).getDefaultDialerApp(eq(USER0));
verify(mRoleManagerAdapter, times(2)).getDefaultDialerApp(eq(USER1));
verify(mRoleManagerAdapter, times(2)).getDefaultDialerApp(eq(USER2));
@@ -192,6 +201,8 @@
packageChangeIntent.putExtra(Intent.EXTRA_REPLACING, false);
mPackageChangeReceiver.onReceive(mContext, packageChangeIntent);
+ waitForHandlerAction(mDefaultDialerCache.mHandler, DELAY_TOLERANCE);
+
verify(mRoleManagerAdapter, times(2)).getDefaultDialerApp(eq(USER0));
verify(mRoleManagerAdapter, times(1)).getDefaultDialerApp(eq(USER1));
verify(mRoleManagerAdapter, times(1)).getDefaultDialerApp(eq(USER2));
@@ -208,6 +219,8 @@
Uri.fromParts("package", "ppp.qqq.zzz", null));
mPackageChangeReceiver.onReceive(mContext, packageChangeIntent);
+ waitForHandlerAction(mDefaultDialerCache.mHandler, DELAY_TOLERANCE);
+
verify(mRoleManagerAdapter, times(2)).getDefaultDialerApp(eq(USER0));
verify(mRoleManagerAdapter, times(2)).getDefaultDialerApp(eq(USER1));
verify(mRoleManagerAdapter, times(2)).getDefaultDialerApp(eq(USER2));
@@ -225,6 +238,8 @@
packageChangeIntent.putExtra(Intent.EXTRA_REPLACING, true);
mPackageChangeReceiver.onReceive(mContext, packageChangeIntent);
+ waitForHandlerAction(mDefaultDialerCache.mHandler, DELAY_TOLERANCE);
+
verify(mRoleManagerAdapter, times(1)).getDefaultDialerApp(eq(USER0));
verify(mRoleManagerAdapter, times(1)).getDefaultDialerApp(eq(USER1));
verify(mRoleManagerAdapter, times(1)).getDefaultDialerApp(eq(USER2));
@@ -240,7 +255,9 @@
when(mRoleManagerAdapter.getDefaultDialerApp(eq(USER0))).thenReturn(DIALER2);
when(mRoleManagerAdapter.getDefaultDialerApp(eq(USER1))).thenReturn(DIALER2);
when(mRoleManagerAdapter.getDefaultDialerApp(eq(USER2))).thenReturn(DIALER2);
+
mDefaultDialerSettingObserver.onChange(false);
+ waitForHandlerAction(mDefaultDialerCache.mHandler, DELAY_TOLERANCE);
verify(mRoleManagerAdapter, times(2)).getDefaultDialerApp(eq(USER0));
verify(mRoleManagerAdapter, times(2)).getDefaultDialerApp(eq(USER2));
diff --git a/tests/src/com/android/server/telecom/tests/PhoneAccountRegistrarTest.java b/tests/src/com/android/server/telecom/tests/PhoneAccountRegistrarTest.java
index 45b4ed1..b6c3743 100644
--- a/tests/src/com/android/server/telecom/tests/PhoneAccountRegistrarTest.java
+++ b/tests/src/com/android/server/telecom/tests/PhoneAccountRegistrarTest.java
@@ -135,7 +135,7 @@
.delete();
when(mDefaultDialerCache.getDefaultDialerApplication(anyInt()))
.thenReturn("com.android.dialer");
- when(mAppLabelProxy.getAppLabel(anyString()))
+ when(mAppLabelProxy.getAppLabel(anyString(), any()))
.thenReturn(TEST_LABEL);
mRegistrar = new PhoneAccountRegistrar(
mComponentContextFixture.getTestDouble().getApplicationContext(), mLock, FILE_NAME,
@@ -1606,7 +1606,7 @@
.setCapabilities(PhoneAccount.CAPABILITY_SUPPORTS_TRANSACTIONAL_OPERATIONS);
// WHEN
- when(mAppLabelProxy.getAppLabel(anyString())).thenReturn(invalidLabel);
+ when(mAppLabelProxy.getAppLabel(anyString(), any())).thenReturn(invalidLabel);
// THEN
try {
diff --git a/tests/src/com/android/server/telecom/tests/RingerTest.java b/tests/src/com/android/server/telecom/tests/RingerTest.java
index c4d9678..46916fd 100644
--- a/tests/src/com/android/server/telecom/tests/RingerTest.java
+++ b/tests/src/com/android/server/telecom/tests/RingerTest.java
@@ -66,6 +66,7 @@
import androidx.test.filters.SmallTest;
+import com.android.server.telecom.AnomalyReporterAdapter;
import com.android.server.telecom.AsyncRingtonePlayer;
import com.android.server.telecom.Call;
import com.android.server.telecom.CallState;
@@ -123,6 +124,7 @@
@Mock NotificationManager mockNotificationManager;
@Mock Ringer.AccessibilityManagerAdapter mockAccessibilityManagerAdapter;
@Mock private FeatureFlags mFeatureFlags;
+ @Mock private AnomalyReporterAdapter mAnomalyReporterAdapter;
@Spy Ringer.VibrationEffectProxy spyVibrationEffectProxy;
@@ -178,7 +180,7 @@
mRingerUnderTest = new Ringer(mockPlayerFactory, mContext, mockSystemSettingsUtil,
asyncRingtonePlayer, mockRingtoneFactory, mockVibrator, spyVibrationEffectProxy,
mockInCallController, mockNotificationManager, mockAccessibilityManagerAdapter,
- mFeatureFlags);
+ mFeatureFlags, mAnomalyReporterAdapter);
// This future is used to wait for AsyncRingtonePlayer to finish its part.
mRingerUnderTest.setBlockOnRingingFuture(mRingCompletionFuture);
}
diff --git a/tests/src/com/android/server/telecom/tests/SessionManagerTest.java b/tests/src/com/android/server/telecom/tests/SessionManagerTest.java
index 3e82eac..631d522 100644
--- a/tests/src/com/android/server/telecom/tests/SessionManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/SessionManagerTest.java
@@ -21,10 +21,14 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import android.telecom.Log;
import android.telecom.Logging.Session;
import android.telecom.Logging.SessionManager;
+import com.android.server.telecom.flags.Flags;
+
import androidx.test.filters.SmallTest;
import org.junit.After;
@@ -57,13 +61,11 @@
@Before
public void setUp() throws Exception {
super.setUp();
- mTestSessionManager = new SessionManager();
+ mTestSessionManager = new SessionManager(null);
mTestSessionManager.registerSessionListener(((sessionName, timeMs) -> {
mfullSessionCompleteTime = timeMs;
mFullSessionMethodName = sessionName;
}));
- // Remove automatic stale session cleanup for testing
- mTestSessionManager.mCleanStaleSessions = null;
}
@Override
@@ -411,4 +413,33 @@
assertTrue(mTestSessionManager.mSessionMapper.isEmpty());
assertNull(sessionRef.get());
}
+
+ /**
+ * If Telecom gets into a situation where there are MANY sub-sessions created in a deep tree,
+ * ensure that cleanup still happens properly.
+ */
+ @SmallTest
+ @Test
+ public void testManySubsessionCleanupStress() {
+ // This test will mostly likely fail with recursion due to stack overflow
+ if (!Flags.endSessionImprovements()) return;
+ Log.setIsExtendedLoggingEnabled(false);
+ mTestSessionManager.mCurrentThreadId = () -> TEST_PARENT_THREAD_ID;
+ mTestSessionManager.startSession(TEST_PARENT_NAME, null);
+ Session parentSession = mTestSessionManager.mSessionMapper.get(TEST_PARENT_THREAD_ID);
+ Session subsession;
+ try {
+ for (int i = 0; i < 10000; i++) {
+ subsession = mTestSessionManager.createSubsession();
+ mTestSessionManager.endSession();
+ mTestSessionManager.continueSession(subsession, TEST_CHILD_NAME + i);
+ }
+ mTestSessionManager.endSession();
+ } catch (Exception e) {
+ fail("Exception: " + e);
+ }
+ assertTrue(mTestSessionManager.mSessionMapper.isEmpty());
+ assertTrue(parentSession.isSessionCompleted());
+ assertTrue(parentSession.getChildSessions().isEmpty());
+ }
}
diff --git a/tests/src/com/android/server/telecom/tests/SessionTest.java b/tests/src/com/android/server/telecom/tests/SessionTest.java
index 5378596..4cddc89 100644
--- a/tests/src/com/android/server/telecom/tests/SessionTest.java
+++ b/tests/src/com/android/server/telecom/tests/SessionTest.java
@@ -269,6 +269,6 @@
}
private Session createTestSession(String name, String methodName) {
- return new Session(name, methodName, 0, false, null);
+ return new Session(name, methodName, 0, false, false ,null);
}
}
diff --git a/tests/src/com/android/server/telecom/tests/TelecomMetricsControllerTest.java b/tests/src/com/android/server/telecom/tests/TelecomMetricsControllerTest.java
index e2ab8d6..4d494f3 100644
--- a/tests/src/com/android/server/telecom/tests/TelecomMetricsControllerTest.java
+++ b/tests/src/com/android/server/telecom/tests/TelecomMetricsControllerTest.java
@@ -21,8 +21,11 @@
import static com.android.server.telecom.TelecomStatsLog.TELECOM_ERROR_STATS;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyObject;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import android.app.StatsManager;
@@ -119,18 +122,27 @@
}
@Test
- public void testRegisterAtomIsSameInstance() {
+ public void testRegisterAtom() {
+ StatsManager statsManager = mContext.getSystemService(StatsManager.class);
ApiStats stats = mock(ApiStats.class);
mTelecomMetricsController.registerAtom(TELECOM_API_STATS, stats);
+ verify(statsManager, times(1)).setPullAtomCallback(eq(TELECOM_API_STATS), anyObject(),
+ anyObject(), eq(mTelecomMetricsController));
assertThat(mTelecomMetricsController.getStats().get(TELECOM_API_STATS))
.isSameInstanceAs(stats);
}
@Test
public void testDestroy() {
+ StatsManager statsManager = mContext.getSystemService(StatsManager.class);
mTelecomMetricsController.destroy();
+
+ verify(statsManager, times(1)).clearPullAtomCallback(eq(CALL_AUDIO_ROUTE_STATS));
+ verify(statsManager, times(1)).clearPullAtomCallback(eq(CALL_STATS));
+ verify(statsManager, times(1)).clearPullAtomCallback(eq(TELECOM_API_STATS));
+ verify(statsManager, times(1)).clearPullAtomCallback(eq(TELECOM_ERROR_STATS));
assertThat(mTelecomMetricsController.getStats()).isEmpty();
}
diff --git a/tests/src/com/android/server/telecom/tests/TelecomPulledAtomTest.java b/tests/src/com/android/server/telecom/tests/TelecomPulledAtomTest.java
index bc8aeac..d3c7859 100644
--- a/tests/src/com/android/server/telecom/tests/TelecomPulledAtomTest.java
+++ b/tests/src/com/android/server/telecom/tests/TelecomPulledAtomTest.java
@@ -37,10 +37,13 @@
import static org.mockito.Mockito.verify;
import android.app.StatsManager;
+import android.content.ComponentName;
import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
import android.os.Looper;
-import android.os.UserHandle;
import android.telecom.PhoneAccount;
+import android.telecom.PhoneAccountHandle;
import android.util.StatsEvent;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -66,7 +69,10 @@
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
+import java.util.Random;
@RunWith(AndroidJUnit4.class)
public class TelecomPulledAtomTest extends TelecomTestCase {
@@ -201,12 +207,14 @@
createTestFileForApiStats(System.currentTimeMillis() - MIN_PULL_INTERVAL_MILLIS - 1);
ApiStats apiStats = spy(new ApiStats(mSpyContext, mLooper));
final List<StatsEvent> data = new ArrayList<>();
+ int sizePulled = apiStats.mPulledAtoms.telecomApiStats.length;
int result = apiStats.pull(data);
assertEquals(StatsManager.PULL_SUCCESS, result);
verify(apiStats).onPull(eq(data));
- assertEquals(data.size(), apiStats.mPulledAtoms.telecomApiStats.length);
+ assertEquals(data.size(), sizePulled);
+ assertEquals(apiStats.mPulledAtoms.telecomApiStats.length, 0);
}
@Test
@@ -227,12 +235,14 @@
createTestFileForAudioRouteStats(System.currentTimeMillis() - MIN_PULL_INTERVAL_MILLIS - 1);
AudioRouteStats audioRouteStats = spy(new AudioRouteStats(mSpyContext, mLooper));
final List<StatsEvent> data = new ArrayList<>();
+ int sizePulled = audioRouteStats.mPulledAtoms.callAudioRouteStats.length;
int result = audioRouteStats.pull(data);
assertEquals(StatsManager.PULL_SUCCESS, result);
verify(audioRouteStats).onPull(eq(data));
- assertEquals(data.size(), audioRouteStats.mPulledAtoms.callAudioRouteStats.length);
+ assertEquals(data.size(), sizePulled);
+ assertEquals(audioRouteStats.mPulledAtoms.callAudioRouteStats.length, 0);
}
@Test
@@ -253,12 +263,14 @@
createTestFileForCallStats(System.currentTimeMillis() - MIN_PULL_INTERVAL_MILLIS - 1);
CallStats callStats = spy(new CallStats(mSpyContext, mLooper));
final List<StatsEvent> data = new ArrayList<>();
+ int sizePulled = callStats.mPulledAtoms.callStats.length;
int result = callStats.pull(data);
assertEquals(StatsManager.PULL_SUCCESS, result);
verify(callStats).onPull(eq(data));
- assertEquals(data.size(), callStats.mPulledAtoms.callStats.length);
+ assertEquals(data.size(), sizePulled);
+ assertEquals(callStats.mPulledAtoms.callStats.length, 0);
}
@Test
@@ -279,35 +291,119 @@
createTestFileForErrorStats(System.currentTimeMillis() - MIN_PULL_INTERVAL_MILLIS - 1);
ErrorStats errorStats = spy(new ErrorStats(mSpyContext, mLooper));
final List<StatsEvent> data = new ArrayList<>();
+ int sizePulled = errorStats.mPulledAtoms.telecomErrorStats.length;
int result = errorStats.pull(data);
assertEquals(StatsManager.PULL_SUCCESS, result);
verify(errorStats).onPull(eq(data));
- assertEquals(data.size(), errorStats.mPulledAtoms.telecomErrorStats.length);
+ assertEquals(data.size(), sizePulled);
+ assertEquals(errorStats.mPulledAtoms.telecomErrorStats.length, 0);
}
@Test
- public void testApiStatsLog() throws Exception {
+ public void testApiStatsLogCount() throws Exception {
ApiStats apiStats = spy(new ApiStats(mSpyContext, mLooper));
+ ApiStats.ApiEvent event = new ApiStats.ApiEvent(VALUE_API_ID, VALUE_UID, VALUE_API_RESULT);
- apiStats.log(VALUE_API_ID, VALUE_UID, VALUE_API_RESULT);
- waitForHandlerAction(apiStats, TEST_TIMEOUT);
+ for (int i = 0; i < 10; i++) {
+ apiStats.log(event);
+ waitForHandlerAction(apiStats, TEST_TIMEOUT);
- verify(apiStats, times(1)).onAggregate();
- verify(apiStats, times(1)).save(eq(DELAY_FOR_PERSISTENT_MILLIS));
- assertEquals(apiStats.mPulledAtoms.telecomApiStats.length, 1);
- verifyMessageForApiStats(apiStats.mPulledAtoms.telecomApiStats[0], VALUE_API_ID,
- VALUE_UID, VALUE_API_RESULT, 1);
+ verify(apiStats, times(i + 1)).onAggregate();
+ verify(apiStats, times(i + 1)).save(eq(DELAY_FOR_PERSISTENT_MILLIS));
+ assertEquals(apiStats.mPulledAtoms.telecomApiStats.length, 1);
+ verifyMessageForApiStats(apiStats.mPulledAtoms.telecomApiStats[0], VALUE_API_ID,
+ VALUE_UID, VALUE_API_RESULT, i + 1);
+ }
+ }
- apiStats.log(VALUE_API_ID, VALUE_UID, VALUE_API_RESULT);
- waitForHandlerAction(apiStats, TEST_TIMEOUT);
+ @Test
+ public void testApiStatsLogEvent() throws Exception {
+ final int[] apis = {
+ ApiStats.API_UNSPECIFIC,
+ ApiStats.API_ACCEPTHANDOVER,
+ ApiStats.API_ACCEPTRINGINGCALL,
+ ApiStats.API_ACCEPTRINGINGCALLWITHVIDEOSTATE,
+ ApiStats.API_ADDCALL,
+ ApiStats.API_ADDNEWINCOMINGCALL,
+ ApiStats.API_ADDNEWINCOMINGCONFERENCE,
+ ApiStats.API_ADDNEWUNKNOWNCALL,
+ ApiStats.API_CANCELMISSEDCALLSNOTIFICATION,
+ ApiStats.API_CLEARACCOUNTS,
+ ApiStats.API_CREATELAUNCHEMERGENCYDIALERINTENT,
+ ApiStats.API_CREATEMANAGEBLOCKEDNUMBERSINTENT,
+ ApiStats.API_DUMP,
+ ApiStats.API_DUMPCALLANALYTICS,
+ ApiStats.API_ENABLEPHONEACCOUNT,
+ ApiStats.API_ENDCALL,
+ ApiStats.API_GETADNURIFORPHONEACCOUNT,
+ ApiStats.API_GETALLPHONEACCOUNTHANDLES,
+ ApiStats.API_GETALLPHONEACCOUNTS,
+ ApiStats.API_GETALLPHONEACCOUNTSCOUNT,
+ ApiStats.API_GETCALLCAPABLEPHONEACCOUNTS,
+ ApiStats.API_GETCALLSTATE,
+ ApiStats.API_GETCALLSTATEUSINGPACKAGE,
+ ApiStats.API_GETCURRENTTTYMODE,
+ ApiStats.API_GETDEFAULTDIALERPACKAGE,
+ ApiStats.API_GETDEFAULTDIALERPACKAGEFORUSER,
+ ApiStats.API_GETDEFAULTOUTGOINGPHONEACCOUNT,
+ ApiStats.API_GETDEFAULTPHONEAPP,
+ ApiStats.API_GETLINE1NUMBER,
+ ApiStats.API_GETOWNSELFMANAGEDPHONEACCOUNTS,
+ ApiStats.API_GETPHONEACCOUNT,
+ ApiStats.API_GETPHONEACCOUNTSFORPACKAGE,
+ ApiStats.API_GETPHONEACCOUNTSSUPPORTINGSCHEME,
+ ApiStats.API_GETREGISTEREDPHONEACCOUNTS,
+ ApiStats.API_GETSELFMANAGEDPHONEACCOUNTS,
+ ApiStats.API_GETSIMCALLMANAGER,
+ ApiStats.API_GETSIMCALLMANAGERFORUSER,
+ ApiStats.API_GETSYSTEMDIALERPACKAGE,
+ ApiStats.API_GETUSERSELECTEDOUTGOINGPHONEACCOUNT,
+ ApiStats.API_GETVOICEMAILNUMBER,
+ ApiStats.API_HANDLEPINMMI,
+ ApiStats.API_HANDLEPINMMIFORPHONEACCOUNT,
+ ApiStats.API_HASMANAGEONGOINGCALLSPERMISSION,
+ ApiStats.API_ISINCALL,
+ ApiStats.API_ISINCOMINGCALLPERMITTED,
+ ApiStats.API_ISINEMERGENCYCALL,
+ ApiStats.API_ISINMANAGEDCALL,
+ ApiStats.API_ISINSELFMANAGEDCALL,
+ ApiStats.API_ISOUTGOINGCALLPERMITTED,
+ ApiStats.API_ISRINGING,
+ ApiStats.API_ISTTYSUPPORTED,
+ ApiStats.API_ISVOICEMAILNUMBER,
+ ApiStats.API_PLACECALL,
+ ApiStats.API_REGISTERPHONEACCOUNT,
+ ApiStats.API_SETDEFAULTDIALER,
+ ApiStats.API_SETUSERSELECTEDOUTGOINGPHONEACCOUNT,
+ ApiStats.API_SHOWINCALLSCREEN,
+ ApiStats.API_SILENCERINGER,
+ ApiStats.API_STARTCONFERENCE,
+ ApiStats.API_UNREGISTERPHONEACCOUNT,
+ };
+ final int[] results = {ApiStats.RESULT_UNKNOWN, ApiStats.RESULT_NORMAL,
+ ApiStats.RESULT_EXCEPTION, ApiStats.RESULT_PERMISSION};
+ ApiStats apiStats = spy(new ApiStats(mSpyContext, mLooper));
+ Random rand = new Random();
+ Map<ApiStats.ApiEvent, Integer> eventMap = new HashMap<>();
- verify(apiStats, times(2)).onAggregate();
- verify(apiStats, times(2)).save(eq(DELAY_FOR_PERSISTENT_MILLIS));
- assertEquals(apiStats.mPulledAtoms.telecomApiStats.length, 1);
- verifyMessageForApiStats(apiStats.mPulledAtoms.telecomApiStats[0], VALUE_API_ID,
- VALUE_UID, VALUE_API_RESULT, 2);
+ for (int i = 0; i < 10; i++) {
+ int api = apis[rand.nextInt(apis.length)];
+ int uid = rand.nextInt(65535);
+ int result = results[rand.nextInt(results.length)];
+ ApiStats.ApiEvent event = new ApiStats.ApiEvent(api, uid, result);
+ eventMap.put(event, eventMap.getOrDefault(event, 0) + 1);
+
+ apiStats.log(event);
+ waitForHandlerAction(apiStats, TEST_TIMEOUT);
+
+ verify(apiStats, times(i + 1)).onAggregate();
+ verify(apiStats, times(i + 1)).save(eq(DELAY_FOR_PERSISTENT_MILLIS));
+ assertEquals(apiStats.mPulledAtoms.telecomApiStats.length, eventMap.size());
+ assertTrue(hasMessageForApiStats(apiStats.mPulledAtoms.telecomApiStats,
+ api, uid, result, eventMap.get(event)));
+ }
}
@Test
@@ -570,8 +666,19 @@
@Test
public void testCallStatsOnStartThenEnd() throws Exception {
int duration = 1000;
- UserHandle uh = UserHandle.of(UserHandle.USER_SYSTEM);
+ int fakeUid = 10010;
PhoneAccount account = mock(PhoneAccount.class);
+ Call.CallingPackageIdentity callingPackage = new Call.CallingPackageIdentity();
+ PackageManager pm = mock(PackageManager.class);
+ ApplicationInfo ai = new ApplicationInfo();
+ ai.uid = fakeUid;
+ doReturn(ai).when(pm).getApplicationInfo(any(), anyInt());
+ doReturn(pm).when(mSpyContext).getPackageManager();
+ Context fakeContext = spy(mContext);
+ doReturn("").when(fakeContext).getPackageName();
+ ComponentName cn = new ComponentName(fakeContext, this.getClass());
+ PhoneAccountHandle handle = mock(PhoneAccountHandle.class);
+ doReturn(cn).when(handle).getComponentName();
Call call = mock(Call.class);
doReturn(true).when(call).isIncoming();
doReturn(account).when(call).getPhoneAccountFromHandle();
@@ -579,7 +686,8 @@
doReturn(false).when(account).hasCapabilities(eq(PhoneAccount.CAPABILITY_SELF_MANAGED));
doReturn(true).when(account).hasCapabilities(eq(PhoneAccount.CAPABILITY_CALL_PROVIDER));
doReturn(true).when(account).hasCapabilities(eq(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION));
- doReturn(uh).when(call).getAssociatedUser();
+ doReturn(callingPackage).when(call).getCallingPackageIdentity();
+ doReturn(handle).when(call).getTargetPhoneAccount();
CallStats callStats = spy(new CallStats(mSpyContext, mLooper));
callStats.onCallStart(call);
@@ -590,14 +698,25 @@
verify(callStats, times(1)).log(eq(CALL_STATS__CALL_DIRECTION__DIR_INCOMING),
eq(false), eq(false), eq(false), eq(CALL_STATS__ACCOUNT_TYPE__ACCOUNT_SIM),
- eq(UserHandle.USER_SYSTEM), eq(duration));
+ eq(fakeUid), eq(duration));
}
@Test
public void testCallStatsOnMultipleAudioDevices() throws Exception {
int duration = 1000;
- UserHandle uh = UserHandle.of(UserHandle.USER_SYSTEM);
+ int fakeUid = 10010;
PhoneAccount account = mock(PhoneAccount.class);
+ Call.CallingPackageIdentity callingPackage = new Call.CallingPackageIdentity();
+ PackageManager pm = mock(PackageManager.class);
+ ApplicationInfo ai = new ApplicationInfo();
+ ai.uid = fakeUid;
+ doReturn(ai).when(pm).getApplicationInfo(any(), anyInt());
+ doReturn(pm).when(mSpyContext).getPackageManager();
+ Context fakeContext = spy(mContext);
+ doReturn("").when(fakeContext).getPackageName();
+ ComponentName cn = new ComponentName(fakeContext, this.getClass());
+ PhoneAccountHandle handle = mock(PhoneAccountHandle.class);
+ doReturn(cn).when(handle).getComponentName();
Call call = mock(Call.class);
doReturn(true).when(call).isIncoming();
doReturn(account).when(call).getPhoneAccountFromHandle();
@@ -605,7 +724,8 @@
doReturn(false).when(account).hasCapabilities(eq(PhoneAccount.CAPABILITY_SELF_MANAGED));
doReturn(true).when(account).hasCapabilities(eq(PhoneAccount.CAPABILITY_CALL_PROVIDER));
doReturn(true).when(account).hasCapabilities(eq(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION));
- doReturn(uh).when(call).getAssociatedUser();
+ doReturn(callingPackage).when(call).getCallingPackageIdentity();
+ doReturn(handle).when(call).getTargetPhoneAccount();
CallStats callStats = spy(new CallStats(mSpyContext, mLooper));
callStats.onCallStart(call);
@@ -619,30 +739,88 @@
verify(callStats, times(1)).log(eq(CALL_STATS__CALL_DIRECTION__DIR_INCOMING),
eq(false), eq(false), eq(true), eq(CALL_STATS__ACCOUNT_TYPE__ACCOUNT_SIM),
- eq(UserHandle.USER_SYSTEM), eq(duration));
+ eq(fakeUid), eq(duration));
}
@Test
- public void testErrorStatsLog() throws Exception {
+ public void testErrorStatsLogCount() throws Exception {
ErrorStats errorStats = spy(new ErrorStats(mSpyContext, mLooper));
+ for (int i = 0; i < 10; i++) {
+ errorStats.log(VALUE_MODULE_ID, VALUE_ERROR_ID);
+ waitForHandlerAction(errorStats, TEST_TIMEOUT);
- errorStats.log(VALUE_MODULE_ID, VALUE_ERROR_ID);
- waitForHandlerAction(errorStats, TEST_TIMEOUT);
+ verify(errorStats, times(i + 1)).onAggregate();
+ verify(errorStats, times(i + 1)).save(eq(DELAY_FOR_PERSISTENT_MILLIS));
+ assertEquals(errorStats.mPulledAtoms.telecomErrorStats.length, 1);
+ verifyMessageForErrorStats(errorStats.mPulledAtoms.telecomErrorStats[0],
+ VALUE_MODULE_ID,
+ VALUE_ERROR_ID, i + 1);
+ }
+ }
- verify(errorStats, times(1)).onAggregate();
- verify(errorStats, times(1)).save(eq(DELAY_FOR_PERSISTENT_MILLIS));
- assertEquals(errorStats.mPulledAtoms.telecomErrorStats.length, 1);
- verifyMessageForErrorStats(errorStats.mPulledAtoms.telecomErrorStats[0], VALUE_MODULE_ID,
- VALUE_ERROR_ID, 1);
+ @Test
+ public void testErrorStatsLogEvent() throws Exception {
+ ErrorStats errorStats = spy(new ErrorStats(mSpyContext, mLooper));
+ int[] modules = {
+ ErrorStats.SUB_UNKNOWN,
+ ErrorStats.SUB_CALL_AUDIO,
+ ErrorStats.SUB_CALL_LOGS,
+ ErrorStats.SUB_CALL_MANAGER,
+ ErrorStats.SUB_CONNECTION_SERVICE,
+ ErrorStats.SUB_EMERGENCY_CALL,
+ ErrorStats.SUB_IN_CALL_SERVICE,
+ ErrorStats.SUB_MISC,
+ ErrorStats.SUB_PHONE_ACCOUNT,
+ ErrorStats.SUB_SYSTEM_SERVICE,
+ ErrorStats.SUB_TELEPHONY,
+ ErrorStats.SUB_UI,
+ ErrorStats.SUB_VOIP_CALL,
+ };
+ int[] errors = {
+ ErrorStats.ERROR_UNKNOWN,
+ ErrorStats.ERROR_EXTERNAL_EXCEPTION,
+ ErrorStats.ERROR_INTERNAL_EXCEPTION,
+ ErrorStats.ERROR_AUDIO_ROUTE_RETRY_REJECTED,
+ ErrorStats.ERROR_BT_GET_SERVICE_FAILURE,
+ ErrorStats.ERROR_BT_REGISTER_CALLBACK_FAILURE,
+ ErrorStats.ERROR_AUDIO_ROUTE_UNAVAILABLE,
+ ErrorStats.ERROR_EMERGENCY_NUMBER_DETERMINED_FAILURE,
+ ErrorStats.ERROR_NOTIFY_CALL_STREAM_START_FAILURE,
+ ErrorStats.ERROR_NOTIFY_CALL_STREAM_STATE_CHANGED_FAILURE,
+ ErrorStats.ERROR_NOTIFY_CALL_STREAM_STOP_FAILURE,
+ ErrorStats.ERROR_RTT_STREAM_CLOSE_FAILURE,
+ ErrorStats.ERROR_RTT_STREAM_CREATE_FAILURE,
+ ErrorStats.ERROR_SET_MUTED_FAILURE,
+ ErrorStats.ERROR_VIDEO_PROVIDER_SET_FAILURE,
+ ErrorStats.ERROR_WIRED_HEADSET_NOT_AVAILABLE,
+ ErrorStats.ERROR_LOG_CALL_FAILURE,
+ ErrorStats.ERROR_RETRIEVING_ACCOUNT_EMERGENCY,
+ ErrorStats.ERROR_RETRIEVING_ACCOUNT,
+ ErrorStats.ERROR_EMERGENCY_CALL_ABORTED_NO_ACCOUNT,
+ ErrorStats.ERROR_DEFAULT_MO_ACCOUNT_MISMATCH,
+ ErrorStats.ERROR_ESTABLISHING_CONNECTION,
+ ErrorStats.ERROR_REMOVING_CALL,
+ ErrorStats.ERROR_STUCK_CONNECTING_EMERGENCY,
+ ErrorStats.ERROR_STUCK_CONNECTING,
+ };
+ Random rand = new Random();
+ Map<Long, Integer> eventMap = new HashMap<>();
- errorStats.log(VALUE_MODULE_ID, VALUE_ERROR_ID);
- waitForHandlerAction(errorStats, TEST_TIMEOUT);
+ for (int i = 0; i < 10; i++) {
+ int module = modules[rand.nextInt(modules.length)];
+ int error = errors[rand.nextInt(errors.length)];
+ long key = (long) module << 32 | error;
+ eventMap.put(key, eventMap.getOrDefault(key, 0) + 1);
- verify(errorStats, times(2)).onAggregate();
- verify(errorStats, times(2)).save(eq(DELAY_FOR_PERSISTENT_MILLIS));
- assertEquals(errorStats.mPulledAtoms.telecomErrorStats.length, 1);
- verifyMessageForErrorStats(errorStats.mPulledAtoms.telecomErrorStats[0], VALUE_MODULE_ID,
- VALUE_ERROR_ID, 2);
+ errorStats.log(module, error);
+ waitForHandlerAction(errorStats, DELAY_TOLERANCE);
+
+ verify(errorStats, times(i + 1)).onAggregate();
+ verify(errorStats, times(i + 1)).save(eq(DELAY_FOR_PERSISTENT_MILLIS));
+ assertEquals(errorStats.mPulledAtoms.telecomErrorStats.length, eventMap.size());
+ assertTrue(hasMessageForErrorStats(
+ errorStats.mPulledAtoms.telecomErrorStats, module, error, eventMap.get(key)));
+ }
}
private void createTestFileForApiStats(long timestamps) throws IOException {
@@ -664,7 +842,7 @@
}
private void verifyTestDataForApiStats(final PulledAtomsClass.PulledAtoms atom,
- long timestamps) {
+ long timestamps) {
assertNotNull(atom);
assertEquals(atom.getTelecomApiStatsPullTimestampMillis(), timestamps);
assertNotNull(atom.telecomApiStats);
@@ -677,13 +855,24 @@
}
private void verifyMessageForApiStats(final PulledAtomsClass.TelecomApiStats msg, int apiId,
- int uid, int result, int count) {
+ int uid, int result, int count) {
assertEquals(msg.getApiName(), apiId);
assertEquals(msg.getUid(), uid);
assertEquals(msg.getApiResult(), result);
assertEquals(msg.getCount(), count);
}
+ private boolean hasMessageForApiStats(final PulledAtomsClass.TelecomApiStats[] msgs, int apiId,
+ int uid, int result, int count) {
+ for (PulledAtomsClass.TelecomApiStats msg : msgs) {
+ if (msg.getApiName() == apiId && msg.getUid() == uid && msg.getApiResult() == result
+ && msg.getCount() == count) {
+ return true;
+ }
+ }
+ return false;
+ }
+
private void createTestFileForAudioRouteStats(long timestamps) throws IOException {
PulledAtomsClass.PulledAtoms atom = new PulledAtomsClass.PulledAtoms();
atom.callAudioRouteStats =
@@ -704,7 +893,7 @@
}
private void verifyTestDataForAudioRouteStats(final PulledAtomsClass.PulledAtoms atom,
- long timestamps) {
+ long timestamps) {
assertNotNull(atom);
assertEquals(atom.getCallAudioRouteStatsPullTimestampMillis(), timestamps);
assertNotNull(atom.callAudioRouteStats);
@@ -750,7 +939,7 @@
}
private void verifyTestDataForCallStats(final PulledAtomsClass.PulledAtoms atom,
- long timestamps) {
+ long timestamps) {
assertNotNull(atom);
assertEquals(atom.getCallStatsPullTimestampMillis(), timestamps);
assertNotNull(atom.callStats);
@@ -782,8 +971,8 @@
new PulledAtomsClass.TelecomErrorStats[VALUE_ATOM_COUNT];
for (int i = 0; i < VALUE_ATOM_COUNT; i++) {
atom.telecomErrorStats[i] = new PulledAtomsClass.TelecomErrorStats();
- atom.telecomErrorStats[i].setSubmoduleName(VALUE_MODULE_ID);
- atom.telecomErrorStats[i].setErrorName(VALUE_ERROR_ID);
+ atom.telecomErrorStats[i].setSubmodule(VALUE_MODULE_ID);
+ atom.telecomErrorStats[i].setError(VALUE_ERROR_ID);
atom.telecomErrorStats[i].setCount(VALUE_ERROR_COUNT);
}
atom.setTelecomErrorStatsPullTimestampMillis(timestamps);
@@ -807,8 +996,19 @@
private void verifyMessageForErrorStats(final PulledAtomsClass.TelecomErrorStats msg,
int moduleId, int errorId, int count) {
- assertEquals(msg.getSubmoduleName(), moduleId);
- assertEquals(msg.getErrorName(), errorId);
+ assertEquals(msg.getSubmodule(), moduleId);
+ assertEquals(msg.getError(), errorId);
assertEquals(msg.getCount(), count);
}
+
+ private boolean hasMessageForErrorStats(final PulledAtomsClass.TelecomErrorStats[] msgs,
+ int moduleId, int errorId, int count) {
+ for (PulledAtomsClass.TelecomErrorStats msg : msgs) {
+ if (msg.getSubmodule() == moduleId && msg.getError() == errorId
+ && msg.getCount() == count) {
+ return true;
+ }
+ }
+ return false;
+ }
}
diff --git a/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java b/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java
index dc5f325..220d783 100644
--- a/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java
+++ b/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java
@@ -93,12 +93,14 @@
import com.android.server.telecom.PhoneAccountRegistrar;
import com.android.server.telecom.TelecomServiceImpl;
import com.android.server.telecom.TelecomSystem;
+import com.android.server.telecom.callsequencing.CallTransaction;
import com.android.server.telecom.components.UserCallIntentProcessor;
import com.android.server.telecom.components.UserCallIntentProcessorFactory;
import com.android.server.telecom.flags.FeatureFlags;
-import com.android.server.telecom.voip.IncomingCallTransaction;
-import com.android.server.telecom.voip.OutgoingCallTransaction;
-import com.android.server.telecom.voip.TransactionManager;
+import com.android.server.telecom.metrics.TelecomMetricsController;
+import com.android.server.telecom.callsequencing.voip.IncomingCallTransaction;
+import com.android.server.telecom.callsequencing.voip.OutgoingCallTransaction;
+import com.android.server.telecom.callsequencing.TransactionManager;
import org.junit.After;
import org.junit.Before;
@@ -115,6 +117,7 @@
import java.util.HashSet;
import java.util.List;
import java.util.Set;
+import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.function.IntConsumer;
@@ -204,9 +207,13 @@
@Mock private com.android.internal.telephony.flags.FeatureFlags mTelephonyFeatureFlags;
@Mock private InCallController mInCallController;
+ @Mock private TelecomMetricsController mMockTelecomMetricsController;
+ @Mock private OutgoingCallTransaction mOutgoingCallTransaction;
+ @Mock private IncomingCallTransaction mIncomingCallTransaction;
private final TelecomSystem.SyncRoot mLock = new TelecomSystem.SyncRoot() { };
+ private static final String SYSTEM_UI_PACKAGE = "com.android.systemui";
private static final String DEFAULT_DIALER_PACKAGE = "com.google.android.dialer";
private static final UserHandle USER_HANDLE_16 = new UserHandle(16);
private static final UserHandle USER_HANDLE_17 = new UserHandle(17);
@@ -257,7 +264,9 @@
mSettingsSecureAdapter,
mFeatureFlags,
mTelephonyFeatureFlags,
- mLock);
+ mLock,
+ mMockTelecomMetricsController,
+ SYSTEM_UI_PACKAGE);
telecomServiceImpl.setTransactionManager(mTransactionManager);
telecomServiceImpl.setAnomalyReporterAdapter(mAnomalyReporterAdapter);
mTSIBinder = telecomServiceImpl.getBinder();
@@ -277,6 +286,7 @@
when(mPackageManager.getPackageUid(anyString(), eq(0))).thenReturn(Binder.getCallingUid());
when(mFeatureFlags.earlyBindingToIncallService()).thenReturn(true);
when(mTelephonyFeatureFlags.workProfileApiSplit()).thenReturn(false);
+ when(mFeatureFlags.enableCallSequencing()).thenReturn(false);
}
@Override
@@ -452,6 +462,9 @@
// WHEN
when(mFakePhoneAccountRegistrar.getPhoneAccountUnchecked(TEL_PA_HANDLE_CURRENT)).thenReturn(
phoneAccount);
+ when(mFakeCallsManager.createTransactionalCall(any(String.class),
+ any(CallAttributes.class), any(Bundle.class), any(String.class)))
+ .thenReturn(CompletableFuture.completedFuture(mOutgoingCallTransaction));
doReturn(phoneAccount).when(mFakePhoneAccountRegistrar).getPhoneAccount(
eq(TEL_PA_HANDLE_CURRENT), any(UserHandle.class));
@@ -480,6 +493,9 @@
doReturn(phoneAccount).when(mFakePhoneAccountRegistrar).getPhoneAccount(
eq(TEL_PA_HANDLE_CURRENT), any(UserHandle.class));
+ when(mFakeCallsManager.createTransactionalCall(any(String.class),
+ any(CallAttributes.class), any(Bundle.class), any(String.class)))
+ .thenReturn(CompletableFuture.completedFuture(mIncomingCallTransaction));
mTSIBinder.addCall(mIncomingCallAttributes, mICallEventCallback, "1", CALLING_PACKAGE);
@@ -2304,7 +2320,8 @@
}
/**
- * Ensure self-managed calls cannot be ended using {@link TelecomManager#endCall()}.
+ * Ensure self-managed calls cannot be ended using {@link TelecomManager#endCall()} when the
+ * caller of this method is not considered privileged.
* @throws Exception
*/
@SmallTest
@@ -2321,7 +2338,8 @@
/**
* Ensure self-managed calls cannot be answered using {@link TelecomManager#acceptRingingCall()}
- * or {@link TelecomManager#acceptRingingCall(int)}.
+ * or {@link TelecomManager#acceptRingingCall(int)} when the caller of these methods is not
+ * considered privileged.
* @throws Exception
*/
@SmallTest
@@ -2336,6 +2354,53 @@
verify(mFakeCallsManager, never()).answerCall(eq(call), anyInt());
}
+ /**
+ * Ensure self-managed calls can be answered using {@link TelecomManager#acceptRingingCall()}
+ * or {@link TelecomManager#acceptRingingCall(int)} if the caller of these methods is
+ * privileged.
+ * @throws Exception
+ */
+ @SmallTest
+ @Test
+ public void testCanAnswerSelfManagedCallIfPrivileged() throws Exception {
+ when(mFeatureFlags.allowSystemAppsResolveVoipCalls()).thenReturn(true);
+ // Configure the test so that the caller of acceptRingingCall is considered privileged:
+ when(mPackageManager.getPackageUid(SYSTEM_UI_PACKAGE, 0))
+ .thenReturn(Binder.getCallingUid());
+
+ // Ensure that the call is successfully accepted:
+ Call call = mock(Call.class);
+ when(call.isSelfManaged()).thenReturn(true);
+ when(call.getState()).thenReturn(CallState.ACTIVE);
+ when(mFakeCallsManager.getFirstCallWithState(any()))
+ .thenReturn(call);
+ mTSIBinder.acceptRingingCall(TEST_PACKAGE);
+ verify(mFakeCallsManager).answerCall(eq(call), anyInt());
+ }
+
+ /**
+ * Ensure self-managed calls can be ended using {@link TelecomManager#endCall()} when the
+ * caller of these methods is privileged.
+ * @throws Exception
+ */
+ @SmallTest
+ @Test
+ public void testCanEndSelfManagedCallIfPrivileged() throws Exception {
+ when(mFeatureFlags.allowSystemAppsResolveVoipCalls()).thenReturn(true);
+ // Configure the test so that the caller of endCall is considered privileged:
+ when(mPackageManager.getPackageUid(SYSTEM_UI_PACKAGE, 0))
+ .thenReturn(Binder.getCallingUid());
+ // Set up the call:
+ Call call = mock(Call.class);
+ when(call.isSelfManaged()).thenReturn(true);
+ when(call.getState()).thenReturn(CallState.ACTIVE);
+ when(mFakeCallsManager.getFirstCallWithState(any()))
+ .thenReturn(call);
+ // Ensure that the call is successfully ended:
+ assertTrue(mTSIBinder.endCall(TEST_PACKAGE));
+ verify(mFakeCallsManager).disconnectCall(eq(call));
+ }
+
@SmallTest
@Test
public void testGetAdnUriForPhoneAccount() throws Exception {
diff --git a/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java b/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
index 4463d65..1e65011 100644
--- a/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
+++ b/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
@@ -224,6 +224,7 @@
@Mock
com.android.internal.telephony.flags.FeatureFlags mTelephonyFlags;
+ private static final String SYSTEM_UI_PACKAGE = "com.android.systemui";
final ComponentName mInCallServiceComponentNameX =
new ComponentName(
"incall-service-package-X",
@@ -580,7 +581,8 @@
ContactsAsyncHelper.ContentResolverAdapter adapter) {
return new ContactsAsyncHelper(adapter, mHandlerThread.getLooper());
}
- }, mDeviceIdleControllerAdapter, mAccessibilityManagerAdapter,
+ }, mDeviceIdleControllerAdapter, SYSTEM_UI_PACKAGE,
+ mAccessibilityManagerAdapter,
Runnable::run,
Runnable::run,
mBlockedNumbersAdapter,
diff --git a/tests/src/com/android/server/telecom/tests/TelecomTestCase.java b/tests/src/com/android/server/telecom/tests/TelecomTestCase.java
index e8389a0..5b5c3ed 100644
--- a/tests/src/com/android/server/telecom/tests/TelecomTestCase.java
+++ b/tests/src/com/android/server/telecom/tests/TelecomTestCase.java
@@ -51,8 +51,8 @@
mComponentContextFixture = new ComponentContextFixture(mFeatureFlags);
mContext = mComponentContextFixture.getTestDouble().getApplicationContext();
- Log.setSessionContext(mComponentContextFixture.getTestDouble().getApplicationContext());
- Log.getSessionManager().mCleanStaleSessions = null;
+ Log.setSessionManager(mComponentContextFixture.getTestDouble().getApplicationContext(),
+ null);
}
public void tearDown() throws Exception {
diff --git a/tests/src/com/android/server/telecom/tests/TransactionTests.java b/tests/src/com/android/server/telecom/tests/TransactionTests.java
index 5876474..0a23913 100644
--- a/tests/src/com/android/server/telecom/tests/TransactionTests.java
+++ b/tests/src/com/android/server/telecom/tests/TransactionTests.java
@@ -16,24 +16,25 @@
package com.android.server.telecom.tests;
-import static com.android.server.telecom.voip.VideoStateTranslation.TransactionalVideoStateToVideoProfileState;
-import static com.android.server.telecom.voip.VideoStateTranslation.VideoProfileStateToTransactionalVideoState;
+import static com.android.server.telecom.callsequencing.voip.VideoStateTranslation
+ .TransactionalVideoStateToVideoProfileState;
+import static com.android.server.telecom.callsequencing.voip.VideoStateTranslation
+ .VideoProfileStateToTransactionalVideoState;
+import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.atLeastOnce;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.timeout;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.isA;
-import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.isA;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.ComponentName;
@@ -59,30 +60,26 @@
import com.android.server.telecom.CallsManager;
import com.android.server.telecom.ClockProxy;
import com.android.server.telecom.ConnectionServiceWrapper;
-import com.android.server.telecom.flags.FeatureFlags;
import com.android.server.telecom.PhoneNumberUtilsAdapter;
import com.android.server.telecom.TelecomSystem;
+import com.android.server.telecom.callsequencing.CallTransactionResult;
+import com.android.server.telecom.callsequencing.TransactionManager;
+import com.android.server.telecom.callsequencing.VerifyCallStateChangeTransaction;
+import com.android.server.telecom.callsequencing.voip.EndCallTransaction;
+import com.android.server.telecom.callsequencing.voip.HoldCallTransaction;
+import com.android.server.telecom.callsequencing.voip.IncomingCallTransaction;
+import com.android.server.telecom.callsequencing.voip.MaybeHoldCallForNewCallTransaction;
+import com.android.server.telecom.callsequencing.voip.OutgoingCallTransaction;
+import com.android.server.telecom.callsequencing.voip.RequestNewActiveCallTransaction;
import com.android.server.telecom.ui.ToastFactory;
-import com.android.server.telecom.voip.EndCallTransaction;
-import com.android.server.telecom.voip.HoldCallTransaction;
-import com.android.server.telecom.voip.IncomingCallTransaction;
-import com.android.server.telecom.voip.OutgoingCallTransaction;
-import com.android.server.telecom.voip.MaybeHoldCallForNewCallTransaction;
-import com.android.server.telecom.voip.RequestNewActiveCallTransaction;
-import com.android.server.telecom.voip.TransactionManager;
-import com.android.server.telecom.voip.VerifyCallStateChangeTransaction;
-import com.android.server.telecom.voip.VideoStateTranslation;
-import com.android.server.telecom.voip.VoipCallTransactionResult;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
-
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
-import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
@@ -295,20 +292,21 @@
.setCallType(CallAttributes.VIDEO_CALL)
.build();
+ Bundle extras = new Bundle();
OutgoingCallTransaction t = new OutgoingCallTransaction(null,
- mContext, null, mCallsManager, new Bundle(), mFeatureFlags);
+ mContext, null, mCallsManager, extras, mFeatureFlags);
// WHEN
when(mFeatureFlags.transactionalVideoState()).thenReturn(true);
t.setFeatureFlags(mFeatureFlags);
// THEN
- assertEquals(VideoProfile.STATE_AUDIO_ONLY, t
- .generateExtras(audioOnlyAttributes)
+ assertEquals(VideoProfile.STATE_AUDIO_ONLY, OutgoingCallTransaction
+ .generateExtras(null, extras, audioOnlyAttributes, mFeatureFlags)
.getInt(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE));
- assertEquals(VideoProfile.STATE_BIDIRECTIONAL, t
- .generateExtras(videoAttributes)
+ assertEquals(VideoProfile.STATE_BIDIRECTIONAL, OutgoingCallTransaction
+ .generateExtras(null, extras, videoAttributes, mFeatureFlags)
.getInt(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE));
}
@@ -421,7 +419,7 @@
// THEN
verify(mMockCall1, times(1)).addCallStateListener(t.getCallStateListenerImpl());
- assertEquals(VoipCallTransactionResult.RESULT_SUCCEED,
+ assertEquals(CallTransactionResult.RESULT_SUCCEED,
t.getTransactionResult().get(2, TimeUnit.SECONDS).getResult());
verify(mMockCall1, atLeastOnce()).removeCallStateListener(any());
}
@@ -451,9 +449,9 @@
callSpy.setState(initialState, "manual set in test");
// Mocks some methods to not call the real method.
- doNothing().when(callSpy).unhold();
- doNothing().when(callSpy).hold();
- doNothing().when(callSpy).disconnect();
+ doReturn(null).when(callSpy).unhold();
+ doReturn(null).when(callSpy).hold();
+ doReturn(null).when(callSpy).disconnect();
return callSpy;
}
diff --git a/tests/src/com/android/server/telecom/tests/TransactionalServiceWrapperTest.java b/tests/src/com/android/server/telecom/tests/TransactionalServiceWrapperTest.java
index fa5f2a2..fea6135 100644
--- a/tests/src/com/android/server/telecom/tests/TransactionalServiceWrapperTest.java
+++ b/tests/src/com/android/server/telecom/tests/TransactionalServiceWrapperTest.java
@@ -39,10 +39,10 @@
import com.android.server.telecom.TelecomSystem;
import com.android.server.telecom.TransactionalServiceRepository;
import com.android.server.telecom.TransactionalServiceWrapper;
-import com.android.server.telecom.voip.EndCallTransaction;
-import com.android.server.telecom.voip.HoldCallTransaction;
-import com.android.server.telecom.voip.SerialTransaction;
-import com.android.server.telecom.voip.TransactionManager;
+import com.android.server.telecom.callsequencing.voip.EndCallTransaction;
+import com.android.server.telecom.callsequencing.voip.HoldCallTransaction;
+import com.android.server.telecom.callsequencing.voip.SerialTransaction;
+import com.android.server.telecom.callsequencing.TransactionManager;
import org.junit.After;
import org.junit.Before;
@@ -83,9 +83,8 @@
Mockito.when(mCallsManager.getLock()).thenReturn(mLock);
Mockito.when(mCallEventCallback.asBinder()).thenReturn(mIBinder);
mTransactionalServiceWrapper = new TransactionalServiceWrapper(mCallEventCallback,
- mCallsManager, SERVICE_HANDLE, mMockCall1, mRepository);
-
- mTransactionalServiceWrapper.setTransactionManager(mTransactionManager);
+ mCallsManager, SERVICE_HANDLE, mMockCall1, mRepository, mTransactionManager,
+ false /*call sequencing*/);
}
@Override
@@ -98,7 +97,8 @@
public void testTransactionalServiceWrapperStartState() throws Exception {
TransactionalServiceWrapper service =
new TransactionalServiceWrapper(mCallEventCallback,
- mCallsManager, SERVICE_HANDLE, mMockCall1, mRepository);
+ mCallsManager, SERVICE_HANDLE, mMockCall1, mRepository, mTransactionManager,
+ false /*call sequencing*/);
assertEquals(SERVICE_HANDLE, service.getPhoneAccountHandle());
assertEquals(1, service.getNumberOfTrackedCalls());
@@ -108,7 +108,8 @@
public void testTransactionalServiceWrapperCallCount() throws Exception {
TransactionalServiceWrapper service =
new TransactionalServiceWrapper(mCallEventCallback,
- mCallsManager, SERVICE_HANDLE, mMockCall1, mRepository);
+ mCallsManager, SERVICE_HANDLE, mMockCall1, mRepository, mTransactionManager,
+ false /*call sequencing*/);
assertEquals(1, service.getNumberOfTrackedCalls());
service.trackCall(mMockCall2);
diff --git a/tests/src/com/android/server/telecom/tests/VoipCallMonitorTest.java b/tests/src/com/android/server/telecom/tests/VoipCallMonitorTest.java
index 7f7399c..bf68f8c 100644
--- a/tests/src/com/android/server/telecom/tests/VoipCallMonitorTest.java
+++ b/tests/src/com/android/server/telecom/tests/VoipCallMonitorTest.java
@@ -49,7 +49,7 @@
import com.android.server.telecom.Call;
import com.android.server.telecom.CallState;
import com.android.server.telecom.TelecomSystem;
-import com.android.server.telecom.voip.VoipCallMonitor;
+import com.android.server.telecom.callsequencing.voip.VoipCallMonitor;
import org.junit.Before;
import org.junit.Test;
diff --git a/tests/src/com/android/server/telecom/tests/VoipCallTransactionTest.java b/tests/src/com/android/server/telecom/tests/VoipCallTransactionTest.java
index c5be130..c479aac 100644
--- a/tests/src/com/android/server/telecom/tests/VoipCallTransactionTest.java
+++ b/tests/src/com/android/server/telecom/tests/VoipCallTransactionTest.java
@@ -25,11 +25,11 @@
import androidx.test.filters.SmallTest;
import com.android.server.telecom.TelecomSystem;
-import com.android.server.telecom.voip.ParallelTransaction;
-import com.android.server.telecom.voip.SerialTransaction;
-import com.android.server.telecom.voip.TransactionManager;
-import com.android.server.telecom.voip.VoipCallTransaction;
-import com.android.server.telecom.voip.VoipCallTransactionResult;
+import com.android.server.telecom.callsequencing.voip.ParallelTransaction;
+import com.android.server.telecom.callsequencing.voip.SerialTransaction;
+import com.android.server.telecom.callsequencing.TransactionManager;
+import com.android.server.telecom.callsequencing.CallTransaction;
+import com.android.server.telecom.callsequencing.CallTransactionResult;
import org.junit.After;
import org.junit.Before;
@@ -51,7 +51,7 @@
private TransactionManager mTransactionManager;
private static final TelecomSystem.SyncRoot mLock = new TelecomSystem.SyncRoot() { };
- private class TestVoipCallTransaction extends VoipCallTransaction {
+ private class TestVoipCallTransaction extends CallTransaction {
public static final int SUCCESS = 0;
public static final int FAILED = 1;
public static final int TIMEOUT = 2;
@@ -70,27 +70,27 @@
}
@Override
- public CompletionStage<VoipCallTransactionResult> processTransaction(Void v) {
+ public CompletionStage<CallTransactionResult> processTransaction(Void v) {
if (mType == EXCEPTION) {
mLog.append(mName).append(" exception;\n");
throw new IllegalStateException("TEST EXCEPTION");
}
- CompletableFuture<VoipCallTransactionResult> resultFuture = new CompletableFuture<>();
+ CompletableFuture<CallTransactionResult> resultFuture = new CompletableFuture<>();
mHandler.postDelayed(() -> {
if (mType == SUCCESS) {
mLog.append(mName).append(" success;\n");
resultFuture.complete(
- new VoipCallTransactionResult(VoipCallTransactionResult.RESULT_SUCCEED,
+ new CallTransactionResult(CallTransactionResult.RESULT_SUCCEED,
null));
} else if (mType == FAILED) {
mLog.append(mName).append(" failed;\n");
resultFuture.complete(
- new VoipCallTransactionResult(CallException.CODE_ERROR_UNKNOWN,
+ new CallTransactionResult(CallException.CODE_ERROR_UNKNOWN,
null));
} else {
mLog.append(mName).append(" timeout;\n");
resultFuture.complete(
- new VoipCallTransactionResult(CallException.CODE_ERROR_UNKNOWN,
+ new CallTransactionResult(CallException.CODE_ERROR_UNKNOWN,
"timeout"));
}
}, mSleepTime);
@@ -122,7 +122,7 @@
@Test
public void testSerialTransactionSuccess()
throws ExecutionException, InterruptedException, TimeoutException {
- List<VoipCallTransaction> subTransactions = new ArrayList<>();
+ List<CallTransaction> subTransactions = new ArrayList<>();
TestVoipCallTransaction t1 = new TestVoipCallTransaction("t1", 1000L,
TestVoipCallTransaction.SUCCESS);
TestVoipCallTransaction t2 = new TestVoipCallTransaction("t2", 1000L,
@@ -132,13 +132,13 @@
subTransactions.add(t1);
subTransactions.add(t2);
subTransactions.add(t3);
- CompletableFuture<VoipCallTransactionResult> resultFuture = new CompletableFuture<>();
- OutcomeReceiver<VoipCallTransactionResult, CallException> outcomeReceiver =
+ CompletableFuture<CallTransactionResult> resultFuture = new CompletableFuture<>();
+ OutcomeReceiver<CallTransactionResult, CallException> outcomeReceiver =
resultFuture::complete;
String expectedLog = "t1 success;\nt2 success;\nt3 success;\n";
mTransactionManager.addTransaction(new SerialTransaction(subTransactions, mLock),
outcomeReceiver);
- assertEquals(VoipCallTransactionResult.RESULT_SUCCEED,
+ assertEquals(CallTransactionResult.RESULT_SUCCEED,
resultFuture.get(5000L, TimeUnit.MILLISECONDS).getResult());
assertEquals(expectedLog, mLog.toString());
verifyTransactionsFinished(t1, t2, t3);
@@ -148,7 +148,7 @@
@Test
public void testSerialTransactionFailed()
throws ExecutionException, InterruptedException, TimeoutException {
- List<VoipCallTransaction> subTransactions = new ArrayList<>();
+ List<CallTransaction> subTransactions = new ArrayList<>();
TestVoipCallTransaction t1 = new TestVoipCallTransaction("t1", 1000L,
TestVoipCallTransaction.SUCCESS);
TestVoipCallTransaction t2 = new TestVoipCallTransaction("t2", 1000L,
@@ -159,10 +159,10 @@
subTransactions.add(t2);
subTransactions.add(t3);
CompletableFuture<String> exceptionFuture = new CompletableFuture<>();
- OutcomeReceiver<VoipCallTransactionResult, CallException> outcomeReceiver =
- new OutcomeReceiver<VoipCallTransactionResult, CallException>() {
+ OutcomeReceiver<CallTransactionResult, CallException> outcomeReceiver =
+ new OutcomeReceiver<CallTransactionResult, CallException>() {
@Override
- public void onResult(VoipCallTransactionResult result) {
+ public void onResult(CallTransactionResult result) {
}
@@ -183,7 +183,7 @@
@Test
public void testParallelTransactionSuccess()
throws ExecutionException, InterruptedException, TimeoutException {
- List<VoipCallTransaction> subTransactions = new ArrayList<>();
+ List<CallTransaction> subTransactions = new ArrayList<>();
TestVoipCallTransaction t1 = new TestVoipCallTransaction("t1", 1000L,
TestVoipCallTransaction.SUCCESS);
TestVoipCallTransaction t2 = new TestVoipCallTransaction("t2", 500L,
@@ -193,12 +193,12 @@
subTransactions.add(t1);
subTransactions.add(t2);
subTransactions.add(t3);
- CompletableFuture<VoipCallTransactionResult> resultFuture = new CompletableFuture<>();
- OutcomeReceiver<VoipCallTransactionResult, CallException> outcomeReceiver =
+ CompletableFuture<CallTransactionResult> resultFuture = new CompletableFuture<>();
+ OutcomeReceiver<CallTransactionResult, CallException> outcomeReceiver =
resultFuture::complete;
mTransactionManager.addTransaction(new ParallelTransaction(subTransactions, mLock),
outcomeReceiver);
- assertEquals(VoipCallTransactionResult.RESULT_SUCCEED,
+ assertEquals(CallTransactionResult.RESULT_SUCCEED,
resultFuture.get(5000L, TimeUnit.MILLISECONDS).getResult());
String log = mLog.toString();
assertTrue(log.contains("t1 success;\n"));
@@ -211,7 +211,7 @@
@Test
public void testParallelTransactionFailed()
throws ExecutionException, InterruptedException, TimeoutException {
- List<VoipCallTransaction> subTransactions = new ArrayList<>();
+ List<CallTransaction> subTransactions = new ArrayList<>();
TestVoipCallTransaction t1 = new TestVoipCallTransaction("t1", 1000L,
TestVoipCallTransaction.SUCCESS);
TestVoipCallTransaction t2 = new TestVoipCallTransaction("t2", 500L,
@@ -222,10 +222,10 @@
subTransactions.add(t2);
subTransactions.add(t3);
CompletableFuture<String> exceptionFuture = new CompletableFuture<>();
- OutcomeReceiver<VoipCallTransactionResult, CallException> outcomeReceiver =
+ OutcomeReceiver<CallTransactionResult, CallException> outcomeReceiver =
new OutcomeReceiver<>() {
@Override
- public void onResult(VoipCallTransactionResult result) {
+ public void onResult(CallTransactionResult result) {
}
@@ -248,10 +248,10 @@
TestVoipCallTransaction t = new TestVoipCallTransaction("t", 10000L,
TestVoipCallTransaction.SUCCESS);
CompletableFuture<String> exceptionFuture = new CompletableFuture<>();
- OutcomeReceiver<VoipCallTransactionResult, CallException> outcomeReceiver =
+ OutcomeReceiver<CallTransactionResult, CallException> outcomeReceiver =
new OutcomeReceiver<>() {
@Override
- public void onResult(VoipCallTransactionResult result) {
+ public void onResult(CallTransactionResult result) {
}
@@ -275,10 +275,10 @@
TestVoipCallTransaction t2 = new TestVoipCallTransaction("t2", 1000L,
TestVoipCallTransaction.SUCCESS);
CompletableFuture<String> exceptionFuture = new CompletableFuture<>();
- OutcomeReceiver<VoipCallTransactionResult, CallException> outcomeExceptionReceiver =
+ OutcomeReceiver<CallTransactionResult, CallException> outcomeExceptionReceiver =
new OutcomeReceiver<>() {
@Override
- public void onResult(VoipCallTransactionResult result) {
+ public void onResult(CallTransactionResult result) {
}
@Override
@@ -291,12 +291,12 @@
exceptionFuture.get(7000L, TimeUnit.MILLISECONDS);
assertTrue(mLog.toString().contains("t1 exception;\n"));
// Verify an exception in a processing a previous transaction does not stall the next one.
- CompletableFuture<VoipCallTransactionResult> resultFuture = new CompletableFuture<>();
- OutcomeReceiver<VoipCallTransactionResult, CallException> outcomeReceiver =
+ CompletableFuture<CallTransactionResult> resultFuture = new CompletableFuture<>();
+ OutcomeReceiver<CallTransactionResult, CallException> outcomeReceiver =
resultFuture::complete;
mTransactionManager.addTransaction(t2, outcomeReceiver);
String expectedLog = "t1 exception;\nt2 success;\n";
- assertEquals(VoipCallTransactionResult.RESULT_SUCCEED,
+ assertEquals(CallTransactionResult.RESULT_SUCCEED,
resultFuture.get(5000L, TimeUnit.MILLISECONDS).getResult());
assertEquals(expectedLog, mLog.toString());
verifyTransactionsFinished(t1, t2);
@@ -317,10 +317,10 @@
TestVoipCallTransaction.EXCEPTION);
// verify the TransactionManager informs the client of the failed transaction
CompletableFuture<String> exceptionFuture = new CompletableFuture<>();
- OutcomeReceiver<VoipCallTransactionResult, CallException> outcomeExceptionReceiver =
+ OutcomeReceiver<CallTransactionResult, CallException> outcomeExceptionReceiver =
new OutcomeReceiver<>() {
@Override
- public void onResult(VoipCallTransactionResult result) {
+ public void onResult(CallTransactionResult result) {
}
@Override
@@ -346,10 +346,10 @@
TestVoipCallTransaction.SUCCESS);
TestVoipCallTransaction t3 = new TestVoipCallTransaction("t3", 1000L,
TestVoipCallTransaction.SUCCESS);
- OutcomeReceiver<VoipCallTransactionResult, CallException> outcomeExceptionReceiver =
+ OutcomeReceiver<CallTransactionResult, CallException> outcomeExceptionReceiver =
new OutcomeReceiver<>() {
@Override
- public void onResult(VoipCallTransactionResult result) {
+ public void onResult(CallTransactionResult result) {
throw new IllegalStateException("RESULT EXCEPTION");
}
@@ -358,10 +358,10 @@
}
};
mTransactionManager.addTransaction(t1, outcomeExceptionReceiver);
- OutcomeReceiver<VoipCallTransactionResult, CallException> outcomeException2Receiver =
+ OutcomeReceiver<CallTransactionResult, CallException> outcomeException2Receiver =
new OutcomeReceiver<>() {
@Override
- public void onResult(VoipCallTransactionResult result) {
+ public void onResult(CallTransactionResult result) {
}
@Override
@@ -371,12 +371,12 @@
};
mTransactionManager.addTransaction(t2, outcomeException2Receiver);
// Verify an exception in a previous transaction result does not stall the next one.
- CompletableFuture<VoipCallTransactionResult> resultFuture = new CompletableFuture<>();
- OutcomeReceiver<VoipCallTransactionResult, CallException> outcomeReceiver =
+ CompletableFuture<CallTransactionResult> resultFuture = new CompletableFuture<>();
+ OutcomeReceiver<CallTransactionResult, CallException> outcomeReceiver =
resultFuture::complete;
mTransactionManager.addTransaction(t3, outcomeReceiver);
String expectedLog = "t1 success;\nt2 success;\nt3 success;\n";
- assertEquals(VoipCallTransactionResult.RESULT_SUCCEED,
+ assertEquals(CallTransactionResult.RESULT_SUCCEED,
resultFuture.get(5000L, TimeUnit.MILLISECONDS).getResult());
assertEquals(expectedLog, mLog.toString());
verifyTransactionsFinished(t1, t2, t3);