蓝牙

Android 提供完整的蓝牙实现,支持许多常见的车载蓝牙配置文件。还有许多增强功能改进了与其他设备和服务之间的性能和体验。

蓝牙连接管理

在 Android 中, CarBluetoothService 维护当前用户的蓝牙设备以及 IVI 的每个配置文件连接的优先级列表。设备按照定义的优先级顺序连接到配置文件。何时启用、停用配置文件以及何时将设备连接到配置文件由默认连接策略驱动,如果需要,可以使用 资源叠加层 覆盖该策略。

配置车载连接管理

停用默认手机策略

Android 蓝牙堆栈维护手机连接策略,该策略默认处于启用状态。必须在您的设备上停用此策略,以免其与 CarBluetoothService 中预期的车载策略发生冲突。虽然 Car 产品叠加层应为您处理此问题,但您可以在 资源叠加层 中通过在 MAXIMUM_CONNECTED_DEVICES 中的 /packages/apps/Bluetooth/res/values/config.xml 中将 enable_phone_policy 设置为 false 来停用手机策略。

使用默认车载策略

CarBluetoothService 维护默认配置文件权限。已知设备及其配置文件重新连接优先级的列表位于 service/src/com/android/car/BluetoothProfileDeviceManager.java 中。

此外,蓝牙连接管理策略可以在 service/src/com/android/car/BluetoothDeviceConnectionPolicy.java 中找到。默认情况下,此策略定义了蓝牙应何时连接和断开与已配对设备的连接。它还管理车载特定情况,以确定何时应打开和关闭适配器。

创建您自己的自定义车载连接管理策略

如果默认车载策略不足以满足您的需求,也可以停用它,而改用您自己的自定义策略。您的自定义策略至少负责确定何时启用和停用蓝牙适配器以及何时连接设备。可以使用各种事件来启用/停用蓝牙适配器并启动设备连接,包括由于特定车载属性更改而触发的事件。

停用默认车载策略

首先,要使用自定义策略,必须通过在 资源叠加层 中将 useDefaultBluetoothConnectionPolicy 设置为 false 来停用默认车载策略。此资源最初定义为 MAXIMUM_CONNECTED_DEVICES 的一部分,位于 packages/services/Car/service/res/values/config.xml 中。

启用和停用蓝牙适配器

您的策略的核心功能之一是在适当的时候打开和关闭蓝牙适配器。您可以使用 BluetoothAdapter.enable()BluetoothAdapter.disable() 框架 API 来启用和停用适配器。这些调用应尊重用户通过“设置”或任何其他方式选择的持久状态。一种方法如下:

/**
 * Turn on the Bluetooth adapter.
 */
private void enableBluetooth() {
    BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
    if (bluetoothAdapter == null) {
        return;
    }
    bluetoothAdapter.enable();
}

/**
 * Turn off the Bluetooth adapter.
 */
private void disableBluetooth() {
    BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
    if (bluetoothAdapter == null) {
        return;
    }
    // Will shut down _without_ persisting the off state as the desired state
    // of the Bluetooth adapter for next start up. This does nothing if the adapter
    // is already off, keeping the existing saved desired state for next reboot.
    bluetoothAdapter.disable(false);
}

确定何时打开和关闭蓝牙适配器

通过您的自定义策略,您可以自由确定哪些事件指示启用和停用适配器的最佳时间。一种方法是使用 CarPowerManager 中的电源状态 MAXIMUM_CONNECTED_DEVICES

private final CarPowerStateListenerWithCompletion mCarPowerStateListener =
        new CarPowerStateListenerWithCompletion() {
    @Override
    public void onStateChanged(int state, CompletableFuture<Void> future) {
        if (state == CarPowerManager.CarPowerStateListener.ON) {
            if (isBluetoothPersistedOn()) {
                enableBluetooth();
            }
            return;
        }

        // "Shutdown Prepare" is when the user perceives the car as off
        // This is a good time to turn off Bluetooth
        if (state == CarPowerManager.CarPowerStateListener.SHUTDOWN_PREPARE) {
            disableBluetooth();

            // Let CarPowerManagerService know we're ready to shut down
            if (future != null) {
                future.complete(null);
            }
            return;
        }
    }
};

确定何时连接设备

