蓝牙

Android 提供了蓝牙的完整实现,可支持多种常见的车载蓝牙配置文件。其多项增强功能还可优化其他设备和服务的性能和使用体验。

蓝牙连接管理

Android 中的 CarBluetoothService 会维护当前用户的蓝牙设备以及连接到 IVI 的每个配置文件的优先级列表。设备按指定的优先级顺序连接到配置文件。何时启用、停用配置文件以及将设备连接到配置文件取决于默认的连接政策,如有需要,可使用资源叠加层覆盖该政策。

配置 Automotive 连接管理

停用默认的电话政策

Android 蓝牙堆栈会维护手机上默认启用的连接政策。您必须在设备上停用该政策,以免与 CarBluetoothService 中的 Automotive 政策冲突。虽然汽车产品叠加层应该会实现这一点,您也可以在 /packages/apps/Bluetooth/res/values/config.xml 中将 enable_phone_policy 设置为 false 来在资源叠加层中停用该电话政策。

使用默认的 Automotive 政策

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

您还可以在 service/src/com/android/car/BluetoothDeviceConnectionPolicy.java 中找到蓝牙连接管理政策。默认情况下,该政策会提供有关蓝牙应在哪些情况下连接到绑定的设备以及在哪些情况下断开连接的信息,并管理应在哪些汽车特有情形下开启和关闭适配器。

创建您自己的自定义 Automotive 连接管理政策

如果默认的 Automotive 政策无法满足您的需求,可将其停用并改用您自己的自定义政策。您的自定义政策至少需要确定何时启用和停用蓝牙适配器,以及何时连接设备。您可以根据多种事件来启用/停用蓝牙适配器以及启动设备连接,包括因特定的汽车属性发生变化而触发的事件。

停用默认的 Automotive 政策

要使用自定义政策,必须先在资源叠加层中将 useDefaultBluetoothConnectionPolicy 设置为 false 来停用默认的 Automotive 政策。该资源最初在 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 will do nothing if the adapter
    // is already off, keeping the existing saved desired state for next reboot.
    bluetoothAdapter.disable(false);
}

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

借助自定义政策,您可以根据需要来确定哪些事件代表着启用和停用适配器的最佳时机。其中一种方法就是使用 CarPowerManager 所维护的电源状态:

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 will perceive 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 application’s context
                Car car = Car.createCar(mContext);
                CarBluetoothManager carBluetoothManager =
                        (CarBluetoothManager) car.getCarManager(Car.BLUETOOTH_SERVICE);
                carBluetoothManager.connectDevices();
            }
        }
    }
}

验证 Automotive 连接管理

要验证连接政策的行为,最简单的方法是在 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

最后,如果您已构建自己的 Automotive 政策,那么验证任何自定义连接行为都需要控制您选择用来触发设备连接的事件。

Automotive 蓝牙配置文件

Android 中的 IVI 支持通过蓝牙同时连接多台设备。通过多设备蓝牙电话服务,用户可以同时连接多台设备(例如个人电话和工作电话),并使用任一设备进行免触摸通话。

连接限制由各个蓝牙配置文件强制执行,这通常属于配置文件服务本身的实现范围。默认情况下,CarBluetoothService 不再对可连接的设备数量上限作出进一步判断。

免触摸配置文件

借助蓝牙免触摸配置文件 (HFP),可在车上通过所连接远程设备拨打和接听电话。每台设备连接都会单独向 TelecomManager 注册一个电话帐号,TelecomManager 会将可用电话帐号发送到 IVI 应用。

IVI 可通过 HFP 连接到多台设备。 HeadsetClientService 中的 MAX_STATE_MACHINES_POSSIBLE 指定了可通过 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.registerPhoneAcccount(account);
        mTelecomManager.enablePhoneAccount(account.getAccountHandle(), true);
    }
}

通过蓝牙发起紧急呼叫

在 Android 10 之前,调用紧急呼叫服务时需要直接在手机上拨号且会调用特殊设备(如有,例如在检测到危险或由用户操作后自动触发)。从 Android 10 开始,如果某个紧急电话号码已包含在 apps/Bluetooth/res/values/config.xml 中,车载拨号器可直接呼叫:

<!-- For supporting emergency call through the hfp client connection service --> <bool name=”hfp_client_connection_service_support_emergency_call”>true</bool>

以这种方式实现紧急呼叫时,语音识别等其他应用也可呼叫紧急电话号码。

电话簿访问配置文件

蓝牙电话簿访问配置文件 (PBAP) 会从已连接的远程设备下载联系人信息和通话记录。PBAP 可维护一个可搜索的联系人信息汇总列表,该列表由 PBAP 客户端状态机进行更新。每台已连接的设备都是与单独的 PBAP 客户端状态机交互,因此在用户拨打电话时,联系人会与适当的设备相关联。

PBAP 是单向的,因此需要 IVI 来实例化与所需移动设备的连接。 PbapClientService 中的 MAXIMUM_DEVICES 指定了 IVI 允许同时连接的 PBAP 设备数量上限。PBAP 客户端会将每个已连接设备的联系人信息存储在联系人提供程序,应用可进行访问以获取每台设备的电话簿。

此外,配置文件同时获得 IVI 和移动设备的授权时才能连接。当 PBAP 客户端断开连接时,内部数据库会移除与之前所连接设备上的所有联系人信息和通话记录。

消息访问配置文件

借助蓝牙消息访问配置文件 (MAP),可在车上通过连接的远程设备收发短信。目前不会将短信内容存储在 IVI 本地存储空间,而是每当连接的远程设备收到短信时,IVI 会接收相应短信并对其进行解析,然后在 intent 中广播消息内容,应用便可收到相应内容。

要连接到移动设备以收发短信,IVI 必须启动 MAP 连接。 MapClientService 中的 MAXIMUM_CONNECTED_DEVICES 指定了 IVI 允许同时连接的 MAP 设备数量上限。同时获得 IVI 和移动设备的授权时每个连接才能传输短信。

高级音频分发配置文件

借助蓝牙高级音频分发配置文件 (A2DP),汽车可以接收来自已连接的远程设备流式传输的音频。

与其他配置文件不同,该配置文件在原生堆栈而非 Java 中设置可连接的 A2DP 设备的数量上限。目前已使用 packages/modules/Bluetooth/system/btif/src/btif_av.cc 中的 kDefaultMaxConnectedAudioDevices 变量将该值硬编码为 1

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

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

要让 IVI 通过 AVRCP 浏览 Android 手机上的特定媒体播放器,手机上的媒体应用必须提供 MediaBrowserService 并允许 com.android.bluetooth 访问相应服务。构建媒体浏览器服务一文详细介绍了如何实现这一点。