Merge "Log the business call composer values if set" into main
diff --git a/Android.bp b/Android.bp
index 7e57a3f..68a8e39 100644
--- a/Android.bp
+++ b/Android.bp
@@ -1,12 +1,13 @@
package {
+ default_team: "trendy_team_fwk_telecom",
default_applicable_licenses: ["Android-Apache-2.0"],
}
genrule {
name: "statslog-telecom-java-gen",
tools: ["stats-log-api-gen"],
- cmd: "$(location stats-log-api-gen) --java $(out) --module telecom"
- + " --javaPackage com.android.server.telecom --javaClass TelecomStatsLog",
+ cmd: "$(location stats-log-api-gen) --java $(out) --module telecom" +
+ " --javaPackage com.android.server.telecom --javaClass TelecomStatsLog",
out: ["com/android/server/telecom/TelecomStatsLog.java"],
}
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 90e4bd9..a9b6154 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -66,6 +66,7 @@
<uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT"/>
<uses-permission android:name="android.permission.ACCESS_LAST_KNOWN_CELL_ID"/>
<uses-permission android:name="android.permission.STATUS_BAR_SERVICE" />
+ <uses-permission android:name="android.permission.MODIFY_AUDIO_ROUTING" />
<permission android:name="android.permission.BROADCAST_CALLLOG_INFO"
android:label="Broadcast the call type/duration information"
diff --git a/OWNERS b/OWNERS
index 7e68aea..9c071f9 100644
--- a/OWNERS
+++ b/OWNERS
@@ -1,7 +1,8 @@
+# Bug component: 151185
breadley@google.com
tgunn@google.com
-xiaotonj@google.com
tjstuart@google.com
rgreenwalt@google.com
pmadapurmath@google.com
grantmenke@google.com
+huiwang@google.com
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 eb2731b..40b75a2 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"
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 1a19480..cdfcc30 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 e1a652b..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"
@@ -12,4 +13,11 @@
namespace: "telecom"
description: "Ensure that users are able to return to call from keyguard UI for ECC"
bug: "306582821"
-}
\ No newline at end of file
+}
+
+flag {
+ name: "separately_bind_to_bt_incall_service"
+ namespace: "telecom"
+ description: "Binding/Unbinding to BluetoothInCallServices in proper time to improve call audio"
+ bug: "306395598"
+}
diff --git a/flags/telecom_resolve_hidden_dependencies.aconfig b/flags/telecom_resolve_hidden_dependencies.aconfig
index ecc0123..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: "android_platform_telecom"
+ namespace: "telecom"
description: "Mainland cleanup for hidden dependencies"
- bug: "b/303440370"
+ 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/res/values-my/strings.xml b/res/values-my/strings.xml
index 9ead5f4..c9e5593 100644
--- a/res/values-my/strings.xml
+++ b/res/values-my/strings.xml
@@ -19,7 +19,7 @@
<string name="telecommAppLabel" product="default" msgid="1825598513414129827">"ဖုန်းခေါ်ဆိုမှုများ"</string>
<string name="userCallActivityLabel" product="default" msgid="3605391260292846248">"ဖုန်း"</string>
<string name="unknown" msgid="6993977514360123431">"မသိပါ"</string>
- <string name="notification_missedCallTitle" msgid="5060387047205532974">"လွဲသွားသော ဖုန်းခေါ်မှု"</string>
+ <string name="notification_missedCallTitle" msgid="5060387047205532974">"လွတ်သွားသော ခေါ်ဆိုမှု"</string>
<string name="notification_missedWorkCallTitle" msgid="6965463282259034953">"လွတ်သွားသည့် အလုပ်ဆိုင်ရာ ခေါ်ဆိုမှု"</string>
<string name="notification_missedCallsTitle" msgid="3910479625507893809">"လွဲသွားသော ဖုန်းခေါ်မှုများ"</string>
<string name="notification_missedCallsMsg" msgid="5055782736170916682">"<xliff:g id="NUM_MISSED_CALLS">%s</xliff:g> လွဲသွားသော ဖုန်းခေါ်မှုများ"</string>
diff --git a/res/values-or/strings.xml b/res/values-or/strings.xml
index 787711b..6f3ebe3 100644
--- a/res/values-or/strings.xml
+++ b/res/values-or/strings.xml
@@ -24,7 +24,7 @@
<string name="notification_missedCallsTitle" msgid="3910479625507893809">"ମିସ୍ଡ କଲ୍"</string>
<string name="notification_missedCallsMsg" msgid="5055782736170916682">"<xliff:g id="NUM_MISSED_CALLS">%s</xliff:g>ଟି ମିସ୍ଡ କଲ୍"</string>
<string name="notification_missedCallTicker" msgid="6731461957487087769">"<xliff:g id="MISSED_CALL_FROM">%s</xliff:g>ଙ୍କ ଠାରୁ ମିସ୍-କଲ୍ ମିଳିଛି"</string>
- <string name="notification_missedCall_call_back" msgid="7900333283939789732">"କଲବ୍ୟାକ୍ କରନ୍ତୁ"</string>
+ <string name="notification_missedCall_call_back" msgid="7900333283939789732">"କଲବେକ କରନ୍ତୁ"</string>
<string name="notification_missedCall_message" msgid="4054698824390076431">"ମେସେଜ୍ ଦିଅନ୍ତୁ"</string>
<string name="notification_disconnectedCall_title" msgid="1790131923692416928">"କଲ୍ ବିଚ୍ଛିନ୍ନ କରାଯାଇଛି"</string>
<string name="notification_disconnectedCall_body" msgid="600491714584417536">"ଏକ ଜରୁରୀକାଳୀନ କଲ୍ କରାଯାଇଥିବାରୁ <xliff:g id="CALLER">%s</xliff:g>ଙ୍କୁ କରାଯାଇଥିବା କଲ୍ ବିଚ୍ଛିନ୍ନ କରାଯାଇଛି।"</string>
diff --git a/res/values/config.xml b/res/values/config.xml
index c38a6ec..bf30720 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -79,4 +79,7 @@
<!-- When true, the options in the call blocking settings to block unavailable and unknown
callers are combined into a single toggle. -->
<bool name="combine_options_to_block_unavailable_and_unknown_callers">true</bool>
+
+ <!-- System bluetooth stack package name -->
+ <string name="system_bluetooth_stack">com.android.bluetooth</string>
</resources>
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
new file mode 100644
index 0000000..5037cf5
--- /dev/null
+++ b/src/com/android/server/telecom/AudioRoute.java
@@ -0,0 +1,285 @@
+/*
+ * Copyright (C) 2023 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;
+
+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.PENDING_ROUTE_FAILED;
+import static com.android.server.telecom.CallAudioRouteAdapter.SPEAKER_OFF;
+import static com.android.server.telecom.CallAudioRouteAdapter.SPEAKER_ON;
+
+import android.annotation.IntDef;
+import android.media.AudioDeviceInfo;
+import android.media.AudioManager;
+import android.telecom.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.HashMap;
+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.ScheduledExecutorService;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+public class AudioRoute {
+ public static class Factory {
+ private final ScheduledExecutorService mScheduledExecutorService =
+ new ScheduledThreadPoolExecutor(1);
+ private final CompletableFuture<AudioRoute> mAudioRouteFuture = new CompletableFuture<>();
+ public AudioRoute create(@AudioRouteType int type, String bluetoothAddress,
+ AudioManager audioManager) throws RuntimeException {
+ createRetry(type, bluetoothAddress, audioManager, MAX_CONNECTION_RETRIES);
+ try {
+ return mAudioRouteFuture.get();
+ } catch (InterruptedException | ExecutionException e) {
+ throw new RuntimeException("Error when creating requested audio route");
+ }
+ }
+ private void createRetry(@AudioRouteType int type, String bluetoothAddress,
+ AudioManager audioManager, int retryCount) {
+ if (retryCount == 0) {
+ mAudioRouteFuture.complete(null);
+ }
+
+ Log.i(this, "creating AudioRoute with type %s and address %s, retry count %d",
+ DEVICE_TYPE_STRINGS.get(type), bluetoothAddress, retryCount);
+ AudioDeviceInfo routeInfo = null;
+ List<AudioDeviceInfo> infos = audioManager.getAvailableCommunicationDevices();
+ List<Integer> possibleInfoTypes = AUDIO_ROUTE_TYPE_TO_DEVICE_INFO_TYPE.get(type);
+ for (AudioDeviceInfo info : infos) {
+ Log.i(this, "type: " + info.getType());
+ if (possibleInfoTypes != null && possibleInfoTypes.contains(info.getType())) {
+ if (BT_AUDIO_ROUTE_TYPES.contains(type)) {
+ if (bluetoothAddress.equals(info.getAddress())) {
+ routeInfo = info;
+ break;
+ }
+ } else {
+ routeInfo = info;
+ break;
+ }
+ }
+ }
+ 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);
+ } else {
+ mAudioRouteFuture.complete(new AudioRoute(type, bluetoothAddress, routeInfo));
+ }
+ }
+ }
+
+ private static final long RETRY_TIME_DELAY = 500L;
+ private static final int MAX_CONNECTION_RETRIES = 2;
+ public static final int TYPE_INVALID = 0;
+ public static final int TYPE_EARPIECE = 1;
+ public static final int TYPE_WIRED = 2;
+ public static final int TYPE_SPEAKER = 3;
+ public static final int TYPE_DOCK = 4;
+ public static final int TYPE_BLUETOOTH_SCO = 5;
+ public static final int TYPE_BLUETOOTH_HA = 6;
+ public static final int TYPE_BLUETOOTH_LE = 7;
+ @IntDef(prefix = "TYPE", value = {
+ TYPE_INVALID,
+ TYPE_EARPIECE,
+ TYPE_WIRED,
+ TYPE_SPEAKER,
+ TYPE_DOCK,
+ TYPE_BLUETOOTH_SCO,
+ TYPE_BLUETOOTH_HA,
+ TYPE_BLUETOOTH_LE
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface AudioRouteType {}
+
+ private @AudioRouteType int mAudioRouteType;
+ private String mBluetoothAddress;
+ private AudioDeviceInfo mInfo;
+ public static final Set<Integer> BT_AUDIO_DEVICE_INFO_TYPES = Set.of(
+ AudioDeviceInfo.TYPE_BLE_HEADSET,
+ AudioDeviceInfo.TYPE_BLE_SPEAKER,
+ AudioDeviceInfo.TYPE_BLE_BROADCAST,
+ AudioDeviceInfo.TYPE_HEARING_AID,
+ AudioDeviceInfo.TYPE_BLUETOOTH_SCO
+ );
+
+ public static final Set<Integer> BT_AUDIO_ROUTE_TYPES = Set.of(
+ AudioRoute.TYPE_BLUETOOTH_SCO,
+ AudioRoute.TYPE_BLUETOOTH_HA,
+ AudioRoute.TYPE_BLUETOOTH_LE
+ );
+
+ public static final HashMap<Integer, String> DEVICE_TYPE_STRINGS;
+ static {
+ DEVICE_TYPE_STRINGS = new HashMap<>();
+ DEVICE_TYPE_STRINGS.put(TYPE_EARPIECE, "TYPE_EARPIECE");
+ DEVICE_TYPE_STRINGS.put(TYPE_WIRED, "TYPE_WIRED_HEADSET");
+ DEVICE_TYPE_STRINGS.put(TYPE_SPEAKER, "TYPE_SPEAKER");
+ DEVICE_TYPE_STRINGS.put(TYPE_DOCK, "TYPE_DOCK");
+ 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");
+ }
+
+ public static final HashMap<Integer, Integer> DEVICE_INFO_TYPETO_AUDIO_ROUTE_TYPE;
+ static {
+ DEVICE_INFO_TYPETO_AUDIO_ROUTE_TYPE = new HashMap<>();
+ DEVICE_INFO_TYPETO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_BUILTIN_EARPIECE,
+ TYPE_EARPIECE);
+ DEVICE_INFO_TYPETO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, TYPE_SPEAKER);
+ DEVICE_INFO_TYPETO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_WIRED_HEADSET, TYPE_WIRED);
+ DEVICE_INFO_TYPETO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_WIRED_HEADPHONES, TYPE_WIRED);
+ DEVICE_INFO_TYPETO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_BLUETOOTH_SCO,
+ TYPE_BLUETOOTH_SCO);
+ DEVICE_INFO_TYPETO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_USB_DEVICE, TYPE_WIRED);
+ DEVICE_INFO_TYPETO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_USB_ACCESSORY, TYPE_WIRED);
+ DEVICE_INFO_TYPETO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_DOCK, TYPE_DOCK);
+ DEVICE_INFO_TYPETO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_USB_HEADSET, TYPE_WIRED);
+ DEVICE_INFO_TYPETO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_HEARING_AID,
+ TYPE_BLUETOOTH_HA);
+ DEVICE_INFO_TYPETO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_BLE_HEADSET,
+ TYPE_BLUETOOTH_LE);
+ DEVICE_INFO_TYPETO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_BLE_SPEAKER,
+ TYPE_BLUETOOTH_LE);
+ DEVICE_INFO_TYPETO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_BLE_BROADCAST,
+ TYPE_BLUETOOTH_LE);
+ DEVICE_INFO_TYPETO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_DOCK_ANALOG, TYPE_DOCK);
+ }
+
+ private static final HashMap<Integer, List<Integer>> AUDIO_ROUTE_TYPE_TO_DEVICE_INFO_TYPE;
+ static {
+ AUDIO_ROUTE_TYPE_TO_DEVICE_INFO_TYPE = new HashMap<>();
+ List<Integer> earpieceDeviceInfoTypes = new ArrayList<>();
+ earpieceDeviceInfoTypes.add(AudioDeviceInfo.TYPE_BUILTIN_EARPIECE);
+ AUDIO_ROUTE_TYPE_TO_DEVICE_INFO_TYPE.put(TYPE_EARPIECE, earpieceDeviceInfoTypes);
+
+ List<Integer> wiredDeviceInfoTypes = new ArrayList<>();
+ wiredDeviceInfoTypes.add(AudioDeviceInfo.TYPE_WIRED_HEADSET);
+ wiredDeviceInfoTypes.add(AudioDeviceInfo.TYPE_WIRED_HEADPHONES);
+ wiredDeviceInfoTypes.add(AudioDeviceInfo.TYPE_USB_DEVICE);
+ wiredDeviceInfoTypes.add(AudioDeviceInfo.TYPE_USB_ACCESSORY);
+ wiredDeviceInfoTypes.add(AudioDeviceInfo.TYPE_USB_HEADSET);
+ AUDIO_ROUTE_TYPE_TO_DEVICE_INFO_TYPE.put(TYPE_WIRED, wiredDeviceInfoTypes);
+
+ List<Integer> speakerDeviceInfoTypes = new ArrayList<>();
+ speakerDeviceInfoTypes.add(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER);
+ AUDIO_ROUTE_TYPE_TO_DEVICE_INFO_TYPE.put(TYPE_SPEAKER, speakerDeviceInfoTypes);
+
+ List<Integer> dockDeviceInfoTypes = new ArrayList<>();
+ dockDeviceInfoTypes.add(AudioDeviceInfo.TYPE_DOCK);
+ dockDeviceInfoTypes.add(AudioDeviceInfo.TYPE_DOCK_ANALOG);
+ AUDIO_ROUTE_TYPE_TO_DEVICE_INFO_TYPE.put(TYPE_DOCK, dockDeviceInfoTypes);
+
+ List<Integer> bluetoothScoDeviceInfoTypes = new ArrayList<>();
+ bluetoothScoDeviceInfoTypes.add(AudioDeviceInfo.TYPE_BLUETOOTH_A2DP);
+ bluetoothScoDeviceInfoTypes.add(AudioDeviceInfo.TYPE_BLUETOOTH_SCO);
+ AUDIO_ROUTE_TYPE_TO_DEVICE_INFO_TYPE.put(TYPE_BLUETOOTH_SCO, bluetoothScoDeviceInfoTypes);
+
+ List<Integer> bluetoothHearingAidDeviceInfoTypes = new ArrayList<>();
+ bluetoothHearingAidDeviceInfoTypes.add(AudioDeviceInfo.TYPE_HEARING_AID);
+ AUDIO_ROUTE_TYPE_TO_DEVICE_INFO_TYPE.put(TYPE_BLUETOOTH_HA,
+ bluetoothHearingAidDeviceInfoTypes);
+
+ List<Integer> bluetoothLeDeviceInfoTypes = new ArrayList<>();
+ bluetoothLeDeviceInfoTypes.add(AudioDeviceInfo.TYPE_BLE_HEADSET);
+ bluetoothLeDeviceInfoTypes.add(AudioDeviceInfo.TYPE_BLE_SPEAKER);
+ bluetoothLeDeviceInfoTypes.add(AudioDeviceInfo.TYPE_BLE_BROADCAST);
+ AUDIO_ROUTE_TYPE_TO_DEVICE_INFO_TYPE.put(TYPE_BLUETOOTH_LE, bluetoothLeDeviceInfoTypes);
+ }
+
+ int getType() {
+ return mAudioRouteType;
+ }
+
+ String getBluetoothAddress() {
+ return mBluetoothAddress;
+ }
+
+ // Invoked when entered pending route whose dest route is this route
+ void onDestRouteAsPendingRoute(boolean active, PendingAudioRoute pendingAudioRoute,
+ AudioManager audioManager) {
+ if (pendingAudioRoute.isActive() && !active) {
+ audioManager.clearCommunicationDevice();
+ } else if (active) {
+ if (mAudioRouteType == TYPE_BLUETOOTH_SCO) {
+ pendingAudioRoute.addMessage(BT_AUDIO_CONNECTED);
+ } else if (mAudioRouteType == TYPE_SPEAKER) {
+ pendingAudioRoute.addMessage(SPEAKER_ON);
+ }
+ if (!audioManager.setCommunicationDevice(mInfo)) {
+ pendingAudioRoute.onMessageReceived(PENDING_ROUTE_FAILED);
+ }
+ }
+ }
+
+ void onOrigRouteAsPendingRoute(boolean active, PendingAudioRoute pendingAudioRoute,
+ AudioManager audioManager) {
+ if (active) {
+ if (mAudioRouteType == TYPE_BLUETOOTH_SCO) {
+ pendingAudioRoute.addMessage(BT_AUDIO_DISCONNECTED);
+ } else if (mAudioRouteType == TYPE_SPEAKER) {
+ pendingAudioRoute.addMessage(SPEAKER_OFF);
+ }
+ }
+ }
+
+ @VisibleForTesting
+ public AudioRoute(@AudioRouteType int type, String bluetoothAddress, AudioDeviceInfo info) {
+ mAudioRouteType = type;
+ mBluetoothAddress = bluetoothAddress;
+ mInfo = info;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == null) {
+ return false;
+ }
+ if (!(obj instanceof AudioRoute otherRoute)) {
+ return false;
+ }
+ if (mAudioRouteType != otherRoute.getType()) {
+ return false;
+ }
+ return !BT_AUDIO_ROUTE_TYPES.contains(mAudioRouteType) || mBluetoothAddress.equals(
+ otherRoute.getBluetoothAddress());
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mAudioRouteType, mBluetoothAddress);
+ }
+
+ @Override
+ public String toString() {
+ return getClass().getSimpleName() + "[Type=" + DEVICE_TYPE_STRINGS.get(mAudioRouteType)
+ + ", Address=" + ((mBluetoothAddress != null) ? mBluetoothAddress : "invalid")
+ + "]";
+ }
+}
diff --git a/src/com/android/server/telecom/Call.java b/src/com/android/server/telecom/Call.java
index 341d77e..624399b 100644
--- a/src/com/android/server/telecom/Call.java
+++ b/src/com/android/server/telecom/Call.java
@@ -27,13 +27,11 @@
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
import android.net.Uri;
-import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.OutcomeReceiver;
import android.os.ParcelFileDescriptor;
-import android.os.Parcelable;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.Trace;
@@ -73,7 +71,6 @@
import com.android.internal.telecom.IVideoProvider;
import com.android.internal.util.Preconditions;
import com.android.server.telecom.flags.FeatureFlags;
-import com.android.server.telecom.flags.Flags;
import com.android.server.telecom.stats.CallFailureCause;
import com.android.server.telecom.stats.CallStateChangedAtomWriter;
import com.android.server.telecom.ui.ToastFactory;
@@ -95,6 +92,7 @@
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
@@ -796,6 +794,13 @@
*/
private CompletableFuture<Boolean> mDisconnectFuture;
+ /**
+ * {@link CompletableFuture} used to delay audio routing change for a ringing call until the
+ * corresponding bluetooth {@link android.telecom.InCallService} is successfully bound or timed
+ * out.
+ */
+ private CompletableFuture<Boolean> mBtIcsFuture;
+
private FeatureFlags mFlags;
/**
@@ -4693,6 +4698,30 @@
}
/**
+ * Set the bluetooth {@link android.telecom.InCallService} binding completion or timeout future
+ * which is used to delay the audio routing change after the bluetooth stack get notified about
+ * the ringing calls.
+ * @param btIcsFuture the {@link CompletableFuture}
+ */
+ public void setBtIcsFuture(CompletableFuture<Boolean> btIcsFuture) {
+ mBtIcsFuture = btIcsFuture;
+ }
+
+ /**
+ * Wait for bluetooth {@link android.telecom.InCallService} binding completion or timeout. Used
+ * for audio routing operations for a ringing call.
+ */
+ public void waitForBtIcs() {
+ if (mBtIcsFuture != null) {
+ try {
+ mBtIcsFuture.get();
+ } catch (InterruptedException | ExecutionException e) {
+ // ignore
+ }
+ }
+ }
+
+ /**
* @return {@code true} if the connection has been created by the underlying
* {@link ConnectionService}, {@code false} otherwise.
*/
diff --git a/src/com/android/server/telecom/CallAudioCommunicationDeviceTracker.java b/src/com/android/server/telecom/CallAudioCommunicationDeviceTracker.java
index 5fc2414..3a05eb5 100644
--- a/src/com/android/server/telecom/CallAudioCommunicationDeviceTracker.java
+++ b/src/com/android/server/telecom/CallAudioCommunicationDeviceTracker.java
@@ -16,6 +16,8 @@
package com.android.server.telecom;
+import static com.android.server.telecom.AudioRoute.BT_AUDIO_DEVICE_INFO_TYPES;
+
import android.bluetooth.BluetoothDevice;
import android.content.Context;
import android.media.AudioDeviceInfo;
@@ -28,7 +30,6 @@
import java.util.Arrays;
import java.util.List;
-import java.util.Set;
import java.util.concurrent.Semaphore;
/**
@@ -41,12 +42,6 @@
// Use -1 indicates device is not set for any communication use case
private static final int sAUDIO_DEVICE_TYPE_INVALID = -1;
- // Possible bluetooth audio device types
- private static final Set<Integer> sBT_AUDIO_DEVICE_TYPES = Set.of(
- AudioDeviceInfo.TYPE_BLE_HEADSET,
- AudioDeviceInfo.TYPE_HEARING_AID,
- AudioDeviceInfo.TYPE_BLUETOOTH_SCO
- );
private AudioManager mAudioManager;
private BluetoothRouteManager mBluetoothRouteManager;
private int mAudioDeviceType = sAUDIO_DEVICE_TYPE_INVALID;
@@ -101,7 +96,7 @@
mLock.tryAcquire();
}
// There is only one audio device type associated with each type of BT device.
- boolean isBtDevice = sBT_AUDIO_DEVICE_TYPES.contains(audioDeviceType);
+ boolean isBtDevice = BT_AUDIO_DEVICE_INFO_TYPES.contains(audioDeviceType);
Log.i(this, "setCommunicationDevice: type = %s, isBtDevice = %s, btDevice = %s",
audioDeviceType, isBtDevice, btDevice);
@@ -182,7 +177,7 @@
mLock.tryAcquire();
}
// There is only one audio device type associated with each type of BT device.
- boolean isBtDevice = sBT_AUDIO_DEVICE_TYPES.contains(audioDeviceType);
+ boolean isBtDevice = BT_AUDIO_DEVICE_INFO_TYPES.contains(audioDeviceType);
Log.i(this, "clearCommunicationDevice: type = %s, isBtDevice = %s",
audioDeviceType, isBtDevice);
diff --git a/src/com/android/server/telecom/CallAudioManager.java b/src/com/android/server/telecom/CallAudioManager.java
index 96bf2c6..e5678a0 100644
--- a/src/com/android/server/telecom/CallAudioManager.java
+++ b/src/com/android/server/telecom/CallAudioManager.java
@@ -750,6 +750,10 @@
private void onCallEnteringRinging() {
if (mRingingCalls.size() == 1) {
+ // Wait until the BT ICS binding completed to request further audio route change
+ for (Call ringingCall: mRingingCalls) {
+ ringingCall.waitForBtIcs();
+ }
mCallAudioModeStateMachine.sendMessageWithArgs(
CallAudioModeStateMachine.NEW_RINGING_CALL,
makeArgsForModeStateMachine());
diff --git a/src/com/android/server/telecom/CallAudioRouteAdapter.java b/src/com/android/server/telecom/CallAudioRouteAdapter.java
index 7f7b43c..f76d47d 100644
--- a/src/com/android/server/telecom/CallAudioRouteAdapter.java
+++ b/src/com/android/server/telecom/CallAudioRouteAdapter.java
@@ -1,15 +1,131 @@
package com.android.server.telecom;
+import android.bluetooth.BluetoothDevice;
import android.os.Handler;
import android.telecom.CallAudioState;
+import android.util.SparseArray;
import com.android.internal.util.IndentingPrintWriter;
public interface CallAudioRouteAdapter {
+ /** Valid values for msg.what */
+ int CONNECT_WIRED_HEADSET = 1;
+ int DISCONNECT_WIRED_HEADSET = 2;
+ int CONNECT_DOCK = 5;
+ int DISCONNECT_DOCK = 6;
+ int BLUETOOTH_DEVICE_LIST_CHANGED = 7;
+ int BT_ACTIVE_DEVICE_PRESENT = 8;
+ int BT_ACTIVE_DEVICE_GONE = 9;
+ int BT_DEVICE_ADDED = 10;
+ int BT_DEVICE_REMOVED = 11;
+
+ int SWITCH_EARPIECE = 1001;
+ int SWITCH_BLUETOOTH = 1002;
+ int SWITCH_HEADSET = 1003;
+ int SWITCH_SPEAKER = 1004;
+ // Wired headset, earpiece, or speakerphone, in that order of precedence.
+ int SWITCH_BASELINE_ROUTE = 1005;
+
+ // Messages denoting that the speakerphone was turned on/off. Used to update state when we
+ // weren't the ones who turned it on/off
+ int SPEAKER_ON = 1006;
+ int SPEAKER_OFF = 1007;
+
+ // Messages denoting that the streaming route switch request was sent.
+ int STREAMING_FORCE_ENABLED = 1008;
+ int STREAMING_FORCE_DISABLED = 1009;
+
+ int USER_SWITCH_EARPIECE = 1101;
+ int USER_SWITCH_BLUETOOTH = 1102;
+ int USER_SWITCH_HEADSET = 1103;
+ int USER_SWITCH_SPEAKER = 1104;
+ int USER_SWITCH_BASELINE_ROUTE = 1105;
+
+ int UPDATE_SYSTEM_AUDIO_ROUTE = 1201;
+
+ // These three messages indicate state changes that come from BluetoothRouteManager.
+ // They may be triggered by the BT stack doing something on its own or they may be sent after
+ // we request that the BT stack do something. Any logic for these messages should take into
+ // account the possibility that the event indicated has already been processed (i.e. handling
+ // should be idempotent).
+ int BT_AUDIO_DISCONNECTED = 1301;
+ int BT_AUDIO_CONNECTED = 1302;
+ int BT_AUDIO_PENDING = 1303;
+
+ int MUTE_ON = 3001;
+ int MUTE_OFF = 3002;
+ int TOGGLE_MUTE = 3003;
+ int MUTE_EXTERNALLY_CHANGED = 3004;
+
+ int SWITCH_FOCUS = 4001;
+
+ // Used in testing to execute verifications. Not compatible with subsessions.
+ int RUN_RUNNABLE = 9001;
+
+ // Used for PendingAudioRoute to notify audio switch success
+ int EXIT_PENDING_ROUTE = 10001;
+ // Used for PendingAudioRoute to notify audio switch timeout
+ int PENDING_ROUTE_TIMEOUT = 10002;
+ // Used for PendingAudioRoute to notify audio switch failed
+ int PENDING_ROUTE_FAILED = 10003;
+
+ /** Valid values for mAudioFocusType */
+ int NO_FOCUS = 1;
+ int ACTIVE_FOCUS = 2;
+ int RINGING_FOCUS = 3;
+
+ /** Valid arg for BLUETOOTH_DEVICE_LIST_CHANGED */
+ int DEVICE_CONNECTED = 1;
+ int DEVICE_DISCONNECTED = 2;
+
+ SparseArray<String> MESSAGE_CODE_TO_NAME = new SparseArray<String>() {{
+ put(CONNECT_WIRED_HEADSET, "CONNECT_WIRED_HEADSET");
+ put(DISCONNECT_WIRED_HEADSET, "DISCONNECT_WIRED_HEADSET");
+ put(CONNECT_DOCK, "CONNECT_DOCK");
+ put(DISCONNECT_DOCK, "DISCONNECT_DOCK");
+ put(BLUETOOTH_DEVICE_LIST_CHANGED, "BLUETOOTH_DEVICE_LIST_CHANGED");
+ put(BT_ACTIVE_DEVICE_PRESENT, "BT_ACTIVE_DEVICE_PRESENT");
+ put(BT_ACTIVE_DEVICE_GONE, "BT_ACTIVE_DEVICE_GONE");
+ put(BT_DEVICE_ADDED, "BT_DEVICE_ADDED");
+ put(BT_DEVICE_REMOVED, "BT_DEVICE_REMOVED");
+
+ put(SWITCH_EARPIECE, "SWITCH_EARPIECE");
+ put(SWITCH_BLUETOOTH, "SWITCH_BLUETOOTH");
+ put(SWITCH_HEADSET, "SWITCH_HEADSET");
+ put(SWITCH_SPEAKER, "SWITCH_SPEAKER");
+ put(SWITCH_BASELINE_ROUTE, "SWITCH_BASELINE_ROUTE");
+ put(SPEAKER_ON, "SPEAKER_ON");
+ put(SPEAKER_OFF, "SPEAKER_OFF");
+
+ put(USER_SWITCH_EARPIECE, "USER_SWITCH_EARPIECE");
+ put(USER_SWITCH_BLUETOOTH, "USER_SWITCH_BLUETOOTH");
+ put(USER_SWITCH_HEADSET, "USER_SWITCH_HEADSET");
+ put(USER_SWITCH_SPEAKER, "USER_SWITCH_SPEAKER");
+ put(USER_SWITCH_BASELINE_ROUTE, "USER_SWITCH_BASELINE_ROUTE");
+
+ put(UPDATE_SYSTEM_AUDIO_ROUTE, "UPDATE_SYSTEM_AUDIO_ROUTE");
+
+ put(BT_AUDIO_DISCONNECTED, "BT_AUDIO_DISCONNECTED");
+ put(BT_AUDIO_CONNECTED, "BT_AUDIO_CONNECTED");
+ put(BT_AUDIO_PENDING, "BT_AUDIO_PENDING");
+
+ put(MUTE_ON, "MUTE_ON");
+ put(MUTE_OFF, "MUTE_OFF");
+ put(TOGGLE_MUTE, "TOGGLE_MUTE");
+ put(MUTE_EXTERNALLY_CHANGED, "MUTE_EXTERNALLY_CHANGED");
+
+ put(SWITCH_FOCUS, "SWITCH_FOCUS");
+
+ put(RUN_RUNNABLE, "RUN_RUNNABLE");
+
+ put(EXIT_PENDING_ROUTE, "EXIT_PENDING_ROUTE");
+ }};
+
void initialize();
void sendMessageWithSessionInfo(int message);
void sendMessageWithSessionInfo(int message, int arg);
void sendMessageWithSessionInfo(int message, int arg, String data);
+ void sendMessageWithSessionInfo(int message, int arg, BluetoothDevice bluetoothDevice);
void sendMessage(int message, Runnable r);
void setCallAudioManager(CallAudioManager callAudioManager);
CallAudioState getCurrentCallAudioState();
diff --git a/src/com/android/server/telecom/CallAudioRouteController.java b/src/com/android/server/telecom/CallAudioRouteController.java
index f8c49bb..c1d7d0c 100644
--- a/src/com/android/server/telecom/CallAudioRouteController.java
+++ b/src/com/android/server/telecom/CallAudioRouteController.java
@@ -1,41 +1,226 @@
+/*
+ * Copyright (C) 2023 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;
+import static com.android.server.telecom.AudioRoute.BT_AUDIO_ROUTE_TYPES;
+import static com.android.server.telecom.AudioRoute.TYPE_INVALID;
+
+import android.bluetooth.BluetoothDevice;
+import android.content.Context;
+import android.media.AudioAttributes;
+import android.media.AudioDeviceAttributes;
+import android.media.AudioDeviceInfo;
+import android.media.AudioManager;
+import android.media.audiopolicy.AudioProductStrategy;
import android.os.Handler;
import android.os.HandlerThread;
-import android.os.Looper;
+import android.os.Message;
import android.telecom.CallAudioState;
+import android.telecom.Log;
+import android.telecom.Logging.Session;
+import android.util.ArrayMap;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.SomeArgs;
import com.android.internal.util.IndentingPrintWriter;
-public class CallAudioRouteController implements CallAudioRouteAdapter {
- private Handler mHandler;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
- public CallAudioRouteController() {
+public class CallAudioRouteController implements CallAudioRouteAdapter {
+ private static final long TIMEOUT_LIMIT = 2000L;
+ private static final AudioRoute DUMMY_ROUTE = new AudioRoute(TYPE_INVALID, null, null);
+ private static final Map<Integer, Integer> ROUTE_MAP;
+ static {
+ ROUTE_MAP = new ArrayMap<>();
+ ROUTE_MAP.put(AudioRoute.TYPE_EARPIECE, CallAudioState.ROUTE_EARPIECE);
+ ROUTE_MAP.put(AudioRoute.TYPE_WIRED, CallAudioState.ROUTE_WIRED_HEADSET);
+ ROUTE_MAP.put(AudioRoute.TYPE_SPEAKER, CallAudioState.ROUTE_SPEAKER);
+ ROUTE_MAP.put(AudioRoute.TYPE_DOCK, CallAudioState.ROUTE_SPEAKER);
+ 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);
+ }
+
+ private final CallsManager mCallsManager;
+ private AudioManager mAudioManager;
+ private final Handler mHandler;
+ private final WiredHeadsetManager mWiredHeadsetManager;
+ private Set<AudioRoute> mAvailableRoutes;
+ private AudioRoute mCurrentRoute;
+ private AudioRoute mEarpieceWiredRoute;
+ private AudioRoute mSpeakerDockRoute;
+ private Map<AudioRoute, BluetoothDevice> mBluetoothRoutes;
+ private Map<Integer, AudioRoute> mTypeRoutes;
+ private PendingAudioRoute mPendingAudioRoute;
+ private AudioRoute.Factory mAudioRouteFactory;
+ private CallAudioState mCallAudioState;
+ private boolean mIsMute;
+ private boolean mIsPending;
+ private boolean mIsActive;
+
+ public CallAudioRouteController(
+ Context context,
+ CallsManager callsManager,
+ AudioRoute.Factory audioRouteFactory,
+ WiredHeadsetManager wiredHeadsetManager) {
+ mCallsManager = callsManager;
+ mAudioManager = context.getSystemService(AudioManager.class);
+ mAudioRouteFactory = audioRouteFactory;
+ mWiredHeadsetManager = wiredHeadsetManager;
+ mIsMute = false;
HandlerThread handlerThread = new HandlerThread(this.getClass().getSimpleName());
handlerThread.start();
- mHandler = new Handler(handlerThread.getLooper());
+ 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;
+ }
+ postHandleMessage(msg);
+ }
+ };
}
@Override
public void initialize() {
+ mAvailableRoutes = new HashSet<>();
+ mBluetoothRoutes = new ArrayMap<>();
+ mTypeRoutes = new ArrayMap<>();
+ mPendingAudioRoute = new PendingAudioRoute(this, mAudioManager);
+
+ int supportMask = calculateSupportedRouteMask();
+ if ((supportMask & CallAudioState.ROUTE_SPEAKER) != 0) {
+ // Create spekaer routes
+ mSpeakerDockRoute = mAudioRouteFactory.create(AudioRoute.TYPE_SPEAKER, null,
+ mAudioManager);
+ if (mSpeakerDockRoute == null) {
+ Log.w(this, "Can't find available audio device info for route TYPE_SPEAKER");
+ } else {
+ mTypeRoutes.put(AudioRoute.TYPE_SPEAKER, mSpeakerDockRoute);
+ mAvailableRoutes.add(mSpeakerDockRoute);
+ }
+ }
+
+ if ((supportMask & CallAudioState.ROUTE_WIRED_HEADSET) != 0) {
+ // Create wired headset routes
+ mEarpieceWiredRoute = mAudioRouteFactory.create(AudioRoute.TYPE_WIRED, null,
+ mAudioManager);
+ if (mEarpieceWiredRoute == null) {
+ Log.w(this, "Can't find available audio device info for route TYPE_WIRED_HEADSET");
+ } else {
+ mTypeRoutes.put(AudioRoute.TYPE_WIRED, mEarpieceWiredRoute);
+ mAvailableRoutes.add(mEarpieceWiredRoute);
+ }
+ } else if ((supportMask & CallAudioState.ROUTE_EARPIECE) != 0) {
+ // Create earpiece routes
+ mEarpieceWiredRoute = mAudioRouteFactory.create(AudioRoute.TYPE_EARPIECE, null,
+ mAudioManager);
+ if (mEarpieceWiredRoute == null) {
+ Log.w(this, "Can't find available audio device info for route TYPE_EARPIECE");
+ } else {
+ mTypeRoutes.put(AudioRoute.TYPE_EARPIECE, mEarpieceWiredRoute);
+ mAvailableRoutes.add(mEarpieceWiredRoute);
+ }
+ }
+
+ // set current route
+ if (mEarpieceWiredRoute != null) {
+ mCurrentRoute = mEarpieceWiredRoute;
+ } else {
+ mCurrentRoute = mSpeakerDockRoute;
+ }
+ mIsActive = false;
+ mCallAudioState = new CallAudioState(mIsMute, ROUTE_MAP.get(mCurrentRoute.getType()),
+ supportMask, null, new HashSet<>());
}
@Override
public void sendMessageWithSessionInfo(int message) {
+ sendMessageWithSessionInfo(message, 0, (String) null);
}
@Override
public void sendMessageWithSessionInfo(int message, int arg) {
-
+ sendMessageWithSessionInfo(message, arg, (String) null);
}
@Override
public void sendMessageWithSessionInfo(int message, int arg, String data) {
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = Log.createSubsession();
+ args.arg2 = data;
+ sendMessage(message, arg, 0, args);
+ }
+ @Override
+ public void sendMessageWithSessionInfo(int message, int arg, BluetoothDevice bluetoothDevice) {
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = Log.createSubsession();
+ args.arg2 = bluetoothDevice;
+ sendMessage(message, arg, 0, args);
}
@Override
public void sendMessage(int message, Runnable r) {
+ r.run();
+ }
+ private void sendMessage(int what, int arg1, int arg2, Object obj) {
+ mHandler.sendMessage(Message.obtain(mHandler, what, arg1, arg2, obj));
}
@Override
@@ -49,7 +234,7 @@
@Override
public boolean isHfpDeviceAvailable() {
- return false;
+ return !mBluetoothRoutes.isEmpty();
}
@Override
@@ -59,6 +244,287 @@
@Override
public void dump(IndentingPrintWriter pw) {
+ }
+ private void preHandleMessage(Message msg) {
+ if (msg.obj instanceof SomeArgs) {
+ Session session = (Session) ((SomeArgs) msg.obj).arg1;
+ String messageCodeName = MESSAGE_CODE_TO_NAME.get(msg.what, "unknown");
+ Log.continueSession(session, "CARC.pM_" + messageCodeName);
+ Log.i(this, "Message received: %s=%d, arg1=%d", messageCodeName, msg.what, msg.arg1);
+ }
+ }
+
+ private void postHandleMessage(Message msg) {
+ Log.endSession();
+ if (msg.obj instanceof SomeArgs) {
+ ((SomeArgs) msg.obj).recycle();
+ }
+ }
+
+ public boolean isActive() {
+ return mIsActive;
+ }
+
+ public boolean isPending() {
+ return mIsPending;
+ }
+
+ private void routeTo(boolean active, AudioRoute destRoute) {
+ if (mIsPending) {
+ if (destRoute.equals(mPendingAudioRoute.getDestRoute()) && (mIsActive == active)) {
+ return;
+ }
+ Log.i(this, "Override current pending route destination from %s(active=%b) to "
+ + "%s(active=%b)",
+ mPendingAudioRoute.getDestRoute(), mIsActive, destRoute, active);
+ // override pending route while keep waiting for still pending messages for the
+ // previous pending route
+ mPendingAudioRoute.setOrigRoute(mIsActive, mPendingAudioRoute.getDestRoute());
+ mPendingAudioRoute.setDestRoute(active, destRoute);
+ } else {
+ 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)) {
+ mPendingAudioRoute.setOrigRoute(mIsActive, mCurrentRoute);
+ } else {
+ // Avoid waiting for pending messages for an unavailable route
+ mPendingAudioRoute.setOrigRoute(mIsActive, DUMMY_ROUTE);
+ }
+ mPendingAudioRoute.setDestRoute(active, destRoute);
+ mIsPending = true;
+ }
+ mPendingAudioRoute.evaluatePendingState();
+ postTimeoutMessage();
+ }
+
+ private void postTimeoutMessage() {
+ // reset timeout handler
+ mHandler.removeMessages(PENDING_ROUTE_TIMEOUT);
+ mHandler.postDelayed(() -> mHandler.sendMessage(
+ Message.obtain(mHandler, PENDING_ROUTE_TIMEOUT)), TIMEOUT_LIMIT);
+ }
+
+ private void handleBtAudioActive(BluetoothDevice bluetoothDevice) {
+ if (mIsPending) {
+ if (Objects.equals(mPendingAudioRoute.getDestRoute().getBluetoothAddress(),
+ bluetoothDevice.getAddress())) {
+ mPendingAudioRoute.onMessageReceived(BT_AUDIO_CONNECTED);
+ }
+ } else {
+ // ignore, not triggered by telecom
+ }
+ }
+
+ private void handleBtAudioInactive(BluetoothDevice bluetoothDevice) {
+ if (mIsPending) {
+ if (Objects.equals(mPendingAudioRoute.getOrigRoute().getBluetoothAddress(),
+ bluetoothDevice.getAddress())) {
+ mPendingAudioRoute.onMessageReceived(BT_AUDIO_DISCONNECTED);
+ }
+ } else {
+ // ignore, not triggered by telecom
+ }
+ }
+
+ private void handleBtConnected(@AudioRoute.AudioRouteType int type,
+ BluetoothDevice bluetoothDevice) {
+ AudioRoute bluetoothRoute = null;
+ bluetoothRoute = mAudioRouteFactory.create(type, bluetoothDevice.getAddress(),
+ mAudioManager);
+ if (bluetoothRoute == null) {
+ Log.w(this, "Can't find available audio device info for route type:"
+ + AudioRoute.DEVICE_TYPE_STRINGS.get(type));
+ } else {
+ Log.i(this, "bluetooth route added: " + bluetoothRoute);
+ mAvailableRoutes.add(bluetoothRoute);
+ mBluetoothRoutes.put(bluetoothRoute, bluetoothDevice);
+ onAvailableRoutesChanged();
+ }
+ }
+
+ private void handleBtDisconnected(@AudioRoute.AudioRouteType int type,
+ BluetoothDevice bluetoothDevice) {
+ // Clean up unavailable routes
+ AudioRoute bluetoothRoute = getBluetoothRoute(type, bluetoothDevice.getAddress());
+ if (bluetoothRoute != null) {
+ Log.i(this, "bluetooth route removed: " + bluetoothRoute);
+ mBluetoothRoutes.remove(bluetoothRoute);
+ mAvailableRoutes.remove(bluetoothRoute);
+ onAvailableRoutesChanged();
+ }
+
+ // 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 */));
+ }
+ }
+ }
+
+ private void handleBtActiveDevicePresent(@AudioRoute.AudioRouteType int type,
+ String deviceAddress) {
+ AudioRoute bluetoothRoute = getBluetoothRoute(type, deviceAddress);
+ if (bluetoothRoute != null) {
+ Log.i(this, "request to route to bluetooth route: %s(active=%b)", bluetoothRoute,
+ mIsActive);
+ routeTo(mIsActive, bluetoothRoute);
+ }
+ }
+
+ private void handleBtActiveDeviceGone(@AudioRoute.AudioRouteType int type) {
+ 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);
+ } else {
+ routeTo(mIsActive, getPreferredAudioRouteFromDefault(false/* includeBluetooth */));
+ }
+ onAvailableRoutesChanged();
+ }
+ }
+
+ public void handleExitPendingRoute() {
+ if (mIsPending) {
+ Log.i(this, "Exit pending route and enter %s(active=%b)",
+ mPendingAudioRoute.getDestRoute(), mPendingAudioRoute.isActive());
+ 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);
+ }
+ 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));
+ }
+ }
+ updateCallAudioState(new CallAudioState(mIsMute, mCallAudioState.getRoute(), routeMask,
+ mCallAudioState.getActiveBluetoothDevice(), availableBluetoothDevices));
+ }
+
+ private void updateCallAudioState(CallAudioState callAudioState) {
+ CallAudioState oldState = mCallAudioState;
+ mCallAudioState = callAudioState;
+ mCallsManager.onCallAudioStateChanged(oldState, mCallAudioState);
+ }
+
+ private AudioRoute getPreferredAudioRouteFromStrategy() {
+ // Get audio produce strategy
+ AudioProductStrategy strategy = null;
+ final AudioAttributes attr = new AudioAttributes.Builder()
+ .setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION)
+ .build();
+ List<AudioProductStrategy> strategies = AudioManager.getAudioProductStrategies();
+ for (AudioProductStrategy s : strategies) {
+ if (s.supportsAudioAttributes(attr)) {
+ strategy = s;
+ }
+ }
+ if (strategy == null) {
+ return null;
+ }
+
+ // Get preferred device
+ AudioDeviceAttributes deviceAttr = mAudioManager.getPreferredDeviceForStrategy(strategy);
+ if (deviceAttr == null) {
+ return null;
+ }
+
+ // Get corresponding audio route
+ @AudioRoute.AudioRouteType int type = AudioRoute.DEVICE_INFO_TYPETO_AUDIO_ROUTE_TYPE.get(
+ deviceAttr.getType());
+ if (BT_AUDIO_ROUTE_TYPES.contains(type)) {
+ return getBluetoothRoute(type, deviceAttr.getAddress());
+ } else {
+ return mTypeRoutes.get(deviceAttr.getType());
+
+ }
+ }
+
+ public AudioRoute getPreferredAudioRouteFromDefault(boolean includeBluetooth) {
+ if (mBluetoothRoutes.isEmpty() || !includeBluetooth) {
+ return mEarpieceWiredRoute != null ? mEarpieceWiredRoute : mSpeakerDockRoute;
+ } else {
+ // Most recent active route will always be the last in the array
+ return mBluetoothRoutes.keySet().stream().toList().get(mBluetoothRoutes.size() - 1);
+ }
+ }
+
+ private int calculateSupportedRouteMask() {
+ int routeMask = CallAudioState.ROUTE_SPEAKER;
+
+ if (mWiredHeadsetManager.isPluggedIn()) {
+ routeMask |= CallAudioState.ROUTE_WIRED_HEADSET;
+ } else {
+ AudioDeviceInfo[] deviceList = mAudioManager.getDevices(
+ AudioManager.GET_DEVICES_OUTPUTS);
+ for (AudioDeviceInfo device: deviceList) {
+ if (device.getType() == AudioDeviceInfo.TYPE_BUILTIN_EARPIECE) {
+ routeMask |= CallAudioState.ROUTE_EARPIECE;
+ break;
+ }
+ }
+ }
+ return routeMask;
+ }
+
+ public Set<AudioRoute> getAvailableRoutes() {
+ return mAvailableRoutes;
+ }
+
+ public AudioRoute getCurrentRoute() {
+ return mCurrentRoute;
+ }
+
+ private AudioRoute getBluetoothRoute(@AudioRoute.AudioRouteType int audioRouteType,
+ String address) {
+ for (AudioRoute route : mBluetoothRoutes.keySet()) {
+ if (route.getType() == audioRouteType && route.getBluetoothAddress().equals(address)) {
+ return route;
+ }
+ }
+ return null;
+ }
+
+ @VisibleForTesting
+ public void setAudioManager(AudioManager audioManager) {
+ mAudioManager = audioManager;
+ }
+
+ @VisibleForTesting
+ public void setAudioRouteFactory(AudioRoute.Factory audioRouteFactory) {
+ mAudioRouteFactory = audioRouteFactory;
+ }
+
+ @VisibleForTesting
+ public void setActive(boolean active) {
+ mIsActive = active;
}
}
diff --git a/src/com/android/server/telecom/CallAudioRouteStateMachine.java b/src/com/android/server/telecom/CallAudioRouteStateMachine.java
index c0bb50e..26c25e8 100644
--- a/src/com/android/server/telecom/CallAudioRouteStateMachine.java
+++ b/src/com/android/server/telecom/CallAudioRouteStateMachine.java
@@ -120,63 +120,6 @@
/** Direct the audio stream through another device. */
public static final int ROUTE_STREAMING = CallAudioState.ROUTE_STREAMING;
- /** Valid values for msg.what */
- public static final int CONNECT_WIRED_HEADSET = 1;
- public static final int DISCONNECT_WIRED_HEADSET = 2;
- public static final int CONNECT_DOCK = 5;
- public static final int DISCONNECT_DOCK = 6;
- public static final int BLUETOOTH_DEVICE_LIST_CHANGED = 7;
- public static final int BT_ACTIVE_DEVICE_PRESENT = 8;
- public static final int BT_ACTIVE_DEVICE_GONE = 9;
-
- public static final int SWITCH_EARPIECE = 1001;
- public static final int SWITCH_BLUETOOTH = 1002;
- public static final int SWITCH_HEADSET = 1003;
- public static final int SWITCH_SPEAKER = 1004;
- // Wired headset, earpiece, or speakerphone, in that order of precedence.
- public static final int SWITCH_BASELINE_ROUTE = 1005;
-
- // Messages denoting that the speakerphone was turned on/off. Used to update state when we
- // weren't the ones who turned it on/off
- public static final int SPEAKER_ON = 1006;
- public static final int SPEAKER_OFF = 1007;
-
- // Messages denoting that the streaming route switch request was sent.
- public static final int STREAMING_FORCE_ENABLED = 1008;
- public static final int STREAMING_FORCE_DISABLED = 1009;
-
- public static final int USER_SWITCH_EARPIECE = 1101;
- public static final int USER_SWITCH_BLUETOOTH = 1102;
- public static final int USER_SWITCH_HEADSET = 1103;
- public static final int USER_SWITCH_SPEAKER = 1104;
- public static final int USER_SWITCH_BASELINE_ROUTE = 1105;
-
- public static final int UPDATE_SYSTEM_AUDIO_ROUTE = 1201;
-
- // These three messages indicate state changes that come from BluetoothRouteManager.
- // They may be triggered by the BT stack doing something on its own or they may be sent after
- // we request that the BT stack do something. Any logic for these messages should take into
- // account the possibility that the event indicated has already been processed (i.e. handling
- // should be idempotent).
- public static final int BT_AUDIO_DISCONNECTED = 1301;
- public static final int BT_AUDIO_CONNECTED = 1302;
- public static final int BT_AUDIO_PENDING = 1303;
-
- public static final int MUTE_ON = 3001;
- public static final int MUTE_OFF = 3002;
- public static final int TOGGLE_MUTE = 3003;
- public static final int MUTE_EXTERNALLY_CHANGED = 3004;
-
- public static final int SWITCH_FOCUS = 4001;
-
- // Used in testing to execute verifications. Not compatible with subsessions.
- public static final int RUN_RUNNABLE = 9001;
-
- /** Valid values for mAudioFocusType */
- public static final int NO_FOCUS = 1;
- public static final int ACTIVE_FOCUS = 2;
- public static final int RINGING_FOCUS = 3;
-
/** Valid values for the first argument for SWITCH_BASELINE_ROUTE */
public static final int NO_INCLUDE_BLUETOOTH_IN_BASELINE = 0;
public static final int INCLUDE_BLUETOOTH_IN_BASELINE = 1;
@@ -189,45 +132,6 @@
put(CallAudioState.ROUTE_WIRED_HEADSET, LogUtils.Events.AUDIO_ROUTE_HEADSET);
}};
- private static final SparseArray<String> MESSAGE_CODE_TO_NAME = new SparseArray<String>() {{
- put(CONNECT_WIRED_HEADSET, "CONNECT_WIRED_HEADSET");
- put(DISCONNECT_WIRED_HEADSET, "DISCONNECT_WIRED_HEADSET");
- put(CONNECT_DOCK, "CONNECT_DOCK");
- put(DISCONNECT_DOCK, "DISCONNECT_DOCK");
- put(BLUETOOTH_DEVICE_LIST_CHANGED, "BLUETOOTH_DEVICE_LIST_CHANGED");
- put(BT_ACTIVE_DEVICE_PRESENT, "BT_ACTIVE_DEVICE_PRESENT");
- put(BT_ACTIVE_DEVICE_GONE, "BT_ACTIVE_DEVICE_GONE");
-
- put(SWITCH_EARPIECE, "SWITCH_EARPIECE");
- put(SWITCH_BLUETOOTH, "SWITCH_BLUETOOTH");
- put(SWITCH_HEADSET, "SWITCH_HEADSET");
- put(SWITCH_SPEAKER, "SWITCH_SPEAKER");
- put(SWITCH_BASELINE_ROUTE, "SWITCH_BASELINE_ROUTE");
- put(SPEAKER_ON, "SPEAKER_ON");
- put(SPEAKER_OFF, "SPEAKER_OFF");
-
- put(USER_SWITCH_EARPIECE, "USER_SWITCH_EARPIECE");
- put(USER_SWITCH_BLUETOOTH, "USER_SWITCH_BLUETOOTH");
- put(USER_SWITCH_HEADSET, "USER_SWITCH_HEADSET");
- put(USER_SWITCH_SPEAKER, "USER_SWITCH_SPEAKER");
- put(USER_SWITCH_BASELINE_ROUTE, "USER_SWITCH_BASELINE_ROUTE");
-
- put(UPDATE_SYSTEM_AUDIO_ROUTE, "UPDATE_SYSTEM_AUDIO_ROUTE");
-
- put(BT_AUDIO_DISCONNECTED, "BT_AUDIO_DISCONNECTED");
- put(BT_AUDIO_CONNECTED, "BT_AUDIO_CONNECTED");
- put(BT_AUDIO_PENDING, "BT_AUDIO_PENDING");
-
- put(MUTE_ON, "MUTE_ON");
- put(MUTE_OFF, "MUTE_OFF");
- put(TOGGLE_MUTE, "TOGGLE_MUTE");
- put(MUTE_EXTERNALLY_CHANGED, "MUTE_EXTERNALLY_CHANGED");
-
- put(SWITCH_FOCUS, "SWITCH_FOCUS");
-
- put(RUN_RUNNABLE, "RUN_RUNNABLE");
- }};
-
private static final String ACTIVE_EARPIECE_ROUTE_NAME = "ActiveEarpieceRoute";
private static final String ACTIVE_BLUETOOTH_ROUTE_NAME = "ActiveBluetoothRoute";
private static final String ACTIVE_SPEAKER_ROUTE_NAME = "ActiveSpeakerRoute";
@@ -1737,11 +1641,11 @@
}
public void sendMessageWithSessionInfo(int message, int arg) {
- sendMessageWithSessionInfo(message, arg, null);
+ sendMessageWithSessionInfo(message, arg, (String) null);
}
public void sendMessageWithSessionInfo(int message) {
- sendMessageWithSessionInfo(message, 0, null);
+ sendMessageWithSessionInfo(message, 0, (String) null);
}
public void sendMessageWithSessionInfo(int message, int arg, String data) {
@@ -1751,6 +1655,10 @@
sendMessage(message, arg, 0, args);
}
+ public void sendMessageWithSessionInfo(int message, int arg, BluetoothDevice bluetoothDevice) {
+ // ignore, only used in CallAudioRouteController
+ }
+
@Override
public void sendMessage(int message, Runnable r) {
super.sendMessage(message, r);
diff --git a/src/com/android/server/telecom/CallIntentProcessor.java b/src/com/android/server/telecom/CallIntentProcessor.java
index 062c872..c02d20d 100644
--- a/src/com/android/server/telecom/CallIntentProcessor.java
+++ b/src/com/android/server/telecom/CallIntentProcessor.java
@@ -172,9 +172,14 @@
// Show the toast to warn user that it is a personal call though initiated in work
// profile.
if (fixedInitiatingUser) {
- Toast.makeText(context, Looper.getMainLooper(),
- context.getString(R.string.toast_personal_call_msg),
- Toast.LENGTH_LONG).show();
+ if (featureFlags.telecomResolveHiddenDependencies()) {
+ Toast.makeText(context, context.getString(R.string.toast_personal_call_msg),
+ Toast.LENGTH_LONG).show();
+ } else {
+ Toast.makeText(context, Looper.getMainLooper(),
+ context.getString(R.string.toast_personal_call_msg),
+ Toast.LENGTH_LONG).show();
+ }
}
} else {
Log.i(CallIntentProcessor.class,
diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java
index 5734405..de7d84a 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;
@@ -164,6 +164,7 @@
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
@@ -555,7 +556,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);
}
}
@@ -645,9 +646,11 @@
featureFlags
);
} else {
- callAudioRouteAdapter = new CallAudioRouteController();
+ callAudioRouteAdapter = new CallAudioRouteController(
+ context, this, new AudioRoute.Factory(), wiredHeadsetManager);
}
callAudioRouteAdapter.initialize();
+ bluetoothStateReceiver.setCallAudioRouteAdapter(callAudioRouteAdapter);
CallAudioRoutePeripheralAdapter callAudioRoutePeripheralAdapter =
new CallAudioRoutePeripheralAdapter(
@@ -748,7 +751,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<>();
@@ -886,11 +889,11 @@
}
private IncomingCallFilterGraph setUpCallFilterGraph(Call incomingCall) {
+ TelecomManager telecomManager = mContext.getSystemService(TelecomManager.class);
incomingCall.setIsUsingCallFiltering(true);
String carrierPackageName = getCarrierPackageName();
UserHandle userHandle = incomingCall.getAssociatedUser();
- String defaultDialerPackageName = TelecomManager.from(mContext).
- getDefaultDialerPackage(userHandle);
+ String defaultDialerPackageName = telecomManager.getDefaultDialerPackage(userHandle);
String userChosenPackageName = getRoleManagerAdapter().
getDefaultCallScreeningApp(userHandle);
AppLabelProxy appLabelProxy = packageName -> AppLabelProxy.Util.getAppLabel(
@@ -962,8 +965,10 @@
if (incomingCall.getState() != CallState.DISCONNECTED &&
incomingCall.getState() != CallState.DISCONNECTING) {
- setCallState(incomingCall, CallState.RINGING,
- result.shouldAllowCall ? "successful incoming call" : "blocking call");
+ if (!mFeatureFlags.separatelyBindToBtIncallService()) {
+ setCallState(incomingCall, CallState.RINGING,
+ result.shouldAllowCall ? "successful incoming call" : "blocking call");
+ }
} else {
Log.i(this, "onCallFilteringCompleted: call already disconnected.");
return;
@@ -1008,6 +1013,10 @@
}
if (result.shouldAllowCall) {
+ if (mFeatureFlags.separatelyBindToBtIncallService()) {
+ incomingCall.setBtIcsFuture(mInCallController.bindToBTService(incomingCall));
+ setCallState(incomingCall, CallState.RINGING, "successful incoming call");
+ }
incomingCall.setPostCallPackageName(
getRoleManagerAdapter().getDefaultCallScreeningApp(
incomingCall.getAssociatedUser()
@@ -1022,7 +1031,6 @@
"Exceeds maximum number of ringing calls.");
incomingCall.setMissedReason(AUTO_MISSED_MAXIMUM_RINGING);
autoMissCallAndLog(incomingCall, result);
- return;
}
} else if (hasMaximumManagedDialingCalls(incomingCall)) {
if (shouldSilenceInsteadOfReject(incomingCall)) {
@@ -1032,7 +1040,6 @@
"dialing calls.");
incomingCall.setMissedReason(AUTO_MISSED_MAXIMUM_DIALING);
autoMissCallAndLog(incomingCall, result);
- return;
}
} else if (result.shouldScreenViaAudio) {
Log.i(this, "onCallFilteringCompleted: starting background audio processing");
@@ -1051,6 +1058,9 @@
} else {
if (result.shouldReject) {
Log.i(this, "onCallFilteringCompleted: blocked call, rejecting.");
+ if (mFeatureFlags.separatelyBindToBtIncallService()) {
+ setCallState(incomingCall, CallState.RINGING, "blocking call");
+ }
incomingCall.reject(false, null);
}
if (result.shouldAddToCallLog) {
@@ -2916,7 +2926,7 @@
if (call.isEmergencyCall()) {
Executors.defaultThreadFactory().newThread(() ->
- BlockedNumberContract.SystemContract.notifyEmergencyContact(mContext))
+ BlockedNumberContract.BlockedNumbers.notifyEmergencyContact(mContext))
.start();
}
@@ -4669,18 +4679,35 @@
}
/**
- * Determines if there are any ongoing self managed calls for the given package/user.
+ * Determines if there are any ongoing self-managed calls for the given package/user.
* @param packageName The package name to check.
- * @param userHandle The userhandle to check.
+ * @param userHandle The {@link UserHandle} to check.
* @return {@code true} if the app has ongoing calls, or {@code false} otherwise.
*/
public boolean isInSelfManagedCall(String packageName, UserHandle userHandle) {
+ return isInSelfManagedCallCrossUsers(packageName, userHandle, false);
+ }
+
+ /**
+ * Determines if there are any ongoing self-managed calls for the given package/user (unless
+ * hasCrossUsers has been enabled).
+ * @param packageName The package name to check.
+ * @param userHandle The {@link UserHandle} to check.
+ * @param hasCrossUserAccess indicates if calls across all users should be returned.
+ * @return {@code true} if the app has ongoing calls, or {@code false} otherwise.
+ */
+ public boolean isInSelfManagedCallCrossUsers(
+ String packageName, UserHandle userHandle, boolean hasCrossUserAccess) {
return mSelfManagedCallsBeingSetup.stream().anyMatch(c -> c.isSelfManaged()
&& c.getTargetPhoneAccount().getComponentName().getPackageName().equals(packageName)
- && c.getTargetPhoneAccount().getUserHandle().equals(userHandle)) ||
- mCalls.stream().anyMatch(c -> c.isSelfManaged()
+ && (!hasCrossUserAccess
+ ? c.getTargetPhoneAccount().getUserHandle().equals(userHandle)
+ : true))
+ || mCalls.stream().anyMatch(c -> c.isSelfManaged()
&& c.getTargetPhoneAccount().getComponentName().getPackageName().equals(packageName)
- && c.getTargetPhoneAccount().getUserHandle().equals(userHandle));
+ && (!hasCrossUserAccess
+ ? c.getTargetPhoneAccount().getUserHandle().equals(userHandle)
+ : true));
}
@VisibleForTesting
diff --git a/src/com/android/server/telecom/DefaultDialerCache.java b/src/com/android/server/telecom/DefaultDialerCache.java
index dc79715..d819780 100644
--- a/src/com/android/server/telecom/DefaultDialerCache.java
+++ b/src/com/android/server/telecom/DefaultDialerCache.java
@@ -142,9 +142,9 @@
private ComponentName mOverrideSystemDialerComponentName;
public DefaultDialerCache(Context context,
- DefaultDialerManagerAdapter defaultDialerManagerAdapter,
- RoleManagerAdapter roleManagerAdapter,
- TelecomSystem.SyncRoot lock) {
+ DefaultDialerManagerAdapter defaultDialerManagerAdapter,
+ RoleManagerAdapter roleManagerAdapter,
+ TelecomSystem.SyncRoot lock) {
mContext = context;
mDefaultDialerManagerAdapter = defaultDialerManagerAdapter;
mRoleManagerAdapter = roleManagerAdapter;
@@ -176,6 +176,10 @@
UserHandle.USER_ALL);
}
+ public String getBTInCallServicePackage() {
+ return mRoleManagerAdapter.getBTInCallService();
+ }
+
public String getDefaultDialerApplication(int userId) {
if (userId == UserHandle.USER_CURRENT) {
userId = ActivityManager.getCurrentUser();
diff --git a/src/com/android/server/telecom/EmergencyCallDiagnosticLogger.java b/src/com/android/server/telecom/EmergencyCallDiagnosticLogger.java
index 09e927c..b8f5239 100644
--- a/src/com/android/server/telecom/EmergencyCallDiagnosticLogger.java
+++ b/src/com/android/server/telecom/EmergencyCallDiagnosticLogger.java
@@ -16,7 +16,7 @@
package com.android.server.telecom;
-import static android.telephony.TelephonyManager.EmergencyCallDiagnosticParams;
+import static android.telephony.TelephonyManager.EmergencyCallDiagnosticData;
import android.os.BugreportManager;
import android.os.DropBoxManager;
@@ -156,19 +156,19 @@
List<Integer> dataCollectionTypes = getDataCollectionTypes(reason);
boolean invokeTelephonyPersistApi = false;
CallEventTimestamps ts = mEmergencyCallsMap.get(call);
- EmergencyCallDiagnosticParams.Builder callDiagnosticBuilder =
- new EmergencyCallDiagnosticParams.Builder();
+ EmergencyCallDiagnosticData.Builder callDiagnosticBuilder =
+ new EmergencyCallDiagnosticData.Builder();
for (Integer dataCollectionType : dataCollectionTypes) {
switch (dataCollectionType) {
case COLLECTION_TYPE_TELECOM_STATE:
if (isTelecomDumpCollectionEnabled()) {
- callDiagnosticBuilder.setTelecomDumpSysCollectionEnabled(true);
+ callDiagnosticBuilder.setTelecomDumpsysCollectionEnabled(true);
invokeTelephonyPersistApi = true;
}
break;
case COLLECTION_TYPE_TELEPHONY_STATE:
if (isTelephonyDumpCollectionEnabled()) {
- callDiagnosticBuilder.setTelephonyDumpSysCollectionEnabled(true);
+ callDiagnosticBuilder.setTelephonyDumpsysCollectionEnabled(true);
invokeTelephonyPersistApi = true;
}
break;
@@ -192,14 +192,14 @@
default:
}
}
- EmergencyCallDiagnosticParams dp = callDiagnosticBuilder.build();
+ EmergencyCallDiagnosticData ecdData = callDiagnosticBuilder.build();
if (invokeTelephonyPersistApi) {
mAsyncTaskExecutor.execute(new Runnable() {
@Override
public void run() {
- Log.i(this, "Requesting Telephony to persist data %s", dp.toString());
+ Log.i(this, "Requesting Telephony to persist data %s", ecdData.toString());
try {
- mTelephonyManager.persistEmergencyCallDiagnosticData(DROPBOX_TAG, dp);
+ mTelephonyManager.persistEmergencyCallDiagnosticData(DROPBOX_TAG, ecdData);
} catch (Exception e) {
Log.w(this,
"Exception while invoking "
diff --git a/src/com/android/server/telecom/InCallAdapter.java b/src/com/android/server/telecom/InCallAdapter.java
index 9ce10bd..514ba48 100755
--- a/src/com/android/server/telecom/InCallAdapter.java
+++ b/src/com/android/server/telecom/InCallAdapter.java
@@ -20,6 +20,7 @@
import android.os.Binder;
import android.os.Bundle;
import android.os.ResultReceiver;
+import android.os.UserHandle;
import android.telecom.CallEndpoint;
import android.telecom.Log;
import android.telecom.PhoneAccountHandle;
@@ -420,7 +421,8 @@
Log.startSession(LogUtils.Sessions.ICA_ENTER_AUDIO_PROCESSING,
mOwnerPackageAbbreviation);
// TODO: enforce the extra permission.
- Binder.withCleanCallingIdentity(() -> {
+ long token = Binder.clearCallingIdentity();
+ try {
synchronized (mLock) {
Call call = mCallIdMapper.getCall(callId);
if (call != null) {
@@ -429,7 +431,9 @@
Log.w(this, "enterBackgroundAudioProcessing, unknown call id: %s", callId);
}
}
- });
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
} finally {
Log.endSession();
}
@@ -440,7 +444,8 @@
try {
Log.startSession(LogUtils.Sessions.ICA_EXIT_AUDIO_PROCESSING,
mOwnerPackageAbbreviation);
- Binder.withCleanCallingIdentity(() -> {
+ long token = Binder.clearCallingIdentity();
+ try {
synchronized (mLock) {
Call call = mCallIdMapper.getCall(callId);
if (call != null) {
@@ -450,7 +455,9 @@
"exitBackgroundAudioProcessing, unknown call id: %s", callId);
}
}
- });
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
} finally {
Log.endSession();
}
diff --git a/src/com/android/server/telecom/InCallController.java b/src/com/android/server/telecom/InCallController.java
index 4ad5f3b..55f48e3 100644
--- a/src/com/android/server/telecom/InCallController.java
+++ b/src/com/android/server/telecom/InCallController.java
@@ -22,7 +22,6 @@
import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.app.ActivityManager;
import android.app.AppOpsManager;
import android.app.KeyguardManager;
import android.app.Notification;
@@ -60,6 +59,7 @@
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
+import android.util.Pair;
import com.android.internal.annotations.VisibleForTesting;
// TODO: Needed for move to system service: import com.android.internal.R;
@@ -73,6 +73,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
+import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
@@ -103,6 +104,8 @@
public static final String SET_IN_CALL_ADAPTER_ERROR_MSG =
"Exception thrown while setting the in-call adapter.";
+ private final com.android.internal.telephony.flags.FeatureFlags mTelephonyFeatureFlags;
+
@VisibleForTesting
public void setAnomalyReporterAdapter(AnomalyReporterAdapter mAnomalyReporterAdapter){
mAnomalyReporter = mAnomalyReporterAdapter;
@@ -327,8 +330,14 @@
addCall(call);
// Notify this new added call
- sendCallToService(call, mInCallServiceInfo,
- mInCallServices.get(userFromCall).get(mInCallServiceInfo));
+ if (mFeatureFlags.separatelyBindToBtIncallService()
+ && mInCallServiceInfo.getType() == IN_CALL_SERVICE_TYPE_BLUETOOTH) {
+ sendCallToService(call, mInCallServiceInfo, mBTInCallServices
+ .get(userFromCall).second);
+ } else {
+ sendCallToService(call, mInCallServiceInfo,
+ mInCallServices.get(userFromCall).get(mInCallServiceInfo));
+ }
}
return CONNECTION_SUCCEEDED;
}
@@ -1176,6 +1185,7 @@
private static final int IN_CALL_SERVICE_TYPE_CAR_MODE_UI = 3;
private static final int IN_CALL_SERVICE_TYPE_NON_UI = 4;
private static final int IN_CALL_SERVICE_TYPE_COMPANION = 5;
+ private static final int IN_CALL_SERVICE_TYPE_BLUETOOTH = 6;
private static final int[] LIVE_CALL_STATES = { CallState.ACTIVE, CallState.PULLING,
CallState.DISCONNECTING };
@@ -1183,8 +1193,13 @@
/** The in-call app implementations, see {@link IInCallService}. */
private final Map<UserHandle, Map<InCallServiceInfo, IInCallService>>
mInCallServices = new ArrayMap<>();
+ private final Map<UserHandle, Pair<InCallServiceInfo, IInCallService>> mBTInCallServices =
+ new ArrayMap<>();
+ private final Map<UserHandle, Map<InCallServiceInfo, IInCallService>>
+ mCombinedInCallServiceMap = new ArrayMap<>();
private final CallIdMapper mCallIdMapper = new CallIdMapper(Call::getId);
+ private final Collection<Call> mPendingEndToneCall = new ArraySet<>();
private final Context mContext;
private final AppOpsManager mAppOpsManager;
@@ -1200,8 +1215,11 @@
mInCallServiceConnections = new ArrayMap<>();
private final Map<UserHandle, NonUIInCallServiceConnectionCollection>
mNonUIInCallServiceConnections = new ArrayMap<>();
+ private final Map<UserHandle, InCallServiceConnection> mBTInCallServiceConnections =
+ new ArrayMap<>();
private final ClockProxy mClockProxy;
private final IBinder mToken = new Binder();
+ private final FeatureFlags mFeatureFlags;
// A set of known non-UI in call services on the device, including those that are disabled.
// We track this so that we can efficiently bind to them when we're notified that a new
@@ -1212,6 +1230,12 @@
// The future will complete with true if binding succeeds, false if it timed out.
private CompletableFuture<Boolean> mBindingFuture = CompletableFuture.completedFuture(true);
+ // Future that's in a completed state unless we're in the middle of a binding to a bluetooth
+ // in-call service.
+ // The future will complete with true if bluetooth in-call service succeeds, false if it timed
+ // out.
+ private CompletableFuture<Boolean> mBtBindingFuture = CompletableFuture.completedFuture(true);
+
private final CarModeTracker mCarModeTracker;
/**
@@ -1240,12 +1264,21 @@
private ArraySet<String> mAllCarrierPrivilegedApps = new ArraySet<>();
private ArraySet<String> mActiveCarrierPrivilegedApps = new ArraySet<>();
- private FeatureFlags mFeatureFlags;
public InCallController(Context context, TelecomSystem.SyncRoot lock, CallsManager callsManager,
SystemStateHelper systemStateHelper, DefaultDialerCache defaultDialerCache,
Timeouts.Adapter timeoutsAdapter, EmergencyCallHelper emergencyCallHelper,
CarModeTracker carModeTracker, ClockProxy clockProxy, FeatureFlags featureFlags) {
+ this(context, lock, callsManager, systemStateHelper, defaultDialerCache, timeoutsAdapter,
+ emergencyCallHelper, carModeTracker, clockProxy, featureFlags, null);
+ }
+
+ @VisibleForTesting
+ public InCallController(Context context, TelecomSystem.SyncRoot lock, CallsManager callsManager,
+ SystemStateHelper systemStateHelper, DefaultDialerCache defaultDialerCache,
+ Timeouts.Adapter timeoutsAdapter, EmergencyCallHelper emergencyCallHelper,
+ CarModeTracker carModeTracker, ClockProxy clockProxy, FeatureFlags featureFlags,
+ com.android.internal.telephony.flags.FeatureFlags telephonyFeatureFlags) {
mContext = context;
mAppOpsManager = context.getSystemService(AppOpsManager.class);
mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class);
@@ -1263,6 +1296,12 @@
userAddedFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
mContext.registerReceiver(mUserAddedReceiver, userAddedFilter);
mFeatureFlags = featureFlags;
+ if (telephonyFeatureFlags != null) {
+ mTelephonyFeatureFlags = telephonyFeatureFlags;
+ } else {
+ mTelephonyFeatureFlags =
+ new com.android.internal.telephony.flags.FeatureFlagsImpl();
+ }
}
private void restrictPhoneCallOps() {
@@ -1342,65 +1381,89 @@
// Track the call if we don't already know about it.
addCall(call);
- if (!isBoundAndConnectedToServices(userFromCall)) {
- Log.i(this, "onCallAdded: %s; not bound or connected.", call);
- // We are not bound, or we're not connected.
- bindToServices(call);
+ if (mFeatureFlags.separatelyBindToBtIncallService()) {
+ boolean bindBTService = false;
+ boolean bindOtherServices = false;
+ if (!isBoundAndConnectedToBTService(userFromCall)) {
+ Log.i(this, "onCallAdded: %s; not bound or connected to BT ICS.", call);
+ bindBTService = true;
+ bindToBTService(call);
+ }
+ if (!isBoundAndConnectedToServices(userFromCall)) {
+ Log.i(this, "onCallAdded: %s; not bound or connected to other ICS.", call);
+ // We are not bound, or we're not connected.
+ bindOtherServices = true;
+ bindToOtherServices(call);
+ }
+ if (!bindBTService || !bindOtherServices) {
+ addCallToConnectedServices(call, userFromCall);
+ }
} else {
- InCallServiceConnection inCallServiceConnection =
- mInCallServiceConnections.get(userFromCall);
-
- // We are bound, and we are connected.
- adjustServiceBindingsForEmergency(userFromCall);
-
- // This is in case an emergency call is added while there is an existing call.
- mEmergencyCallHelper.maybeGrantTemporaryLocationPermission(call,
- userFromCall);
-
- if (inCallServiceConnection != null) {
- Log.i(this, "mInCallServiceConnection isConnected=%b",
- inCallServiceConnection.isConnected());
+ if (!isBoundAndConnectedToServices(userFromCall)) {
+ Log.i(this, "onCallAdded: %s; not bound or connected.", call);
+ // We are not bound, or we're not connected.
+ bindToServices(call, false);
+ } else {
+ addCallToConnectedServices(call, userFromCall);
}
+ }
+ }
- List<ComponentName> componentsUpdated = new ArrayList<>();
- if (mInCallServices.containsKey(userFromCall)) {
- for (Map.Entry<InCallServiceInfo, IInCallService> entry : mInCallServices.
- get(userFromCall).entrySet()) {
- InCallServiceInfo info = entry.getKey();
+ private void addCallToConnectedServices(Call call, UserHandle userFromCall) {
+ InCallServiceConnection inCallServiceConnection =
+ mInCallServiceConnections.get(userFromCall);
- if (call.isExternalCall() && !info.isExternalCallsSupported()) {
- continue;
- }
+ // We are bound, and we are connected.
+ adjustServiceBindingsForEmergency(userFromCall);
- if (call.isSelfManaged() && (!call.visibleToInCallService()
- || !info.isSelfManagedCallsSupported())) {
- continue;
- }
+ // This is in case an emergency call is added while there is an existing call.
+ mEmergencyCallHelper.maybeGrantTemporaryLocationPermission(call, userFromCall);
- // Only send the RTT call if it's a UI in-call service
- boolean includeRttCall = false;
- if (inCallServiceConnection != null) {
- includeRttCall = info.equals(inCallServiceConnection.getInfo());
- }
+ if (inCallServiceConnection != null) {
+ Log.i(this, "mInCallServiceConnection isConnected=%b",
+ inCallServiceConnection.isConnected());
+ }
- componentsUpdated.add(info.getComponentName());
- IInCallService inCallService = entry.getValue();
+ List<ComponentName> componentsUpdated = new ArrayList<>();
+ Map<UserHandle, Map<InCallController.InCallServiceInfo, IInCallService>> serviceMap =
+ getCombinedInCallServiceMap();
+ if (serviceMap.containsKey(userFromCall)) {
+ for (Map.Entry<InCallServiceInfo, IInCallService> entry :
+ serviceMap.get(userFromCall).entrySet()) {
+ InCallServiceInfo info = entry.getKey();
- ParcelableCall parcelableCall = ParcelableCallUtils.toParcelableCall(call,
- true /* includeVideoProvider */,
- mCallsManager.getPhoneAccountRegistrar(),
- info.isExternalCallsSupported(), includeRttCall,
- info.getType() == IN_CALL_SERVICE_TYPE_SYSTEM_UI ||
- info.getType() == IN_CALL_SERVICE_TYPE_NON_UI);
- try {
- inCallService.addCall(
- sanitizeParcelableCallForService(info, parcelableCall));
- updateCallTracking(call, info, true /* isAdd */);
- } catch (RemoteException ignored) {
- }
+ if (call.isExternalCall() && !info.isExternalCallsSupported()) {
+ continue;
}
- Log.i(this, "Call added to components: %s", componentsUpdated);
+
+ if (call.isSelfManaged() && (!call.visibleToInCallService()
+ || !info.isSelfManagedCallsSupported())) {
+ continue;
+ }
+
+ // Only send the RTT call if it's a UI in-call service
+ boolean includeRttCall = false;
+ if (inCallServiceConnection != null) {
+ includeRttCall = info.equals(inCallServiceConnection.getInfo());
+ }
+
+ componentsUpdated.add(info.getComponentName());
+ IInCallService inCallService = entry.getValue();
+
+ ParcelableCall parcelableCall = ParcelableCallUtils.toParcelableCall(call,
+ true /* includeVideoProvider */,
+ mCallsManager.getPhoneAccountRegistrar(),
+ info.isExternalCallsSupported(), includeRttCall,
+ info.getType() == IN_CALL_SERVICE_TYPE_SYSTEM_UI ||
+ info.getType() == IN_CALL_SERVICE_TYPE_NON_UI);
+ try {
+ inCallService.addCall(
+ sanitizeParcelableCallForService(info, parcelableCall));
+ updateCallTracking(call, info, true /* isAdd */);
+ } catch (RemoteException ignored) {
+ }
}
+ Log.i(this, "Call added to ICS: %s", componentsUpdated);
}
}
@@ -1449,15 +1512,36 @@
}
@Override
+ public void onDisconnectedTonePlaying(Call call, boolean isTonePlaying) {
+ Log.i(this, "onDisconnectedTonePlaying: %s -> %b", call, isTonePlaying);
+
+ if (mFeatureFlags.separatelyBindToBtIncallService()) {
+ synchronized (mLock) {
+ mPendingEndToneCall.remove(call);
+ if (!mPendingEndToneCall.isEmpty()) {
+ return;
+ }
+ UserHandle userHandle = getUserFromCall(call);
+ if (mBTInCallServiceConnections.containsKey(userHandle)) {
+ mBTInCallServiceConnections.get(userHandle).disconnect();
+ mBTInCallServiceConnections.remove(userHandle);
+ }
+ }
+ }
+ }
+
+ @Override
public void onExternalCallChanged(Call call, boolean isExternalCall) {
Log.i(this, "onExternalCallChanged: %s -> %b", call, isExternalCall);
List<ComponentName> componentsUpdated = new ArrayList<>();
UserHandle userFromCall = getUserFromCall(call);
- if (!isExternalCall && mInCallServices.containsKey(userFromCall)) {
+ Map<UserHandle, Map<InCallController.InCallServiceInfo, IInCallService>> serviceMap =
+ getCombinedInCallServiceMap();
+ if (!isExternalCall && serviceMap.containsKey(userFromCall)) {
// The call was external but it is no longer external. We must now add it to any
// InCallServices which do not support external calls.
- for (Map.Entry<InCallServiceInfo, IInCallService> entry : mInCallServices.
+ for (Map.Entry<InCallServiceInfo, IInCallService> entry : serviceMap.
get(userFromCall).entrySet()) {
InCallServiceInfo info = entry.getKey();
@@ -1496,9 +1580,9 @@
// InCallServices which do not support external calls.
// Remove the call by sending a call update indicating the call was disconnected.
Log.i(this, "Removing external call %s", call);
- if (mInCallServices.containsKey(userFromCall)) {
- for (Map.Entry<InCallServiceInfo, IInCallService> entry : mInCallServices.
- get(userFromCall).entrySet()) {
+ if (serviceMap.containsKey(userFromCall)) {
+ for (Map.Entry<InCallServiceInfo, IInCallService> entry :
+ serviceMap.get(userFromCall).entrySet()) {
InCallServiceInfo info = entry.getKey();
if (info.isExternalCallsSupported()) {
// For InCallServices which support external calls, we do not need to remove
@@ -1534,6 +1618,8 @@
@Override
public void onCallStateChanged(Call call, int oldState, int newState) {
+ Log.i(this, "onCallStateChanged: Call state changed for TC@%s: %s -> %s", call.getId(),
+ CallState.toString(oldState), CallState.toString(newState));
maybeTrackMicrophoneUse(isMuted());
updateCall(call);
}
@@ -1549,11 +1635,13 @@
@Override
public void onCallAudioStateChanged(CallAudioState oldCallAudioState,
CallAudioState newCallAudioState) {
- if (!mInCallServices.isEmpty()) {
+ Map<UserHandle, Map<InCallController.InCallServiceInfo, IInCallService>> serviceMap =
+ getCombinedInCallServiceMap();
+ if (!serviceMap.isEmpty()) {
Log.i(this, "Calling onAudioStateChanged, audioState: %s -> %s", oldCallAudioState,
newCallAudioState);
maybeTrackMicrophoneUse(newCallAudioState.isMuted());
- mInCallServices.values().forEach(inCallServices -> {
+ serviceMap.values().forEach(inCallServices -> {
for (IInCallService inCallService : inCallServices.values()) {
try {
inCallService.onCallAudioStateChanged(newCallAudioState);
@@ -1566,9 +1654,11 @@
@Override
public void onCallEndpointChanged(CallEndpoint callEndpoint) {
- if (!mInCallServices.isEmpty()) {
+ Map<UserHandle, Map<InCallController.InCallServiceInfo, IInCallService>> serviceMap =
+ getCombinedInCallServiceMap();
+ if (!serviceMap.isEmpty()) {
Log.i(this, "Calling onCallEndpointChanged");
- mInCallServices.values().forEach(inCallServices -> {
+ serviceMap.values().forEach(inCallServices -> {
for (IInCallService inCallService : inCallServices.values()) {
try {
inCallService.onCallEndpointChanged(callEndpoint);
@@ -1582,10 +1672,12 @@
@Override
public void onAvailableCallEndpointsChanged(Set<CallEndpoint> availableCallEndpoints) {
- if (!mInCallServices.isEmpty()) {
+ Map<UserHandle, Map<InCallController.InCallServiceInfo, IInCallService>> serviceMap =
+ getCombinedInCallServiceMap();
+ if (!serviceMap.isEmpty()) {
Log.i(this, "Calling onAvailableCallEndpointsChanged");
List<CallEndpoint> availableEndpoints = new ArrayList<>(availableCallEndpoints);
- mInCallServices.values().forEach(inCallServices -> {
+ serviceMap.values().forEach(inCallServices -> {
for (IInCallService inCallService : inCallServices.values()) {
try {
inCallService.onAvailableCallEndpointsChanged(availableEndpoints);
@@ -1599,9 +1691,11 @@
@Override
public void onMuteStateChanged(boolean isMuted) {
- if (!mInCallServices.isEmpty()) {
+ Map<UserHandle, Map<InCallController.InCallServiceInfo, IInCallService>> serviceMap =
+ getCombinedInCallServiceMap();
+ if (!serviceMap.isEmpty()) {
Log.i(this, "Calling onMuteStateChanged");
- mInCallServices.values().forEach(inCallServices -> {
+ serviceMap.values().forEach(inCallServices -> {
for (IInCallService inCallService : inCallServices.values()) {
try {
inCallService.onMuteStateChanged(isMuted);
@@ -1615,9 +1709,11 @@
@Override
public void onCanAddCallChanged(boolean canAddCall) {
- if (!mInCallServices.isEmpty()) {
+ Map<UserHandle, Map<InCallController.InCallServiceInfo, IInCallService>> serviceMap =
+ getCombinedInCallServiceMap();
+ if (!serviceMap.isEmpty()) {
Log.i(this, "onCanAddCallChanged : %b", canAddCall);
- mInCallServices.values().forEach(inCallServices -> {
+ serviceMap.values().forEach(inCallServices -> {
for (IInCallService inCallService : inCallServices.values()) {
try {
inCallService.onCanAddCallChanged(canAddCall);
@@ -1630,9 +1726,11 @@
void onPostDialWait(Call call, String remaining) {
UserHandle userFromCall = getUserFromCall(call);
- if (mInCallServices.containsKey(userFromCall)) {
+ Map<UserHandle, Map<InCallController.InCallServiceInfo, IInCallService>> serviceMap =
+ getCombinedInCallServiceMap();
+ if (serviceMap.containsKey(userFromCall)) {
Log.i(this, "Calling onPostDialWait, remaining = %s", remaining);
- for (IInCallService inCallService : mInCallServices.get(userFromCall).values()) {
+ for (IInCallService inCallService: serviceMap.get(userFromCall).values()) {
try {
inCallService.setPostDialWait(mCallIdMapper.getCallId(call), remaining);
} catch (RemoteException ignored) {
@@ -1713,12 +1811,14 @@
boolean isLockscreenRestricted = keyguardManager != null
&& keyguardManager.isKeyguardLocked();
UserHandle currentUser = mCallsManager.getCurrentUserHandle();
+ Map<UserHandle, Map<InCallController.InCallServiceInfo, IInCallService>> serviceMap =
+ getCombinedInCallServiceMap();
// Handle cases when calls are placed from the keyguard UI screen, which operates under
// the admin user. This needs to account for emergency calls placed from secondary/guest
// users as well as the work profile. Once the screen is locked, the user should be able to
// return to the call (from the keyguard UI).
if (mFeatureFlags.eccKeyguard() && mCallsManager.isInEmergencyCall()
- && isLockscreenRestricted && !mInCallServices.containsKey(callingUser)) {
+ && isLockscreenRestricted && !serviceMap.containsKey(callingUser)) {
// If screen is locked and the current user is the system, query calls for the work
// profile user, if available. Otherwise, the user is in the secondary/guest profile,
// so we can default to the system user.
@@ -1732,8 +1832,8 @@
callingUser = currentUser;
}
}
- if (mInCallServices.containsKey(callingUser)) {
- for (IInCallService inCallService : mInCallServices.get(callingUser).values()) {
+ if (serviceMap.containsKey(callingUser)) {
+ for (IInCallService inCallService : serviceMap.get(callingUser).values()) {
try {
inCallService.bringToForeground(showDialpad);
} catch (RemoteException ignored) {
@@ -1746,7 +1846,7 @@
@VisibleForTesting
public Map<UserHandle, Map<InCallServiceInfo, IInCallService>> getInCallServices() {
- return mInCallServices;
+ return getCombinedInCallServiceMap();
}
@VisibleForTesting
@@ -1755,9 +1855,11 @@
}
void silenceRinger(Set<UserHandle> userHandles) {
+ Map<UserHandle, Map<InCallController.InCallServiceInfo, IInCallService>> serviceMap =
+ getCombinedInCallServiceMap();
userHandles.forEach(userHandle -> {
- if (mInCallServices.containsKey(userHandle)) {
- for (IInCallService inCallService : mInCallServices.get(userHandle).values()) {
+ if (serviceMap.containsKey(userHandle)) {
+ for (IInCallService inCallService : serviceMap.get(userHandle).values()) {
try {
inCallService.silenceRinger();
} catch (RemoteException ignored) {
@@ -1769,8 +1871,10 @@
private void notifyConnectionEvent(Call call, String event, Bundle extras) {
UserHandle userFromCall = getUserFromCall(call);
- if (mInCallServices.containsKey(userFromCall)) {
- for (IInCallService inCallService : mInCallServices.get(userFromCall).values()) {
+ Map<UserHandle, Map<InCallController.InCallServiceInfo, IInCallService>> serviceMap =
+ getCombinedInCallServiceMap();
+ if (serviceMap.containsKey(userFromCall)) {
+ for (IInCallService inCallService : serviceMap.get(userFromCall).values()) {
try {
Log.i(this, "notifyConnectionEvent {Call: %s, Event: %s, Extras:[%s]}",
(call != null ? call.toString() : "null"),
@@ -1785,8 +1889,10 @@
private void notifyRttInitiationFailure(Call call, int reason) {
UserHandle userFromCall = getUserFromCall(call);
- if (mInCallServices.containsKey(userFromCall)) {
- mInCallServices.get(userFromCall).entrySet().stream()
+ Map<UserHandle, Map<InCallController.InCallServiceInfo, IInCallService>> serviceMap =
+ getCombinedInCallServiceMap();
+ if (serviceMap.containsKey(userFromCall)) {
+ serviceMap.get(userFromCall).entrySet().stream()
.filter((entry) -> entry.getKey().equals(mInCallServiceConnections.
get(userFromCall).getInfo()))
.forEach((entry) -> {
@@ -1803,8 +1909,10 @@
private void notifyRemoteRttRequest(Call call, int requestId) {
UserHandle userFromCall = getUserFromCall(call);
- if (mInCallServices.containsKey(userFromCall)) {
- mInCallServices.get(userFromCall).entrySet().stream()
+ Map<UserHandle, Map<InCallController.InCallServiceInfo, IInCallService>> serviceMap =
+ getCombinedInCallServiceMap();
+ if (serviceMap.containsKey(userFromCall)) {
+ serviceMap.get(userFromCall).entrySet().stream()
.filter((entry) -> entry.getKey().equals(mInCallServiceConnections.
get(userFromCall).getInfo()))
.forEach((entry) -> {
@@ -1821,8 +1929,10 @@
private void notifyHandoverFailed(Call call, int error) {
UserHandle userFromCall = getUserFromCall(call);
- if (mInCallServices.containsKey(userFromCall)) {
- for (IInCallService inCallService : mInCallServices.get(userFromCall).values()) {
+ Map<UserHandle, Map<InCallController.InCallServiceInfo, IInCallService>> serviceMap =
+ getCombinedInCallServiceMap();
+ if (serviceMap.containsKey(userFromCall)) {
+ for (IInCallService inCallService : serviceMap.get(userFromCall).values()) {
try {
inCallService.onHandoverFailed(mCallIdMapper.getCallId(call), error);
} catch (RemoteException ignored) {
@@ -1833,8 +1943,10 @@
private void notifyHandoverComplete(Call call) {
UserHandle userFromCall = getUserFromCall(call);
- if (mInCallServices.containsKey(userFromCall)) {
- for (IInCallService inCallService : mInCallServices.get(userFromCall).values()) {
+ Map<UserHandle, Map<InCallController.InCallServiceInfo, IInCallService>> serviceMap =
+ getCombinedInCallServiceMap();
+ if (serviceMap.containsKey(userFromCall)) {
+ for (IInCallService inCallService : serviceMap.get(userFromCall).values()) {
try {
inCallService.onHandoverComplete(mCallIdMapper.getCallId(call));
} catch (RemoteException ignored) {
@@ -1862,26 +1974,69 @@
mNonUIInCallServiceConnections.get(userHandle).disconnect();
mNonUIInCallServiceConnections.remove(userHandle);
}
- mInCallServices.remove(userHandle);
+ getCombinedInCallServiceMap().remove(userHandle);
+ if (mFeatureFlags.separatelyBindToBtIncallService()) {
+ updateCombinedInCallServiceMap(userHandle);
+ }
+ }
+
+ /**
+ * Binds to Bluetooth InCallServices. Method-invoker must check
+ * {@link #isBoundAndConnectedToBTService(UserHandle)} before invoking.
+ *
+ * @param call The newly added call that triggered the binding to the in-call services.
+ */
+ public CompletableFuture<Boolean> bindToBTService(Call call) {
+ // Track the call if we don't already know about it.
+ addCall(call);
+ UserHandle userFromCall = getUserFromCall(call);
+
+ List<InCallServiceInfo> infos = getInCallServiceComponents(userFromCall,
+ IN_CALL_SERVICE_TYPE_BLUETOOTH);
+ if (infos.size() == 0 || infos.get(0) == null) {
+ Log.w(this, "No available BT service");
+ mBtBindingFuture = CompletableFuture.completedFuture(false);
+ return mBtBindingFuture;
+ }
+ mBtBindingFuture = new CompletableFuture<Boolean>().completeOnTimeout(false,
+ mTimeoutsAdapter.getCallBindBluetoothInCallServicesDelay(
+ mContext.getContentResolver()), TimeUnit.MILLISECONDS);
+ new InCallServiceBindingConnection(infos.get(0)).connect(call);
+ return mBtBindingFuture;
}
/**
* Binds to all the UI-providing InCallService as well as system-implemented non-UI
- * InCallServices. Method-invoker must check {@link #isBoundAndConnectedToServices()}
- * before invoking.
+ * InCallServices except BT InCallServices. Method-invoker must check
+ * {@link #isBoundAndConnectedToServices(UserHandle)} before invoking.
*
* @param call The newly added call that triggered the binding to the in-call services.
*/
- @VisibleForTesting
- public void bindToServices(Call call) {
- UserHandle userFromCall = getUserFromCall(call);
- UserHandle parentUser = null;
- UserManager um = mContext.getSystemService(UserManager.class);
+ public void bindToOtherServices(Call call) {
+ bindToServices(call, true);
+ }
- if (um.isManagedProfile(userFromCall.getIdentifier())) {
+ /**
+ * Binds to all the UI-providing InCallService as well as system-implemented non-UI
+ * InCallServices. Method-invoker must check {@link #isBoundAndConnectedToServices(UserHandle)}
+ * before invoking.
+ *
+ * @param call The newly added call that triggered the binding to the in-call
+ * services.
+ * @param skipBTServices Boolean variable to specify if the binding to BT InCallService should
+ * be skipped
+ */
+ @VisibleForTesting
+ public void bindToServices(Call call, boolean skipBTServices) {
+ UserHandle userFromCall = getUserFromCall(call);
+ UserManager um = mContext.getSystemService(UserManager.class);
+ UserHandle parentUser = mTelephonyFeatureFlags.workProfileApiSplit()
+ ? um.getProfileParent(userFromCall) : null;
+ if (!mTelephonyFeatureFlags.workProfileApiSplit()
+ && um.isManagedProfile(userFromCall.getIdentifier())) {
parentUser = um.getProfileParent(userFromCall);
- Log.i(this, "child:%s parent:%s", userFromCall, parentUser);
}
+ Log.i(this, "child:%s parent:%s", userFromCall, parentUser);
if (!mInCallServiceConnections.containsKey(userFromCall)) {
InCallServiceConnection dialerInCall = null;
@@ -1937,7 +2092,7 @@
// Only connect to the non-ui InCallServices if we actually connected to the main UI
// one, or if the call is self-managed (in which case we'd still want to keep Wear, BT,
// etc. informed.
- connectToNonUiInCallServices(call);
+ connectToNonUiInCallServices(call, skipBTServices);
mBindingFuture = new CompletableFuture<Boolean>().completeOnTimeout(false,
mTimeoutsAdapter.getCallRemoveUnbindInCallServicesDelay(
mContext.getContentResolver()),
@@ -1952,21 +2107,22 @@
packageChangedFilter, null, null);
}
- private void updateNonUiInCallServices(Call call) {
+ private void updateNonUiInCallServices(Call call, boolean skipBTService) {
UserHandle userFromCall = getUserFromCall(call);
- UserHandle parentUser = null;
UserManager um = mContext.getSystemService(UserManager.class);
- if(um.isManagedProfile(userFromCall.getIdentifier()))
- {
+ UserHandle parentUser = mTelephonyFeatureFlags.workProfileApiSplit()
+ ? um.getProfileParent(userFromCall) : null;
+
+ if (!mTelephonyFeatureFlags.workProfileApiSplit()
+ && um.isManagedProfile(userFromCall.getIdentifier())) {
parentUser = um.getProfileParent(userFromCall);
}
List<InCallServiceInfo> nonUIInCallComponents =
getInCallServiceComponents(userFromCall, IN_CALL_SERVICE_TYPE_NON_UI);
List<InCallServiceInfo> nonUIInCallComponentsForParent = new ArrayList<>();
- if(parentUser != null)
- {
+ if(parentUser != null) {
//also get Non-UI services using parent handle.
nonUIInCallComponentsForParent =
getInCallServiceComponents(parentUser, IN_CALL_SERVICE_TYPE_NON_UI);
@@ -2006,10 +2162,10 @@
nonUIInCalls));
}
- private void connectToNonUiInCallServices(Call call) {
+ private void connectToNonUiInCallServices(Call call, boolean skipBTService) {
UserHandle userFromCall = getUserFromCall(call);
if (!mNonUIInCallServiceConnections.containsKey(userFromCall)) {
- updateNonUiInCallServices(call);
+ updateNonUiInCallServices(call, skipBTService);
}
mNonUIInCallServiceConnections.get(userFromCall).connect(call);
}
@@ -2278,6 +2434,12 @@
return IN_CALL_SERVICE_TYPE_DEFAULT_DIALER_UI;
}
+ String bluetoothPackage = mDefaultDialerCache.getBTInCallServicePackage();
+ if (serviceInfo.packageName != null && serviceInfo.packageName.equals(bluetoothPackage)
+ && (hasControlInCallPermission || hasAppOpsPermittedManageOngoingCalls)) {
+ return IN_CALL_SERVICE_TYPE_BLUETOOTH;
+ }
+
// Also allow any in-call service that has the control-experience permission (to ensure
// that it is a system app) and doesn't claim to show any UI.
if (!isUIService && !isCarModeUIService && (hasControlInCallPermission ||
@@ -2318,9 +2480,24 @@
trackCallingUserInterfaceStarted(info);
}
IInCallService inCallService = IInCallService.Stub.asInterface(service);
- mInCallServices.putIfAbsent(userHandle,
- new ArrayMap<InCallController.InCallServiceInfo, IInCallService>());
- mInCallServices.get(userHandle).put(info, inCallService);
+ if (mFeatureFlags.separatelyBindToBtIncallService()
+ && info.getType() == IN_CALL_SERVICE_TYPE_BLUETOOTH) {
+ if (mBtBindingFuture.isDone()) {
+ // Binding completed after the timeout. Clean up this binding
+ return false;
+ } else {
+ mBtBindingFuture.complete(true);
+ }
+ mBTInCallServices.put(userHandle, new Pair<>(info, inCallService));
+ } else {
+ mInCallServices.putIfAbsent(userHandle, new ArrayMap<>());
+ mInCallServices.get(userHandle).put(info, inCallService);
+ }
+
+ if (mFeatureFlags.separatelyBindToBtIncallService()) {
+ updateCombinedInCallServiceMap(userHandle);
+ }
+
try {
inCallService.setInCallAdapter(
new InCallAdapter(
@@ -2409,6 +2586,11 @@
if (mInCallServices.containsKey(userHandle)) {
mInCallServices.get(userHandle).remove(disconnectedInfo);
}
+ if (mFeatureFlags.separatelyBindToBtIncallService()
+ && disconnectedInfo.getType() == IN_CALL_SERVICE_TYPE_BLUETOOTH) {
+ mBTInCallServices.remove(userHandle);
+ updateCombinedInCallServiceMap(userHandle);
+ }
}
/**
@@ -2429,17 +2611,19 @@
* @param rttInfoChanged {@code true} if any information about the RTT session changed,
* {@code false} otherwise.
* @param exceptPackageName When specified, this package name will not get a call update.
- * Used ONLY from {@link Call#putConnectionServiceExtras(int, Bundle, String)} to
+ * Used ONLY from {@link Call#putConnectionServiceExtras(Bundle)} to
* ensure we can propagate extras changes between InCallServices but
* not inform the requestor of their own change.
*/
private void updateCall(Call call, boolean videoProviderChanged, boolean rttInfoChanged,
String exceptPackageName) {
UserHandle userFromCall = getUserFromCall(call);
- if (mInCallServices.containsKey(userFromCall)) {
+ Map<UserHandle, Map<InCallController.InCallServiceInfo, IInCallService>> serviceMap =
+ getCombinedInCallServiceMap();
+ if (serviceMap.containsKey(userFromCall)) {
Log.i(this, "Sending updateCall %s", call);
List<ComponentName> componentsUpdated = new ArrayList<>();
- for (Map.Entry<InCallServiceInfo, IInCallService> entry : mInCallServices.
+ for (Map.Entry<InCallServiceInfo, IInCallService> entry : serviceMap.
get(userFromCall).entrySet()) {
InCallServiceInfo info = entry.getKey();
ComponentName componentName = info.getComponentName();
@@ -2507,6 +2691,9 @@
}
maybeTrackMicrophoneUse(isMuted());
+ if (mFeatureFlags.separatelyBindToBtIncallService()) {
+ mPendingEndToneCall.add(call);
+ }
}
/**
@@ -2519,6 +2706,14 @@
return mInCallServiceConnections.get(userHandle).isConnected();
}
+ @VisibleForTesting
+ public boolean isBoundAndConnectedToBTService(UserHandle userHandle) {
+ if (!mBTInCallServiceConnections.containsKey(userHandle)) {
+ return false;
+ }
+ return mBTInCallServiceConnections.get(userHandle).isConnected();
+ }
+
/**
* @return A future that is pending whenever we are in the middle of binding to an
* incall service.
@@ -2533,9 +2728,11 @@
* @param pw The {@code IndentingPrintWriter} to write the state to.
*/
public void dump(IndentingPrintWriter pw) {
- pw.println("mInCallServices (InCalls registered):");
+ pw.println("combinedInCallServiceMap (InCalls registered):");
pw.increaseIndent();
- mInCallServices.values().forEach(inCallServices -> {
+ Map<UserHandle, Map<InCallController.InCallServiceInfo, IInCallService>> serviceMap =
+ getCombinedInCallServiceMap();
+ serviceMap.values().forEach(inCallServices -> {
for (InCallServiceInfo info : inCallServices.keySet()) {
pw.println(info);
}
@@ -2960,4 +3157,36 @@
}
return false;
}
+
+ private void updateCombinedInCallServiceMap(UserHandle user) {
+ synchronized (mLock) {
+ Map<InCallServiceInfo, IInCallService> serviceMap;
+ if (mInCallServices.containsKey(user)) {
+ serviceMap = mInCallServices.get(user);
+ } else {
+ serviceMap = new HashMap<>();
+ }
+ if (mFeatureFlags.separatelyBindToBtIncallService()
+ && mBTInCallServices.containsKey(user)) {
+ Pair<InCallServiceInfo, IInCallService> btServicePair = mBTInCallServices.get(user);
+ serviceMap.put(btServicePair.first, btServicePair.second);
+ }
+ if (!serviceMap.isEmpty()) {
+ mCombinedInCallServiceMap.put(user, serviceMap);
+ } else {
+ mCombinedInCallServiceMap.remove(user);
+ }
+ }
+ }
+
+ private Map<UserHandle,
+ Map<InCallController.InCallServiceInfo, IInCallService>> getCombinedInCallServiceMap() {
+ synchronized (mLock) {
+ if (mFeatureFlags.separatelyBindToBtIncallService()) {
+ return mCombinedInCallServiceMap;
+ } else {
+ return mInCallServices;
+ }
+ }
+ }
}
diff --git a/src/com/android/server/telecom/PendingAudioRoute.java b/src/com/android/server/telecom/PendingAudioRoute.java
new file mode 100644
index 0000000..5fa3048
--- /dev/null
+++ b/src/com/android/server/telecom/PendingAudioRoute.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2023 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;
+
+import static com.android.server.telecom.CallAudioRouteAdapter.PENDING_ROUTE_FAILED;
+
+import android.media.AudioManager;
+
+import java.util.ArrayList;
+
+/**
+ * Used to represent the intermediate state during audio route switching.
+ * Usually, audio route switching start with a communication device setting request to audio
+ * framework and will be completed with corresponding success broadcasts or messages. Instance of
+ * this class is responsible for tracking the pending success signals according to the original
+ * audio route and the destination audio route of this switching.
+ */
+public class PendingAudioRoute {
+ private CallAudioRouteController mCallAudioRouteController;
+ private AudioManager mAudioManager;
+ /**
+ * The {@link AudioRoute} that this pending audio switching started with
+ */
+ private AudioRoute mOrigRoute;
+ /**
+ * The expected destination {@link AudioRoute} of this pending audio switching, can be changed
+ * by new switching request during the ongoing switching
+ */
+ private AudioRoute mDestRoute;
+ private ArrayList<Integer> mPendingMessages;
+ private boolean mActive;
+ PendingAudioRoute(CallAudioRouteController controller, AudioManager audioManager) {
+ mCallAudioRouteController = controller;
+ mAudioManager = audioManager;
+ mPendingMessages = new ArrayList<>();
+ mActive = false;
+ }
+
+ void setOrigRoute(boolean active, AudioRoute origRoute) {
+ origRoute.onOrigRouteAsPendingRoute(active, this, mAudioManager);
+ mOrigRoute = origRoute;
+ }
+
+ AudioRoute getOrigRoute() {
+ return mOrigRoute;
+ }
+
+ void setDestRoute(boolean active, AudioRoute destRoute) {
+ destRoute.onDestRouteAsPendingRoute(active, this, mAudioManager);
+ mActive = active;
+ mDestRoute = destRoute;
+ }
+
+ AudioRoute getDestRoute() {
+ return mDestRoute;
+ }
+
+ public void addMessage(int message) {
+ mPendingMessages.add(message);
+ }
+
+ 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);
+ mCallAudioRouteController.sendMessageWithSessionInfo(
+ CallAudioRouteAdapter.EXIT_PENDING_ROUTE);
+ }
+
+ // Removes the first occurrence of the specified message from this list, if it is present.
+ mPendingMessages.remove((Object) message);
+ evaluatePendingState();
+ }
+
+ public void evaluatePendingState() {
+ if (mPendingMessages.isEmpty()) {
+ mCallAudioRouteController.sendMessageWithSessionInfo(
+ CallAudioRouteAdapter.EXIT_PENDING_ROUTE);
+ }
+ }
+
+ public boolean isActive() {
+ return mActive;
+ }
+}
diff --git a/src/com/android/server/telecom/PhoneAccountRegistrar.java b/src/com/android/server/telecom/PhoneAccountRegistrar.java
index de73269..fc90edd 100644
--- a/src/com/android/server/telecom/PhoneAccountRegistrar.java
+++ b/src/com/android/server/telecom/PhoneAccountRegistrar.java
@@ -58,6 +58,7 @@
// TODO: Needed for move to system service: import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.flags.FeatureFlags;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.XmlUtils;
import com.android.modules.utils.ModifiedUtf8;
@@ -81,10 +82,12 @@
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.stream.Collectors;
@@ -181,19 +184,19 @@
private interface PhoneAccountRegistrarWriteLock {}
private final PhoneAccountRegistrarWriteLock mWriteLock =
new PhoneAccountRegistrarWriteLock() {};
- private final com.android.internal.telephony.flags.FeatureFlags mTelephonyFeatureFlags;
+ private final FeatureFlags mTelephonyFeatureFlags;
@VisibleForTesting
public PhoneAccountRegistrar(Context context, TelecomSystem.SyncRoot lock,
DefaultDialerCache defaultDialerCache, AppLabelProxy appLabelProxy,
- com.android.internal.telephony.flags.FeatureFlags telephonyFeatureFlags) {
+ FeatureFlags telephonyFeatureFlags) {
this(context, lock, FILE_NAME, defaultDialerCache, appLabelProxy, telephonyFeatureFlags);
}
@VisibleForTesting
public PhoneAccountRegistrar(Context context, TelecomSystem.SyncRoot lock, String fileName,
DefaultDialerCache defaultDialerCache, AppLabelProxy appLabelProxy,
- com.android.internal.telephony.flags.FeatureFlags telephonyFeatureFlags) {
+ FeatureFlags telephonyFeatureFlags) {
mAtomicFile = new AtomicFile(new File(context.getFilesDir(), fileName));
@@ -951,6 +954,9 @@
enforceCharacterLimit(account);
enforceIconSizeLimit(account);
enforceMaxPhoneAccountLimit(account);
+ if (mTelephonyFeatureFlags.simultaneousCallingIndications()) {
+ enforceSimultaneousCallingRestrictionLimit(account);
+ }
addOrReplacePhoneAccount(account);
}
@@ -1079,6 +1085,43 @@
}
/**
+ * Enforce size limits on the simultaneous calling restriction of a PhoneAccount.
+ * If a PhoneAccount has a simultaneous calling restriction on it, enforce the following: the
+ * number of PhoneAccountHandles in the Set can not exceed the per app restriction on
+ * PhoneAccounts registered and each PhoneAccountHandle's fields must not exceed the per field
+ * character limit.
+ * @param account The PhoneAccount to enforce simultaneous calling restrictions on.
+ * @throws IllegalArgumentException if the PhoneAccount exceeds size limits.
+ */
+ public void enforceSimultaneousCallingRestrictionLimit(@NonNull PhoneAccount account) {
+ if (!account.hasSimultaneousCallingRestriction()) return;
+ Set<PhoneAccountHandle> restrictions = account.getSimultaneousCallingRestriction();
+ if (restrictions.size() > MAX_PHONE_ACCOUNT_REGISTRATIONS) {
+ throw new IllegalArgumentException("Can not register a PhoneAccount with a number"
+ + "of simultaneous calling restrictions that is greater than "
+ + MAX_PHONE_ACCOUNT_REGISTRATIONS);
+ }
+ for (PhoneAccountHandle handle : restrictions) {
+ ComponentName component = handle.getComponentName();
+ if (component.getPackageName().length() > MAX_PHONE_ACCOUNT_FIELD_CHAR_LIMIT) {
+ throw new IllegalArgumentException("A PhoneAccountHandle added as part of "
+ + "a simultaneous calling restriction has a package name that has exceeded "
+ + "the character limit of " + MAX_PHONE_ACCOUNT_FIELD_CHAR_LIMIT);
+ }
+ if (component.getClassName().length() > MAX_PHONE_ACCOUNT_FIELD_CHAR_LIMIT) {
+ throw new IllegalArgumentException("A PhoneAccountHandle added as part of "
+ + "a simultaneous calling restriction has a class name that has exceeded "
+ + "the character limit of " + MAX_PHONE_ACCOUNT_FIELD_CHAR_LIMIT);
+ }
+ if (handle.getId().length() > MAX_PHONE_ACCOUNT_FIELD_CHAR_LIMIT) {
+ throw new IllegalArgumentException("A PhoneAccountHandle added as part of "
+ + "a simultaneous calling restriction has an ID that has exceeded "
+ + "the character limit of " + MAX_PHONE_ACCOUNT_FIELD_CHAR_LIMIT);
+ }
+ }
+ }
+
+ /**
* Enforce a character limit on all PA and PAH string or char-sequence fields.
*
* @param account to enforce check on
@@ -1878,7 +1921,7 @@
sortPhoneAccounts();
ByteArrayOutputStream os = new ByteArrayOutputStream();
XmlSerializer serializer = Xml.resolveSerializer(os);
- writeToXml(mState, serializer, mContext);
+ writeToXml(mState, serializer, mContext, mTelephonyFeatureFlags);
serializer.flush();
new AsyncXmlWriter().execute(os);
} catch (IOException e) {
@@ -1899,7 +1942,7 @@
try {
XmlPullParser parser = Xml.resolvePullParser(is);
parser.nextTag();
- mState = readFromXml(parser, mContext);
+ mState = readFromXml(parser, mContext, mTelephonyFeatureFlags);
migratePhoneAccountHandle(mState);
versionChanged = mState.versionNumber < EXPECTED_STATE_VERSION;
@@ -1934,14 +1977,14 @@
}
}
- private static void writeToXml(State state, XmlSerializer serializer, Context context)
- throws IOException {
- sStateXml.writeToXml(state, serializer, context);
+ private static void writeToXml(State state, XmlSerializer serializer, Context context,
+ FeatureFlags telephonyFeatureFlags) throws IOException {
+ sStateXml.writeToXml(state, serializer, context, telephonyFeatureFlags);
}
- private static State readFromXml(XmlPullParser parser, Context context)
- throws IOException, XmlPullParserException {
- State s = sStateXml.readFromXml(parser, 0, context);
+ private static State readFromXml(XmlPullParser parser, Context context,
+ FeatureFlags telephonyFeatureFlags) throws IOException, XmlPullParserException {
+ State s = sStateXml.readFromXml(parser, 0, context, telephonyFeatureFlags);
return s != null ? s : new State();
}
@@ -2007,8 +2050,8 @@
/**
* Write the supplied object to XML
*/
- public abstract void writeToXml(T o, XmlSerializer serializer, Context context)
- throws IOException;
+ public abstract void writeToXml(T o, XmlSerializer serializer, Context context,
+ FeatureFlags telephonyFeatureFlags) throws IOException;
/**
* Read from the supplied XML into a new object, returning null in case of an
@@ -2017,8 +2060,8 @@
* object's writeToXml(). This object tries to fail early without modifying
* 'parser' if it does not recognize the data it sees.
*/
- public abstract T readFromXml(XmlPullParser parser, int version, Context context)
- throws IOException, XmlPullParserException;
+ public abstract T readFromXml(XmlPullParser parser, int version, Context context,
+ FeatureFlags telephonyFeatureFlags) throws IOException, XmlPullParserException;
protected void writeTextIfNonNull(String tagName, Object value, XmlSerializer serializer)
throws IOException {
@@ -2030,6 +2073,29 @@
}
/**
+ * Serializes a List of PhoneAccountHandles.
+ * @param tagName The tag for the List
+ * @param handles The List of PhoneAccountHandles to serialize
+ * @param serializer The serializer
+ * @throws IOException if serialization fails.
+ */
+ protected void writePhoneAccountHandleSet(String tagName, Set<PhoneAccountHandle> handles,
+ XmlSerializer serializer, Context context, FeatureFlags telephonyFeatureFlags)
+ throws IOException {
+ serializer.startTag(null, tagName);
+ if (handles != null) {
+ serializer.attribute(null, ATTRIBUTE_LENGTH, Objects.toString(handles.size()));
+ for (PhoneAccountHandle handle : handles) {
+ sPhoneAccountHandleXml.writeToXml(handle, serializer, context,
+ telephonyFeatureFlags);
+ }
+ } else {
+ serializer.attribute(null, ATTRIBUTE_LENGTH, "0");
+ }
+ serializer.endTag(null, tagName);
+ }
+
+ /**
* Serializes a string array.
*
* @param tagName The tag name for the string array.
@@ -2123,6 +2189,21 @@
serializer.endTag(null, tagName);
}
+ protected Set<PhoneAccountHandle> readPhoneAccountHandleSet(XmlPullParser parser,
+ int version, Context context, FeatureFlags telephonyFeatureFlags)
+ throws IOException, XmlPullParserException {
+ int length = Integer.parseInt(parser.getAttributeValue(null, ATTRIBUTE_LENGTH));
+ Set<PhoneAccountHandle> handles = new HashSet<>(length);
+ if (length == 0) return handles;
+
+ int outerDepth = parser.getDepth();
+ while (XmlUtils.nextElementWithin(parser, outerDepth)) {
+ handles.add(sPhoneAccountHandleXml.readFromXml(parser, version, context,
+ telephonyFeatureFlags));
+ }
+ return handles;
+ }
+
/**
* Reads a string array from the XML parser.
*
@@ -2230,8 +2311,8 @@
private static final String VERSION = "version";
@Override
- public void writeToXml(State o, XmlSerializer serializer, Context context)
- throws IOException {
+ public void writeToXml(State o, XmlSerializer serializer, Context context,
+ FeatureFlags telephonyFeatureFlags) throws IOException {
if (o != null) {
serializer.startTag(null, CLASS_STATE);
serializer.attribute(null, VERSION, Objects.toString(EXPECTED_STATE_VERSION));
@@ -2239,14 +2320,15 @@
serializer.startTag(null, DEFAULT_OUTGOING);
for (DefaultPhoneAccountHandle defaultPhoneAccountHandle : o
.defaultOutgoingAccountHandles.values()) {
- sDefaultPhoneAcountHandleXml
- .writeToXml(defaultPhoneAccountHandle, serializer, context);
+ sDefaultPhoneAccountHandleXml
+ .writeToXml(defaultPhoneAccountHandle, serializer, context,
+ telephonyFeatureFlags);
}
serializer.endTag(null, DEFAULT_OUTGOING);
serializer.startTag(null, ACCOUNTS);
for (PhoneAccount m : o.accounts) {
- sPhoneAccountXml.writeToXml(m, serializer, context);
+ sPhoneAccountXml.writeToXml(m, serializer, context, telephonyFeatureFlags);
}
serializer.endTag(null, ACCOUNTS);
@@ -2255,8 +2337,8 @@
}
@Override
- public State readFromXml(XmlPullParser parser, int version, Context context)
- throws IOException, XmlPullParserException {
+ public State readFromXml(XmlPullParser parser, int version, Context context,
+ FeatureFlags telephonyFeatureFlags) throws IOException, XmlPullParserException {
if (parser.getName().equals(CLASS_STATE)) {
State s = new State();
@@ -2272,7 +2354,8 @@
// assume there are no groups.
parser.nextTag();
PhoneAccountHandle phoneAccountHandle = sPhoneAccountHandleXml
- .readFromXml(parser, s.versionNumber, context);
+ .readFromXml(parser, s.versionNumber, context,
+ telephonyFeatureFlags);
UserManager userManager = UserManager.get(context);
UserInfo primaryUser = userManager.getPrimaryUser();
if (primaryUser != null) {
@@ -2287,8 +2370,9 @@
int defaultAccountHandlesDepth = parser.getDepth();
while (XmlUtils.nextElementWithin(parser, defaultAccountHandlesDepth)) {
DefaultPhoneAccountHandle accountHandle
- = sDefaultPhoneAcountHandleXml
- .readFromXml(parser, s.versionNumber, context);
+ = sDefaultPhoneAccountHandleXml
+ .readFromXml(parser, s.versionNumber, context,
+ telephonyFeatureFlags);
if (accountHandle != null && s.accounts != null) {
s.defaultOutgoingAccountHandles
.put(accountHandle.userHandle, accountHandle);
@@ -2299,7 +2383,7 @@
int accountsDepth = parser.getDepth();
while (XmlUtils.nextElementWithin(parser, accountsDepth)) {
PhoneAccount account = sPhoneAccountXml.readFromXml(parser,
- s.versionNumber, context);
+ s.versionNumber, context, telephonyFeatureFlags);
if (account != null && s.accounts != null) {
s.accounts.add(account);
@@ -2314,7 +2398,7 @@
};
@VisibleForTesting
- public static final XmlSerialization<DefaultPhoneAccountHandle> sDefaultPhoneAcountHandleXml =
+ public static final XmlSerialization<DefaultPhoneAccountHandle> sDefaultPhoneAccountHandleXml =
new XmlSerialization<DefaultPhoneAccountHandle>() {
private static final String CLASS_DEFAULT_OUTGOING_PHONE_ACCOUNT_HANDLE
= "default_outgoing_phone_account_handle";
@@ -2324,7 +2408,7 @@
@Override
public void writeToXml(DefaultPhoneAccountHandle o, XmlSerializer serializer,
- Context context) throws IOException {
+ Context context, FeatureFlags telephonyFeatureFlags) throws IOException {
if (o != null) {
final UserManager userManager = UserManager.get(context);
final long serialNumber = userManager.getSerialNumberForUser(o.userHandle);
@@ -2334,7 +2418,7 @@
writeNonNullString(GROUP_ID, o.groupId, serializer);
serializer.startTag(null, ACCOUNT_HANDLE);
sPhoneAccountHandleXml.writeToXml(o.phoneAccountHandle, serializer,
- context);
+ context, telephonyFeatureFlags);
serializer.endTag(null, ACCOUNT_HANDLE);
serializer.endTag(null, CLASS_DEFAULT_OUTGOING_PHONE_ACCOUNT_HANDLE);
}
@@ -2343,7 +2427,7 @@
@Override
public DefaultPhoneAccountHandle readFromXml(XmlPullParser parser, int version,
- Context context)
+ Context context, FeatureFlags telephonyFeatureFlags)
throws IOException, XmlPullParserException {
if (parser.getName().equals(CLASS_DEFAULT_OUTGOING_PHONE_ACCOUNT_HANDLE)) {
int outerDepth = parser.getDepth();
@@ -2354,7 +2438,7 @@
if (parser.getName().equals(ACCOUNT_HANDLE)) {
parser.nextTag();
accountHandle = sPhoneAccountHandleXml.readFromXml(parser, version,
- context);
+ context, telephonyFeatureFlags);
} else if (parser.getName().equals(USER_SERIAL_NUMBER)) {
parser.next();
userSerialNumberString = parser.getText();
@@ -2405,16 +2489,19 @@
private static final String ICON = "icon";
private static final String EXTRAS = "extras";
private static final String ENABLED = "enabled";
+ private static final String SIMULTANEOUS_CALLING_RESTRICTION
+ = "simultaneous_calling_restriction";
@Override
- public void writeToXml(PhoneAccount o, XmlSerializer serializer, Context context)
- throws IOException {
+ public void writeToXml(PhoneAccount o, XmlSerializer serializer, Context context,
+ FeatureFlags telephonyFeatureFlags) throws IOException {
if (o != null) {
serializer.startTag(null, CLASS_PHONE_ACCOUNT);
if (o.getAccountHandle() != null) {
serializer.startTag(null, ACCOUNT_HANDLE);
- sPhoneAccountHandleXml.writeToXml(o.getAccountHandle(), serializer, context);
+ sPhoneAccountHandleXml.writeToXml(o.getAccountHandle(), serializer, context,
+ telephonyFeatureFlags);
serializer.endTag(null, ACCOUNT_HANDLE);
}
@@ -2431,13 +2518,19 @@
writeTextIfNonNull(ENABLED, o.isEnabled() ? "true" : "false" , serializer);
writeTextIfNonNull(SUPPORTED_AUDIO_ROUTES, Integer.toString(
o.getSupportedAudioRoutes()), serializer);
+ if (o.hasSimultaneousCallingRestriction()
+ && telephonyFeatureFlags.simultaneousCallingIndications()) {
+ writePhoneAccountHandleSet(SIMULTANEOUS_CALLING_RESTRICTION,
+ o.getSimultaneousCallingRestriction(), serializer, context,
+ telephonyFeatureFlags);
+ }
serializer.endTag(null, CLASS_PHONE_ACCOUNT);
}
}
- public PhoneAccount readFromXml(XmlPullParser parser, int version, Context context)
- throws IOException, XmlPullParserException {
+ public PhoneAccount readFromXml(XmlPullParser parser, int version, Context context,
+ FeatureFlags telephonyFeatureFlags) throws IOException, XmlPullParserException {
if (parser.getName().equals(CLASS_PHONE_ACCOUNT)) {
int outerDepth = parser.getDepth();
PhoneAccountHandle accountHandle = null;
@@ -2456,12 +2549,13 @@
Icon icon = null;
boolean enabled = false;
Bundle extras = null;
+ Set<PhoneAccountHandle> simultaneousCallingRestriction = null;
while (XmlUtils.nextElementWithin(parser, outerDepth)) {
if (parser.getName().equals(ACCOUNT_HANDLE)) {
parser.nextTag();
accountHandle = sPhoneAccountHandleXml.readFromXml(parser, version,
- context);
+ context, telephonyFeatureFlags);
} else if (parser.getName().equals(ADDRESS)) {
parser.next();
address = Uri.parse(parser.getText());
@@ -2506,6 +2600,12 @@
} else if (parser.getName().equals(SUPPORTED_AUDIO_ROUTES)) {
parser.next();
supportedAudioRoutes = Integer.parseInt(parser.getText());
+ } else if (parser.getName().equals(SIMULTANEOUS_CALLING_RESTRICTION)) {
+ // We can not flag this because we always need to handle the case where
+ // this info is in the XML for parsing reasons. We only flag setting the
+ // parsed value below based on the flag.
+ simultaneousCallingRestriction = readPhoneAccountHandleSet(parser, version,
+ context, telephonyFeatureFlags);
}
}
@@ -2587,6 +2687,9 @@
} else if (!TextUtils.isEmpty(iconPackageName)) {
builder.setIcon(Icon.createWithResource(iconPackageName, iconResId));
// TODO: Need to set tint.
+ } else if (simultaneousCallingRestriction != null
+ && telephonyFeatureFlags.simultaneousCallingIndications()) {
+ builder.setSimultaneousCallingRestriction(simultaneousCallingRestriction);
}
return builder.build();
@@ -2618,8 +2721,8 @@
private static final String USER_SERIAL_NUMBER = "user_serial_number";
@Override
- public void writeToXml(PhoneAccountHandle o, XmlSerializer serializer, Context context)
- throws IOException {
+ public void writeToXml(PhoneAccountHandle o, XmlSerializer serializer, Context context,
+ FeatureFlags telephonyFeatureFlags) throws IOException {
if (o != null) {
serializer.startTag(null, CLASS_PHONE_ACCOUNT_HANDLE);
@@ -2641,8 +2744,8 @@
}
@Override
- public PhoneAccountHandle readFromXml(XmlPullParser parser, int version, Context context)
- throws IOException, XmlPullParserException {
+ public PhoneAccountHandle readFromXml(XmlPullParser parser, int version, Context context,
+ FeatureFlags telephonyFeatureFlags) throws IOException, XmlPullParserException {
if (parser.getName().equals(CLASS_PHONE_ACCOUNT_HANDLE)) {
String componentNameString = null;
String idString = null;
@@ -2682,8 +2785,4 @@
return null;
}
};
-
- private String nullToEmpty(String str) {
- return str == null ? "" : str;
- }
}
diff --git a/src/com/android/server/telecom/RoleManagerAdapter.java b/src/com/android/server/telecom/RoleManagerAdapter.java
index 8fdfb11..9f515e6 100644
--- a/src/com/android/server/telecom/RoleManagerAdapter.java
+++ b/src/com/android/server/telecom/RoleManagerAdapter.java
@@ -54,7 +54,7 @@
/**
* Returns the package name of the app which fills the {@link android.app.role.RoleManager} call
* screening role.
- * @return the package name of the app filling the role, {@code null} otherwise}.
+ * @return the package name of the app filling the role, {@code null} otherwise.
*/
String getDefaultCallScreeningApp(UserHandle userHandle);
@@ -67,9 +67,25 @@
void setTestDefaultCallScreeningApp(String packageName);
/**
+ * Returns the package name of the package which fills the {@link android.app.role.RoleManager}
+ * bt in-call service role.
+ * @return the package name of the package filling the role, {@code null} otherwise.
+ */
+ String getBTInCallService();
+
+ /**
+ * Override the {@link android.app.role.RoleManager} bt in-call service package with another
+ * value.
+ * Used for testing purposes only.
+ * @param packageName Package name of the package to fill the bt in-call service role. Where
+ * {@code null}, the override is removed.
+ */
+ void setTestBTInCallService(String packageName);
+
+ /**
* Returns the package name of the app which fills the {@link android.app.role.RoleManager}
* {@link android.app.role.RoleManager#ROLE_DIALER} role.
- * @return the package name of the app filling the role, {@code null} otherwise}.
+ * @return the package name of the app filling the role, {@code null} otherwise.
*/
String getDefaultDialerApp(int user);
diff --git a/src/com/android/server/telecom/RoleManagerAdapterImpl.java b/src/com/android/server/telecom/RoleManagerAdapterImpl.java
index ac35b3d..33ec466 100644
--- a/src/com/android/server/telecom/RoleManagerAdapterImpl.java
+++ b/src/com/android/server/telecom/RoleManagerAdapterImpl.java
@@ -41,6 +41,7 @@
private String mOverrideDefaultCallScreeningApp = null;
private String mOverrideDefaultDialerApp = null;
private List<String> mOverrideCallCompanionApps = new ArrayList<>();
+ private String mOverrideBTInCallService = null;
private Context mContext;
private RoleManager mRoleManager;
private UserHandle mCurrentUserHandle;
@@ -77,6 +78,20 @@
}
@Override
+ public String getBTInCallService() {
+ if (mOverrideBTInCallService != null) {
+ return mOverrideBTInCallService;
+ }
+ return getBluetoothInCallServicePackageName();
+ }
+
+ @Override
+ public void setTestBTInCallService(String packageName) {
+ mOverrideBTInCallService = packageName;
+ }
+
+
+ @Override
public String getDefaultDialerApp(int user) {
if (mOverrideDefaultDialerApp != null) {
return mOverrideDefaultDialerApp;
@@ -151,6 +166,10 @@
return roleHolders.get(0);
}
+ private String getBluetoothInCallServicePackageName() {
+ return mContext.getResources().getString(R.string.system_bluetooth_stack);
+ }
+
/**
* Returns the application label that corresponds to the given package name
*
diff --git a/src/com/android/server/telecom/TelecomServiceImpl.java b/src/com/android/server/telecom/TelecomServiceImpl.java
index 61fa9ba..08566c9 100644
--- a/src/com/android/server/telecom/TelecomServiceImpl.java
+++ b/src/com/android/server/telecom/TelecomServiceImpl.java
@@ -1599,7 +1599,7 @@
&& accountExtra != null && accountExtra.getBoolean(
PhoneAccount.EXTRA_SKIP_CALL_FILTERING,
false)) {
- mCallsManager.getInCallController().bindToServices(null);
+ mCallsManager.getInCallController().bindToServices(null, false);
}
}
} finally {
@@ -1951,7 +1951,7 @@
synchronized (mLock) {
long token = Binder.clearCallingIdentity();
try {
- BlockedNumberContract.SystemContract.endBlockSuppression(mContext);
+ BlockedNumberContract.BlockedNumbers.endBlockSuppression(mContext);
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -1994,8 +1994,12 @@
if (args != null && args.length > 0 && Analytics.ANALYTICS_DUMPSYS_ARG.equals(
args[0])) {
- Binder.withCleanCallingIdentity(() ->
- Analytics.dumpToEncodedProto(mContext, writer, args));
+ long token = Binder.clearCallingIdentity();
+ try {
+ Analytics.dumpToEncodedProto(mContext, writer, args);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
return;
}
@@ -2243,7 +2247,8 @@
try {
synchronized (mLock) {
enforceShellOnly(Binder.getCallingUid(), "cleanupStuckCalls");
- Binder.withCleanCallingIdentity(() -> {
+ long token = Binder.clearCallingIdentity();
+ try {
Set<UserHandle> userHandles = new HashSet<>();
for (Call call : mCallsManager.getCalls()) {
call.cleanup();
@@ -2256,7 +2261,9 @@
for (UserHandle userHandle : userHandles) {
mCallsManager.getInCallController().unbindFromServices(userHandle);
}
- });
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}
} finally {
Log.endSession();
@@ -2333,11 +2340,14 @@
try {
synchronized (mLock) {
enforceShellOnly(Binder.getCallingUid(), "resetCarMode");
- Binder.withCleanCallingIdentity(() -> {
+ long token = Binder.clearCallingIdentity();
+ try {
UiModeManager uiModeManager =
mContext.getSystemService(UiModeManager.class);
uiModeManager.disableCarMode(UiModeManager.DISABLE_CAR_MODE_ALL_PRIORITIES);
- });
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}
} finally {
Log.endSession();
@@ -2496,23 +2506,28 @@
* @param packageName the package name of the app to check calls for.
* @param userHandle the user handle on which to check for calls.
* @param callingPackage The caller's package name.
+ * @param detectForAllUsers indicates if calls should be detected across all users. If it is
+ * set to true, the userHandle parameter is disregarded.
* @return {@code true} if there are ongoing calls, {@code false} otherwise.
*/
@Override
public boolean isInSelfManagedCall(String packageName, UserHandle userHandle,
- String callingPackage) {
+ String callingPackage, boolean detectForAllUsers) {
try {
- if (Binder.getCallingUid() != Process.SYSTEM_UID) {
- throw new SecurityException("Only the system can call this API");
- }
mContext.enforceCallingOrSelfPermission(READ_PRIVILEGED_PHONE_STATE,
"READ_PRIVILEGED_PHONE_STATE required.");
+ // Ensure that the caller has the INTERACT_ACROSS_USERS permission if it's trying
+ // to access calls that don't belong to it.
+ if (detectForAllUsers || !Binder.getCallingUserHandle().equals(userHandle)) {
+ enforceInAppCrossUserPermission();
+ }
Log.startSession("TSI.iISMC", Log.getPackageAbbreviation(callingPackage));
synchronized (mLock) {
long token = Binder.clearCallingIdentity();
try {
- return mCallsManager.isInSelfManagedCall(packageName, userHandle);
+ return mCallsManager.isInSelfManagedCallCrossUsers(
+ packageName, userHandle, detectForAllUsers);
} finally {
Binder.restoreCallingIdentity(token);
}
diff --git a/src/com/android/server/telecom/TelecomSystem.java b/src/com/android/server/telecom/TelecomSystem.java
index 4310945..9f6fcba 100644
--- a/src/com/android/server/telecom/TelecomSystem.java
+++ b/src/com/android/server/telecom/TelecomSystem.java
@@ -141,6 +141,7 @@
private final TelecomServiceImpl mTelecomServiceImpl;
private final ContactsAsyncHelper mContactsAsyncHelper;
private final DialerCodeReceiver mDialerCodeReceiver;
+ private final FeatureFlags mFeatureFlags;
private boolean mIsBootComplete = false;
@@ -231,6 +232,7 @@
BlockedNumbersAdapter blockedNumbersAdapter,
FeatureFlags featureFlags) {
mContext = context.getApplicationContext();
+ mFeatureFlags = featureFlags;
LogUtils.initLogging(mContext);
android.telecom.Log.setLock(mLock);
AnomalyReporter.initialize(mContext);
@@ -347,14 +349,22 @@
ToastFactory toastFactory = new ToastFactory() {
@Override
public Toast makeText(Context context, int resId, int duration) {
- return Toast.makeText(context, context.getMainLooper(),
- context.getString(resId),
- duration);
+ if (mFeatureFlags.telecomResolveHiddenDependencies()) {
+ return Toast.makeText(context, resId, duration);
+ } else {
+ return Toast.makeText(context, context.getMainLooper(),
+ context.getString(resId),
+ duration);
+ }
}
@Override
public Toast makeText(Context context, CharSequence text, int duration) {
- return Toast.makeText(context, context.getMainLooper(), text, duration);
+ if (mFeatureFlags.telecomResolveHiddenDependencies()) {
+ return Toast.makeText(context, text, duration);
+ } else {
+ return Toast.makeText(context, context.getMainLooper(), text, duration);
+ }
}
};
diff --git a/src/com/android/server/telecom/Timeouts.java b/src/com/android/server/telecom/Timeouts.java
index c5fdd4c..abc7ff6 100644
--- a/src/com/android/server/telecom/Timeouts.java
+++ b/src/com/android/server/telecom/Timeouts.java
@@ -41,6 +41,10 @@
return Timeouts.getCallScreeningTimeoutMillis(cr);
}
+ public long getCallBindBluetoothInCallServicesDelay(ContentResolver cr) {
+ return Timeouts.getCallBindBluetoothInCallServicesDelay(cr);
+ }
+
public long getCallRemoveUnbindInCallServicesDelay(ContentResolver cr) {
return Timeouts.getCallRemoveUnbindInCallServicesDelay(cr);
}
@@ -270,6 +274,11 @@
60000L /* 1 minute */);
}
+ public static long getCallBindBluetoothInCallServicesDelay(ContentResolver contentResolver) {
+ return get(contentResolver, "call_bind_bluetooth_in_call_services_delay",
+ 2000L /* 2 seconds */);
+ }
+
/**
* Returns the amount of delay before unbinding the in-call services after all the calls
* are removed.
diff --git a/src/com/android/server/telecom/bluetooth/BluetoothStateReceiver.java b/src/com/android/server/telecom/bluetooth/BluetoothStateReceiver.java
index d2521ac..6d80cd5 100644
--- a/src/com/android/server/telecom/bluetooth/BluetoothStateReceiver.java
+++ b/src/com/android/server/telecom/bluetooth/BluetoothStateReceiver.java
@@ -16,6 +16,15 @@
package com.android.server.telecom.bluetooth;
+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.bluetooth.BluetoothRouteManager.BT_AUDIO_IS_ON;
+import static com.android.server.telecom.bluetooth.BluetoothRouteManager.BT_AUDIO_LOST;
+
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHeadset;
@@ -32,11 +41,11 @@
import android.telecom.Logging.Session;
import com.android.internal.os.SomeArgs;
+import com.android.server.telecom.AudioRoute;
import com.android.server.telecom.CallAudioCommunicationDeviceTracker;
+import com.android.server.telecom.CallAudioRouteAdapter;
import com.android.server.telecom.flags.FeatureFlags;
-
-import static com.android.server.telecom.bluetooth.BluetoothRouteManager.BT_AUDIO_IS_ON;
-import static com.android.server.telecom.bluetooth.BluetoothRouteManager.BT_AUDIO_LOST;
+import com.android.server.telecom.flags.Flags;
public class BluetoothStateReceiver extends BroadcastReceiver {
@@ -61,6 +70,7 @@
private final BluetoothDeviceManager mBluetoothDeviceManager;
private CallAudioCommunicationDeviceTracker mCommunicationDeviceTracker;
private FeatureFlags mFeatureFlags;
+ private CallAudioRouteAdapter mCallAudioRouteAdapter;
public void onReceive(Context context, Intent intent) {
Log.startSession("BSR.oR");
@@ -106,14 +116,24 @@
args.arg2 = device.getAddress();
switch (bluetoothHeadsetAudioState) {
case BluetoothHeadset.STATE_AUDIO_CONNECTED:
- if (!mIsInCall) {
- Log.i(LOG_TAG, "Ignoring BT audio on since we're not in a call");
- return;
+ if (Flags.useRefactoredAudioRouteSwitching()) {
+ mCallAudioRouteAdapter.sendMessageWithSessionInfo(BT_AUDIO_CONNECTED, 0,
+ device);
+ } else {
+ if (!mIsInCall) {
+ Log.i(LOG_TAG, "Ignoring BT audio on since we're not in a call");
+ return;
+ }
+ mBluetoothRouteManager.sendMessage(BT_AUDIO_IS_ON, args);
}
- mBluetoothRouteManager.sendMessage(BT_AUDIO_IS_ON, args);
break;
case BluetoothHeadset.STATE_AUDIO_DISCONNECTED:
- mBluetoothRouteManager.sendMessage(BT_AUDIO_LOST, args);
+ if (Flags.useRefactoredAudioRouteSwitching()) {
+ mCallAudioRouteAdapter.sendMessageWithSessionInfo(BT_AUDIO_DISCONNECTED, 0,
+ device);
+ } else {
+ mBluetoothRouteManager.sendMessage(BT_AUDIO_LOST, args);
+ }
break;
}
}
@@ -131,12 +151,16 @@
}
int deviceType;
+ @AudioRoute.AudioRouteType int audioRouteType;
if (BluetoothLeAudio.ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED.equals(intent.getAction())) {
deviceType = BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO;
+ audioRouteType = AudioRoute.TYPE_BLUETOOTH_LE;
} else if (BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED.equals(intent.getAction())) {
deviceType = BluetoothDeviceManager.DEVICE_TYPE_HEARING_AID;
+ audioRouteType = AudioRoute.TYPE_BLUETOOTH_HA;
} else if (BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED.equals(intent.getAction())) {
deviceType = BluetoothDeviceManager.DEVICE_TYPE_HEADSET;
+ audioRouteType = AudioRoute.TYPE_BLUETOOTH_SCO;
} else {
Log.w(LOG_TAG, "handleConnectionStateChanged: %s invalid device type", device);
return;
@@ -147,10 +171,20 @@
device.getAddress(), bluetoothHeadsetState);
if (bluetoothHeadsetState == BluetoothProfile.STATE_CONNECTED) {
- mBluetoothDeviceManager.onDeviceConnected(device, deviceType);
+ if (Flags.useRefactoredAudioRouteSwitching()) {
+ mCallAudioRouteAdapter.sendMessageWithSessionInfo(BT_DEVICE_ADDED,
+ audioRouteType, device);
+ } else {
+ mBluetoothDeviceManager.onDeviceConnected(device, deviceType);
+ }
} else if (bluetoothHeadsetState == BluetoothProfile.STATE_DISCONNECTED
|| bluetoothHeadsetState == BluetoothProfile.STATE_DISCONNECTING) {
- mBluetoothDeviceManager.onDeviceDisconnected(device, deviceType);
+ if (Flags.useRefactoredAudioRouteSwitching()) {
+ mCallAudioRouteAdapter.sendMessageWithSessionInfo(BT_DEVICE_REMOVED,
+ audioRouteType, device);
+ } else {
+ mBluetoothDeviceManager.onDeviceDisconnected(device, deviceType);
+ }
}
}
@@ -159,12 +193,16 @@
intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE, BluetoothDevice.class);
int deviceType;
+ @AudioRoute.AudioRouteType int audioRouteType;
if (BluetoothLeAudio.ACTION_LE_AUDIO_ACTIVE_DEVICE_CHANGED.equals(intent.getAction())) {
deviceType = BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO;
+ audioRouteType = AudioRoute.TYPE_BLUETOOTH_LE;
} else if (BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED.equals(intent.getAction())) {
deviceType = BluetoothDeviceManager.DEVICE_TYPE_HEARING_AID;
+ audioRouteType = AudioRoute.TYPE_BLUETOOTH_HA;
} else if (BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED.equals(intent.getAction())) {
deviceType = BluetoothDeviceManager.DEVICE_TYPE_HEADSET;
+ audioRouteType = AudioRoute.TYPE_BLUETOOTH_SCO;
} else {
Log.w(LOG_TAG, "handleActiveDeviceChanged: %s invalid device type", device);
return;
@@ -173,72 +211,84 @@
Log.i(LOG_TAG, "Device %s is now the preferred BT device for %s", device,
BluetoothDeviceManager.getDeviceTypeString(deviceType));
- mBluetoothRouteManager.onActiveDeviceChanged(device, deviceType);
- if (deviceType == BluetoothDeviceManager.DEVICE_TYPE_HEARING_AID ||
- deviceType == BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO) {
- Session session = Log.createSubsession();
- SomeArgs args = SomeArgs.obtain();
- args.arg1 = session;
+ if (Flags.useRefactoredAudioRouteSwitching()) {
if (device == null) {
- mBluetoothRouteManager.sendMessage(BT_AUDIO_LOST, args);
+ mCallAudioRouteAdapter.sendMessageWithSessionInfo(BT_ACTIVE_DEVICE_GONE,
+ audioRouteType);
} else {
- if (!mIsInCall) {
- Log.i(LOG_TAG, "Ignoring audio on since we're not in a call");
- return;
- }
- args.arg2 = device.getAddress();
-
- boolean usePreferredAudioProfile = false;
- BluetoothAdapter bluetoothAdapter = mBluetoothDeviceManager.getBluetoothAdapter();
- int preferredDuplexProfile = BluetoothProfile.LE_AUDIO;
- if (bluetoothAdapter != null) {
- Bundle preferredAudioProfiles = bluetoothAdapter.getPreferredAudioProfiles(
- device);
- if (preferredAudioProfiles != null && !preferredAudioProfiles.isEmpty()
- && preferredAudioProfiles.getInt(BluetoothAdapter.AUDIO_MODE_DUPLEX)
- != 0) {
- Log.i(this, "Preferred duplex profile for device=" + device + " is "
- + preferredAudioProfiles.getInt(
- BluetoothAdapter.AUDIO_MODE_DUPLEX));
- usePreferredAudioProfile = true;
- preferredDuplexProfile =
- preferredAudioProfiles.getInt(BluetoothAdapter.AUDIO_MODE_DUPLEX);
- }
- }
-
- if (deviceType == BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO) {
- /* In Le Audio case, once device got Active, the Telecom needs to make sure it
- * is set as communication device before we can say that BT_AUDIO_IS_ON
- */
- boolean isLeAudioSetForCommunication =
- mFeatureFlags.callAudioCommunicationDeviceRefactor()
- ? mCommunicationDeviceTracker.setCommunicationDevice(
- AudioDeviceInfo.TYPE_BLE_HEADSET, device)
- : mBluetoothDeviceManager.setLeAudioCommunicationDevice();
- if ((!usePreferredAudioProfile
- || preferredDuplexProfile == BluetoothProfile.LE_AUDIO)
- && !isLeAudioSetForCommunication) {
- Log.w(LOG_TAG,
- "Device %s cannot be use as LE audio communication device.",
- device);
+ mCallAudioRouteAdapter.sendMessageWithSessionInfo(BT_ACTIVE_DEVICE_PRESENT,
+ audioRouteType, device.getAddress());
+ }
+ } else {
+ mBluetoothRouteManager.onActiveDeviceChanged(device, deviceType);
+ if (deviceType == BluetoothDeviceManager.DEVICE_TYPE_HEARING_AID ||
+ deviceType == BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO) {
+ Session session = Log.createSubsession();
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = session;
+ if (device == null) {
+ mBluetoothRouteManager.sendMessage(BT_AUDIO_LOST, args);
+ } else {
+ if (!mIsInCall) {
+ Log.i(LOG_TAG, "Ignoring audio on since we're not in a call");
return;
}
- } else {
- boolean isHearingAidSetForCommunication =
- mFeatureFlags.callAudioCommunicationDeviceRefactor()
- ? mCommunicationDeviceTracker.setCommunicationDevice(
- AudioDeviceInfo.TYPE_HEARING_AID, null)
- : mBluetoothDeviceManager.setHearingAidCommunicationDevice();
- /* deviceType == BluetoothDeviceManager.DEVICE_TYPE_HEARING_AID */
- if (!isHearingAidSetForCommunication) {
- Log.w(LOG_TAG,
- "Device %s cannot be use as hearing aid communication device.",
+ args.arg2 = device.getAddress();
+
+ boolean usePreferredAudioProfile = false;
+ BluetoothAdapter bluetoothAdapter = mBluetoothDeviceManager
+ .getBluetoothAdapter();
+ int preferredDuplexProfile = BluetoothProfile.LE_AUDIO;
+ if (bluetoothAdapter != null) {
+ Bundle preferredAudioProfiles = bluetoothAdapter.getPreferredAudioProfiles(
device);
+ if (preferredAudioProfiles != null && !preferredAudioProfiles.isEmpty()
+ && preferredAudioProfiles.getInt(BluetoothAdapter.AUDIO_MODE_DUPLEX)
+ != 0) {
+ Log.i(this, "Preferred duplex profile for device=" + device + " is "
+ + preferredAudioProfiles.getInt(
+ BluetoothAdapter.AUDIO_MODE_DUPLEX));
+ usePreferredAudioProfile = true;
+ preferredDuplexProfile =
+ preferredAudioProfiles.getInt(
+ BluetoothAdapter.AUDIO_MODE_DUPLEX);
+ }
+ }
+
+ if (deviceType == BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO) {
+ /* In Le Audio case, once device got Active, the Telecom needs to make sure
+ * it is set as communication device before we can say that BT_AUDIO_IS_ON
+ */
+ boolean isLeAudioSetForCommunication =
+ mFeatureFlags.callAudioCommunicationDeviceRefactor()
+ ? mCommunicationDeviceTracker.setCommunicationDevice(
+ AudioDeviceInfo.TYPE_BLE_HEADSET, device)
+ : mBluetoothDeviceManager.setLeAudioCommunicationDevice();
+ if ((!usePreferredAudioProfile
+ || preferredDuplexProfile == BluetoothProfile.LE_AUDIO)
+ && !isLeAudioSetForCommunication) {
+ Log.w(LOG_TAG,
+ "Device %s cannot be use as LE audio communication device.",
+ device);
+ }
} else {
- mBluetoothRouteManager.sendMessage(BT_AUDIO_IS_ON, args);
+ boolean isHearingAidSetForCommunication =
+ mFeatureFlags.callAudioCommunicationDeviceRefactor()
+ ? mCommunicationDeviceTracker.setCommunicationDevice(
+ AudioDeviceInfo.TYPE_HEARING_AID, null)
+ : mBluetoothDeviceManager
+ .setHearingAidCommunicationDevice();
+ /* deviceType == BluetoothDeviceManager.DEVICE_TYPE_HEARING_AID */
+ if (!isHearingAidSetForCommunication) {
+ Log.w(LOG_TAG,
+ "Device %s cannot be use as hearing aid communication device.",
+ device);
+ } else {
+ mBluetoothRouteManager.sendMessage(BT_AUDIO_IS_ON, args);
+ }
}
}
- }
+ }
}
}
@@ -259,4 +309,8 @@
public void setIsInCall(boolean isInCall) {
mIsInCall = isInCall;
}
+
+ public void setCallAudioRouteAdapter(CallAudioRouteAdapter adapter) {
+ mCallAudioRouteAdapter = adapter;
+ }
}
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/AppUninstallBroadcastReceiver.java b/src/com/android/server/telecom/components/AppUninstallBroadcastReceiver.java
index 3a0d517..b7e5880 100644
--- a/src/com/android/server/telecom/components/AppUninstallBroadcastReceiver.java
+++ b/src/com/android/server/telecom/components/AppUninstallBroadcastReceiver.java
@@ -74,7 +74,7 @@
* @param packageName The name of the removed package.
*/
private void handlePackageRemoved(Context context, String packageName) {
- final TelecomManager telecomManager = TelecomManager.from(context);
+ final TelecomManager telecomManager = context.getSystemService(TelecomManager.class);
if (telecomManager != null) {
telecomManager.clearAccountsForPackage(packageName);
}
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/EnableAccountPreferenceFragment.java b/src/com/android/server/telecom/settings/EnableAccountPreferenceFragment.java
index c2a0500..d9feaff 100644
--- a/src/com/android/server/telecom/settings/EnableAccountPreferenceFragment.java
+++ b/src/com/android/server/telecom/settings/EnableAccountPreferenceFragment.java
@@ -72,7 +72,8 @@
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- mTelecomManager = TelecomManager.from(getActivity());
+ Context context = getActivity();
+ mTelecomManager = context.getSystemService(TelecomManager.class);
}
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/testapps/Android.bp b/testapps/Android.bp
index 11ea474..45ea753 100644
--- a/testapps/Android.bp
+++ b/testapps/Android.bp
@@ -15,6 +15,7 @@
//
package {
+ default_team: "trendy_team_fwk_telecom",
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/testapps/callaudiotest/Android.bp b/testapps/callaudiotest/Android.bp
index 81164e6..d996236 100644
--- a/testapps/callaudiotest/Android.bp
+++ b/testapps/callaudiotest/Android.bp
@@ -15,6 +15,7 @@
//
package {
+ default_team: "trendy_team_fwk_telecom",
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/testapps/carmodedialer/Android.bp b/testapps/carmodedialer/Android.bp
index 9f65b8c..f142bf4 100644
--- a/testapps/carmodedialer/Android.bp
+++ b/testapps/carmodedialer/Android.bp
@@ -15,6 +15,7 @@
//
package {
+ default_team: "trendy_team_fwk_telecom",
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/testapps/companionapp/Android.bp b/testapps/companionapp/Android.bp
index 8718b37..84ee4d3 100644
--- a/testapps/companionapp/Android.bp
+++ b/testapps/companionapp/Android.bp
@@ -15,6 +15,7 @@
//
package {
+ default_team: "trendy_team_fwk_telecom",
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/testapps/src/com/android/server/telecom/testapps/CallNotificationReceiver.java b/testapps/src/com/android/server/telecom/testapps/CallNotificationReceiver.java
index 1549443..ede06c6 100644
--- a/testapps/src/com/android/server/telecom/testapps/CallNotificationReceiver.java
+++ b/testapps/src/com/android/server/telecom/testapps/CallNotificationReceiver.java
@@ -82,6 +82,7 @@
* @param videoState The video state requested for the incoming call.
*/
public static void sendIncomingCallIntent(Context context, Uri handle, int videoState) {
+ TelecomManager telecomManager = context.getSystemService(TelecomManager.class);
PhoneAccountHandle phoneAccount = new PhoneAccountHandle(
new ComponentName(context, TestConnectionService.class),
CallServiceNotifier.SIM_SUBSCRIPTION_ID);
@@ -94,10 +95,11 @@
extras.putParcelable(TestConnectionService.EXTRA_HANDLE, handle);
}
- TelecomManager.from(context).addNewIncomingCall(phoneAccount, extras);
+ telecomManager.addNewIncomingCall(phoneAccount, extras);
}
public static void sendIncomingRttCallIntent(Context context, Uri handle, int videoState) {
+ TelecomManager telecomManager = context.getSystemService(TelecomManager.class);
PhoneAccountHandle phoneAccount = new PhoneAccountHandle(
new ComponentName(context, TestConnectionService.class),
CallServiceNotifier.SIM_SUBSCRIPTION_ID);
@@ -111,11 +113,12 @@
}
extras.putBoolean(TelecomManager.EXTRA_START_CALL_WITH_RTT, true);
- TelecomManager.from(context).addNewIncomingCall(phoneAccount, extras);
+ telecomManager.addNewIncomingCall(phoneAccount, extras);
}
public static void addNewUnknownCall(Context context, Uri handle, Bundle extras) {
Log.i(TAG, "Adding new unknown call with handle " + handle);
+ TelecomManager telecomManager = context.getSystemService(TelecomManager.class);
PhoneAccountHandle phoneAccount = new PhoneAccountHandle(
new ComponentName(context, TestConnectionService.class),
CallServiceNotifier.SIM_SUBSCRIPTION_ID);
@@ -129,7 +132,7 @@
extras.putParcelable(TestConnectionService.EXTRA_HANDLE, handle);
}
- TelecomManager.from(context).addNewUnknownCall(phoneAccount, extras);
+ telecomManager.addNewUnknownCall(phoneAccount, extras);
}
public static void hangupCalls(Context context) {
diff --git a/testapps/src/com/android/server/telecom/testapps/HandoverActivity.java b/testapps/src/com/android/server/telecom/testapps/HandoverActivity.java
index f33022c..d5ddc9b 100644
--- a/testapps/src/com/android/server/telecom/testapps/HandoverActivity.java
+++ b/testapps/src/com/android/server/telecom/testapps/HandoverActivity.java
@@ -17,6 +17,7 @@
package com.android.server.telecom.testapps;
import android.app.Activity;
+import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.telecom.Log;
@@ -61,7 +62,7 @@
if (connection != null) {
connection.setConnectionDisconnected(DisconnectCause.INCOMING_REJECTED);
connection.destroy();
- TelecomManager tm = TelecomManager.from(this);
+ TelecomManager tm = this.getSystemService(TelecomManager.class);
tm.showInCallScreen(false);
}
finish();
diff --git a/testapps/src/com/android/server/telecom/testapps/SelfManagedCallList.java b/testapps/src/com/android/server/telecom/testapps/SelfManagedCallList.java
index 273b060..4a7312c 100644
--- a/testapps/src/com/android/server/telecom/testapps/SelfManagedCallList.java
+++ b/testapps/src/com/android/server/telecom/testapps/SelfManagedCallList.java
@@ -123,6 +123,7 @@
public void registerPhoneAccount(Context context, ComponentName componentName, String id,
Uri address, String name, boolean areCallsLogged) {
+ TelecomManager telecomManager = context.getSystemService(TelecomManager.class);
PhoneAccountHandle handle = new PhoneAccountHandle(componentName, id);
mPhoneAccounts.put(id, handle);
Bundle extras = new Bundle();
@@ -144,7 +145,7 @@
.setExtras(extras)
.setShortDescription(name);
- TelecomManager.from(context).registerPhoneAccount(builder.build());
+ telecomManager.registerPhoneAccount(builder.build());
}
public PhoneAccountHandle getPhoneAccountHandle(String id) {
diff --git a/testapps/src/com/android/server/telecom/testapps/SelfManagedCallingActivity.java b/testapps/src/com/android/server/telecom/testapps/SelfManagedCallingActivity.java
index 5cdaf3d..708bae9 100644
--- a/testapps/src/com/android/server/telecom/testapps/SelfManagedCallingActivity.java
+++ b/testapps/src/com/android/server/telecom/testapps/SelfManagedCallingActivity.java
@@ -23,6 +23,7 @@
import android.app.NotificationManager;
import android.app.UiModeManager;
import android.app.role.RoleManager;
+import android.content.Context;
import android.content.Intent;
import android.media.AudioAttributes;
import android.media.RingtoneManager;
@@ -190,7 +191,7 @@
}
private void placeOutgoingCall() {
- TelecomManager tm = TelecomManager.from(this);
+ TelecomManager tm = this.getSystemService(TelecomManager.class);
PhoneAccountHandle phoneAccountHandle = getSelectedPhoneAccountHandle();
if (mCheckIfPermittedBeforeCalling.isChecked()) {
@@ -215,7 +216,7 @@
}
private void placeSelfManagedOutgoingCall() {
- TelecomManager tm = TelecomManager.from(this);
+ TelecomManager tm = this.getSystemService(TelecomManager.class);
PhoneAccountHandle phoneAccountHandle = getSelectedPhoneAccountHandle();
if (mCheckIfPermittedBeforeCalling.isChecked()) {
@@ -233,14 +234,14 @@
}
private void initiateHandover() {
- TelecomManager tm = TelecomManager.from(this);
+ TelecomManager tm = this.getSystemService(TelecomManager.class);
PhoneAccountHandle phoneAccountHandle = getSelectedPhoneAccountHandle();
Uri address = Uri.parse(mNumber.getText().toString());
tm.acceptHandover(address, VideoProfile.STATE_BIDIRECTIONAL, phoneAccountHandle);
}
private void placeIncomingCall() {
- TelecomManager tm = TelecomManager.from(this);
+ TelecomManager tm = this.getSystemService(TelecomManager.class);
PhoneAccountHandle phoneAccountHandle = getSelectedPhoneAccountHandle();
if (mCheckIfPermittedBeforeCalling.isChecked()) {
@@ -263,7 +264,7 @@
}
private void placeSelfManagedIncomingCall() {
- TelecomManager tm = TelecomManager.from(this);
+ TelecomManager tm = this.getSystemService(TelecomManager.class);
PhoneAccountHandle phoneAccountHandle = mCallList.getPhoneAccountHandle(
SelfManagedCallList.SELF_MANAGED_ACCOUNT_1A);
diff --git a/testapps/streamingtest/Android.bp b/testapps/streamingtest/Android.bp
index bd0a582..8d5cd6c 100644
--- a/testapps/streamingtest/Android.bp
+++ b/testapps/streamingtest/Android.bp
@@ -15,6 +15,7 @@
//
package {
+ default_team: "trendy_team_fwk_telecom",
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/testapps/transactionalVoipApp/Android.bp b/testapps/transactionalVoipApp/Android.bp
index 68089e2..8bac8f1 100644
--- a/testapps/transactionalVoipApp/Android.bp
+++ b/testapps/transactionalVoipApp/Android.bp
@@ -15,6 +15,7 @@
//
package {
+ default_team: "trendy_team_fwk_telecom",
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml
index 4ca6030..1c27b14 100644
--- a/tests/AndroidManifest.xml
+++ b/tests/AndroidManifest.xml
@@ -24,6 +24,7 @@
android:targetSdkVersion="33" />
<uses-permission android:name="android.permission.READ_DEVICE_CONFIG"/>
+ <uses-permission android:name="android.permission.MODIFY_AUDIO_ROUTING" />
<!-- TODO: Needed because we call BluetoothAdapter.getDefaultAdapter() statically, and
BluetoothAdapter is a final class. -->
<uses-permission android:name="android.permission.BLUETOOTH" />
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/CallAudioRouteControllerTest.java b/tests/src/com/android/server/telecom/tests/CallAudioRouteControllerTest.java
new file mode 100644
index 0000000..08576fc
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/CallAudioRouteControllerTest.java
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.telecom.tests;
+
+import 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_DEVICE_ADDED;
+import static com.android.server.telecom.CallAudioRouteAdapter.BT_DEVICE_REMOVED;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.bluetooth.BluetoothDevice;
+import android.media.AudioDeviceInfo;
+import android.media.AudioManager;
+import android.media.audiopolicy.AudioProductStrategy;
+import android.telecom.CallAudioState;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.server.telecom.AudioRoute;
+import com.android.server.telecom.CallAudioRouteController;
+import com.android.server.telecom.CallsManager;
+import com.android.server.telecom.WiredHeadsetManager;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+
+import java.util.HashSet;
+import java.util.Set;
+
+@RunWith(JUnit4.class)
+public class CallAudioRouteControllerTest extends TelecomTestCase {
+ private CallAudioRouteController mController;
+ @Mock WiredHeadsetManager mWiredHeadsetManager;
+ @Mock AudioManager mAudioManager;
+ @Mock AudioDeviceInfo mEarpieceDeviceInfo;
+ @Mock CallsManager mCallsManager;
+ private AudioRoute mEarpieceRoute;
+ private AudioRoute mSpeakerRoute;
+ private static final String BT_ADDRESS_1 = "00:00:00:00:00:01";
+ private static final BluetoothDevice BLUETOOTH_DEVICE_1 =
+ BluetoothRouteManagerTest.makeBluetoothDevice("00:00:00:00:00:01");
+ private static final Set<BluetoothDevice> BLUETOOTH_DEVICES;
+ static {
+ BLUETOOTH_DEVICES = new HashSet<>();
+ BLUETOOTH_DEVICES.add(BLUETOOTH_DEVICE_1);
+ }
+ private static final int TEST_TIMEOUT = 500;
+ AudioRoute.Factory mAudioRouteFactory = new AudioRoute.Factory() {
+ @Override
+ public AudioRoute create(@AudioRoute.AudioRouteType int type, String bluetoothAddress,
+ AudioManager audioManager) {
+ return new AudioRoute(type, bluetoothAddress, null);
+ }
+ };
+
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ when(mWiredHeadsetManager.isPluggedIn()).thenReturn(false);
+ when(mEarpieceDeviceInfo.getType()).thenReturn(AudioDeviceInfo.TYPE_BUILTIN_EARPIECE);
+ when(mAudioManager.getDevices(eq(AudioManager.GET_DEVICES_OUTPUTS))).thenReturn(
+ new AudioDeviceInfo[] {
+ mEarpieceDeviceInfo
+ });
+ doNothing().when(mCallsManager).onCallAudioStateChanged(any(CallAudioState.class),
+ any(CallAudioState.class));
+ mController = new CallAudioRouteController(mContext, mCallsManager, mAudioRouteFactory,
+ mWiredHeadsetManager);
+ mController.setAudioRouteFactory(mAudioRouteFactory);
+ mController.setAudioManager(mAudioManager);
+ mEarpieceRoute = new AudioRoute(AudioRoute.TYPE_EARPIECE, null, null);
+ mSpeakerRoute = new AudioRoute(AudioRoute.TYPE_SPEAKER, null, null);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ mController.getAdapterHandler().getLooper().quit();
+ mController.getAdapterHandler().getLooper().getThread().join();
+ super.tearDown();
+ }
+
+ @SmallTest
+ @Test
+ public void testInitializeWithEarpiece() {
+ mController.initialize();
+ assertEquals(mEarpieceRoute, mController.getCurrentRoute());
+ assertEquals(2, mController.getAvailableRoutes().size());
+ assertTrue(mController.getAvailableRoutes().contains(mSpeakerRoute));
+ }
+
+ @SmallTest
+ @Test
+ public void testInitializeWithoutEarpiece() {
+ when(mAudioManager.getDevices(eq(AudioManager.GET_DEVICES_OUTPUTS))).thenReturn(
+ new AudioDeviceInfo[] {});
+
+ mController.initialize();
+ assertEquals(mSpeakerRoute, mController.getCurrentRoute());
+ }
+
+ @SmallTest
+ @Test
+ public void testActivateAndRemoveBluetoothDeviceDuringCall() {
+ 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(BT_ACTIVE_DEVICE_PRESENT,
+ 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);
+ verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged(
+ any(CallAudioState.class), eq(expectedState));
+
+ mController.sendMessageWithSessionInfo(BT_DEVICE_REMOVED, AudioRoute.TYPE_BLUETOOTH_SCO,
+ BLUETOOTH_DEVICE_1);
+ 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 testActiveDeactivateBluetoothDevice() {
+ when(mAudioManager.getPreferredDeviceForStrategy(nullable(AudioProductStrategy.class)))
+ .thenReturn(null);
+
+ 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));
+
+ expectedState = new CallAudioState(false, CallAudioState.ROUTE_EARPIECE,
+ CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH
+ | CallAudioState.ROUTE_SPEAKER, null, BLUETOOTH_DEVICES);
+ mController.sendMessageWithSessionInfo(BT_ACTIVE_DEVICE_GONE,
+ AudioRoute.TYPE_BLUETOOTH_SCO);
+ verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged(
+ any(CallAudioState.class), eq(expectedState));
+ }
+
+ @SmallTest
+ @Test
+ public void testSwitchFocusInBluetoothRoute() {
+
+ }
+}
diff --git a/tests/src/com/android/server/telecom/tests/CallsManagerTest.java b/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
index be00125..a7ccb0a 100644
--- a/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
@@ -36,6 +36,7 @@
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
@@ -102,6 +103,7 @@
import com.android.server.telecom.ConnectionServiceFocusManager;
import com.android.server.telecom.ConnectionServiceFocusManager.ConnectionServiceFocusManagerFactory;
import com.android.server.telecom.ConnectionServiceWrapper;
+import com.android.server.telecom.CreateConnectionResponse;
import com.android.server.telecom.DefaultDialerCache;
import com.android.server.telecom.EmergencyCallDiagnosticLogger;
import com.android.server.telecom.EmergencyCallHelper;
@@ -110,6 +112,7 @@
import com.android.server.telecom.HeadsetMediaButtonFactory;
import com.android.server.telecom.InCallController;
import com.android.server.telecom.InCallControllerFactory;
+import com.android.server.telecom.DefaultDialerCache;
import com.android.server.telecom.InCallTonePlayer;
import com.android.server.telecom.InCallWakeLockController;
import com.android.server.telecom.InCallWakeLockControllerFactory;
@@ -143,6 +146,8 @@
import org.junit.runners.JUnit4;
import org.mockito.ArgumentCaptor;
import org.mockito.ArgumentMatchers;
+import org.mockito.InOrder;
+import org.mockito.Matchers;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
@@ -374,6 +379,7 @@
eq(WORK_HANDLE), any())).thenReturn(WORK_ACCOUNT);
when(mToastFactory.makeText(any(), anyInt(), anyInt())).thenReturn(mToast);
when(mToastFactory.makeText(any(), any(), anyInt())).thenReturn(mToast);
+ when(mFeatureFlags.separatelyBindToBtIncallService()).thenReturn(false);
}
@Override
@@ -3188,7 +3194,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));
@@ -3197,7 +3203,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));
@@ -3308,6 +3314,28 @@
assertTrue(mCallsManager.isInSelfManagedCall(TEST_PACKAGE_NAME, TEST_USER_HANDLE));
}
+ @SmallTest
+ @Test
+ public void testBindToBtServiceSeparately() {
+ when(mFeatureFlags.separatelyBindToBtIncallService()).thenReturn(true);
+ Call call = addSpyCall(CallState.NEW);
+ CallFilteringResult result = new CallFilteringResult.Builder()
+ .setShouldAllowCall(true)
+ .setShouldReject(false)
+ .build();
+ when(mInCallController.bindToBTService(eq(call))).thenReturn(
+ CompletableFuture.completedFuture(true));
+ when(mInCallController.isBoundAndConnectedToBTService(any(UserHandle.class)))
+ .thenReturn(false);
+
+ mCallsManager.onCallFilteringComplete(call, result, false);
+
+ InOrder inOrder = inOrder(mInCallController, call, mInCallController);
+
+ inOrder.verify(mInCallController).bindToBTService(eq(call));
+ inOrder.verify(call).setState(eq(CallState.RINGING), anyString());
+ }
+
private Call addSpyCall() {
return addSpyCall(SIM_2_HANDLE, CallState.ACTIVE);
diff --git a/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java b/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java
index 4a86dac..54aaa4c 100644
--- a/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java
+++ b/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java
@@ -290,6 +290,8 @@
return Context.DROPBOX_SERVICE;
} else if (svcClass == BugreportManager.class) {
return Context.BUGREPORT_SERVICE;
+ } else if (svcClass == TelecomManager.class) {
+ return Context.TELECOM_SERVICE;
}
throw new UnsupportedOperationException(svcClass.getName());
}
diff --git a/tests/src/com/android/server/telecom/tests/EmergencyCallDiagnosticLoggerTest.java b/tests/src/com/android/server/telecom/tests/EmergencyCallDiagnosticLoggerTest.java
index c63a3d5..41426c0 100644
--- a/tests/src/com/android/server/telecom/tests/EmergencyCallDiagnosticLoggerTest.java
+++ b/tests/src/com/android/server/telecom/tests/EmergencyCallDiagnosticLoggerTest.java
@@ -17,7 +17,7 @@
package com.android.server.telecom.tests;
-import static android.telephony.TelephonyManager.EmergencyCallDiagnosticParams;
+import static android.telephony.TelephonyManager.EmergencyCallDiagnosticData;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
@@ -238,16 +238,16 @@
mEmergencyCallDiagnosticLogger.reportStuckCall(call);
//for stuck calls, we should always be persisting some data
- ArgumentCaptor<EmergencyCallDiagnosticParams> captor =
- ArgumentCaptor.forClass(EmergencyCallDiagnosticParams.class);
+ ArgumentCaptor<EmergencyCallDiagnosticData> captor =
+ ArgumentCaptor.forClass(EmergencyCallDiagnosticData.class);
verify(mTm, times(1)).persistEmergencyCallDiagnosticData(eq(DROP_BOX_TAG),
captor.capture());
- EmergencyCallDiagnosticParams dp = captor.getValue();
+ EmergencyCallDiagnosticData ecdData = captor.getValue();
- assertNotNull(dp);
+ assertNotNull(ecdData);
assertTrue(
- dp.isLogcatCollectionEnabled() || dp.isTelecomDumpSysCollectionEnabled()
- || dp.isTelephonyDumpSysCollectionEnabled());
+ ecdData.isLogcatCollectionEnabled() || ecdData.isTelecomDumpsysCollectionEnabled()
+ || ecdData.isTelephonyDumpsysCollectionEnabled());
//tracking should end
assertEquals(0, mEmergencyCallDiagnosticLogger.getEmergencyCallsMap().size());
@@ -265,17 +265,16 @@
mEmergencyCallDiagnosticLogger.onCallRemoved(call);
//for non-local disconnect of non-active call, we should always be persisting some data
- ArgumentCaptor<TelephonyManager.EmergencyCallDiagnosticParams> captor =
- ArgumentCaptor.forClass(
- TelephonyManager.EmergencyCallDiagnosticParams.class);
+ ArgumentCaptor<EmergencyCallDiagnosticData> captor =
+ ArgumentCaptor.forClass(EmergencyCallDiagnosticData.class);
verify(mTm, times(1)).persistEmergencyCallDiagnosticData(eq(DROP_BOX_TAG),
captor.capture());
- TelephonyManager.EmergencyCallDiagnosticParams dp = captor.getValue();
+ EmergencyCallDiagnosticData ecdData = captor.getValue();
- assertNotNull(dp);
+ assertNotNull(ecdData);
assertTrue(
- dp.isLogcatCollectionEnabled() || dp.isTelecomDumpSysCollectionEnabled()
- || dp.isTelephonyDumpSysCollectionEnabled());
+ ecdData.isLogcatCollectionEnabled() || ecdData.isTelecomDumpsysCollectionEnabled()
+ || ecdData.isTelephonyDumpsysCollectionEnabled());
//tracking should end
assertEquals(0, mEmergencyCallDiagnosticLogger.getEmergencyCallsMap().size());
diff --git a/tests/src/com/android/server/telecom/tests/InCallControllerTests.java b/tests/src/com/android/server/telecom/tests/InCallControllerTests.java
index ba678fa..24b14de 100644
--- a/tests/src/com/android/server/telecom/tests/InCallControllerTests.java
+++ b/tests/src/com/android/server/telecom/tests/InCallControllerTests.java
@@ -19,7 +19,6 @@
import static com.android.server.telecom.InCallController.IN_CALL_SERVICE_NOTIFICATION_ID;
import static com.android.server.telecom.InCallController.NOTIFICATION_TAG;
import static com.android.server.telecom.tests.TelecomSystemTest.TEST_TIMEOUT;
-
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
@@ -185,6 +184,9 @@
private static final String APPOP_NONUI_PKG = "appop_nonui_pkg";
private static final String APPOP_NONUI_CLASS = "appop_nonui_cls";
private static final int APPOP_NONUI_UID = 7;
+ private static final String BT_PKG = "btpkg";
+ private static final String BT_CLS = "btcls";
+ private static final int BT_UID = 900974;
private static final PhoneAccountHandle PA_HANDLE =
new PhoneAccountHandle(new ComponentName("pa_pkg", "pa_cls"),
@@ -206,6 +208,7 @@
private UserHandle mChildUserHandle = UserHandle.of(10);
private @Mock Call mMockChildUserCall;
private UserHandle mParentUserHandle = UserHandle.of(1);
+ private @Mock com.android.internal.telephony.flags.FeatureFlags mTelephonyFeatureFlags;
@Override
@Before
@@ -224,6 +227,7 @@
when(mDefaultDialerCache.getSystemDialerApplication()).thenReturn(SYS_PKG);
when(mDefaultDialerCache.getSystemDialerComponent()).thenReturn(
new ComponentName(SYS_PKG, SYS_CLASS));
+ when(mDefaultDialerCache.getBTInCallServicePackage()).thenReturn(BT_PKG);
mEmergencyCallHelper = new EmergencyCallHelper(mMockContext, mDefaultDialerCache,
mTimeoutsAdapter);
when(mMockCallsManager.getRoleManagerAdapter()).thenReturn(mMockRoleManagerAdapter);
@@ -235,9 +239,11 @@
mMockPermissionInfo);
when(mMockContext.getAttributionSource()).thenReturn(new AttributionSource(Process.myUid(),
"com.android.server.telecom.tests", null));
+ when(mTelephonyFeatureFlags.workProfileApiSplit()).thenReturn(false);
mInCallController = new InCallController(mMockContext, mLock, mMockCallsManager,
mMockSystemStateHelper, mDefaultDialerCache, mTimeoutsAdapter,
- mEmergencyCallHelper, mCarModeTracker, mClockProxy, mFeatureFlags);
+ mEmergencyCallHelper, mCarModeTracker, mClockProxy, mFeatureFlags,
+ mTelephonyFeatureFlags);
// Capture the broadcast receiver registered.
doAnswer(invocation -> {
mRegisteredReceiver = invocation.getArgument(0);
@@ -268,6 +274,8 @@
return new String[] { NONUI_PKG };
case APPOP_NONUI_UID:
return new String[] { APPOP_NONUI_PKG };
+ case BT_UID:
+ return new String[] { BT_PKG };
}
return null;
}).when(mMockPackageManager).getPackagesForUid(anyInt());
@@ -310,6 +318,7 @@
// Mock user info to allow binding on user stored in the phone account (mUserHandle).
when(mMockUserManager.getUserInfo(anyInt())).thenReturn(mMockUserInfo);
when(mMockUserInfo.isManagedProfile()).thenReturn(true);
+ when(mFeatureFlags.separatelyBindToBtIncallService()).thenReturn(false);
}
@Override
@@ -399,7 +408,7 @@
.thenReturn(300_000L);
setupMockPackageManager(false /* default */, true /* system */, false /* external calls */);
- mInCallController.bindToServices(mMockCall);
+ mInCallController.bindToServices(mMockCall, false);
ArgumentCaptor<Intent> bindIntentCaptor = ArgumentCaptor.forClass(Intent.class);
verify(mMockContext).bindServiceAsUser(
@@ -434,7 +443,7 @@
Intent queryIntent = new Intent(InCallService.SERVICE_INTERFACE);
setupMockPackageManager(false /* default */, true /* system */, false /* external calls */);
- mInCallController.bindToServices(mMockCall);
+ mInCallController.bindToServices(mMockCall, false);
ArgumentCaptor<Intent> bindIntentCaptor = ArgumentCaptor.forClass(Intent.class);
verify(mMockContext).bindServiceAsUser(
@@ -473,7 +482,7 @@
anyInt(), eq(mUserHandle))).thenReturn(true);
setupMockPackageManager(true /* default */, true /* system */, false /* external calls */);
- mInCallController.bindToServices(mMockCall);
+ mInCallController.bindToServices(mMockCall, false);
// Query for the different InCallServices
ArgumentCaptor<Intent> queryIntentCaptor = ArgumentCaptor.forClass(Intent.class);
@@ -535,7 +544,7 @@
setupMockPackageManager(true /* default */, true /* system */, false /* external calls */);
setupMockPackageManagerLocationPermission(SYS_PKG, false /* granted */);
- mInCallController.bindToServices(mMockCall);
+ mInCallController.bindToServices(mMockCall, false);
// Query for the different InCallServices
ArgumentCaptor<Intent> queryIntentCaptor = ArgumentCaptor.forClass(Intent.class);
@@ -596,7 +605,7 @@
setupMockPackageManager(true /* default */, true /* system */, false /* external calls */);
setupMockPackageManagerLocationPermission(SYS_PKG, false /* granted */);
- mInCallController.bindToServices(mMockCall);
+ mInCallController.bindToServices(mMockCall, false);
ArgumentCaptor<Intent> bindIntentCaptor = ArgumentCaptor.forClass(Intent.class);
verify(mMockContext, times(1)).bindServiceAsUser(
@@ -626,7 +635,7 @@
setupMockPackageManager(true /* default */, true /* system */, false /* external calls */);
setupMockPackageManagerLocationPermission(SYS_PKG, false /* granted */);
- mInCallController.bindToServices(mMockCall);
+ mInCallController.bindToServices(mMockCall, false);
ArgumentCaptor<Intent> bindIntentCaptor = ArgumentCaptor.forClass(Intent.class);
verify(mMockContext, times(1)).bindServiceAsUser(
@@ -658,7 +667,7 @@
setupMockPackageManager(true /* default */, true /* system */, false /* external calls */);
setupMockPackageManagerLocationPermission(SYS_PKG, false /* granted */);
- mInCallController.bindToServices(mMockCall);
+ mInCallController.bindToServices(mMockCall, false);
ArgumentCaptor<Intent> bindIntentCaptor = ArgumentCaptor.forClass(Intent.class);
verify(mMockContext, times(1)).bindServiceAsUser(
@@ -686,7 +695,7 @@
setupMockPackageManager(true /* default */, true /* system */, false /* external calls */);
setupMockPackageManagerLocationPermission(SYS_PKG, false /* granted */);
- mInCallController.bindToServices(mMockCall);
+ mInCallController.bindToServices(mMockCall, false);
ArgumentCaptor<Intent> bindIntentCaptor = ArgumentCaptor.forClass(Intent.class);
verify(mMockContext, times(1)).bindServiceAsUser(
@@ -735,7 +744,7 @@
setupMockPackageManager(true /* default */, true /* system */, false /* external calls */);
setupMockPackageManagerLocationPermission(SYS_PKG, false /* granted */);
- mInCallController.bindToServices(mMockCall);
+ mInCallController.bindToServices(mMockCall, false);
// Query for the different InCallServices
ArgumentCaptor<Intent> queryIntentCaptor = ArgumentCaptor.forClass(Intent.class);
@@ -817,7 +826,7 @@
.thenReturn(true);
setupMockPackageManager(true /* default */, true /* system */, false /* external calls */);
- mInCallController.bindToServices(mMockCall);
+ mInCallController.bindToServices(mMockCall, false);
// Query for the different InCallServices
ArgumentCaptor<Intent> queryIntentCaptor = ArgumentCaptor.forClass(Intent.class);
@@ -898,7 +907,7 @@
when(mDefaultDialerCache.getDefaultDialerApplication(CURRENT_USER_ID)).thenReturn(DEF_PKG);
setupMockPackageManager(true /* default */, true /* system */, false /* external calls */);
- mInCallController.bindToServices(mMockCall);
+ mInCallController.bindToServices(mMockCall, false);
ArgumentCaptor<Intent> bindIntentCaptor = ArgumentCaptor.forClass(Intent.class);
ArgumentCaptor<ServiceConnection> serviceConnectionCaptor =
@@ -945,7 +954,7 @@
mInCallController.handleCarModeChange(UiModeManager.DEFAULT_PRIORITY, CAR_PKG, true);
// Now bind; we should only bind to one app.
- mInCallController.bindToServices(mMockCall);
+ mInCallController.bindToServices(mMockCall, false);
// Bind InCallServices
ArgumentCaptor<Intent> bindIntentCaptor = ArgumentCaptor.forClass(Intent.class);
@@ -1027,7 +1036,7 @@
.thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DISABLED);
mInCallController.addCall(mMockCall);
- mInCallController.bindToServices(mMockCall);
+ mInCallController.bindToServices(mMockCall, false);
// There will be 4 calls for the various types of ICS.
verify(mMockPackageManager, times(4)).queryIntentServicesAsUser(
@@ -1195,7 +1204,7 @@
public void testBindToService_IncludeExternal() throws Exception {
setupMocks(true /* isExternalCall */);
setupMockPackageManager(true /* default */, true /* system */, true /* external calls */);
- mInCallController.bindToServices(mMockCall);
+ mInCallController.bindToServices(mMockCall, false);
// Query for the different InCallServices
ArgumentCaptor<Intent> queryIntentCaptor = ArgumentCaptor.forClass(Intent.class);
@@ -1246,7 +1255,7 @@
when(mMockCallsManager.getCalls()).thenReturn(Collections.singletonList(mMockCall));
setupMockPackageManager(true /* default */, true /* system */, false /* external calls */);
- mInCallController.bindToServices(mMockCall);
+ mInCallController.bindToServices(mMockCall, false);
ArgumentCaptor<Intent> bindIntentCaptor = ArgumentCaptor.forClass(Intent.class);
ArgumentCaptor<ServiceConnection> serviceConnectionCaptor =
@@ -1295,7 +1304,7 @@
mInCallController.handleCarModeChange(UiModeManager.DEFAULT_PRIORITY, CAR_PKG, true);
// Now bind; we should only bind to one app.
- mInCallController.bindToServices(mMockCall);
+ mInCallController.bindToServices(mMockCall, false);
// Bind InCallServices
ArgumentCaptor<Intent> bindIntentCaptor = ArgumentCaptor.forClass(Intent.class);
@@ -1314,7 +1323,7 @@
public void testNoBindToInvalidService_CarModeUI() throws Exception {
setupMocks(true /* isExternalCall */);
setupMockPackageManager(true /* default */, true /* system */, true /* external calls */);
- mInCallController.bindToServices(mMockCall);
+ mInCallController.bindToServices(mMockCall, false);
when(mMockPackageManager.checkPermission(
matches(Manifest.permission.CONTROL_INCALL_EXPERIENCE),
@@ -1366,7 +1375,7 @@
anyInt(), any(AttributionSource.class), nullable(String.class)));
// Now bind; we should bind to the system dialer and app op non ui app.
- mInCallController.bindToServices(mMockCall);
+ mInCallController.bindToServices(mMockCall, false);
// Bind InCallServices
ArgumentCaptor<Intent> bindIntentCaptor = ArgumentCaptor.forClass(Intent.class);
@@ -1410,7 +1419,7 @@
when(mDefaultDialerCache.getDefaultDialerApplication(CURRENT_USER_ID)).thenReturn(null);
// we should bind to only the non ui app.
- mInCallController.bindToServices(mMockCall);
+ mInCallController.bindToServices(mMockCall, false);
// Bind InCallServices
ArgumentCaptor<Intent> bindIntentCaptor = ArgumentCaptor.forClass(Intent.class);
@@ -1443,7 +1452,7 @@
matches(DEF_PKG))).thenReturn(PackageManager.PERMISSION_DENIED);
when(mMockCall.getName()).thenReturn("evil");
- mInCallController.bindToServices(mMockCall);
+ mInCallController.bindToServices(mMockCall, false);
// Bind InCallServices
ArgumentCaptor<Intent> bindIntentCaptor = ArgumentCaptor.forClass(Intent.class);
@@ -1480,7 +1489,7 @@
setupMocks(true /* isExternalCall */);
setupMockPackageManager(true /* default */, true /* system */, true /* external calls */);
// Bind to default dialer.
- mInCallController.bindToServices(mMockCall);
+ mInCallController.bindToServices(mMockCall, false);
// Uninstall an unrelated app.
mSystemStateListener.onPackageUninstalled("com.joe.stuff");
@@ -1504,7 +1513,7 @@
setupMocks(true /* isExternalCall */);
setupMockPackageManager(true /* default */, true /* system */, true /* external calls */);
// Bind to default dialer.
- mInCallController.bindToServices(mMockCall);
+ mInCallController.bindToServices(mMockCall, false);
// Enable car mode and enter car mode at default priority.
when(mMockSystemStateHelper.isCarModeOrProjectionActive()).thenReturn(true);
@@ -1572,7 +1581,7 @@
setupMockPackageManager(true /* default */, true /* nonui */, false /* appop_nonui */ ,
true /* system */, false /* external calls */,
false /* self mgd in default*/, false /* self mgd in car*/);
- mInCallController.bindToServices(mMockCall);
+ mInCallController.bindToServices(mMockCall, false);
ArgumentCaptor<Intent> bindIntentCaptor = ArgumentCaptor.forClass(Intent.class);
ArgumentCaptor<ServiceConnection> serviceConnectionCaptor =
@@ -1641,7 +1650,7 @@
// Bind; we should not bind to anything right now; the dialer does not support self
// managed calls.
- mInCallController.bindToServices(mMockCall);
+ mInCallController.bindToServices(mMockCall, false);
// Bind InCallServices; make sure no binding took place. InCallController handles not
// binding initially, but the rebind (see next test case) will always happen.
@@ -1680,7 +1689,7 @@
// Bind; we should not bind to anything right now; the dialer does not support self
// managed calls.
- mInCallController.bindToServices(mMockCall);
+ mInCallController.bindToServices(mMockCall, false);
// Bind InCallServices; make sure no binding took place.
verify(mMockContext, never()).bindServiceAsUser(
@@ -1782,7 +1791,7 @@
assertFalse(mUserHandle.equals(UserHandle.USER_CURRENT));
when(mMockUserInfo.isManagedProfile()).thenReturn(false);
- mInCallController.bindToServices(mMockCall);
+ mInCallController.bindToServices(mMockCall, false);
// Bind InCallService on UserHandle.CURRENT and not the user from the call (mUserHandle)
ArgumentCaptor<Intent> bindIntentCaptor = ArgumentCaptor.forClass(Intent.class);
@@ -1803,7 +1812,7 @@
when(mMockCall.getAssociatedUser()).thenReturn(testUser);
// Bind to ICS. The mapping should've been inserted with the testUser as the key.
- mInCallController.bindToServices(mMockCall);
+ mInCallController.bindToServices(mMockCall, false);
assertTrue(mInCallController.getInCallServiceConnections().containsKey(testUser));
// Set the target phone account. Simulates the flow when the user has chosen which sim to
@@ -1831,7 +1840,7 @@
when(mMockCall.isIncoming()).thenReturn(true);
// Bind to ICS. The mapping should've been inserted with the testUser as the key.
- mInCallController.bindToServices(mMockCall);
+ mInCallController.bindToServices(mMockCall, false);
assertTrue(mInCallController.getInCallServiceConnections().containsKey(testUser));
// Remove the call. This invokes getUserFromCall to remove the ICS mapping.
@@ -1904,7 +1913,7 @@
when(call.getId()).thenReturn("TC@" + id);
}
- private void setupMocksForWorkProfileTest() {
+ private void setupMocksForProfileTest() {
when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
when(mMockCallsManager.isInEmergencyCall()).thenReturn(false);
when(mMockChildUserCall.isIncoming()).thenReturn(false);
@@ -1920,9 +1929,9 @@
when(mMockUserInfo.getUserHandle()).thenReturn(mParentUserHandle);
when(mMockChildUserInfo.getUserHandle()).thenReturn(mChildUserHandle);
when(mMockUserInfo.isManagedProfile()).thenReturn(false);
- when(mMockChildUserInfo.isManagedProfile()).thenReturn(true);
+ when(mMockChildUserInfo.isManagedProfile()).thenReturn(false);
when(mMockChildUserCall.getAssociatedUser()).thenReturn(mChildUserHandle);
- when(mMockCallsManager.getCurrentUserHandle()).thenReturn(mChildUserHandle);
+ when(mMockCallsManager.getCurrentUserHandle()).thenReturn(mParentUserHandle);
when(mMockUserManager.getProfileParent(mChildUserHandle.getIdentifier())).thenReturn(
mMockUserInfo);
when(mMockUserManager.getProfileParent(mChildUserHandle)).thenReturn(mParentUserHandle);
@@ -1930,14 +1939,14 @@
mMockUserInfo);
when(mMockUserManager.getUserInfo(eq(mChildUserHandle.getIdentifier()))).thenReturn(
mMockChildUserInfo);
- when(mMockUserManager.isManagedProfile(mChildUserHandle.getIdentifier())).thenReturn(true);
when(mMockUserManager.isManagedProfile(mParentUserHandle.getIdentifier())).thenReturn(
false);
+ when(mTelephonyFeatureFlags.workProfileApiSplit()).thenReturn(true);
}
@Test
- public void testManagedProfileCallQueriesIcsUsingParentUserToo() throws Exception {
- setupMocksForWorkProfileTest();
+ public void testProfileCallQueriesIcsUsingParentUserToo() throws Exception {
+ setupMocksForProfileTest();
setupMockPackageManager(true /* default */, true /* system */, false /* external calls */);
setupMockPackageManager(true /* default */,
true /*useNonUiInCalls*/, true /*useAppOpNonUiInCalls*/,
@@ -1946,9 +1955,8 @@
true /*includeSelfManagedCallsInCarModeDialer*/,
true /*includeSelfManagedCallsInNonUi*/);
- //pass in call by child/work-profileuser
- mInCallController.bindToServices(mMockChildUserCall);
-
+ //pass in call by child/profile user
+ mInCallController.bindToServices(mMockChildUserCall, false);
// Verify that queryIntentServicesAsUser is also called with parent handle
// Query for the different InCallServices
ArgumentCaptor<Integer> userIdCaptor = ArgumentCaptor.forClass(Integer.class);
@@ -1966,6 +1974,32 @@
userIds.contains(mParentUserHandle.getIdentifier()));
}
+ @Test
+ public void testSeparatelyBluetoothService() {
+ Intent expectedIntent = new Intent(InCallService.SERVICE_INTERFACE);
+ expectedIntent.setPackage(mDefaultDialerCache.getBTInCallServicePackage());
+ LinkedList<ResolveInfo> resolveInfo = new LinkedList<ResolveInfo>();
+ resolveInfo.add(getBluetoothResolveinfo());
+ when(mFeatureFlags.separatelyBindToBtIncallService()).thenReturn(true);
+ when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+ doAnswer(invocation -> {
+ Object[] args = invocation.getArguments();
+ LinkedList<ResolveInfo> resolveInfo1 = new LinkedList<ResolveInfo>();
+ Intent intent = (Intent) args[0];
+ if (intent.getAction().equals(InCallService.SERVICE_INTERFACE)) {
+ resolveInfo1.add(getBluetoothResolveinfo());
+ }
+ return resolveInfo1;
+ }).when(mMockPackageManager).queryIntentServicesAsUser(any(Intent.class), anyInt(),
+ anyInt());
+
+ mInCallController.bindToBTService(mMockCall);
+
+ ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
+ verify(mMockContext).bindServiceAsUser(captor.capture(), any(ServiceConnection.class),
+ anyInt(), any(UserHandle.class));
+ }
+
private void setupMocks(boolean isExternalCall) {
setupMocks(isExternalCall, false /* isSelfManagedCall */);
}
@@ -2090,6 +2124,18 @@
}};
}
+ private ResolveInfo getBluetoothResolveinfo() {
+ return new ResolveInfo() {{
+ serviceInfo = new ServiceInfo();
+ serviceInfo.packageName = BT_PKG;
+ serviceInfo.name = BT_CLS;
+ serviceInfo.applicationInfo = new ApplicationInfo();
+ serviceInfo.applicationInfo.uid = BT_UID;
+ serviceInfo.enabled = true;
+ serviceInfo.permission = Manifest.permission.BIND_INCALL_SERVICE;
+ }};
+ }
+
private void setupMockPackageManager(final boolean useDefaultDialer,
final boolean useSystemDialer, final boolean includeExternalCalls) {
setupMockPackageManager(useDefaultDialer, false, false, useSystemDialer, includeExternalCalls,
diff --git a/tests/src/com/android/server/telecom/tests/PhoneAccountRegistrarTest.java b/tests/src/com/android/server/telecom/tests/PhoneAccountRegistrarTest.java
index 2d10f40..0ce5836 100644
--- a/tests/src/com/android/server/telecom/tests/PhoneAccountRegistrarTest.java
+++ b/tests/src/com/android/server/telecom/tests/PhoneAccountRegistrarTest.java
@@ -26,7 +26,9 @@
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
@@ -62,6 +64,7 @@
import androidx.test.filters.SmallTest;
import com.android.internal.telecom.IConnectionService;
+import com.android.internal.telephony.flags.FeatureFlags;
import com.android.internal.util.FastXmlSerializer;
import com.android.server.telecom.AppLabelProxy;
import com.android.server.telecom.DefaultDialerCache;
@@ -90,6 +93,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -115,7 +119,7 @@
@Mock private TelecomManager mTelecomManager;
@Mock private DefaultDialerCache mDefaultDialerCache;
@Mock private AppLabelProxy mAppLabelProxy;
- @Mock private com.android.internal.telephony.flags.FeatureFlags mTelephonyFeatureFlags;
+ @Mock private FeatureFlags mTelephonyFeatureFlags;
@Override
@Before
@@ -155,12 +159,12 @@
public void testPhoneAccountHandle() throws Exception {
PhoneAccountHandle input = new PhoneAccountHandle(new ComponentName("pkg0", "cls0"), "id0");
PhoneAccountHandle result = roundTripXml(this, input,
- PhoneAccountRegistrar.sPhoneAccountHandleXml, mContext);
+ PhoneAccountRegistrar.sPhoneAccountHandleXml, mContext, mTelephonyFeatureFlags);
assertPhoneAccountHandleEquals(input, result);
PhoneAccountHandle inputN = new PhoneAccountHandle(new ComponentName("pkg0", "cls0"), null);
PhoneAccountHandle resultN = roundTripXml(this, inputN,
- PhoneAccountRegistrar.sPhoneAccountHandleXml, mContext);
+ PhoneAccountRegistrar.sPhoneAccountHandleXml, mContext, mTelephonyFeatureFlags);
Log.i(this, "inputN = %s, resultN = %s", inputN, resultN);
assertPhoneAccountHandleEquals(inputN, resultN);
}
@@ -183,7 +187,112 @@
.setIsEnabled(true)
.build();
PhoneAccount result = roundTripXml(this, input, PhoneAccountRegistrar.sPhoneAccountXml,
- mContext);
+ mContext, mTelephonyFeatureFlags);
+
+ assertPhoneAccountEquals(input, result);
+ }
+
+ @MediumTest
+ @Test
+ public void testPhoneAccountParsing_simultaneousCallingRestriction() throws Exception {
+ doReturn(true).when(mTelephonyFeatureFlags).simultaneousCallingIndications();
+ // workaround: UserManager converts the user to a serial and back, we need to mock this
+ // behavior, unfortunately: USER_HANDLE_10 <-> 10L
+ UserManager userManager = UserManager.get(mContext);
+ doReturn(10L).when(userManager).getSerialNumberForUser(eq(USER_HANDLE_10));
+ doReturn(USER_HANDLE_10).when(userManager).getUserForSerialNumber(eq(10L));
+ Bundle testBundle = new Bundle();
+ testBundle.putInt("EXTRA_INT_1", 1);
+ testBundle.putInt("EXTRA_INT_100", 100);
+ testBundle.putBoolean("EXTRA_BOOL_TRUE", true);
+ testBundle.putBoolean("EXTRA_BOOL_FALSE", false);
+ testBundle.putString("EXTRA_STR1", "Hello");
+ testBundle.putString("EXTRA_STR2", "There");
+
+ Set<PhoneAccountHandle> restriction = new HashSet<>(10);
+ for (int i = 0; i < 10; i++) {
+ restriction.add(makeQuickAccountHandleForUser("id" + i, USER_HANDLE_10));
+ }
+
+ PhoneAccount input = makeQuickAccountBuilder("id0", 0, USER_HANDLE_10)
+ .addSupportedUriScheme(PhoneAccount.SCHEME_TEL)
+ .addSupportedUriScheme(PhoneAccount.SCHEME_VOICEMAIL)
+ .setExtras(testBundle)
+ .setIsEnabled(true)
+ .setSimultaneousCallingRestriction(restriction)
+ .build();
+ PhoneAccount result = roundTripXml(this, input, PhoneAccountRegistrar.sPhoneAccountXml,
+ mContext, mTelephonyFeatureFlags);
+
+ assertPhoneAccountEquals(input, result);
+ }
+
+ @MediumTest
+ @Test
+ public void testPhoneAccountParsing_simultaneousCallingRestrictionOnOffFlag() throws Exception {
+ // Start the test with the flag on
+ doReturn(true).when(mTelephonyFeatureFlags).simultaneousCallingIndications();
+ // workaround: UserManager converts the user to a serial and back, we need to mock this
+ // behavior, unfortunately: USER_HANDLE_10 <-> 10L
+ UserManager userManager = UserManager.get(mContext);
+ doReturn(10L).when(userManager).getSerialNumberForUser(eq(USER_HANDLE_10));
+ doReturn(USER_HANDLE_10).when(userManager).getUserForSerialNumber(eq(10L));
+ Bundle testBundle = new Bundle();
+ testBundle.putInt("EXTRA_INT_1", 1);
+ testBundle.putInt("EXTRA_INT_100", 100);
+ testBundle.putBoolean("EXTRA_BOOL_TRUE", true);
+ testBundle.putBoolean("EXTRA_BOOL_FALSE", false);
+ testBundle.putString("EXTRA_STR1", "Hello");
+ testBundle.putString("EXTRA_STR2", "There");
+
+ Set<PhoneAccountHandle> restriction = new HashSet<>(10);
+ for (int i = 0; i < 10; i++) {
+ restriction.add(makeQuickAccountHandleForUser("id" + i, USER_HANDLE_10));
+ }
+
+ PhoneAccount input = makeQuickAccountBuilder("id0", 0, USER_HANDLE_10)
+ .addSupportedUriScheme(PhoneAccount.SCHEME_TEL)
+ .addSupportedUriScheme(PhoneAccount.SCHEME_VOICEMAIL)
+ .setExtras(testBundle)
+ .setIsEnabled(true)
+ .setSimultaneousCallingRestriction(restriction)
+ .build();
+ byte[] xmlData = toXml(input, PhoneAccountRegistrar.sPhoneAccountXml, mContext,
+ mTelephonyFeatureFlags);
+ // Simulate turning off the flag after reboot
+ doReturn(false).when(mTelephonyFeatureFlags).simultaneousCallingIndications();
+ PhoneAccount result = fromXml(xmlData, PhoneAccountRegistrar.sPhoneAccountXml, mContext,
+ mTelephonyFeatureFlags);
+
+ assertNotNull(result);
+ assertFalse(result.hasSimultaneousCallingRestriction());
+ }
+
+ @MediumTest
+ @Test
+ public void testPhoneAccountParsing_simultaneousCallingRestrictionOffOnFlag() throws Exception {
+ // Start the test with the flag on
+ doReturn(false).when(mTelephonyFeatureFlags).simultaneousCallingIndications();
+ Bundle testBundle = new Bundle();
+ testBundle.putInt("EXTRA_INT_1", 1);
+ testBundle.putInt("EXTRA_INT_100", 100);
+ testBundle.putBoolean("EXTRA_BOOL_TRUE", true);
+ testBundle.putBoolean("EXTRA_BOOL_FALSE", false);
+ testBundle.putString("EXTRA_STR1", "Hello");
+ testBundle.putString("EXTRA_STR2", "There");
+
+ PhoneAccount input = makeQuickAccountBuilder("id0", 0, USER_HANDLE_10)
+ .addSupportedUriScheme(PhoneAccount.SCHEME_TEL)
+ .addSupportedUriScheme(PhoneAccount.SCHEME_VOICEMAIL)
+ .setExtras(testBundle)
+ .setIsEnabled(true)
+ .build();
+ byte[] xmlData = toXml(input, PhoneAccountRegistrar.sPhoneAccountXml, mContext,
+ mTelephonyFeatureFlags);
+ // Simulate turning on the flag after reboot
+ doReturn(true).when(mTelephonyFeatureFlags).simultaneousCallingIndications();
+ PhoneAccount result = fromXml(xmlData, PhoneAccountRegistrar.sPhoneAccountXml, mContext,
+ mTelephonyFeatureFlags);
assertPhoneAccountEquals(input, result);
}
@@ -260,7 +369,8 @@
when(UserManager.get(mContext).getUserForSerialNumber(0L))
.thenReturn(input.userHandle);
DefaultPhoneAccountHandle result = roundTripXml(this, input,
- PhoneAccountRegistrar.sDefaultPhoneAcountHandleXml, mContext);
+ PhoneAccountRegistrar.sDefaultPhoneAccountHandleXml, mContext,
+ mTelephonyFeatureFlags);
assertDefaultPhoneAccountHandleEquals(input, result);
}
@@ -290,7 +400,7 @@
.setExtras(testBundle)
.build();
PhoneAccount result = roundTripXml(this, input, PhoneAccountRegistrar.sPhoneAccountXml,
- mContext);
+ mContext, mTelephonyFeatureFlags);
Bundle extras = result.getExtras();
assertFalse(extras.keySet().contains("EXTRA_STR2"));
@@ -304,8 +414,7 @@
public void testState() throws Exception {
PhoneAccountRegistrar.State input = makeQuickState();
PhoneAccountRegistrar.State result = roundTripXml(this, input,
- PhoneAccountRegistrar.sStateXml,
- mContext);
+ PhoneAccountRegistrar.sStateXml, mContext, mTelephonyFeatureFlags);
assertStateEquals(input, result);
}
@@ -1625,6 +1734,107 @@
}
/**
+ * Ensure an IllegalArgumentException is thrown when adding too many PhoneAccountHandles to
+ * a PhoneAccount.
+ */
+ @Test
+ public void testLimitOnSimultaneousCallingRestriction_tooManyElements() throws Exception {
+ doReturn(true).when(mTelephonyFeatureFlags).simultaneousCallingIndications();
+ mComponentContextFixture.addConnectionService(makeQuickConnectionServiceComponentName(),
+ Mockito.mock(IConnectionService.class));
+ Set<PhoneAccountHandle> tooManyElements = new HashSet<>(11);
+ for (int i = 0; i < 11; i++) {
+ tooManyElements.add(makeQuickAccountHandle(TEST_ID + i));
+ }
+ PhoneAccount tooManyRestrictionsPA = new PhoneAccount.Builder(
+ makeQuickAccountHandle(TEST_ID), TEST_LABEL)
+ .setSimultaneousCallingRestriction(tooManyElements)
+ .build();
+ try {
+ mRegistrar.registerPhoneAccount(tooManyRestrictionsPA);
+ fail("should have hit registrations exception in "
+ + "enforceSimultaneousCallingRestrictionLimit");
+ } catch (IllegalArgumentException e) {
+ // pass test
+ }
+ }
+
+ /**
+ * Ensure an IllegalArgumentException is thrown when adding a PhoneAccountHandle where the
+ * package name field is too large.
+ */
+ @Test
+ public void testLimitOnSimultaneousCallingRestriction_InvalidPackageName() throws Exception {
+ doReturn(true).when(mTelephonyFeatureFlags).simultaneousCallingIndications();
+ mComponentContextFixture.addConnectionService(makeQuickConnectionServiceComponentName(),
+ Mockito.mock(IConnectionService.class));
+ Set<PhoneAccountHandle> invalidElement = new HashSet<>(1);
+ invalidElement.add(new PhoneAccountHandle(new ComponentName(INVALID_STR, "Class"),
+ TEST_ID));
+ PhoneAccount invalidRestrictionPA = new PhoneAccount.Builder(
+ makeQuickAccountHandle(TEST_ID), TEST_LABEL)
+ .setSimultaneousCallingRestriction(invalidElement)
+ .build();
+ try {
+ mRegistrar.registerPhoneAccount(invalidRestrictionPA);
+ fail("should have hit package name size limit exception in "
+ + "enforceSimultaneousCallingRestrictionLimit");
+ } catch (IllegalArgumentException e) {
+ // pass test
+ }
+ }
+
+ /**
+ * Ensure an IllegalArgumentException is thrown when adding a PhoneAccountHandle where the
+ * class name field is too large.
+ */
+ @Test
+ public void testLimitOnSimultaneousCallingRestriction_InvalidClassName() throws Exception {
+ doReturn(true).when(mTelephonyFeatureFlags).simultaneousCallingIndications();
+ mComponentContextFixture.addConnectionService(makeQuickConnectionServiceComponentName(),
+ Mockito.mock(IConnectionService.class));
+ Set<PhoneAccountHandle> invalidElement = new HashSet<>(1);
+ invalidElement.add(new PhoneAccountHandle(new ComponentName("pkg", INVALID_STR),
+ TEST_ID));
+ PhoneAccount invalidRestrictionPA = new PhoneAccount.Builder(
+ makeQuickAccountHandle(TEST_ID), TEST_LABEL)
+ .setSimultaneousCallingRestriction(invalidElement)
+ .build();
+ try {
+ mRegistrar.registerPhoneAccount(invalidRestrictionPA);
+ fail("should have hit class name size limit exception in "
+ + "enforceSimultaneousCallingRestrictionLimit");
+ } catch (IllegalArgumentException e) {
+ // pass test
+ }
+ }
+
+ /**
+ * Ensure an IllegalArgumentException is thrown when adding a PhoneAccountHandle where the
+ * ID field is too large.
+ */
+ @Test
+ public void testLimitOnSimultaneousCallingRestriction_InvalidIdSize() throws Exception {
+ doReturn(true).when(mTelephonyFeatureFlags).simultaneousCallingIndications();
+ mComponentContextFixture.addConnectionService(makeQuickConnectionServiceComponentName(),
+ Mockito.mock(IConnectionService.class));
+ Set<PhoneAccountHandle> invalidIdElement = new HashSet<>(1);
+ invalidIdElement.add(new PhoneAccountHandle(makeQuickConnectionServiceComponentName(),
+ INVALID_STR));
+ PhoneAccount invalidRestrictionPA = new PhoneAccount.Builder(
+ makeQuickAccountHandle(TEST_ID), TEST_LABEL)
+ .setSimultaneousCallingRestriction(invalidIdElement)
+ .build();
+ try {
+ mRegistrar.registerPhoneAccount(invalidRestrictionPA);
+ fail("should have hit ID size limit exception in "
+ + "enforceSimultaneousCallingRestrictionLimit");
+ } catch (IllegalArgumentException e) {
+ // pass test
+ }
+ }
+
+ /**
* Ensure an IllegalArgumentException is thrown when adding an address over the limit
*/
@Test
@@ -1869,35 +2079,41 @@
Object self,
T input,
PhoneAccountRegistrar.XmlSerialization<T> xml,
- Context context)
+ Context context,
+ FeatureFlags telephonyFeatureFlags)
throws Exception {
Log.d(self, "Input = %s", input);
- byte[] data;
- {
- XmlSerializer serializer = new FastXmlSerializer();
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
- serializer.setOutput(new BufferedOutputStream(baos), "utf-8");
- xml.writeToXml(input, serializer, context);
- serializer.flush();
- data = baos.toByteArray();
- }
+ byte[] data = toXml(input, xml, context, telephonyFeatureFlags);
Log.i(self, "====== XML data ======\n%s", new String(data));
- T result = null;
- {
- XmlPullParser parser = Xml.newPullParser();
- parser.setInput(new BufferedInputStream(new ByteArrayInputStream(data)), null);
- parser.nextTag();
- result = xml.readFromXml(parser, MAX_VERSION, context);
- }
+ T result = fromXml(data, xml, context, telephonyFeatureFlags);
Log.i(self, "result = " + result);
return result;
}
+ private static <T> byte[] toXml(T input, PhoneAccountRegistrar.XmlSerialization<T> xml,
+ Context context, FeatureFlags telephonyFeatureFlags) throws Exception {
+ XmlSerializer serializer = new FastXmlSerializer();
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ serializer.setOutput(new BufferedOutputStream(baos), "utf-8");
+ xml.writeToXml(input, serializer, context, telephonyFeatureFlags);
+ serializer.flush();
+ return baos.toByteArray();
+ }
+
+ private static <T> T fromXml(byte[] data, PhoneAccountRegistrar.XmlSerialization<T> xml,
+ Context context, FeatureFlags telephonyFeatureFlags) throws Exception {
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(new BufferedInputStream(new ByteArrayInputStream(data)), null);
+ parser.nextTag();
+ return xml.readFromXml(parser, MAX_VERSION, context, telephonyFeatureFlags);
+
+ }
+
private static void assertPhoneAccountHandleEquals(PhoneAccountHandle a, PhoneAccountHandle b) {
if (a != b) {
assertEquals(
@@ -1946,6 +2162,12 @@
assertEquals(a.getSupportedUriSchemes(), b.getSupportedUriSchemes());
assertBundlesEqual(a.getExtras(), b.getExtras());
assertEquals(a.isEnabled(), b.isEnabled());
+ assertEquals(a.hasSimultaneousCallingRestriction(),
+ b.hasSimultaneousCallingRestriction());
+ if (a.hasSimultaneousCallingRestriction()) {
+ assertEquals(a.getSimultaneousCallingRestriction(),
+ b.getSimultaneousCallingRestriction());
+ }
} else {
fail("Phone accounts not equal: " + a + ", " + b);
}
diff --git a/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java b/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java
index 24b23af..a36e8ea 100644
--- a/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java
+++ b/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java
@@ -1167,7 +1167,7 @@
verify(mFakePhoneAccountRegistrar).getPhoneAccount(
TEL_PA_HANDLE_16, TEL_PA_HANDLE_16.getUserHandle());
- verify(mInCallController, never()).bindToServices(any());
+ verify(mInCallController, never()).bindToServices(any(), anyBoolean());
addCallTestHelper(TelecomManager.ACTION_INCOMING_CALL,
CallIntentProcessor.KEY_IS_INCOMING_CALL, extras,
TEL_PA_HANDLE_16, false);
@@ -1189,7 +1189,7 @@
mTSIBinder.addNewIncomingCall(TEL_PA_HANDLE_16, extras, CALLING_PACKAGE);
- verify(mInCallController, never()).bindToServices(null);
+ verify(mInCallController, never()).bindToServices(eq(null), anyBoolean());
}
@SmallTest
@@ -1207,7 +1207,7 @@
mTSIBinder.addNewIncomingCall(TEL_PA_HANDLE_16, extras, CALLING_PACKAGE);
- verify(mInCallController).bindToServices(null);
+ verify(mInCallController).bindToServices(eq(null), anyBoolean());
}
@SmallTest
@@ -1225,7 +1225,7 @@
mTSIBinder.addNewIncomingCall(TEL_PA_HANDLE_16, extras, CALLING_PACKAGE);
- verify(mInCallController, never()).bindToServices(null);
+ verify(mInCallController, never()).bindToServices(eq(null), anyBoolean());
}
@SmallTest
@@ -1244,7 +1244,7 @@
mTSIBinder.addNewIncomingCall(TEL_PA_HANDLE_16, extras, CALLING_PACKAGE);
- verify(mInCallController, never()).bindToServices(null);
+ verify(mInCallController, never()).bindToServices(eq(null), anyBoolean());
}