同样,当您确定应触发设备连接开始的事件时, CarBluetoothManager 提供了 connectDevices() API 调用,该调用将继续根据为每个蓝牙配置文件定义的优先级列表连接设备。

您可能希望执行此操作的一个示例是每当蓝牙适配器打开时

private class BluetoothBroadcastReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
        if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(action)) {
            int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1);
            if (state == BluetoothAdapter.STATE_ON) {
                // mContext should be your app's context
                Car car = Car.createCar(mContext);
                CarBluetoothManager carBluetoothManager =
                        (CarBluetoothManager) car.getCarManager(Car.BLUETOOTH_SERVICE);
                carBluetoothManager.connectDevices();
            }
        }
    }
}

验证车载连接管理

验证连接策略行为的最简单方法是在 IVI 上启用蓝牙,并验证它是否自动按正确的顺序连接到正确的设备。您可以通过设置界面或使用以下 adb 命令切换蓝牙适配器:

adb shell su u$(adb shell am get-current-user)_system svc bluetooth disable
adb shell su u$(adb shell am get-current-user)_system svc bluetooth enable

此外,以下命令的输出可用于查看与蓝牙连接相关的调试信息:

adb shell dumpsys car_service

最后,如果您构建了自己的车载策略,则验证任何自定义连接行为都需要控制您选择触发设备连接的事件。

车载蓝牙配置文件

在 Android 中,IVI 可以支持多个设备通过蓝牙同时连接。多设备蓝牙电话服务允许用户同时连接单独的设备(例如个人电话和工作电话),并从任一设备进行免提通话。

连接限制由每个单独的蓝牙配置文件强制执行,通常在配置文件服务本身的实现中强制执行。默认情况下, CarBluetoothService 不会对允许的最大连接设备数量进行进一步判断。

免提配置文件

蓝牙免提配置文件 (HFP) 允许车辆通过连接的远程设备拨打和接听电话。 TelecomManager 为每个设备连接注册一个单独的电话帐号,该帐号向 IVI 应用宣传任何可用的电话帐号。

IVI 可以通过 HFP 连接到多个设备。MAX_STATE_MACHINES_POSSIBLE MAXIMUM_CONNECTED_DEVICES HeadsetClientService 中定义了同时 HFP 连接的最大数量。

当用户从设备拨打或接听电话时,相应的电话帐号会创建一个 HfpClientConnection 对象。拨号器应用与 HfpClientConnection 对象交互以管理通话功能,例如接听电话或挂断电话。

应注意的是,默认拨号器应用不支持多个同时连接的 HFP 设备。为了实现多设备 HFP,需要进行自定义以允许用户在拨打电话时选择要使用的设备帐号。然后,该应用使用正确的帐号调用 telecomManager.placeCall。您需要验证其他多设备功能是否也按预期工作。

验证多设备 HFP

要检查多设备连接是否通过蓝牙正常工作:

  1. 使用蓝牙,将设备连接到 IVI 并从设备流式传输音频。
  2. 通过蓝牙将两部手机连接到 IVI。
  3. 选择一部手机。直接从手机拨打一个外拨电话,并使用 IVI 拨打一个外拨电话。
    1. 两次都验证流式传输的音频暂停,并且电话音频通过 IVI 连接的扬声器播放。
  4. 使用同一部手机,直接在手机上接听一个来电,并使用 IVI 接听一个来电。
    1. 两次都验证流式传输的音频暂停,并且电话音频通过 IVI 连接的扬声器播放。
  5. 使用另一部已连接的手机重复步骤 3 和 4。

紧急呼叫

拨打紧急呼叫的能力是车载电话和蓝牙功能的重要方面。可以通过多种方式从 IVI 发起紧急呼叫,包括:

  • 独立 eCall 解决方案
  • 集成到 IVI 中的 eCall 解决方案
  • 在没有内置系统的情况下,依赖连接的蓝牙手机

连接紧急呼叫

虽然 eCall 设备是安全关键设备,但目前尚未集成到 Android 中。可以使用 ConnectionService 通过 Android 公开紧急呼叫功能,这也具有引入紧急呼叫辅助功能选项的优势。要了解详情,请参阅构建通话应用

以下是如何建立紧急 ConnectionService 的示例:

public class YourEmergencyConnectionService extends ConnectionService {

