Merge "Adds TransactionManager dumpsys info and cleanups" into main
diff --git a/flags/Android.bp b/flags/Android.bp
index 386831c..3497db8 100644
--- a/flags/Android.bp
+++ b/flags/Android.bp
@@ -21,23 +21,23 @@
aconfig_declarations {
name: "telecom_flags",
package: "com.android.server.telecom.flags",
+ container: "system",
srcs: [
- "telecom_broadcast_flags.aconfig",
- "telecom_ringer_flag_declarations.aconfig",
- "telecom_api_flags.aconfig",
- "telecom_call_filtering_flags.aconfig",
- "telecom_incallservice_flags.aconfig",
- "telecom_default_phone_account_flags.aconfig",
- "telecom_callaudioroutestatemachine_flags.aconfig",
- "telecom_call_flags.aconfig",
- "telecom_calls_manager_flags.aconfig",
- "telecom_anomaly_report_flags.aconfig",
- "telecom_callaudiomodestatemachine_flags.aconfig",
- "telecom_calllog_flags.aconfig",
- "telecom_resolve_hidden_dependencies.aconfig",
- "telecom_bluetoothroutemanager_flags.aconfig",
- "telecom_work_profile_flags.aconfig",
- "telecom_connection_service_wrapper_flags.aconfig",
+ "telecom_broadcast_flags.aconfig",
+ "telecom_ringer_flag_declarations.aconfig",
+ "telecom_api_flags.aconfig",
+ "telecom_call_filtering_flags.aconfig",
+ "telecom_incallservice_flags.aconfig",
+ "telecom_default_phone_account_flags.aconfig",
+ "telecom_callaudioroutestatemachine_flags.aconfig",
+ "telecom_call_flags.aconfig",
+ "telecom_calls_manager_flags.aconfig",
+ "telecom_anomaly_report_flags.aconfig",
+ "telecom_callaudiomodestatemachine_flags.aconfig",
+ "telecom_calllog_flags.aconfig",
+ "telecom_resolve_hidden_dependencies.aconfig",
+ "telecom_bluetoothroutemanager_flags.aconfig",
+ "telecom_work_profile_flags.aconfig",
+ "telecom_connection_service_wrapper_flags.aconfig",
],
}
-
diff --git a/flags/telecom_anomaly_report_flags.aconfig b/flags/telecom_anomaly_report_flags.aconfig
index dbacc08..6879d86 100644
--- a/flags/telecom_anomaly_report_flags.aconfig
+++ b/flags/telecom_anomaly_report_flags.aconfig
@@ -1,4 +1,5 @@
package: "com.android.server.telecom.flags"
+container: "system"
flag {
name: "gen_anom_report_on_focus_timeout"
diff --git a/flags/telecom_api_flags.aconfig b/flags/telecom_api_flags.aconfig
index 21b83b2..c44bea4 100644
--- a/flags/telecom_api_flags.aconfig
+++ b/flags/telecom_api_flags.aconfig
@@ -1,4 +1,5 @@
package: "com.android.server.telecom.flags"
+container: "system"
flag {
name: "voip_app_actions_support"
@@ -21,10 +22,30 @@
bug: "292597423"
}
-
flag{
name: "set_mute_state"
namespace: "telecom"
description: "transactional calls need the ability to mute the call audio input"
bug: "310669304"
}
+
+flag{
+ name: "get_registered_phone_accounts"
+ namespace: "telecom"
+ description: "When set, self-managed clients can get their own phone accounts"
+ bug: "317132586"
+}
+
+flag{
+ name: "transactional_video_state"
+ namespace: "telecom"
+ description: "when set, clients using transactional implementations will be able to set & get the video state"
+ bug: "311265260"
+}
+
+flag{
+ name: "business_call_composer"
+ namespace: "telecom"
+ description: "Enables enriched calling features (e.g. Business name will show for a call)"
+ bug: "311688497"
+}
diff --git a/flags/telecom_bluetoothroutemanager_flags.aconfig b/flags/telecom_bluetoothroutemanager_flags.aconfig
index ddd8571..1df1e9b 100644
--- a/flags/telecom_bluetoothroutemanager_flags.aconfig
+++ b/flags/telecom_bluetoothroutemanager_flags.aconfig
@@ -1,4 +1,5 @@
package: "com.android.server.telecom.flags"
+container: "system"
flag {
name: "use_actual_address_to_enter_connecting_state"
diff --git a/flags/telecom_broadcast_flags.aconfig b/flags/telecom_broadcast_flags.aconfig
index 348d574..de8dd27 100644
--- a/flags/telecom_broadcast_flags.aconfig
+++ b/flags/telecom_broadcast_flags.aconfig
@@ -1,4 +1,5 @@
package: "com.android.server.telecom.flags"
+container: "system"
flag {
name: "is_new_outgoing_call_broadcast_unblocking"
diff --git a/flags/telecom_call_filtering_flags.aconfig b/flags/telecom_call_filtering_flags.aconfig
index 95e74ce..72f9db3 100644
--- a/flags/telecom_call_filtering_flags.aconfig
+++ b/flags/telecom_call_filtering_flags.aconfig
@@ -1,4 +1,5 @@
package: "com.android.server.telecom.flags"
+container: "system"
flag {
name: "skip_filter_phone_account_perform_dnd_filter"
diff --git a/flags/telecom_call_flags.aconfig b/flags/telecom_call_flags.aconfig
index b5ea6a2..27a4b22 100644
--- a/flags/telecom_call_flags.aconfig
+++ b/flags/telecom_call_flags.aconfig
@@ -1,4 +1,5 @@
package: "com.android.server.telecom.flags"
+container: "system"
flag {
name: "transactional_cs_verifier"
diff --git a/flags/telecom_callaudiomodestatemachine_flags.aconfig b/flags/telecom_callaudiomodestatemachine_flags.aconfig
index b263113..1d81535 100644
--- a/flags/telecom_callaudiomodestatemachine_flags.aconfig
+++ b/flags/telecom_callaudiomodestatemachine_flags.aconfig
@@ -1,4 +1,5 @@
package: "com.android.server.telecom.flags"
+container: "system"
flag {
name: "set_audio_mode_before_abandon_focus"
diff --git a/flags/telecom_callaudioroutestatemachine_flags.aconfig b/flags/telecom_callaudioroutestatemachine_flags.aconfig
index fe21c92..f5da045 100644
--- a/flags/telecom_callaudioroutestatemachine_flags.aconfig
+++ b/flags/telecom_callaudioroutestatemachine_flags.aconfig
@@ -1,4 +1,5 @@
package: "com.android.server.telecom.flags"
+container: "system"
flag {
name: "available_routes_never_updated_after_set_system_audio_state"
diff --git a/flags/telecom_calllog_flags.aconfig b/flags/telecom_calllog_flags.aconfig
index 3ce7b63..593b7e5 100644
--- a/flags/telecom_calllog_flags.aconfig
+++ b/flags/telecom_calllog_flags.aconfig
@@ -1,4 +1,5 @@
package: "com.android.server.telecom.flags"
+container: "system"
flag {
name: "telecom_log_external_wearable_calls"
diff --git a/flags/telecom_calls_manager_flags.aconfig b/flags/telecom_calls_manager_flags.aconfig
index a68042e..de17eee 100644
--- a/flags/telecom_calls_manager_flags.aconfig
+++ b/flags/telecom_calls_manager_flags.aconfig
@@ -1,4 +1,5 @@
package: "com.android.server.telecom.flags"
+container: "system"
flag {
name: "use_improved_listener_order"
diff --git a/flags/telecom_connection_service_wrapper_flags.aconfig b/flags/telecom_connection_service_wrapper_flags.aconfig
index 5f46c27..80a8dfe 100644
--- a/flags/telecom_connection_service_wrapper_flags.aconfig
+++ b/flags/telecom_connection_service_wrapper_flags.aconfig
@@ -1,4 +1,5 @@
package: "com.android.server.telecom.flags"
+container: "system"
flag {
name: "updated_rcs_call_count_tracking"
diff --git a/flags/telecom_default_phone_account_flags.aconfig b/flags/telecom_default_phone_account_flags.aconfig
index 03f324c..e6badde 100644
--- a/flags/telecom_default_phone_account_flags.aconfig
+++ b/flags/telecom_default_phone_account_flags.aconfig
@@ -1,4 +1,5 @@
package: "com.android.server.telecom.flags"
+container: "system"
flag {
name: "only_update_telephony_on_valid_sub_ids"
diff --git a/flags/telecom_incallservice_flags.aconfig b/flags/telecom_incallservice_flags.aconfig
index 1110ca4..08a82ba 100644
--- a/flags/telecom_incallservice_flags.aconfig
+++ b/flags/telecom_incallservice_flags.aconfig
@@ -1,4 +1,5 @@
package: "com.android.server.telecom.flags"
+container: "system"
flag {
name: "early_binding_to_incall_service"
diff --git a/flags/telecom_resolve_hidden_dependencies.aconfig b/flags/telecom_resolve_hidden_dependencies.aconfig
index 6def938..674a968 100644
--- a/flags/telecom_resolve_hidden_dependencies.aconfig
+++ b/flags/telecom_resolve_hidden_dependencies.aconfig
@@ -1,8 +1,9 @@
package: "com.android.server.telecom.flags"
+container: "system"
flag {
name: "telecom_resolve_hidden_dependencies"
namespace: "telecom"
description: "Mainland cleanup for hidden dependencies"
- bug: "b/324090590"
+ bug: "323414215"
}
diff --git a/flags/telecom_ringer_flag_declarations.aconfig b/flags/telecom_ringer_flag_declarations.aconfig
index 54748d0..13577bb 100644
--- a/flags/telecom_ringer_flag_declarations.aconfig
+++ b/flags/telecom_ringer_flag_declarations.aconfig
@@ -1,4 +1,5 @@
package: "com.android.server.telecom.flags"
+container: "system"
flag {
name: "use_device_provided_serialized_ringer_vibration"
diff --git a/flags/telecom_work_profile_flags.aconfig b/flags/telecom_work_profile_flags.aconfig
index 180af59..854568b 100644
--- a/flags/telecom_work_profile_flags.aconfig
+++ b/flags/telecom_work_profile_flags.aconfig
@@ -1,4 +1,5 @@
package: "com.android.server.telecom.flags"
+container: "system"
flag {
name: "associated_user_refactor_for_work_profile"
diff --git a/src/com/android/server/telecom/Analytics.java b/src/com/android/server/telecom/Analytics.java
index bbcf858..45e3340 100644
--- a/src/com/android/server/telecom/Analytics.java
+++ b/src/com/android/server/telecom/Analytics.java
@@ -721,7 +721,7 @@
private static int getCarrierId(Context context) {
SubscriptionManager subscriptionManager =
- context.getSystemService(SubscriptionManager.class);
+ context.getSystemService(SubscriptionManager.class).createForAllUserProfiles();
List<SubscriptionInfo> subInfos = subscriptionManager.getActiveSubscriptionInfoList();
if (subInfos == null) {
return -1;
diff --git a/src/com/android/server/telecom/AudioRoute.java b/src/com/android/server/telecom/AudioRoute.java
index 5037cf5..cdf44a8 100644
--- a/src/com/android/server/telecom/AudioRoute.java
+++ b/src/com/android/server/telecom/AudioRoute.java
@@ -82,13 +82,9 @@
}
}
if (routeInfo == null) {
- CompletableFuture<Boolean> future = new CompletableFuture<>();
- mScheduledExecutorService.schedule(new Runnable() {
- @Override
- public void run() {
- createRetry(type, bluetoothAddress, audioManager, retryCount - 1);
- }
- }, RETRY_TIME_DELAY, TimeUnit.MILLISECONDS);
+ mScheduledExecutorService.schedule(
+ () -> createRetry(type, bluetoothAddress, audioManager, retryCount - 1),
+ RETRY_TIME_DELAY, TimeUnit.MILLISECONDS);
} else {
mAudioRouteFuture.complete(new AudioRoute(type, bluetoothAddress, routeInfo));
}
@@ -105,6 +101,7 @@
public static final int TYPE_BLUETOOTH_SCO = 5;
public static final int TYPE_BLUETOOTH_HA = 6;
public static final int TYPE_BLUETOOTH_LE = 7;
+ public static final int TYPE_STREAMING = 8;
@IntDef(prefix = "TYPE", value = {
TYPE_INVALID,
TYPE_EARPIECE,
@@ -113,7 +110,8 @@
TYPE_DOCK,
TYPE_BLUETOOTH_SCO,
TYPE_BLUETOOTH_HA,
- TYPE_BLUETOOTH_LE
+ TYPE_BLUETOOTH_LE,
+ TYPE_STREAMING
})
@Retention(RetentionPolicy.SOURCE)
public @interface AudioRouteType {}
@@ -145,6 +143,7 @@
DEVICE_TYPE_STRINGS.put(TYPE_BLUETOOTH_SCO, "TYPE_BLUETOOTH_SCO");
DEVICE_TYPE_STRINGS.put(TYPE_BLUETOOTH_HA, "TYPE_BLUETOOTH_HA");
DEVICE_TYPE_STRINGS.put(TYPE_BLUETOOTH_LE, "TYPE_BLUETOOTH_LE");
+ DEVICE_TYPE_STRINGS.put(TYPE_STREAMING, "TYPE_STREAMING");
}
public static final HashMap<Integer, Integer> DEVICE_INFO_TYPETO_AUDIO_ROUTE_TYPE;
@@ -225,6 +224,7 @@
void onDestRouteAsPendingRoute(boolean active, PendingAudioRoute pendingAudioRoute,
AudioManager audioManager) {
if (pendingAudioRoute.isActive() && !active) {
+ Log.i(this, "clearCommunicationDevice");
audioManager.clearCommunicationDevice();
} else if (active) {
if (mAudioRouteType == TYPE_BLUETOOTH_SCO) {
diff --git a/src/com/android/server/telecom/Call.java b/src/com/android/server/telecom/Call.java
index 624399b..f7ad93f 100644
--- a/src/com/android/server/telecom/Call.java
+++ b/src/com/android/server/telecom/Call.java
@@ -19,6 +19,8 @@
import static android.provider.CallLog.Calls.MISSED_REASON_NOT_MISSED;
import static android.telephony.TelephonyManager.EVENT_DISPLAY_EMERGENCY_MESSAGE;
+import static com.android.server.telecom.voip.VideoStateTranslation.VideoProfileStateToTransactionalVideoState;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
@@ -652,6 +654,36 @@
private boolean mIsVideoCallingSupportedByPhoneAccount = false;
/**
+ * Indicates whether this individual calls video state can be changed as opposed to be gated
+ * by the {@link PhoneAccount}.
+ *
+ * {@code True} if the call is Transactional && has the CallAttributes.SUPPORTS_VIDEO_CALLING
+ * capability {@code false} otherwise.
+ */
+ private boolean mTransactionalCallSupportsVideoCalling = false;
+
+ public void setTransactionalCallSupportsVideoCalling(CallAttributes callAttributes) {
+ if (!mIsTransactionalCall) {
+ Log.i(this, "setTransactionalCallSupportsVideoCalling: call is not transactional");
+ return;
+ }
+ if (callAttributes == null) {
+ Log.i(this, "setTransactionalCallSupportsVideoCalling: callAttributes is null");
+ return;
+ }
+ if ((callAttributes.getCallCapabilities() & CallAttributes.SUPPORTS_VIDEO_CALLING)
+ == CallAttributes.SUPPORTS_VIDEO_CALLING) {
+ mTransactionalCallSupportsVideoCalling = true;
+ } else {
+ mTransactionalCallSupportsVideoCalling = false;
+ }
+ }
+
+ public boolean isTransactionalCallSupportsVideoCalling() {
+ return mTransactionalCallSupportsVideoCalling;
+ }
+
+ /**
* Indicates whether or not this call can be pulled if it is an external call. If true, respect
* the Connection Capability set by the ConnectionService. If false, override the capability
* set and always remove the ability to pull this external call.
@@ -3176,8 +3208,7 @@
} else if (mConnectionService != null) {
mConnectionService.onExtrasChanged(this, mExtras);
} else {
- Log.e(this, new NullPointerException(),
- "putExtras failed due to null CS callId=%s", getId());
+ Log.w(this, "putExtras failed due to null CS callId=%s", getId());
}
}
}
@@ -4023,6 +4054,15 @@
videoState = VideoProfile.STATE_AUDIO_ONLY;
}
+ // Transactional calls have the ability to change video calling capabilities on a per-call
+ // basis as opposed to ConnectionService calls which are only based on the PhoneAccount.
+ if (mFlags.transactionalVideoState()
+ && mIsTransactionalCall && !mTransactionalCallSupportsVideoCalling) {
+ Log.i(this, "setVideoState: The transactional does NOT support video calling."
+ + " defaulted to audio (video not supported)");
+ videoState = VideoProfile.STATE_AUDIO_ONLY;
+ }
+
// Track Video State history during the duration of the call.
// Only update the history when the call is active or disconnected. This ensures we do
// not include the video state history when:
@@ -4045,6 +4085,12 @@
}
}
+ if (mFlags.transactionalVideoState()
+ && mIsTransactionalCall && mTransactionalService != null) {
+ int transactionalVS = VideoProfileStateToTransactionalVideoState(mVideoState);
+ mTransactionalService.onVideoStateChanged(this, transactionalVS);
+ }
+
if (VideoProfile.isVideo(videoState)) {
mAnalytics.setCallIsVideo(true);
}
diff --git a/src/com/android/server/telecom/CallAudioRouteAdapter.java b/src/com/android/server/telecom/CallAudioRouteAdapter.java
index f76d47d..5585d09 100644
--- a/src/com/android/server/telecom/CallAudioRouteAdapter.java
+++ b/src/com/android/server/telecom/CallAudioRouteAdapter.java
@@ -97,6 +97,9 @@
put(SPEAKER_ON, "SPEAKER_ON");
put(SPEAKER_OFF, "SPEAKER_OFF");
+ put(STREAMING_FORCE_ENABLED, "STREAMING_FORCE_ENABLED");
+ put(STREAMING_FORCE_DISABLED, "STREAMING_FORCE_DISABLED");
+
put(USER_SWITCH_EARPIECE, "USER_SWITCH_EARPIECE");
put(USER_SWITCH_BLUETOOTH, "USER_SWITCH_BLUETOOTH");
put(USER_SWITCH_HEADSET, "USER_SWITCH_HEADSET");
diff --git a/src/com/android/server/telecom/CallAudioRouteController.java b/src/com/android/server/telecom/CallAudioRouteController.java
index c1d7d0c..091c8fc 100644
--- a/src/com/android/server/telecom/CallAudioRouteController.java
+++ b/src/com/android/server/telecom/CallAudioRouteController.java
@@ -19,24 +19,36 @@
import static com.android.server.telecom.AudioRoute.BT_AUDIO_ROUTE_TYPES;
import static com.android.server.telecom.AudioRoute.TYPE_INVALID;
+import android.app.ActivityManager;
import android.bluetooth.BluetoothDevice;
+import android.content.BroadcastReceiver;
import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.UserInfo;
import android.media.AudioAttributes;
import android.media.AudioDeviceAttributes;
import android.media.AudioDeviceInfo;
import android.media.AudioManager;
+import android.media.IAudioService;
import android.media.audiopolicy.AudioProductStrategy;
+import android.os.Binder;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
+import android.os.RemoteException;
+import android.os.UserHandle;
import android.telecom.CallAudioState;
import android.telecom.Log;
import android.telecom.Logging.Session;
import android.util.ArrayMap;
+import androidx.annotation.NonNull;
+
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.SomeArgs;
import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.telecom.bluetooth.BluetoothRouteManager;
import java.util.HashSet;
import java.util.List;
@@ -57,20 +69,83 @@
ROUTE_MAP.put(AudioRoute.TYPE_BLUETOOTH_SCO, CallAudioState.ROUTE_BLUETOOTH);
ROUTE_MAP.put(AudioRoute.TYPE_BLUETOOTH_HA, CallAudioState.ROUTE_BLUETOOTH);
ROUTE_MAP.put(AudioRoute.TYPE_BLUETOOTH_LE, CallAudioState.ROUTE_BLUETOOTH);
+ ROUTE_MAP.put(AudioRoute.TYPE_STREAMING, CallAudioState.ROUTE_STREAMING);
}
private final CallsManager mCallsManager;
+ private final Context mContext;
private AudioManager mAudioManager;
+ private CallAudioManager mCallAudioManager;
+ private final BluetoothRouteManager mBluetoothRouteManager;
+ private final CallAudioManager.AudioServiceFactory mAudioServiceFactory;
private final Handler mHandler;
private final WiredHeadsetManager mWiredHeadsetManager;
private Set<AudioRoute> mAvailableRoutes;
private AudioRoute mCurrentRoute;
private AudioRoute mEarpieceWiredRoute;
private AudioRoute mSpeakerDockRoute;
+ private AudioRoute mStreamingRoute;
+ private Set<AudioRoute> mStreamingRoutes;
private Map<AudioRoute, BluetoothDevice> mBluetoothRoutes;
private Map<Integer, AudioRoute> mTypeRoutes;
private PendingAudioRoute mPendingAudioRoute;
private AudioRoute.Factory mAudioRouteFactory;
+ private int mFocusType;
+ private final Object mLock = new Object();
+ private final BroadcastReceiver mSpeakerPhoneChangeReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ Log.startSession("CARC.mSPCR");
+ try {
+ if (AudioManager.ACTION_SPEAKERPHONE_STATE_CHANGED.equals(intent.getAction())) {
+ if (mAudioManager != null) {
+ AudioDeviceInfo info = mAudioManager.getCommunicationDevice();
+ if ((info != null) &&
+ (info.getType() == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER)) {
+ sendMessageWithSessionInfo(SPEAKER_ON);
+ } else {
+ sendMessageWithSessionInfo(SPEAKER_OFF);
+ }
+ }
+ } else {
+ Log.w(this, "Received non-speakerphone-change intent");
+ }
+ } finally {
+ Log.endSession();
+ }
+ }
+ };
+ private final BroadcastReceiver mMuteChangeReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ Log.startSession("CARC.mCR");
+ try {
+ if (AudioManager.ACTION_MICROPHONE_MUTE_CHANGED.equals(intent.getAction())) {
+ if (mCallsManager.isInEmergencyCall()) {
+ Log.i(this, "Mute was externally changed when there's an emergency call. "
+ + "Forcing mute back off.");
+ sendMessageWithSessionInfo(MUTE_OFF);
+ } else {
+ sendMessageWithSessionInfo(MUTE_EXTERNALLY_CHANGED);
+ }
+ } else if (AudioManager.STREAM_MUTE_CHANGED_ACTION.equals(intent.getAction())) {
+ int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
+ boolean isStreamMuted = intent.getBooleanExtra(
+ AudioManager.EXTRA_STREAM_VOLUME_MUTED, false);
+
+ if (streamType == AudioManager.STREAM_RING && !isStreamMuted
+ && mCallAudioManager != null) {
+ Log.i(this, "Ring stream was un-muted.");
+ mCallAudioManager.onRingerModeChange();
+ }
+ } else {
+ Log.w(this, "Received non-mute-change intent");
+ }
+ } finally {
+ Log.endSession();
+ }
+ }
+ };
private CallAudioState mCallAudioState;
private boolean mIsMute;
private boolean mIsPending;
@@ -79,59 +154,142 @@
public CallAudioRouteController(
Context context,
CallsManager callsManager,
+ CallAudioManager.AudioServiceFactory audioServiceFactory,
AudioRoute.Factory audioRouteFactory,
- WiredHeadsetManager wiredHeadsetManager) {
+ WiredHeadsetManager wiredHeadsetManager,
+ BluetoothRouteManager bluetoothRouteManager) {
+ mContext = context;
mCallsManager = callsManager;
mAudioManager = context.getSystemService(AudioManager.class);
+ mAudioServiceFactory = audioServiceFactory;
mAudioRouteFactory = audioRouteFactory;
mWiredHeadsetManager = wiredHeadsetManager;
mIsMute = false;
+ mBluetoothRouteManager = bluetoothRouteManager;
+ mFocusType = NO_FOCUS;
HandlerThread handlerThread = new HandlerThread(this.getClass().getSimpleName());
handlerThread.start();
+
+ // Register broadcast receivers
+ 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);
+ micMuteChangedFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
+ context.registerReceiver(mMuteChangeReceiver, micMuteChangedFilter);
+
+ IntentFilter muteChangedFilter = new IntentFilter(AudioManager.STREAM_MUTE_CHANGED_ACTION);
+ muteChangedFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
+ context.registerReceiver(mMuteChangeReceiver, muteChangedFilter);
+
+ // Create handler
mHandler = new Handler(handlerThread.getLooper()) {
@Override
- public void handleMessage(Message msg) {
- preHandleMessage(msg);
- String address;
- BluetoothDevice bluetoothDevice;
- @AudioRoute.AudioRouteType int type;
- switch (msg.what) {
- case BT_AUDIO_CONNECTED:
- bluetoothDevice = (BluetoothDevice) ((SomeArgs) msg.obj).arg2;
- handleBtAudioActive(bluetoothDevice);
- break;
- case BT_AUDIO_DISCONNECTED:
- bluetoothDevice = (BluetoothDevice) ((SomeArgs) msg.obj).arg2;
- handleBtAudioInactive(bluetoothDevice);
- break;
- case BT_DEVICE_ADDED:
- type = msg.arg1;
- bluetoothDevice = (BluetoothDevice) ((SomeArgs) msg.obj).arg2;
- handleBtConnected(type, bluetoothDevice);
- break;
- case BT_DEVICE_REMOVED:
- type = msg.arg1;
- bluetoothDevice = (BluetoothDevice) ((SomeArgs) msg.obj).arg2;
- handleBtDisconnected(type, bluetoothDevice);
- break;
- case BLUETOOTH_DEVICE_LIST_CHANGED:
- break;
- case BT_ACTIVE_DEVICE_PRESENT:
- type = msg.arg1;
- address = (String) ((SomeArgs) msg.obj).arg2;
- handleBtActiveDevicePresent(type, address);
- break;
- case BT_ACTIVE_DEVICE_GONE:
- type = msg.arg1;
- handleBtActiveDeviceGone(type);
- break;
- case EXIT_PENDING_ROUTE:
- handleExitPendingRoute();
- break;
- default:
- break;
+ public void handleMessage(@NonNull Message msg) {
+ synchronized (this) {
+ preHandleMessage(msg);
+ String address;
+ BluetoothDevice bluetoothDevice;
+ int focus;
+ @AudioRoute.AudioRouteType int type;
+ switch (msg.what) {
+ case CONNECT_WIRED_HEADSET:
+ handleWiredHeadsetConnected();
+ break;
+ case DISCONNECT_WIRED_HEADSET:
+ handleWiredHeadsetDisconnected();
+ break;
+ case CONNECT_DOCK:
+ handleDockConnected();
+ break;
+ case DISCONNECT_DOCK:
+ handleDockDisconnected();
+ break;
+ case BLUETOOTH_DEVICE_LIST_CHANGED:
+ break;
+ case BT_ACTIVE_DEVICE_PRESENT:
+ type = msg.arg1;
+ address = (String) ((SomeArgs) msg.obj).arg2;
+ handleBtActiveDevicePresent(type, address);
+ break;
+ case BT_ACTIVE_DEVICE_GONE:
+ type = msg.arg1;
+ handleBtActiveDeviceGone(type);
+ break;
+ case BT_DEVICE_ADDED:
+ type = msg.arg1;
+ bluetoothDevice = (BluetoothDevice) ((SomeArgs) msg.obj).arg2;
+ handleBtConnected(type, bluetoothDevice);
+ break;
+ case BT_DEVICE_REMOVED:
+ type = msg.arg1;
+ bluetoothDevice = (BluetoothDevice) ((SomeArgs) msg.obj).arg2;
+ handleBtDisconnected(type, bluetoothDevice);
+ break;
+ case SWITCH_EARPIECE:
+ case USER_SWITCH_EARPIECE:
+ handleSwitchEarpiece();
+ break;
+ case SWITCH_BLUETOOTH:
+ case USER_SWITCH_BLUETOOTH:
+ address = (String) ((SomeArgs) msg.obj).arg2;
+ handleSwitchBluetooth(address);
+ break;
+ case SWITCH_HEADSET:
+ case USER_SWITCH_HEADSET:
+ handleSwitchHeadset();
+ break;
+ case SWITCH_SPEAKER:
+ case USER_SWITCH_SPEAKER:
+ handleSwitchSpeaker();
+ break;
+ case USER_SWITCH_BASELINE_ROUTE:
+ handleSwitchBaselineRoute();
+ break;
+ case SPEAKER_ON:
+ handleSpeakerOn();
+ break;
+ case SPEAKER_OFF:
+ handleSpeakerOff();
+ break;
+ case STREAMING_FORCE_ENABLED:
+ handleStreamingEnabled();
+ break;
+ case STREAMING_FORCE_DISABLED:
+ handleStreamingDisabled();
+ break;
+ case BT_AUDIO_CONNECTED:
+ bluetoothDevice = (BluetoothDevice) ((SomeArgs) msg.obj).arg2;
+ handleBtAudioActive(bluetoothDevice);
+ break;
+ case BT_AUDIO_DISCONNECTED:
+ bluetoothDevice = (BluetoothDevice) ((SomeArgs) msg.obj).arg2;
+ handleBtAudioInactive(bluetoothDevice);
+ break;
+ case MUTE_ON:
+ handleMuteChanged(true);
+ break;
+ case MUTE_OFF:
+ handleMuteChanged(false);
+ break;
+ case MUTE_EXTERNALLY_CHANGED:
+ handleMuteChanged(mAudioManager.isMasterMute());
+ break;
+ case SWITCH_FOCUS:
+ focus = msg.arg1;
+ handleSwitchFocus(focus);
+ break;
+ case EXIT_PENDING_ROUTE:
+ handleExitPendingRoute();
+ break;
+ default:
+ break;
+ }
+ postHandleMessage(msg);
}
- postHandleMessage(msg);
}
};
}
@@ -140,7 +298,10 @@
mAvailableRoutes = new HashSet<>();
mBluetoothRoutes = new ArrayMap<>();
mTypeRoutes = new ArrayMap<>();
+ mStreamingRoutes = new HashSet<>();
mPendingAudioRoute = new PendingAudioRoute(this, mAudioManager);
+ mStreamingRoute = new AudioRoute(AudioRoute.TYPE_STREAMING, null, null);
+ mStreamingRoutes.add(mStreamingRoute);
int supportMask = calculateSupportedRouteMask();
if ((supportMask & CallAudioState.ROUTE_SPEAKER) != 0) {
@@ -225,6 +386,7 @@
@Override
public void setCallAudioManager(CallAudioManager callAudioManager) {
+ mCallAudioManager = callAudioManager;
}
@Override
@@ -271,6 +433,9 @@
}
private void routeTo(boolean active, AudioRoute destRoute) {
+ if (!destRoute.equals(mStreamingRoute) && !getAvailableRoutes().contains(destRoute)) {
+ return;
+ }
if (mIsPending) {
if (destRoute.equals(mPendingAudioRoute.getDestRoute()) && (mIsActive == active)) {
return;
@@ -280,22 +445,24 @@
mPendingAudioRoute.getDestRoute(), mIsActive, destRoute, active);
// override pending route while keep waiting for still pending messages for the
// previous pending route
+ mIsActive = active;
mPendingAudioRoute.setOrigRoute(mIsActive, mPendingAudioRoute.getDestRoute());
mPendingAudioRoute.setDestRoute(active, destRoute);
} else {
- if (mCurrentRoute.equals(destRoute) && (mIsActive = active)) {
+ if (mCurrentRoute.equals(destRoute) && (mIsActive == active)) {
return;
}
Log.i(this, "Enter pending route, orig%s(active=%b), dest%s(active=%b)", mCurrentRoute,
mIsActive, destRoute, active);
// route to pending route
- if (mAvailableRoutes.contains(mCurrentRoute)) {
+ if (getAvailableRoutes().contains(mCurrentRoute)) {
mPendingAudioRoute.setOrigRoute(mIsActive, mCurrentRoute);
} else {
// Avoid waiting for pending messages for an unavailable route
mPendingAudioRoute.setOrigRoute(mIsActive, DUMMY_ROUTE);
}
mPendingAudioRoute.setDestRoute(active, destRoute);
+ mIsActive = active;
mIsPending = true;
}
mPendingAudioRoute.evaluatePendingState();
@@ -309,6 +476,103 @@
Message.obtain(mHandler, PENDING_ROUTE_TIMEOUT)), TIMEOUT_LIMIT);
}
+ private void handleWiredHeadsetConnected() {
+ AudioRoute wiredHeadsetRoute = null;
+ try {
+ wiredHeadsetRoute = mAudioRouteFactory.create(AudioRoute.TYPE_WIRED, null,
+ mAudioManager);
+ } catch (IllegalArgumentException e) {
+ Log.e(this, e, "Can't find available audio device info for route type:"
+ + AudioRoute.DEVICE_TYPE_STRINGS.get(AudioRoute.TYPE_WIRED));
+ }
+
+ if (wiredHeadsetRoute != null) {
+ mAvailableRoutes.add(wiredHeadsetRoute);
+ mAvailableRoutes.remove(mEarpieceWiredRoute);
+ mTypeRoutes.put(AudioRoute.TYPE_WIRED, wiredHeadsetRoute);
+ mEarpieceWiredRoute = wiredHeadsetRoute;
+ routeTo(mIsActive, wiredHeadsetRoute);
+ onAvailableRoutesChanged();
+ }
+ }
+
+ public void handleWiredHeadsetDisconnected() {
+ // Update audio route states
+ AudioRoute wiredHeadsetRoute = mTypeRoutes.remove(AudioRoute.TYPE_WIRED);
+ if (wiredHeadsetRoute != null) {
+ mAvailableRoutes.remove(wiredHeadsetRoute);
+ mEarpieceWiredRoute = null;
+ }
+ AudioRoute earpieceRoute = mTypeRoutes.get(AudioRoute.TYPE_EARPIECE);
+ if (earpieceRoute != null) {
+ mAvailableRoutes.add(earpieceRoute);
+ mEarpieceWiredRoute = earpieceRoute;
+ }
+ onAvailableRoutesChanged();
+
+ // Route to expected state
+ if (mCurrentRoute.equals(wiredHeadsetRoute)) {
+ routeTo(mIsActive, getBaseRoute(true));
+ }
+ }
+
+ private void handleDockConnected() {
+ AudioRoute dockRoute = null;
+ try {
+ dockRoute = mAudioRouteFactory.create(AudioRoute.TYPE_DOCK, null, mAudioManager);
+ } catch (IllegalArgumentException e) {
+ Log.e(this, e, "Can't find available audio device info for route type:"
+ + AudioRoute.DEVICE_TYPE_STRINGS.get(AudioRoute.TYPE_WIRED));
+ }
+
+ if (dockRoute != null) {
+ mAvailableRoutes.add(dockRoute);
+ mAvailableRoutes.remove(mSpeakerDockRoute);
+ mTypeRoutes.put(AudioRoute.TYPE_DOCK, dockRoute);
+ mSpeakerDockRoute = dockRoute;
+ routeTo(mIsActive, dockRoute);
+ onAvailableRoutesChanged();
+ }
+ }
+
+ public void handleDockDisconnected() {
+ // Update audio route states
+ AudioRoute dockRoute = mTypeRoutes.get(AudioRoute.TYPE_DOCK);
+ if (dockRoute != null) {
+ mAvailableRoutes.remove(dockRoute);
+ mSpeakerDockRoute = null;
+ }
+ AudioRoute speakerRoute = mTypeRoutes.get(AudioRoute.TYPE_SPEAKER);
+ if (speakerRoute != null) {
+ mAvailableRoutes.add(speakerRoute);
+ mSpeakerDockRoute = speakerRoute;
+ }
+ onAvailableRoutesChanged();
+
+ // Route to expected state
+ if (mCurrentRoute.equals(dockRoute)) {
+ routeTo(mIsActive, getBaseRoute(true));
+ }
+ }
+
+ private void handleStreamingEnabled() {
+ if (!mCurrentRoute.equals(mStreamingRoute)) {
+ routeTo(mIsActive, mStreamingRoute);
+ } else {
+ Log.i(this, "ignore enable streaming, already in streaming");
+ }
+ }
+
+ private void handleStreamingDisabled() {
+ if (mCurrentRoute.equals(mStreamingRoute)) {
+ mCurrentRoute = DUMMY_ROUTE;
+ onAvailableRoutesChanged();
+ routeTo(mIsActive, getBaseRoute(true));
+ } else {
+ Log.i(this, "ignore disable streaming, not in streaming");
+ }
+ }
+
private void handleBtAudioActive(BluetoothDevice bluetoothDevice) {
if (mIsPending) {
if (Objects.equals(mPendingAudioRoute.getDestRoute().getBluetoothAddress(),
@@ -360,13 +624,7 @@
// Fallback to an available route
if (Objects.equals(mCurrentRoute, bluetoothRoute)) {
- // fallback policy
- AudioRoute destRoute = getPreferredAudioRouteFromStrategy();
- if (destRoute != null && mAvailableRoutes.contains(destRoute)) {
- routeTo(mIsActive, destRoute);
- } else {
- routeTo(mIsActive, getPreferredAudioRouteFromDefault(true/* includeBluetooth */));
- }
+ routeTo(mIsActive, getBaseRoute(false));
}
}
@@ -384,11 +642,142 @@
if ((mIsPending && mPendingAudioRoute.getDestRoute().getType() == type)
|| (!mIsPending && mCurrentRoute.getType() == type)) {
// Fallback to an available route
- AudioRoute destRoute = getPreferredAudioRouteFromStrategy();
- if (destRoute != null && mAvailableRoutes.contains(destRoute)) {
- routeTo(mIsActive, destRoute);
+ routeTo(mIsActive, getBaseRoute(true));
+ }
+ }
+
+ private void handleMuteChanged(boolean mute) {
+ mIsMute = mute;
+ if (mIsMute != mAudioManager.isMasterMute() && mIsActive) {
+ IAudioService audioService = mAudioServiceFactory.getAudioService();
+ Log.i(this, "changing microphone mute state to: %b [serviceIsNull=%b]", mute,
+ audioService == null);
+ if (audioService != null) {
+ try {
+ audioService.setMicrophoneMute(mute, mContext.getOpPackageName(),
+ mCallsManager.getCurrentUserHandle().getIdentifier(),
+ mContext.getAttributionTag());
+ } catch (RemoteException e) {
+ Log.e(this, e, "Remote exception while toggling mute.");
+ return;
+ }
+ }
+ }
+ onMuteStateChanged(mIsMute);
+ }
+
+ private void handleSwitchFocus(int focus) {
+ mFocusType = focus;
+ switch (focus) {
+ case NO_FOCUS -> {
+ if (mIsActive) {
+ handleMuteChanged(false);
+ routeTo(false, mCurrentRoute);
+ }
+ }
+ case ACTIVE_FOCUS -> {
+ if (!mIsActive) {
+ routeTo(true, getBaseRoute(true));
+ }
+ }
+ case RINGING_FOCUS -> {
+ if (!mIsActive) {
+ AudioRoute route = getBaseRoute(true);
+ BluetoothDevice device = mBluetoothRoutes.get(route);
+ if (device != null && !mBluetoothRouteManager.isInbandRingEnabled(device)) {
+ routeTo(false, route);
+ } else {
+ routeTo(true, route);
+ }
+ } else {
+ // active
+ BluetoothDevice device = mBluetoothRoutes.get(mCurrentRoute);
+ if (device != null && !mBluetoothRouteManager.isInbandRingEnabled(device)) {
+ routeTo(false, mCurrentRoute);
+ }
+ }
+ }
+ }
+ }
+
+ public void handleSwitchEarpiece() {
+ AudioRoute earpieceRoute = mTypeRoutes.get(AudioRoute.TYPE_EARPIECE);
+ if (earpieceRoute != null && getAvailableRoutes().contains(earpieceRoute)) {
+ routeTo(mIsActive, earpieceRoute);
+ } else {
+ Log.i(this, "ignore switch earpiece request");
+ }
+ }
+
+ private void handleSwitchBluetooth(String address) {
+ Log.i(this, "handle switch to bluetooth with address %s", address);
+ AudioRoute bluetoothRoute = null;
+ BluetoothDevice bluetoothDevice = null;
+ for (AudioRoute route : getAvailableRoutes()) {
+ if (Objects.equals(address, route.getBluetoothAddress())) {
+ bluetoothRoute = route;
+ bluetoothDevice = mBluetoothRoutes.get(route);
+ break;
+ }
+ }
+
+ if (bluetoothRoute != null && bluetoothDevice != null) {
+ if (mFocusType == RINGING_FOCUS) {
+ routeTo(mBluetoothRouteManager.isInbandRingEnabled(bluetoothDevice) && mIsActive,
+ bluetoothRoute);
} else {
- routeTo(mIsActive, getPreferredAudioRouteFromDefault(false/* includeBluetooth */));
+ routeTo(mIsActive, bluetoothRoute);
+ }
+ } else {
+ Log.i(this, "ignore switch bluetooth request");
+ }
+ }
+
+ private void handleSwitchHeadset() {
+ AudioRoute headsetRoute = mTypeRoutes.get(AudioRoute.TYPE_WIRED);
+ if (headsetRoute != null && getAvailableRoutes().contains(headsetRoute)) {
+ routeTo(mIsActive, headsetRoute);
+ } else {
+ Log.i(this, "ignore switch speaker request");
+ }
+ }
+
+ private void handleSwitchSpeaker() {
+ if (mSpeakerDockRoute != null && getAvailableRoutes().contains(mSpeakerDockRoute)) {
+ routeTo(mIsActive, mSpeakerDockRoute);
+ } else {
+ Log.i(this, "ignore switch speaker request");
+ }
+ }
+
+ private void handleSwitchBaselineRoute() {
+ routeTo(mIsActive, getBaseRoute(true));
+ }
+
+ private void handleSpeakerOn() {
+ if (isPending()) {
+ mPendingAudioRoute.onMessageReceived(SPEAKER_ON);
+ } else {
+ if (mSpeakerDockRoute != null && getAvailableRoutes().contains(mSpeakerDockRoute)) {
+ 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
+ if (mIsActive) {
+ sendMessageWithSessionInfo(SPEAKER_ON);
+ }
+ }
+ }
+ }
+
+ private void handleSpeakerOff() {
+ if (isPending()) {
+ mPendingAudioRoute.onMessageReceived(SPEAKER_OFF);
+ } else if (mCurrentRoute.getType() == AudioRoute.TYPE_SPEAKER) {
+ routeTo(mIsActive, getBaseRoute(true));
+ // 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
+ if (mIsActive) {
+ sendMessageWithSessionInfo(SPEAKER_OFF);
}
onAvailableRoutesChanged();
}
@@ -397,39 +786,53 @@
public void handleExitPendingRoute() {
if (mIsPending) {
Log.i(this, "Exit pending route and enter %s(active=%b)",
- mPendingAudioRoute.getDestRoute(), mPendingAudioRoute.isActive());
+ mPendingAudioRoute.getDestRoute(), mIsActive);
mCurrentRoute = mPendingAudioRoute.getDestRoute();
- mIsActive = mPendingAudioRoute.isActive();
mIsPending = false;
onCurrentRouteChanged();
}
}
private void onCurrentRouteChanged() {
- BluetoothDevice activeBluetoothDevice = null;
- int route = ROUTE_MAP.get(mCurrentRoute.getType());
- if (route == CallAudioState.ROUTE_BLUETOOTH) {
- activeBluetoothDevice = mBluetoothRoutes.get(mCurrentRoute);
+ synchronized (mLock) {
+ BluetoothDevice activeBluetoothDevice = null;
+ int route = ROUTE_MAP.get(mCurrentRoute.getType());
+ if (route == CallAudioState.ROUTE_STREAMING) {
+ updateCallAudioState(new CallAudioState(mIsMute, route, route));
+ return;
+ }
+ if (route == CallAudioState.ROUTE_BLUETOOTH) {
+ activeBluetoothDevice = mBluetoothRoutes.get(mCurrentRoute);
+ }
+ updateCallAudioState(new CallAudioState(mIsMute, route,
+ mCallAudioState.getRawSupportedRouteMask(), activeBluetoothDevice,
+ mCallAudioState.getSupportedBluetoothDevices()));
}
- updateCallAudioState(new CallAudioState(mIsMute, route,
- mCallAudioState.getSupportedRouteMask(), activeBluetoothDevice,
- mCallAudioState.getSupportedBluetoothDevices()));
}
private void onAvailableRoutesChanged() {
- int routeMask = 0;
- Set<BluetoothDevice> availableBluetoothDevices = new HashSet<>();
- for (AudioRoute route : mAvailableRoutes) {
- routeMask |= ROUTE_MAP.get(route.getType());
- if (BT_AUDIO_ROUTE_TYPES.contains(route.getType())) {
- availableBluetoothDevices.add(mBluetoothRoutes.get(route));
+ synchronized (mLock) {
+ int routeMask = 0;
+ Set<BluetoothDevice> availableBluetoothDevices = new HashSet<>();
+ for (AudioRoute route : getAvailableRoutes()) {
+ routeMask |= ROUTE_MAP.get(route.getType());
+ if (BT_AUDIO_ROUTE_TYPES.contains(route.getType())) {
+ availableBluetoothDevices.add(mBluetoothRoutes.get(route));
+ }
}
+ updateCallAudioState(new CallAudioState(mIsMute, mCallAudioState.getRoute(), routeMask,
+ mCallAudioState.getActiveBluetoothDevice(), availableBluetoothDevices));
}
- updateCallAudioState(new CallAudioState(mIsMute, mCallAudioState.getRoute(), routeMask,
- mCallAudioState.getActiveBluetoothDevice(), availableBluetoothDevices));
+ }
+
+ private void onMuteStateChanged(boolean mute) {
+ updateCallAudioState(new CallAudioState(mute, mCallAudioState.getRoute(),
+ mCallAudioState.getSupportedRouteMask(), mCallAudioState.getActiveBluetoothDevice(),
+ mCallAudioState.getSupportedBluetoothDevices()));
}
private void updateCallAudioState(CallAudioState callAudioState) {
+ Log.i(this, "updateCallAudioState: " + callAudioState);
CallAudioState oldState = mCallAudioState;
mCallAudioState = callAudioState;
mCallsManager.onCallAudioStateChanged(oldState, mCallAudioState);
@@ -468,7 +871,7 @@
}
}
- public AudioRoute getPreferredAudioRouteFromDefault(boolean includeBluetooth) {
+ private AudioRoute getPreferredAudioRouteFromDefault(boolean includeBluetooth) {
if (mBluetoothRoutes.isEmpty() || !includeBluetooth) {
return mEarpieceWiredRoute != null ? mEarpieceWiredRoute : mSpeakerDockRoute;
} else {
@@ -495,8 +898,13 @@
return routeMask;
}
+ @VisibleForTesting
public Set<AudioRoute> getAvailableRoutes() {
- return mAvailableRoutes;
+ if (mCurrentRoute.equals(mStreamingRoute)) {
+ return mStreamingRoutes;
+ } else {
+ return mAvailableRoutes;
+ }
}
public AudioRoute getCurrentRoute() {
@@ -513,6 +921,17 @@
return null;
}
+ public AudioRoute getBaseRoute(boolean includeBluetooth) {
+ AudioRoute destRoute = getPreferredAudioRouteFromStrategy();
+ if (destRoute == null) {
+ destRoute = getPreferredAudioRouteFromDefault(includeBluetooth);
+ }
+ if (destRoute != null && !getAvailableRoutes().contains(destRoute)) {
+ destRoute = null;
+ }
+ return destRoute;
+ }
+
@VisibleForTesting
public void setAudioManager(AudioManager audioManager) {
mAudioManager = audioManager;
@@ -525,6 +944,11 @@
@VisibleForTesting
public void setActive(boolean active) {
+ if (active) {
+ mFocusType = ACTIVE_FOCUS;
+ } else {
+ mFocusType = NO_FOCUS;
+ }
mIsActive = active;
}
}
diff --git a/src/com/android/server/telecom/CallLogManager.java b/src/com/android/server/telecom/CallLogManager.java
index 6a9977d..f6360a3 100644
--- a/src/com/android/server/telecom/CallLogManager.java
+++ b/src/com/android/server/telecom/CallLogManager.java
@@ -54,6 +54,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.telecom.callfiltering.CallFilteringResult;
import com.android.server.telecom.flags.FeatureFlags;
+import com.android.server.telecom.flags.Flags;
import java.util.Arrays;
import java.util.Locale;
@@ -418,7 +419,12 @@
paramBuilder.setCallType(callLogType);
paramBuilder.setIsRead(call.isSelfManaged());
paramBuilder.setMissedReason(call.getMissedReason());
-
+ if (Flags.businessCallComposer() && call.getExtras() != null) {
+ paramBuilder.setIsBusinessCall(call.getExtras().getBoolean(
+ android.telecom.Call.EXTRA_IS_BUSINESS_CALL, false));
+ paramBuilder.setBusinessName(call.getExtras().getString(
+ android.telecom.Call.EXTRA_ASSERTED_DISPLAY_NAME, ""));
+ }
sendAddCallBroadcast(callLogType, call.getAgeMillis());
boolean okayToLog =
diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java
index aa8eb57..ae096af 100644
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -79,7 +79,7 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.BlockedNumberContract;
-import android.provider.BlockedNumberContract.SystemContract;
+import android.provider.BlockedNumberContract.BlockedNumbers;
import android.provider.CallLog.Calls;
import android.provider.Settings;
import android.sysprop.TelephonyProperties;
@@ -118,6 +118,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.IntentForwarderActivity;
import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.telecom.bluetooth.BluetoothDeviceManager;
import com.android.server.telecom.bluetooth.BluetoothRouteManager;
import com.android.server.telecom.bluetooth.BluetoothStateReceiver;
import com.android.server.telecom.callfiltering.BlockCheckerAdapter;
@@ -556,7 +557,7 @@
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED.equals(action)
- || SystemContract.ACTION_BLOCK_SUPPRESSION_STATE_CHANGED.equals(action)) {
+ || BlockedNumbers.ACTION_BLOCK_SUPPRESSION_STATE_CHANGED.equals(action)) {
updateEmergencyCallNotificationAsync(context);
}
}
@@ -607,6 +608,7 @@
EmergencyCallDiagnosticLogger emergencyCallDiagnosticLogger,
CallAudioCommunicationDeviceTracker communicationDeviceTracker,
CallStreamingNotification callStreamingNotification,
+ BluetoothDeviceManager bluetoothDeviceManager,
FeatureFlags featureFlags,
IncomingCallFilterGraphProvider incomingCallFilterGraphProvider) {
@@ -632,6 +634,9 @@
mDtmfLocalTonePlayer =
new DtmfLocalTonePlayer(new DtmfLocalTonePlayer.ToneGeneratorProxy());
CallAudioRouteAdapter callAudioRouteAdapter;
+ // TODO: add another flag check when
+ // bluetoothDeviceManager.getBluetoothHeadset().isScoManagedByAudio()
+ // available and return true
if (!featureFlags.useRefactoredAudioRouteSwitching()) {
callAudioRouteAdapter = callAudioRouteStateMachineFactory.create(
context,
@@ -646,8 +651,8 @@
featureFlags
);
} else {
- callAudioRouteAdapter = new CallAudioRouteController(
- context, this, new AudioRoute.Factory(), wiredHeadsetManager);
+ callAudioRouteAdapter = new CallAudioRouteController(context, this, audioServiceFactory,
+ new AudioRoute.Factory(), wiredHeadsetManager, mBluetoothRouteManager);
}
callAudioRouteAdapter.initialize();
bluetoothStateReceiver.setCallAudioRouteAdapter(callAudioRouteAdapter);
@@ -752,7 +757,7 @@
IntentFilter intentFilter = new IntentFilter(
CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
intentFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
- intentFilter.addAction(SystemContract.ACTION_BLOCK_SUPPRESSION_STATE_CHANGED);
+ intentFilter.addAction(BlockedNumbers.ACTION_BLOCK_SUPPRESSION_STATE_CHANGED);
context.registerReceiver(mReceiver, intentFilter, Context.RECEIVER_EXPORTED);
mGraphHandlerThreads = new LinkedList<>();
@@ -2927,7 +2932,7 @@
if (call.isEmergencyCall()) {
Executors.defaultThreadFactory().newThread(() ->
- BlockedNumberContract.SystemContract.notifyEmergencyContact(mContext))
+ BlockedNumberContract.BlockedNumbers.notifyEmergencyContact(mContext))
.start();
}
diff --git a/src/com/android/server/telecom/EmergencyCallDiagnosticLogger.java b/src/com/android/server/telecom/EmergencyCallDiagnosticLogger.java
index b8f5239..f1a4a97 100644
--- a/src/com/android/server/telecom/EmergencyCallDiagnosticLogger.java
+++ b/src/com/android/server/telecom/EmergencyCallDiagnosticLogger.java
@@ -404,7 +404,12 @@
Log.i(this, "skipped dumping diagnostic data");
return;
}
- dumpDiagnosticDataFromDropbox(pw);
+ try {
+ dumpDiagnosticDataFromDropbox(pw);
+ } catch (Exception e) {
+ pw.println("Exception was thrown while dumping diagnostic data from DropBox");
+ e.printStackTrace();
+ }
}
private static class CallEventTimestamps {
diff --git a/src/com/android/server/telecom/ParcelableCallUtils.java b/src/com/android/server/telecom/ParcelableCallUtils.java
index c77e605..3573de8 100644
--- a/src/com/android/server/telecom/ParcelableCallUtils.java
+++ b/src/com/android/server/telecom/ParcelableCallUtils.java
@@ -63,6 +63,7 @@
RESTRICTED_CALL_SCREENING_EXTRA_KEYS = new ArrayList<>();
RESTRICTED_CALL_SCREENING_EXTRA_KEYS.add(android.telecom.Connection.EXTRA_SIP_INVITE);
RESTRICTED_CALL_SCREENING_EXTRA_KEYS.add(ImsCallProfile.EXTRA_IS_BUSINESS_CALL);
+ RESTRICTED_CALL_SCREENING_EXTRA_KEYS.add(ImsCallProfile.EXTRA_ASSERTED_DISPLAY_NAME);
}
public static class Converter {
diff --git a/src/com/android/server/telecom/PendingAudioRoute.java b/src/com/android/server/telecom/PendingAudioRoute.java
index 5fa3048..8de62ed 100644
--- a/src/com/android/server/telecom/PendingAudioRoute.java
+++ b/src/com/android/server/telecom/PendingAudioRoute.java
@@ -76,9 +76,7 @@
public void onMessageReceived(int message) {
if (message == PENDING_ROUTE_FAILED) {
// Fallback to base route
- //TODO: Replace getPreferredAudioRouteFromDefault by getBaseRoute when available and
- // make the replaced one private
- mDestRoute = mCallAudioRouteController.getPreferredAudioRouteFromDefault(true);
+ mDestRoute = mCallAudioRouteController.getBaseRoute(true);
mCallAudioRouteController.sendMessageWithSessionInfo(
CallAudioRouteAdapter.EXIT_PENDING_ROUTE);
}
diff --git a/src/com/android/server/telecom/TelecomServiceImpl.java b/src/com/android/server/telecom/TelecomServiceImpl.java
index 2932fb7..aa721d4 100644
--- a/src/com/android/server/telecom/TelecomServiceImpl.java
+++ b/src/com/android/server/telecom/TelecomServiceImpl.java
@@ -77,7 +77,6 @@
import com.android.internal.telecom.ICallControl;
import com.android.internal.telecom.ICallEventCallback;
import com.android.internal.telecom.ITelecomService;
-import com.android.internal.telephony.flags.Flags;
import com.android.internal.util.IndentingPrintWriter;
import com.android.server.telecom.components.UserCallIntentProcessorFactory;
import com.android.server.telecom.flags.FeatureFlags;
@@ -239,6 +238,9 @@
callEventCallback, mCallsManager, call);
call.setTransactionServiceWrapper(serviceWrapper);
+ if (mFeatureFlags.transactionalVideoState()) {
+ call.setTransactionalCallSupportsVideoCalling(callAttributes);
+ }
ICallControl clientCallControl = serviceWrapper.getICallControl();
if (clientCallControl == null) {
@@ -600,6 +602,53 @@
}
@Override
+ public ParceledListSlice<PhoneAccount> getRegisteredPhoneAccounts(String callingPackage,
+ String callingFeatureId) {
+ try {
+ Log.startSession("TSI.gRPA", Log.getPackageAbbreviation(callingPackage));
+ try {
+ enforceCallingPackage(callingPackage, "getRegisteredPhoneAccounts");
+ } catch (SecurityException se) {
+ EventLog.writeEvent(0x534e4554, "307609763", Binder.getCallingUid(),
+ "getRegisteredPhoneAccounts: invalid calling package");
+ throw se;
+ }
+
+ boolean hasCrossUserAccess = false;
+ try {
+ enforceInAppCrossUserPermission();
+ hasCrossUserAccess = true;
+ } catch (SecurityException e) {
+ // pass through
+ }
+
+ synchronized (mLock) {
+ final UserHandle callingUserHandle = Binder.getCallingUserHandle();
+ long token = Binder.clearCallingIdentity();
+ try {
+ return new ParceledListSlice<>(
+ mPhoneAccountRegistrar.getPhoneAccounts(
+ 0 /* capabilities */,
+ 0 /* excludedCapabilities */,
+ null /* UriScheme */,
+ callingPackage,
+ true /* includeDisabledAccounts */,
+ callingUserHandle,
+ hasCrossUserAccess /* crossUserAccess */,
+ false /* includeAll */));
+ } catch (Exception e) {
+ Log.e(this, e, "getRegisteredPhoneAccounts");
+ throw e;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+ } finally {
+ Log.endSession();
+ }
+ }
+
+ @Override
public int getAllPhoneAccountsCount() {
try {
Log.startSession("TSI.gAPAC");
@@ -787,6 +836,9 @@
Bundle extras = account.getExtras();
if (extras != null
&& extras.getBoolean(PhoneAccount.EXTRA_SKIP_CALL_FILTERING)) {
+ // System apps should be granted the MODIFY_PHONE_STATE permission.
+ enforceModifyPermission(
+ "registerPhoneAccount requires MODIFY_PHONE_STATE permission.");
enforceRegisterSkipCallFiltering();
}
final int callingUid = Binder.getCallingUid();
@@ -1951,7 +2003,7 @@
synchronized (mLock) {
long token = Binder.clearCallingIdentity();
try {
- BlockedNumberContract.SystemContract.endBlockSuppression(mContext);
+ BlockedNumberContract.BlockedNumbers.endBlockSuppression(mContext);
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -3119,7 +3171,6 @@
+ " does not meet the requirements to access the phone number");
}
-
private boolean canReadPrivilegedPhoneState(String callingPackage, String message) {
// The system/default dialer can always read phone state - so that emergency calls will
// still work.
diff --git a/src/com/android/server/telecom/TelecomSystem.java b/src/com/android/server/telecom/TelecomSystem.java
index 9f6fcba..b4c3a4d 100644
--- a/src/com/android/server/telecom/TelecomSystem.java
+++ b/src/com/android/server/telecom/TelecomSystem.java
@@ -425,6 +425,7 @@
emergencyCallDiagnosticLogger,
communicationDeviceTracker,
callStreamingNotification,
+ bluetoothDeviceManager,
featureFlags,
IncomingCallFilterGraph::new);
diff --git a/src/com/android/server/telecom/TransactionalServiceWrapper.java b/src/com/android/server/telecom/TransactionalServiceWrapper.java
index 98f2c9c..d497c6a 100644
--- a/src/com/android/server/telecom/TransactionalServiceWrapper.java
+++ b/src/com/android/server/telecom/TransactionalServiceWrapper.java
@@ -43,10 +43,10 @@
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.ParallelTransaction;
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;
@@ -71,6 +71,7 @@
public static final String ANSWER = "Answer";
public static final String DISCONNECT = "Disconnect";
public static final String START_STREAMING = "StartStreaming";
+ public static final String REQUEST_VIDEO_STATE = "RequestVideoState";
// CallEventCallback : Telecom --> Client (ex. voip app)
public static final String ON_SET_ACTIVE = "onSetActive";
@@ -249,6 +250,17 @@
}
}
+ @Override
+ public void requestVideoState(int videoState, String callId, ResultReceiver callback)
+ throws RemoteException {
+ try {
+ Log.startSession("TSW.rVS");
+ createTransactions(callId, callback, REQUEST_VIDEO_STATE, videoState);
+ } finally {
+ Log.endSession();
+ }
+ }
+
private void createTransactions(String callId, ResultReceiver callback, String action,
Object... objects) {
Log.d(TAG, "createTransactions: callId=" + callId);
@@ -275,6 +287,11 @@
addTransactionsToManager(mStreamingController.getStartStreamingTransaction(mCallsManager,
TransactionalServiceWrapper.this, call, mLock), callback);
break;
+ case REQUEST_VIDEO_STATE:
+ addTransactionsToManager(
+ new RequestVideoStateTransaction(mCallsManager, call,
+ (int) objects[0]), callback);
+ break;
}
} else {
Bundle exceptionBundle = new Bundle();
@@ -563,6 +580,15 @@
}
}
+ public void onVideoStateChanged(Call call, int videoState) {
+ if (call != null) {
+ try {
+ mICallEventCallback.onVideoStateChanged(call.getId(), videoState);
+ } catch (RemoteException e) {
+ }
+ }
+ }
+
public void removeCallFromWrappers(Call call) {
if (call != null) {
try {
diff --git a/src/com/android/server/telecom/bluetooth/BluetoothDeviceManager.java b/src/com/android/server/telecom/bluetooth/BluetoothDeviceManager.java
index 27e5a7d..a0ffe63 100644
--- a/src/com/android/server/telecom/bluetooth/BluetoothDeviceManager.java
+++ b/src/com/android/server/telecom/bluetooth/BluetoothDeviceManager.java
@@ -25,9 +25,8 @@
import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothStatusCodes;
import android.content.Context;
-import android.media.AudioManager;
import android.media.AudioDeviceInfo;
-import android.media.audio.common.AudioDevice;
+import android.media.AudioManager;
import android.os.Bundle;
import android.telecom.Log;
import android.util.ArraySet;
@@ -41,13 +40,17 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
-import java.util.concurrent.Executor;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
public class BluetoothDeviceManager {
@@ -98,6 +101,9 @@
synchronized (mLock) {
String logString;
if (profile == BluetoothProfile.HEADSET) {
+ if (mFeatureFlags.useRefactoredAudioRouteSwitching()) {
+ mBluetoothHeadsetFuture.complete((BluetoothHeadset) proxy);
+ }
mBluetoothHeadset = (BluetoothHeadset) proxy;
logString = "Got BluetoothHeadset: " + mBluetoothHeadset;
} else if (profile == BluetoothProfile.HEARING_AID) {
@@ -137,6 +143,9 @@
LinkedHashMap<String, BluetoothDevice> lostServiceDevices;
String logString;
if (profile == BluetoothProfile.HEADSET) {
+ if (mFeatureFlags.useRefactoredAudioRouteSwitching()) {
+ mBluetoothHeadsetFuture.complete(null);
+ }
mBluetoothHeadset = null;
lostServiceDevices = mHfpDevicesByAddress;
mBluetoothRouteManager.onActiveDeviceChanged(null,
@@ -201,6 +210,7 @@
private BluetoothRouteManager mBluetoothRouteManager;
private BluetoothHeadset mBluetoothHeadset;
+ private CompletableFuture<BluetoothHeadset> mBluetoothHeadsetFuture;
private BluetoothHearingAid mBluetoothHearingAid;
private boolean mLeAudioCallbackRegistered = false;
private BluetoothLeAudio mBluetoothLeAudioService;
@@ -218,8 +228,12 @@
public BluetoothDeviceManager(Context context, BluetoothAdapter bluetoothAdapter,
CallAudioCommunicationDeviceTracker communicationDeviceTracker,
FeatureFlags featureFlags) {
+ mFeatureFlags = featureFlags;
if (bluetoothAdapter != null) {
mBluetoothAdapter = bluetoothAdapter;
+ if (mFeatureFlags.useRefactoredAudioRouteSwitching()) {
+ mBluetoothHeadsetFuture = new CompletableFuture<>();
+ }
bluetoothAdapter.getProfileProxy(context, mBluetoothProfileServiceListener,
BluetoothProfile.HEADSET);
bluetoothAdapter.getProfileProxy(context, mBluetoothProfileServiceListener,
@@ -229,7 +243,6 @@
mAudioManager = context.getSystemService(AudioManager.class);
mExecutor = context.getMainExecutor();
mCommunicationDeviceTracker = communicationDeviceTracker;
- mFeatureFlags = featureFlags;
}
}
@@ -333,7 +346,19 @@
}
public BluetoothHeadset getBluetoothHeadset() {
- return mBluetoothHeadset;
+ if (mFeatureFlags.useRefactoredAudioRouteSwitching()) {
+ try {
+ mBluetoothHeadset = mBluetoothHeadsetFuture.get(500L,
+ TimeUnit.MILLISECONDS);
+ return mBluetoothHeadset;
+ } catch (TimeoutException | InterruptedException | ExecutionException e) {
+ // ignore
+ Log.w(this, "Acquire BluetoothHeadset service failed due to: " + e);
+ return null;
+ }
+ } else {
+ return mBluetoothHeadset;
+ }
}
public BluetoothAdapter getBluetoothAdapter() {
@@ -402,7 +427,7 @@
mHearingAidDeviceSyncIds.put(device, hiSyncId);
targetDeviceMap = mHearingAidDevicesByAddress;
} else if (deviceType == DEVICE_TYPE_HEADSET) {
- if (mBluetoothHeadset == null) {
+ if (getBluetoothHeadset() == null) {
Log.w(this, "Headset service null when receiving device added broadcast");
return;
}
@@ -465,7 +490,7 @@
}
public void disconnectSco() {
- if (mBluetoothHeadset == null) {
+ if (getBluetoothHeadset() == null) {
Log.w(this, "Trying to disconnect audio but no headset service exists.");
} else {
mBluetoothHeadset.disconnectAudio();
@@ -650,7 +675,7 @@
callProfile = BluetoothProfile.HEARING_AID;
} else if (mHfpDevicesByAddress.containsKey(address)) {
Log.i(this, "Telecomm found HFP device for address: " + address);
- if (mBluetoothHeadset == null) {
+ if (getBluetoothHeadset() == null) {
Log.w(this, "Attempting to turn on audio when the headset service is null");
return false;
}
@@ -707,9 +732,15 @@
Log.w(this, "Couldn't set active device to %s", address);
return false;
}
- int scoConnectionRequest = mBluetoothHeadset.connectAudio();
- return scoConnectionRequest == BluetoothStatusCodes.SUCCESS ||
- scoConnectionRequest == BluetoothStatusCodes.ERROR_AUDIO_DEVICE_ALREADY_CONNECTED;
+ if (getBluetoothHeadset() != null) {
+ int scoConnectionRequest = mBluetoothHeadset.connectAudio();
+ return scoConnectionRequest == BluetoothStatusCodes.SUCCESS ||
+ scoConnectionRequest
+ == BluetoothStatusCodes.ERROR_AUDIO_DEVICE_ALREADY_CONNECTED;
+ } else {
+ Log.w(this, "Couldn't find bluetooth headset service");
+ return false;
+ }
} else {
Log.w(this, "Attempting to turn on audio for a disconnected device");
return false;
@@ -739,16 +770,20 @@
// Get the inband ringing enabled status of expected BT device to route call audio instead
// of using the address of currently connected device.
BluetoothDevice activeDevice = mBluetoothRouteManager.getMostRecentlyReportedActiveDevice();
- Log.i(this, "isInbandRingingEnabled: activeDevice: " + activeDevice);
- if (mBluetoothRouteManager.isCachedLeAudioDevice(activeDevice)) {
+ return isInbandRingEnabled(activeDevice);
+ }
+
+ public boolean isInbandRingEnabled(BluetoothDevice bluetoothDevice) {
+ Log.i(this, "isInbandRingEnabled: device: " + bluetoothDevice);
+ if (mBluetoothRouteManager.isCachedLeAudioDevice(bluetoothDevice)) {
if (mBluetoothLeAudioService == null) {
Log.i(this, "isInbandRingingEnabled: no leaudio service available.");
return false;
}
- int groupId = mBluetoothLeAudioService.getGroupId(activeDevice);
+ int groupId = mBluetoothLeAudioService.getGroupId(bluetoothDevice);
return mBluetoothLeAudioService.isInbandRingtoneEnabled(groupId);
} else {
- if (mBluetoothHeadset == null) {
+ if (getBluetoothHeadset() == null) {
Log.i(this, "isInbandRingingEnabled: no headset service available.");
return false;
}
diff --git a/src/com/android/server/telecom/bluetooth/BluetoothRouteManager.java b/src/com/android/server/telecom/bluetooth/BluetoothRouteManager.java
index 235ba56..7da5339 100644
--- a/src/com/android/server/telecom/bluetooth/BluetoothRouteManager.java
+++ b/src/com/android/server/telecom/bluetooth/BluetoothRouteManager.java
@@ -957,6 +957,11 @@
return mDeviceManager.isInbandRingingEnabled();
}
+ @VisibleForTesting
+ public boolean isInbandRingEnabled(BluetoothDevice bluetoothDevice) {
+ return mDeviceManager.isInbandRingEnabled(bluetoothDevice);
+ }
+
private boolean addDevice(String address) {
if (mAudioConnectingStates.containsKey(address)) {
Log.i(this, "Attempting to add device %s twice.", address);
diff --git a/src/com/android/server/telecom/callfiltering/BlockCheckerAdapter.java b/src/com/android/server/telecom/callfiltering/BlockCheckerAdapter.java
index a83f314..1fda542 100644
--- a/src/com/android/server/telecom/callfiltering/BlockCheckerAdapter.java
+++ b/src/com/android/server/telecom/callfiltering/BlockCheckerAdapter.java
@@ -34,22 +34,24 @@
*
* @param context the context of the caller.
* @param phoneNumber the number to check.
- * @param extras the extra attribute of the number.
+ * @param numberPresentation the presentation code associated with the call.
+ * @param isNumberInContacts indicates if the provided number exists as a contact.
* @return result code indicating if the number should be blocked, and if so why.
- * Valid values are: {@link BlockedNumberContract#STATUS_NOT_BLOCKED},
- * {@link BlockedNumberContract#STATUS_BLOCKED_IN_LIST},
- * {@link BlockedNumberContract#STATUS_BLOCKED_NOT_IN_CONTACTS},
- * {@link BlockedNumberContract#STATUS_BLOCKED_PAYPHONE},
- * {@link BlockedNumberContract#STATUS_BLOCKED_RESTRICTED},
- * {@link BlockedNumberContract#STATUS_BLOCKED_UNKNOWN_NUMBER}.
+ * Valid values are: {@link BlockCheckerFilter#STATUS_NOT_BLOCKED},
+ * {@link BlockCheckerFilter#STATUS_BLOCKED_IN_LIST},
+ * {@link BlockCheckerFilter#STATUS_BLOCKED_NOT_IN_CONTACTS},
+ * {@link BlockCheckerFilter#STATUS_BLOCKED_PAYPHONE},
+ * {@link BlockCheckerFilter#STATUS_BLOCKED_RESTRICTED},
+ * {@link BlockCheckerFilter#STATUS_BLOCKED_UNKNOWN_NUMBER}.
*/
- public int getBlockStatus(Context context, String phoneNumber, Bundle extras) {
+ public int getBlockStatus(Context context, String phoneNumber,
+ int numberPresentation, boolean isNumberInContacts) {
int blockStatus = BlockedNumberContract.STATUS_NOT_BLOCKED;
long startTimeNano = System.nanoTime();
try {
- blockStatus = BlockedNumberContract.SystemContract.shouldSystemBlockNumber(
- context, phoneNumber, extras);
+ blockStatus = BlockedNumberContract.BlockedNumbers.shouldSystemBlockNumber(
+ context, phoneNumber, numberPresentation, isNumberInContacts);
if (blockStatus != BlockedNumberContract.STATUS_NOT_BLOCKED) {
Log.d(TAG, phoneNumber + " is blocked.");
}
diff --git a/src/com/android/server/telecom/callfiltering/BlockCheckerFilter.java b/src/com/android/server/telecom/callfiltering/BlockCheckerFilter.java
index 64060c8..5beb5f0 100644
--- a/src/com/android/server/telecom/callfiltering/BlockCheckerFilter.java
+++ b/src/com/android/server/telecom/callfiltering/BlockCheckerFilter.java
@@ -48,6 +48,61 @@
public static final long CALLER_INFO_QUERY_TIMEOUT = 5000;
+ /**
+ * Integer reason indicating whether a call was blocked, and if so why.
+ * @hide
+ */
+ public static final String RES_BLOCK_STATUS = "block_status";
+
+ /**
+ * Integer reason code used with {@link #RES_BLOCK_STATUS} to indicate that a call was not
+ * blocked.
+ * @hide
+ */
+ public static final int STATUS_NOT_BLOCKED = 0;
+
+ /**
+ * Integer reason code used with {@link #RES_BLOCK_STATUS} to indicate that a call was blocked
+ * because it is in the list of blocked numbers maintained by the provider.
+ * @hide
+ */
+ public static final int STATUS_BLOCKED_IN_LIST = 1;
+
+ /**
+ * Integer reason code used with {@link #RES_BLOCK_STATUS} to indicate that a call was blocked
+ * because it is from a restricted number.
+ * @hide
+ */
+ public static final int STATUS_BLOCKED_RESTRICTED = 2;
+
+ /**
+ * Integer reason code used with {@link #RES_BLOCK_STATUS} to indicate that a call was blocked
+ * because it is from an unknown number.
+ * @hide
+ */
+ public static final int STATUS_BLOCKED_UNKNOWN_NUMBER = 3;
+
+ /**
+ * Integer reason code used with {@link #RES_BLOCK_STATUS} to indicate that a call was blocked
+ * because it is from a pay phone.
+ * @hide
+ */
+ public static final int STATUS_BLOCKED_PAYPHONE = 4;
+
+ /**
+ * Integer reason code used with {@link #RES_BLOCK_STATUS} to indicate that a call was blocked
+ * because it is from a number not in the users contacts.
+ * @hide
+ */
+ public static final int STATUS_BLOCKED_NOT_IN_CONTACTS = 5;
+
+ /**
+ * Integer reason code used with {@link #RES_BLOCK_STATUS} to indicate that a call was blocked
+ * because it is from a number not available.
+ * @hide
+ */
+ public static final int STATUS_BLOCKED_UNAVAILABLE = 6;
+
public BlockCheckerFilter(Context context, Call call,
CallerInfoLookupHelper callerInfoLookupHelper,
BlockCheckerAdapter blockCheckerAdapter) {
@@ -96,14 +151,21 @@
private void getBlockStatus(
CompletableFuture<CallFilteringResult> resultFuture) {
- // Set extras
- Bundle extras = new Bundle();
+ // 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)) {
- int presentation = mCall.getHandlePresentation();
- extras.putInt(BlockedNumberContract.EXTRA_CALL_PRESENTATION, presentation);
- if (presentation == TelecomManager.PRESENTATION_ALLOWED) {
- extras.putBoolean(BlockedNumberContract.EXTRA_CONTACT_EXIST, mContactExists);
- }
+ presentation = mCall.getHandlePresentation();
+ } else {
+ presentation = 0;
+ }
+
+ if (presentation == TelecomManager.PRESENTATION_ALLOWED) {
+ isNumberInContacts = mContactExists;
+ } else {
+ isNumberInContacts = false;
}
// Set number
@@ -111,7 +173,8 @@
mCall.getHandle().getSchemeSpecificPart();
CompletableFuture.supplyAsync(
- () -> mBlockCheckerAdapter.getBlockStatus(mContext, number, extras),
+ () -> mBlockCheckerAdapter.getBlockStatus(mContext, number,
+ presentation, isNumberInContacts),
new LoggedHandlerExecutor(mHandler, "BCF.gBS", null))
.thenApplyAsync((x) -> completeResult(resultFuture, x),
new LoggedHandlerExecutor(mHandler, "BCF.gBS", null));
@@ -120,7 +183,7 @@
private int completeResult(CompletableFuture<CallFilteringResult> resultFuture,
int blockStatus) {
CallFilteringResult result;
- if (blockStatus != BlockedNumberContract.STATUS_NOT_BLOCKED) {
+ if (blockStatus != STATUS_NOT_BLOCKED) {
result = new CallFilteringResult.Builder()
.setShouldAllowCall(false)
.setShouldReject(true)
@@ -143,8 +206,7 @@
.build();
}
Log.addEvent(mCall, LogUtils.Events.BLOCK_CHECK_FINISHED,
- BlockedNumberContract.SystemContract.blockStatusToString(blockStatus) + " "
- + result);
+ blockStatusToString(blockStatus) + " " + result);
resultFuture.complete(result);
mHandlerThread.quitSafely();
return blockStatus;
@@ -152,20 +214,20 @@
private int getBlockReason(int blockStatus) {
switch (blockStatus) {
- case BlockedNumberContract.STATUS_BLOCKED_IN_LIST:
+ case STATUS_BLOCKED_IN_LIST:
return CallLog.Calls.BLOCK_REASON_BLOCKED_NUMBER;
- case BlockedNumberContract.STATUS_BLOCKED_UNKNOWN_NUMBER:
- case BlockedNumberContract.STATUS_BLOCKED_UNAVAILABLE:
+ case STATUS_BLOCKED_UNKNOWN_NUMBER:
+ case STATUS_BLOCKED_UNAVAILABLE:
return CallLog.Calls.BLOCK_REASON_UNKNOWN_NUMBER;
- case BlockedNumberContract.STATUS_BLOCKED_RESTRICTED:
+ case STATUS_BLOCKED_RESTRICTED:
return CallLog.Calls.BLOCK_REASON_RESTRICTED_NUMBER;
- case BlockedNumberContract.STATUS_BLOCKED_PAYPHONE:
+ case STATUS_BLOCKED_PAYPHONE:
return CallLog.Calls.BLOCK_REASON_PAY_PHONE;
- case BlockedNumberContract.STATUS_BLOCKED_NOT_IN_CONTACTS:
+ case STATUS_BLOCKED_NOT_IN_CONTACTS:
return CallLog.Calls.BLOCK_REASON_NOT_IN_CONTACTS;
default:
@@ -174,4 +236,27 @@
return CallLog.Calls.BLOCK_REASON_BLOCKED_NUMBER;
}
}
+
+ /**
+ * Converts a block status constant to a string equivalent for logging.
+ */
+ private String blockStatusToString(int blockStatus) {
+ switch (blockStatus) {
+ case STATUS_NOT_BLOCKED:
+ return "not blocked";
+ case STATUS_BLOCKED_IN_LIST:
+ return "blocked - in list";
+ case STATUS_BLOCKED_RESTRICTED:
+ return "blocked - restricted";
+ case STATUS_BLOCKED_UNKNOWN_NUMBER:
+ return "blocked - unknown";
+ case STATUS_BLOCKED_PAYPHONE:
+ return "blocked - payphone";
+ case STATUS_BLOCKED_NOT_IN_CONTACTS:
+ return "blocked - not in contacts";
+ case STATUS_BLOCKED_UNAVAILABLE:
+ return "blocked - unavailable";
+ }
+ return "unknown";
+ }
}
diff --git a/src/com/android/server/telecom/callfiltering/BlockedNumbersAdapter.java b/src/com/android/server/telecom/callfiltering/BlockedNumbersAdapter.java
index b8658d8..f640826 100644
--- a/src/com/android/server/telecom/callfiltering/BlockedNumbersAdapter.java
+++ b/src/com/android/server/telecom/callfiltering/BlockedNumbersAdapter.java
@@ -20,7 +20,7 @@
/**
* Adapter interface that wraps methods from
- * {@link android.provider.BlockedNumberContract.SystemContract} and
+ * {@link android.provider.BlockedNumberContract.BlockedNumbers} and
* {@link com.android.server.telecom.settings.BlockedNumbersUtil} to make things testable.
*/
public interface BlockedNumbersAdapter {
diff --git a/src/com/android/server/telecom/components/TelecomService.java b/src/com/android/server/telecom/components/TelecomService.java
index 9287d33..24e5d57 100644
--- a/src/com/android/server/telecom/components/TelecomService.java
+++ b/src/com/android/server/telecom/components/TelecomService.java
@@ -223,7 +223,7 @@
@Override
public boolean shouldShowEmergencyCallNotification(Context
context) {
- return BlockedNumberContract.SystemContract
+ return BlockedNumberContract.BlockedNumbers
.shouldShowEmergencyCallNotification(context);
}
diff --git a/src/com/android/server/telecom/settings/BlockedNumbersActivity.java b/src/com/android/server/telecom/settings/BlockedNumbersActivity.java
index 5fa5f06..819b270 100644
--- a/src/com/android/server/telecom/settings/BlockedNumbersActivity.java
+++ b/src/com/android/server/telecom/settings/BlockedNumbersActivity.java
@@ -155,7 +155,7 @@
}
};
IntentFilter blockStatusIntentFilter = new IntentFilter(
- BlockedNumberContract.SystemContract.ACTION_BLOCK_SUPPRESSION_STATE_CHANGED);
+ BlockedNumberContract.BlockedNumbers.ACTION_BLOCK_SUPPRESSION_STATE_CHANGED);
blockStatusIntentFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
registerReceiver(mBlockingStatusReceiver, blockStatusIntentFilter,
Context.RECEIVER_EXPORTED);
@@ -183,7 +183,8 @@
}
private void updateButterBar() {
- if (BlockedNumberContract.SystemContract.getBlockSuppressionStatus(this).isSuppressed) {
+ if (BlockedNumberContract.BlockedNumbers
+ .getBlockSuppressionStatus(this).getIsSuppressed()) {
mButterBar.setVisibility(View.VISIBLE);
} else {
mButterBar.setVisibility(View.GONE);
@@ -238,7 +239,7 @@
if (view == mAddButton) {
showAddBlockedNumberDialog();
} else if (view == mReEnableButton) {
- BlockedNumberContract.SystemContract.endBlockSuppression(this);
+ BlockedNumberContract.BlockedNumbers.endBlockSuppression(this);
mButterBar.setVisibility(View.GONE);
}
}
diff --git a/src/com/android/server/telecom/settings/BlockedNumbersUtil.java b/src/com/android/server/telecom/settings/BlockedNumbersUtil.java
index 4be75f8..e0fe81e 100644
--- a/src/com/android/server/telecom/settings/BlockedNumbersUtil.java
+++ b/src/com/android/server/telecom/settings/BlockedNumbersUtil.java
@@ -23,7 +23,7 @@
import android.content.Intent;
import android.os.PersistableBundle;
import android.os.UserHandle;
-import android.provider.BlockedNumberContract.SystemContract;
+import android.provider.BlockedNumberContract.BlockedNumbers;
import android.telephony.CarrierConfigManager;
import android.telephony.PhoneNumberUtils;
import android.text.BidiFormatter;
@@ -148,8 +148,8 @@
* @return If {@code true} means the key enabled in the SharedPreferences,
* {@code false} otherwise.
*/
- public static boolean getEnhancedBlockSetting(Context context, String key) {
- return SystemContract.getEnhancedBlockSetting(context, key);
+ public static boolean getBlockedNumberSetting(Context context, String key) {
+ return BlockedNumbers.getBlockedNumberSetting(context, key);
}
/**
@@ -159,7 +159,7 @@
* @param key preference key of SharedPreferences.
* @param value the register value to the SharedPreferences.
*/
- public static void setEnhancedBlockSetting(Context context, String key, boolean value) {
- SystemContract.setEnhancedBlockSetting(context, key, value);
+ public static void setBlockedNumberSetting(Context context, String key, boolean value) {
+ BlockedNumbers.setBlockedNumberSetting(context, key, value);
}
}
diff --git a/src/com/android/server/telecom/settings/CallBlockDisabledActivity.java b/src/com/android/server/telecom/settings/CallBlockDisabledActivity.java
index 5f42b37..35b7f70 100644
--- a/src/com/android/server/telecom/settings/CallBlockDisabledActivity.java
+++ b/src/com/android/server/telecom/settings/CallBlockDisabledActivity.java
@@ -58,9 +58,9 @@
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
- BlockedNumbersUtil.setEnhancedBlockSetting(
+ BlockedNumbersUtil.setBlockedNumberSetting(
CallBlockDisabledActivity.this,
- BlockedNumberContract.SystemContract
+ BlockedNumberContract.BlockedNumbers
.ENHANCED_SETTING_KEY_SHOW_EMERGENCY_CALL_NOTIFICATION,
false);
BlockedNumbersUtil.updateEmergencyCallNotification(
diff --git a/src/com/android/server/telecom/settings/EnhancedCallBlockingFragment.java b/src/com/android/server/telecom/settings/EnhancedCallBlockingFragment.java
index b1a1b0e..7ea8926 100644
--- a/src/com/android/server/telecom/settings/EnhancedCallBlockingFragment.java
+++ b/src/com/android/server/telecom/settings/EnhancedCallBlockingFragment.java
@@ -23,12 +23,11 @@
import android.preference.PreferenceFragment;
import android.preference.PreferenceScreen;
import android.preference.SwitchPreference;
-import android.provider.BlockedNumberContract.SystemContract;
+import android.provider.BlockedNumberContract.BlockedNumbers;
import android.telephony.CarrierConfigManager;
import android.telephony.SubscriptionManager;
import android.telecom.Log;
import android.view.LayoutInflater;
-import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
@@ -54,13 +53,14 @@
maybeConfigureCallBlockingOptions();
- setOnPreferenceChangeListener(SystemContract.ENHANCED_SETTING_KEY_BLOCK_UNREGISTERED);
- setOnPreferenceChangeListener(SystemContract.ENHANCED_SETTING_KEY_BLOCK_PRIVATE);
- setOnPreferenceChangeListener(SystemContract.ENHANCED_SETTING_KEY_BLOCK_PAYPHONE);
- setOnPreferenceChangeListener(SystemContract.ENHANCED_SETTING_KEY_BLOCK_UNKNOWN);
- setOnPreferenceChangeListener(SystemContract.ENHANCED_SETTING_KEY_BLOCK_UNAVAILABLE);
+ setOnPreferenceChangeListener(BlockedNumbers.ENHANCED_SETTING_KEY_BLOCK_UNREGISTERED);
+ setOnPreferenceChangeListener(BlockedNumbers.ENHANCED_SETTING_KEY_BLOCK_PRIVATE);
+ setOnPreferenceChangeListener(BlockedNumbers.ENHANCED_SETTING_KEY_BLOCK_PAYPHONE);
+ setOnPreferenceChangeListener(BlockedNumbers.ENHANCED_SETTING_KEY_BLOCK_UNKNOWN);
+ setOnPreferenceChangeListener(BlockedNumbers.ENHANCED_SETTING_KEY_BLOCK_UNAVAILABLE);
if (!showPayPhoneBlocking()) {
- Preference payPhoneOption = getPreferenceScreen().findPreference(SystemContract.ENHANCED_SETTING_KEY_BLOCK_PAYPHONE);
+ Preference payPhoneOption = getPreferenceScreen()
+ .findPreference(BlockedNumbers.ENHANCED_SETTING_KEY_BLOCK_PAYPHONE);
getPreferenceScreen().removePreference(payPhoneOption);
}
}
@@ -122,13 +122,13 @@
public void onResume() {
super.onResume();
- updateEnhancedBlockPref(SystemContract.ENHANCED_SETTING_KEY_BLOCK_UNREGISTERED);
- updateEnhancedBlockPref(SystemContract.ENHANCED_SETTING_KEY_BLOCK_PRIVATE);
+ updateEnhancedBlockPref(BlockedNumbers.ENHANCED_SETTING_KEY_BLOCK_UNREGISTERED);
+ updateEnhancedBlockPref(BlockedNumbers.ENHANCED_SETTING_KEY_BLOCK_PRIVATE);
if (showPayPhoneBlocking()) {
- updateEnhancedBlockPref(SystemContract.ENHANCED_SETTING_KEY_BLOCK_PAYPHONE);
+ updateEnhancedBlockPref(BlockedNumbers.ENHANCED_SETTING_KEY_BLOCK_PAYPHONE);
}
- updateEnhancedBlockPref(SystemContract.ENHANCED_SETTING_KEY_BLOCK_UNKNOWN);
- updateEnhancedBlockPref(SystemContract.ENHANCED_SETTING_KEY_BLOCK_UNAVAILABLE);
+ updateEnhancedBlockPref(BlockedNumbers.ENHANCED_SETTING_KEY_BLOCK_UNKNOWN);
+ updateEnhancedBlockPref(BlockedNumbers.ENHANCED_SETTING_KEY_BLOCK_UNAVAILABLE);
}
/**
@@ -137,7 +137,7 @@
private void updateEnhancedBlockPref(String key) {
SwitchPreference pref = (SwitchPreference) findPreference(key);
if (pref != null) {
- pref.setChecked(BlockedNumbersUtil.getEnhancedBlockSetting(getActivity(), key));
+ pref.setChecked(BlockedNumbersUtil.getBlockedNumberSetting(getActivity(), key));
}
}
@@ -147,18 +147,18 @@
if (mIsCombiningRestrictedAndUnknownOption) {
Log.i(this, "onPreferenceChange: changing %s and %s to %b",
preference.getKey(), BLOCK_RESTRICTED_NUMBERS_KEY, (boolean) objValue);
- BlockedNumbersUtil.setEnhancedBlockSetting(getActivity(),
+ BlockedNumbersUtil.setBlockedNumberSetting(getActivity(),
BLOCK_RESTRICTED_NUMBERS_KEY, (boolean) objValue);
}
if (mIsCombiningUnavailableAndUnknownOption) {
Log.i(this, "onPreferenceChange: changing %s and %s to %b",
preference.getKey(), BLOCK_UNAVAILABLE_NUMBERS_KEY, (boolean) objValue);
- BlockedNumbersUtil.setEnhancedBlockSetting(getActivity(),
+ BlockedNumbersUtil.setBlockedNumberSetting(getActivity(),
BLOCK_UNAVAILABLE_NUMBERS_KEY, (boolean) objValue);
}
}
- BlockedNumbersUtil.setEnhancedBlockSetting(getActivity(), preference.getKey(),
+ BlockedNumbersUtil.setBlockedNumberSetting(getActivity(), preference.getKey(),
(boolean) objValue);
return true;
}
diff --git a/src/com/android/server/telecom/voip/RequestVideoStateTransaction.java b/src/com/android/server/telecom/voip/RequestVideoStateTransaction.java
new file mode 100644
index 0000000..64596b1
--- /dev/null
+++ b/src/com/android/server/telecom/voip/RequestVideoStateTransaction.java
@@ -0,0 +1,70 @@
+/*
+ * 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.voip;
+
+import static com.android.server.telecom.voip.VideoStateTranslation.TransactionalVideoStateToVideoProfileState;
+
+import android.telecom.VideoProfile;
+import android.util.Log;
+
+import com.android.server.telecom.CallsManager;
+import com.android.server.telecom.Call;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
+
+public class RequestVideoStateTransaction extends VoipCallTransaction {
+
+ private static final String TAG = RequestVideoStateTransaction.class.getSimpleName();
+ private final Call mCall;
+ private final int mVideoProfileState;
+
+ public RequestVideoStateTransaction(CallsManager callsManager, Call call,
+ int transactionalVideoState) {
+ super(callsManager.getLock());
+ mCall = call;
+ mVideoProfileState = TransactionalVideoStateToVideoProfileState(transactionalVideoState);
+ }
+
+ @Override
+ public CompletionStage<VoipCallTransactionResult> processTransaction(Void v) {
+ Log.d(TAG, "processTransaction");
+ CompletableFuture<VoipCallTransactionResult> future = new CompletableFuture<>();
+
+ if (isRequestingVideoTransmission(mVideoProfileState) &&
+ !mCall.isVideoCallingSupportedByPhoneAccount()) {
+ future.complete(new VoipCallTransactionResult(
+ VoipCallTransactionResult.RESULT_FAILED,
+ "Video calling is not supported by the target account"));
+ } else if (isRequestingVideoTransmission(mVideoProfileState) &&
+ !mCall.isTransactionalCallSupportsVideoCalling()) {
+ future.complete(new VoipCallTransactionResult(
+ VoipCallTransactionResult.RESULT_FAILED,
+ "Video calling is not supported according to the callAttributes"));
+ } else {
+ mCall.setVideoState(mVideoProfileState);
+ future.complete(new VoipCallTransactionResult(
+ VoipCallTransactionResult.RESULT_SUCCEED,
+ "The Video State was changed successfully"));
+ }
+ return future;
+ }
+
+ private boolean isRequestingVideoTransmission(int targetVideoState) {
+ return targetVideoState != VideoProfile.STATE_AUDIO_ONLY;
+ }
+}
\ No newline at end of file
diff --git a/src/com/android/server/telecom/voip/VideoStateTranslation.java b/src/com/android/server/telecom/voip/VideoStateTranslation.java
new file mode 100644
index 0000000..615e4bc
--- /dev/null
+++ b/src/com/android/server/telecom/voip/VideoStateTranslation.java
@@ -0,0 +1,94 @@
+/*
+ * 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.voip;
+
+import android.telecom.CallAttributes;
+import android.telecom.Log;
+import android.telecom.VideoProfile;
+
+/**
+ * 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}.
+ * To be more specific, there are 3 video states (rx, tx, and bi-directional).
+ * {@link CallAttributes.CallType} only has 2 states (audio and video).
+ *
+ * The reason why Transactional calls have fewer states is due to the fact that the framework is
+ * only used by VoIP apps and Telecom only cares to know if the call is audio or video.
+ *
+ * Calls that are backed by a {@link android.telecom.ConnectionService} have the ability to be
+ * managed calls (non-VoIP) and Dialer needs more fine grain video states to update the UI. Thus,
+ * {@link VideoProfile} is used for {@link android.telecom.ConnectionService} backed calls.
+ */
+public class VideoStateTranslation {
+ private static final String TAG = VideoStateTranslation.class.getSimpleName();
+
+ /**
+ * Client --> Telecom
+ * This should be used when the client application is signaling they are changing the video
+ * state.
+ */
+ public static int TransactionalVideoStateToVideoProfileState(int transactionalVideo) {
+ if (transactionalVideo == CallAttributes.AUDIO_CALL) {
+ Log.i(TAG, "%s --> VideoProfile.STATE_AUDIO_ONLY",
+ TransactionalVideoState_toString(transactionalVideo));
+ return VideoProfile.STATE_AUDIO_ONLY;
+ } else {
+ Log.i(TAG, "%s --> VideoProfile.STATE_BIDIRECTIONAL",
+ TransactionalVideoState_toString(transactionalVideo));
+ return VideoProfile.STATE_BIDIRECTIONAL;
+ }
+ }
+
+ /**
+ * Telecom --> Client
+ * This should be used when Telecom is informing the client of a video state change.
+ */
+ public static int VideoProfileStateToTransactionalVideoState(int videoProfileState) {
+ if (videoProfileState == VideoProfile.STATE_AUDIO_ONLY) {
+ Log.i(TAG, "%s --> CallAttributes.AUDIO_CALL",
+ VideoProfileState_toString(videoProfileState));
+ return CallAttributes.AUDIO_CALL;
+ } else {
+ Log.i(TAG, "%s --> CallAttributes.VIDEO_CALL",
+ VideoProfileState_toString(videoProfileState));
+ return CallAttributes.VIDEO_CALL;
+ }
+ }
+
+ private static String TransactionalVideoState_toString(int transactionalVideoState) {
+ if (transactionalVideoState == CallAttributes.AUDIO_CALL) {
+ return "CallAttributes.AUDIO_CALL";
+ } else {
+ return "CallAttributes.VIDEO_CALL";
+ }
+ }
+
+ private static String VideoProfileState_toString(int videoProfileState) {
+ switch (videoProfileState) {
+ case VideoProfile.STATE_BIDIRECTIONAL -> {
+ return "VideoProfile.STATE_BIDIRECTIONAL";
+ }
+ case VideoProfile.STATE_RX_ENABLED -> {
+ return "VideoProfile.STATE_RX_ENABLED";
+ }
+ case VideoProfile.STATE_TX_ENABLED -> {
+ return "VideoProfile.STATE_TX_ENABLED";
+ }
+ }
+ return "VideoProfile.STATE_AUDIO_ONLY";
+ }
+}
diff --git a/testapps/src/com/android/server/telecom/testapps/TestInCallUI.java b/testapps/src/com/android/server/telecom/testapps/TestInCallUI.java
index bdd4c1a..d2aca78 100644
--- a/testapps/src/com/android/server/telecom/testapps/TestInCallUI.java
+++ b/testapps/src/com/android/server/telecom/testapps/TestInCallUI.java
@@ -40,6 +40,8 @@
import android.widget.TextView;
import android.widget.Toast;
+import com.android.server.telecom.flags.Flags;
+
import java.util.Collection;
import java.util.List;
import java.util.Optional;
@@ -263,6 +265,8 @@
.getIntentExtras().getString(TelecomManager.EXTRA_CALL_SUBJECT);
boolean isBusiness = call.getDetails()
.getExtras().getBoolean(ImsCallProfile.EXTRA_IS_BUSINESS_CALL);
+ String businessName = call.getDetails()
+ .getExtras().getString(ImsCallProfile.EXTRA_ASSERTED_DISPLAY_NAME);
StringBuilder display = new StringBuilder();
display.append("priority=");
@@ -286,6 +290,7 @@
display.append(" subject=" + subject);
display.append(" isBusiness=" + isBusiness);
+ display.append(" businessName=" + businessName);
TextView attachmentsTextView = findViewById(R.id.incoming_composer_attachments);
attachmentsTextView.setText(display.toString());
break;
diff --git a/tests/src/com/android/server/telecom/tests/AnalyticsTests.java b/tests/src/com/android/server/telecom/tests/AnalyticsTests.java
index 148db51..9caf0b5 100644
--- a/tests/src/com/android/server/telecom/tests/AnalyticsTests.java
+++ b/tests/src/com/android/server/telecom/tests/AnalyticsTests.java
@@ -87,6 +87,7 @@
super.setUp();
// this is a mock
mSubscriptionManager = mContext.getSystemService(SubscriptionManager.class);
+ when(mSubscriptionManager.createForAllUserProfiles()).thenReturn(mSubscriptionManager);
when(mSubscriptionManager.getActiveSubscriptionInfoList())
.thenReturn(Collections.emptyList());
}
diff --git a/tests/src/com/android/server/telecom/tests/BasicCallTests.java b/tests/src/com/android/server/telecom/tests/BasicCallTests.java
index 9ca3de1..4bca30d 100644
--- a/tests/src/com/android/server/telecom/tests/BasicCallTests.java
+++ b/tests/src/com/android/server/telecom/tests/BasicCallTests.java
@@ -16,6 +16,8 @@
package com.android.server.telecom.tests;
+import static com.android.server.telecom.callfiltering.BlockCheckerFilter.RES_BLOCK_STATUS;
+import static com.android.server.telecom.callfiltering.BlockCheckerFilter.STATUS_BLOCKED_IN_LIST;
import static com.android.server.telecom.tests.ConnectionServiceFixture.STATUS_HINTS_EXTRA;
import static org.junit.Assert.assertEquals;
@@ -887,8 +889,7 @@
@Override
public Bundle answer(InvocationOnMock invocation) throws Throwable {
Bundle bundle = new Bundle();
- bundle.putInt(BlockedNumberContract.RES_BLOCK_STATUS,
- BlockedNumberContract.STATUS_BLOCKED_IN_LIST);
+ bundle.putInt(RES_BLOCK_STATUS, STATUS_BLOCKED_IN_LIST);
return bundle;
}
});
diff --git a/tests/src/com/android/server/telecom/tests/BlockCheckerFilterTest.java b/tests/src/com/android/server/telecom/tests/BlockCheckerFilterTest.java
index 2584b02..e76989c 100644
--- a/tests/src/com/android/server/telecom/tests/BlockCheckerFilterTest.java
+++ b/tests/src/com/android/server/telecom/tests/BlockCheckerFilterTest.java
@@ -16,12 +16,14 @@
package com.android.server.telecom.tests;
-import static android.provider.BlockedNumberContract.STATUS_BLOCKED_IN_LIST;
-import static android.provider.BlockedNumberContract.STATUS_NOT_BLOCKED;
+import static com.android.server.telecom.callfiltering.BlockCheckerFilter.STATUS_BLOCKED_IN_LIST;
+import static com.android.server.telecom.callfiltering.BlockCheckerFilter.STATUS_NOT_BLOCKED;
import static junit.framework.TestCase.assertEquals;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify;
@@ -93,7 +95,7 @@
@Test
public void testBlockNumber() throws Exception {
when(mBlockCheckerAdapter.getBlockStatus(any(Context.class),
- eq(TEST_HANDLE.getSchemeSpecificPart()), any(Bundle.class)))
+ eq(TEST_HANDLE.getSchemeSpecificPart()), anyInt(), anyBoolean()))
.thenReturn(STATUS_BLOCKED_IN_LIST);
setEnhancedBlockingEnabled(false);
@@ -105,7 +107,7 @@
@Test
public void testBlockNumberWhenEnhancedBlockingEnabled() throws Exception {
when(mBlockCheckerAdapter.getBlockStatus(any(Context.class),
- eq(TEST_HANDLE.getSchemeSpecificPart()), any(Bundle.class)))
+ eq(TEST_HANDLE.getSchemeSpecificPart()), anyInt(), anyBoolean()))
.thenReturn(STATUS_BLOCKED_IN_LIST);
setEnhancedBlockingEnabled(true);
@@ -119,7 +121,7 @@
@Test
public void testDontBlockNumber() throws Exception {
when(mBlockCheckerAdapter.getBlockStatus(any(Context.class),
- eq(TEST_HANDLE.getSchemeSpecificPart()), any(Bundle.class)))
+ eq(TEST_HANDLE.getSchemeSpecificPart()), anyInt(), anyBoolean()))
.thenReturn(STATUS_NOT_BLOCKED);
setEnhancedBlockingEnabled(false);
@@ -131,7 +133,7 @@
@Test
public void testDontBlockNumberWhenEnhancedBlockingEnabled() throws Exception {
when(mBlockCheckerAdapter.getBlockStatus(any(Context.class),
- eq(TEST_HANDLE.getSchemeSpecificPart()), any(Bundle.class)))
+ eq(TEST_HANDLE.getSchemeSpecificPart()), anyInt(), anyBoolean()))
.thenReturn(STATUS_NOT_BLOCKED);
setEnhancedBlockingEnabled(true);
diff --git a/tests/src/com/android/server/telecom/tests/BluetoothDeviceManagerTest.java b/tests/src/com/android/server/telecom/tests/BluetoothDeviceManagerTest.java
index 648a831..c516c8e 100644
--- a/tests/src/com/android/server/telecom/tests/BluetoothDeviceManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/BluetoothDeviceManagerTest.java
@@ -403,7 +403,8 @@
when(mAdapter.setActiveDevice(nullable(BluetoothDevice.class),
eq(BluetoothAdapter.ACTIVE_DEVICE_ALL))).thenReturn(true);
mBluetoothDeviceManager.connectAudio(device1.getAddress(), false);
- verify(mAdapter).setActiveDevice(device1, BluetoothAdapter.ACTIVE_DEVICE_PHONE_CALL);
+ verify(mAdapter).setActiveDevice(eq(device1),
+ eq(BluetoothAdapter.ACTIVE_DEVICE_PHONE_CALL));
verify(mAdapter, never()).setActiveDevice(nullable(BluetoothDevice.class),
eq(BluetoothAdapter.ACTIVE_DEVICE_ALL));
mBluetoothDeviceManager.disconnectAudio();
diff --git a/tests/src/com/android/server/telecom/tests/CallAudioRouteControllerTest.java b/tests/src/com/android/server/telecom/tests/CallAudioRouteControllerTest.java
index 08576fc..0a53eb0 100644
--- a/tests/src/com/android/server/telecom/tests/CallAudioRouteControllerTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallAudioRouteControllerTest.java
@@ -16,18 +16,43 @@
package com.android.server.telecom.tests;
+import static com.android.server.telecom.CallAudioRouteAdapter.ACTIVE_FOCUS;
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;
+import static com.android.server.telecom.CallAudioRouteAdapter.CONNECT_WIRED_HEADSET;
+import static com.android.server.telecom.CallAudioRouteAdapter.DISCONNECT_DOCK;
+import static com.android.server.telecom.CallAudioRouteAdapter.DISCONNECT_WIRED_HEADSET;
+import static com.android.server.telecom.CallAudioRouteAdapter.MUTE_OFF;
+import static com.android.server.telecom.CallAudioRouteAdapter.MUTE_ON;
+import static com.android.server.telecom.CallAudioRouteAdapter.NO_FOCUS;
+import static com.android.server.telecom.CallAudioRouteAdapter.RINGING_FOCUS;
+import static com.android.server.telecom.CallAudioRouteAdapter.SPEAKER_OFF;
+import static com.android.server.telecom.CallAudioRouteAdapter.SPEAKER_ON;
+import static com.android.server.telecom.CallAudioRouteAdapter.STREAMING_FORCE_DISABLED;
+import static com.android.server.telecom.CallAudioRouteAdapter.STREAMING_FORCE_ENABLED;
+import static com.android.server.telecom.CallAudioRouteAdapter.SWITCH_EARPIECE;
+import static com.android.server.telecom.CallAudioRouteAdapter.SWITCH_FOCUS;
+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 org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -35,15 +60,19 @@
import android.bluetooth.BluetoothDevice;
import android.media.AudioDeviceInfo;
import android.media.AudioManager;
+import android.media.IAudioService;
import android.media.audiopolicy.AudioProductStrategy;
+import android.os.UserHandle;
import android.telecom.CallAudioState;
import androidx.test.filters.SmallTest;
import com.android.server.telecom.AudioRoute;
+import com.android.server.telecom.CallAudioManager;
import com.android.server.telecom.CallAudioRouteController;
import com.android.server.telecom.CallsManager;
import com.android.server.telecom.WiredHeadsetManager;
+import com.android.server.telecom.bluetooth.BluetoothRouteManager;
import org.junit.After;
import org.junit.Before;
@@ -62,6 +91,9 @@
@Mock AudioManager mAudioManager;
@Mock AudioDeviceInfo mEarpieceDeviceInfo;
@Mock CallsManager mCallsManager;
+ @Mock CallAudioManager.AudioServiceFactory mAudioServiceFactory;
+ @Mock IAudioService mAudioService;
+ @Mock BluetoothRouteManager mBluetoothRouteManager;
private AudioRoute mEarpieceRoute;
private AudioRoute mSpeakerRoute;
private static final String BT_ADDRESS_1 = "00:00:00:00:00:01";
@@ -90,10 +122,16 @@
new AudioDeviceInfo[] {
mEarpieceDeviceInfo
});
+ when(mAudioManager.getPreferredDeviceForStrategy(nullable(AudioProductStrategy.class)))
+ .thenReturn(null);
+ when(mAudioServiceFactory.getAudioService()).thenReturn(mAudioService);
+ when(mContext.getAttributionTag()).thenReturn("");
doNothing().when(mCallsManager).onCallAudioStateChanged(any(CallAudioState.class),
any(CallAudioState.class));
- mController = new CallAudioRouteController(mContext, mCallsManager, mAudioRouteFactory,
- mWiredHeadsetManager);
+ when(mCallsManager.getCurrentUserHandle()).thenReturn(
+ new UserHandle(UserHandle.USER_SYSTEM));
+ mController = new CallAudioRouteController(mContext, mCallsManager, mAudioServiceFactory,
+ mAudioRouteFactory, mWiredHeadsetManager, mBluetoothRouteManager);
mController.setAudioRouteFactory(mAudioRouteFactory);
mController.setAudioManager(mAudioManager);
mEarpieceRoute = new AudioRoute(AudioRoute.TYPE_EARPIECE, null, null);
@@ -148,6 +186,7 @@
AudioRoute.TYPE_BLUETOOTH_SCO, BT_ADDRESS_1);
verify(mAudioManager, timeout(TEST_TIMEOUT)).setCommunicationDevice(
nullable(AudioDeviceInfo.class));
+
expectedState = new CallAudioState(false, CallAudioState.ROUTE_BLUETOOTH,
CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH
| CallAudioState.ROUTE_SPEAKER, BLUETOOTH_DEVICE_1, BLUETOOTH_DEVICES);
@@ -166,9 +205,6 @@
@SmallTest
@Test
public void testActiveDeactivateBluetoothDevice() {
- when(mAudioManager.getPreferredDeviceForStrategy(nullable(AudioProductStrategy.class)))
- .thenReturn(null);
-
mController.initialize();
mController.sendMessageWithSessionInfo(BT_DEVICE_ADDED, AudioRoute.TYPE_BLUETOOTH_SCO,
BLUETOOTH_DEVICE_1);
@@ -192,7 +228,256 @@
@SmallTest
@Test
- public void testSwitchFocusInBluetoothRoute() {
+ public void testSwitchFocusForBluetoothDeviceSupportInbandRinging() {
+ doAnswer(invocation -> {
+ mController.sendMessageWithSessionInfo(BT_AUDIO_CONNECTED, 0, BLUETOOTH_DEVICE_1);
+ return true;
+ }).when(mAudioManager).setCommunicationDevice(nullable(AudioDeviceInfo.class));
+ doAnswer(invocation -> {
+ mController.sendMessageWithSessionInfo(BT_AUDIO_DISCONNECTED, 0, BLUETOOTH_DEVICE_1);
+ return true;
+ }).when(mAudioManager).clearCommunicationDevice();
+ when(mBluetoothRouteManager.isInbandRingEnabled(eq(BLUETOOTH_DEVICE_1))).thenReturn(true);
+ 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());
+
+ mController.sendMessageWithSessionInfo(SWITCH_FOCUS, RINGING_FOCUS);
+ verify(mAudioManager, timeout(TEST_TIMEOUT)).setCommunicationDevice(
+ nullable(AudioDeviceInfo.class));
+ assertTrue(mController.isActive());
+
+ mController.sendMessageWithSessionInfo(SWITCH_FOCUS, ACTIVE_FOCUS);
+ assertTrue(mController.isActive());
+
+ mController.sendMessageWithSessionInfo(SWITCH_FOCUS, NO_FOCUS);
+ verify(mAudioManager, timeout(TEST_TIMEOUT)).clearCommunicationDevice();
+ assertFalse(mController.isActive());
+ }
+
+ @SmallTest
+ @Test
+ public void testConnectAndDisconnectWiredHeadset() {
+ mController.initialize();
+ mController.sendMessageWithSessionInfo(CONNECT_WIRED_HEADSET);
+ CallAudioState expectedState = new CallAudioState(false, CallAudioState.ROUTE_WIRED_HEADSET,
+ CallAudioState.ROUTE_WIRED_HEADSET | CallAudioState.ROUTE_SPEAKER, null,
+ new HashSet<>());
+ verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged(
+ any(CallAudioState.class), eq(expectedState));
+
+ mController.sendMessageWithSessionInfo(DISCONNECT_WIRED_HEADSET);
+ 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));
+ }
+
+ @SmallTest
+ @Test
+ public void testConnectAndDisconnectDock() {
+ mController.initialize();
+ mController.sendMessageWithSessionInfo(CONNECT_DOCK);
+ 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));
+
+ mController.sendMessageWithSessionInfo(DISCONNECT_DOCK);
+ 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));
+ }
+
+ @SmallTest
+ @Test
+ public void testSpeakerToggle() {
+ mController.initialize();
+ mController.setActive(true);
+ 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));
+
+ 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));
+ }
+
+ @SmallTest
+ @Test
+ public void testSpeakerToggleWhenDockConnected() {
+ mController.initialize();
+ mController.setActive(true);
+ mController.sendMessageWithSessionInfo(CONNECT_DOCK);
+ 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));
+
+ 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));
+
+ 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));
+ }
+
+ @SmallTest
+ @Test
+ public void testSwitchEarpiece() {
+ mController.initialize();
+ 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));
+
+ mController.sendMessageWithSessionInfo(USER_SWITCH_EARPIECE);
+ 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));
+ }
+
+ @SmallTest
+ @Test
+ public void testSwitchBluetooth() {
+ doAnswer(invocation -> {
+ mController.sendMessageWithSessionInfo(BT_AUDIO_CONNECTED, 0, BLUETOOTH_DEVICE_1);
+ return true;
+ }).when(mAudioManager).setCommunicationDevice(nullable(AudioDeviceInfo.class));
+
+ 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(USER_SWITCH_BLUETOOTH, 0,
+ BLUETOOTH_DEVICE_1.getAddress());
+ mController.sendMessageWithSessionInfo(BT_AUDIO_CONNECTED, 0, 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));
+ }
+
+ @SmallTest
+ @Test
+ public void tesetSwitchSpeakerAndHeadset() {
+ mController.initialize();
+ mController.sendMessageWithSessionInfo(CONNECT_WIRED_HEADSET);
+ CallAudioState expectedState = new CallAudioState(false, CallAudioState.ROUTE_WIRED_HEADSET,
+ CallAudioState.ROUTE_WIRED_HEADSET | CallAudioState.ROUTE_SPEAKER, null,
+ new HashSet<>());
+ verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged(
+ any(CallAudioState.class), eq(expectedState));
+
+ mController.sendMessageWithSessionInfo(USER_SWITCH_SPEAKER);
+ mController.sendMessageWithSessionInfo(SPEAKER_ON);
+ expectedState = new CallAudioState(false, CallAudioState.ROUTE_SPEAKER,
+ CallAudioState.ROUTE_WIRED_HEADSET | CallAudioState.ROUTE_SPEAKER, null,
+ new HashSet<>());
+ verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged(
+ any(CallAudioState.class), eq(expectedState));
+
+ mController.sendMessageWithSessionInfo(USER_SWITCH_HEADSET);
+ mController.sendMessageWithSessionInfo(SPEAKER_OFF);
+ expectedState = new CallAudioState(false, CallAudioState.ROUTE_WIRED_HEADSET,
+ CallAudioState.ROUTE_WIRED_HEADSET | CallAudioState.ROUTE_SPEAKER, null,
+ new HashSet<>());
+ verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged(
+ any(CallAudioState.class), eq(expectedState));
+ }
+
+ @SmallTest
+ @Test
+ public void testEnableAndDisableStreaming() {
+ mController.initialize();
+ mController.sendMessageWithSessionInfo(STREAMING_FORCE_ENABLED);
+ CallAudioState expectedState = new CallAudioState(false, CallAudioState.ROUTE_STREAMING,
+ CallAudioState.ROUTE_STREAMING, null, new HashSet<>());
+ verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged(
+ any(CallAudioState.class), eq(expectedState));
+
+ mController.sendMessageWithSessionInfo(SPEAKER_ON);
+ verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged(
+ any(CallAudioState.class), eq(expectedState));
+
+ mController.sendMessageWithSessionInfo(CONNECT_WIRED_HEADSET);
+ verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged(
+ any(CallAudioState.class), eq(expectedState));
+
+ mController.sendMessageWithSessionInfo(STREAMING_FORCE_DISABLED);
+ expectedState = new CallAudioState(false, CallAudioState.ROUTE_WIRED_HEADSET,
+ CallAudioState.ROUTE_WIRED_HEADSET | CallAudioState.ROUTE_SPEAKER, null,
+ new HashSet<>());
+ verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged(
+ any(CallAudioState.class), eq(expectedState));
+ }
+
+ @SmallTest
+ @Test
+ public void testToggleMute() throws Exception {
+ when(mAudioManager.isMasterMute()).thenReturn(false);
+
+ mController.initialize();
+ mController.setActive(true);
+
+ mController.sendMessageWithSessionInfo(MUTE_ON);
+ CallAudioState 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));
+
+ when(mAudioManager.isMasterMute()).thenReturn(true);
+ mController.sendMessageWithSessionInfo(MUTE_OFF);
+ expectedState = new CallAudioState(false, CallAudioState.ROUTE_EARPIECE,
+ CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_SPEAKER, null,
+ new HashSet<>());
+ verify(mAudioService, timeout(TEST_TIMEOUT)).setMicrophoneMute(eq(false), anyString(),
+ anyInt(), anyString());
+ verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged(
+ any(CallAudioState.class), eq(expectedState));
}
}
diff --git a/tests/src/com/android/server/telecom/tests/CallsManagerTest.java b/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
index f814d3e..1529629 100644
--- a/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
@@ -127,6 +127,7 @@
import com.android.server.telecom.TelecomSystem;
import com.android.server.telecom.Timeouts;
import com.android.server.telecom.WiredHeadsetManager;
+import com.android.server.telecom.bluetooth.BluetoothDeviceManager;
import com.android.server.telecom.bluetooth.BluetoothRouteManager;
import com.android.server.telecom.bluetooth.BluetoothStateReceiver;
import com.android.server.telecom.callfiltering.BlockedNumbersAdapter;
@@ -290,6 +291,7 @@
@Mock private PhoneCapability mPhoneCapability;
@Mock private CallAudioCommunicationDeviceTracker mCommunicationDeviceTracker;
@Mock private CallStreamingNotification mCallStreamingNotification;
+ @Mock private BluetoothDeviceManager mBluetoothDeviceManager;
@Mock private FeatureFlags mFeatureFlags;
@Mock private IncomingCallFilterGraph mIncomingCallFilterGraph;
private CallsManager mCallsManager;
@@ -366,6 +368,7 @@
mEmergencyCallDiagnosticLogger,
mCommunicationDeviceTracker,
mCallStreamingNotification,
+ mBluetoothDeviceManager,
mFeatureFlags,
(call, listener, context, timeoutsAdapter, lock) -> mIncomingCallFilterGraph);
@@ -3194,7 +3197,7 @@
.thenReturn(true);
mComponentContextFixture.getBroadcastReceivers().forEach(c -> c.onReceive(mContext,
new Intent(
- BlockedNumberContract.SystemContract
+ BlockedNumberContract.BlockedNumbers
.ACTION_BLOCK_SUPPRESSION_STATE_CHANGED)));
verify(mBlockedNumbersAdapter).updateEmergencyCallNotification(any(Context.class),
eq(true));
@@ -3203,7 +3206,7 @@
.thenReturn(false);
mComponentContextFixture.getBroadcastReceivers().forEach(c -> c.onReceive(mContext,
new Intent(
- BlockedNumberContract.SystemContract
+ BlockedNumberContract.BlockedNumbers
.ACTION_BLOCK_SUPPRESSION_STATE_CHANGED)));
verify(mBlockedNumbersAdapter).updateEmergencyCallNotification(any(Context.class),
eq(false));