    @Override
    public Connection onCreateOutgoingConnection(
            PhoneAccountHandle connectionManagerAccount,
            ConnectionRequest request) {
        // Your equipment specific procedure to make ecall
        // ...
    }

    private void onYourEcallEquipmentReady() {

        PhoneAccountHandle handle =
            new PhoneAccountHandle(new ComponentName(context, YourEmergencyConnectionService),
                    YourEmergencyConnectionId);
        PhoneAccount account =
            new PhoneAccount.Builder(handle, eCallOnlyAccount)
            .setSupportedUriSchemes(Arrays.asList(PhoneAccount.SCHEME_TEL))
            .setCapabilities(PhoneAccount.CAPABILITY_PLACE_EMERGENCY_CALLS
                    | PhoneAccount.CAPABILITY_MULTI_USER)
            .build():
        mTelecomManager.registerPhoneAccount(account);
        mTelecomManager.enablePhoneAccount(account.getAccountHandle(), true);
    }
}

为紧急呼叫启用蓝牙

Android 10 之前的紧急呼叫涉及直接从手机拨号并在可用时调用特殊设备(例如,在检测到危险或用户操作时自动触发)。在 Android 10 及更高版本中,车载拨号器可以直接拨打紧急号码,前提是 apps/Bluetooth/res/values/config.xml 中的 MAXIMUM_CONNECTED_DEVICES

<!-- 用于支持通过 hfp 客户端连接服务进行紧急呼叫 --> <bool name=”hfp_client_connection_service_support_emergency_call”>true</bool>

通过以这种方式实现紧急呼叫,其他应用(例如语音识别)也可以拨打紧急号码。

电话簿访问配置文件

蓝牙电话簿访问配置文件 (PBAP) 从连接的远程设备下载联系人和通话记录。PBAP 维护联系人的聚合可搜索列表,该列表由 PBAP 客户端状态机更新。每个连接的设备都与一个单独的 PBAP 客户端状态机交互,从而在拨打电话时将联系人与正确的设备关联。

PBAP 是单向的,因此需要 IVI 实例化与 PbapClientService 的任何连接。 MAXIMUM_CONNECTED_DEVICES 定义了 IVI 允许的同时 PBAP 设备连接的最大数量。PBAP 客户端将每个连接设备的联系人存储在 联系人提供程序 中,然后应用可以访问该提供程序以获取每个设备的电话簿。

此外,配置文件连接必须经过 IVI 和移动设备的授权,才能建立连接。当 PBAP 客户端断开连接时,内部数据库会删除与先前连接的设备关联的所有联系人和通话记录。

消息访问配置文件

蓝牙消息访问配置文件 (MAP) 允许车辆通过连接的远程设备发送和接收短信。目前,消息不会本地存储在 IVI 上。相反,每当连接的远程设备收到消息时,IVI 都会接收并解析该消息,并在 Intent 实例中广播其内容,然后应用可以接收该实例。

为了连接到移动设备以发送和接收消息,IVI 必须启动 MAP 连接。 MapClientService 中的 MAXIMUM_CONNECTED_DEVICES 定义了 IVI 允许的同时 MAP 设备连接的最大数量。每次连接都必须经过 IVI 和移动设备的授权,然后才能传输消息。

高级音频分发配置文件

蓝牙高级音频分发配置文件 (A2DP) 允许车辆接收来自连接的远程设备的音频流。

与其他配置文件不同,连接的 A2DP 设备的最大数量在原生堆栈中而不是在 Java 中强制执行。该值当前硬编码为 1,使用 packages/modules/Bluetooth/system/btif/src/btif_av.cc 中的 kDefaultMaxConnectedAudioDevices 变量。

音频/视频远程控制配置文件

蓝牙音频/视频远程控制配置文件 (AVRCP) 允许车辆控制和浏览连接的远程设备上的媒体播放器。由于 IVI 充当 AVRCP 控制器,因此任何影响音频播放的触发控制都依赖于与目标设备的 A2DP 连接。

为了让 Android 手机上的特定媒体播放器可供 IVI 通过 AVRCP 浏览,手机上的媒体应用必须提供 MediaBrowserService 并允许 com.android.bluetooth 访问该服务。构建媒体浏览器服务详细介绍了如何执行此操作。