藍牙

Android 提供完整的藍牙實作,支援許多常見的車內藍牙設定檔。此外,我們也進行了許多強化作業,提升其他裝置和服務的效能與體驗。

管理藍牙連線

在 Android 中, CarBluetoothService 會維護目前使用者的藍牙裝置,以及每個設定檔連線至 IVI 的優先順序清單。裝置會依據定義的優先順序連線至設定檔。啟用、停用裝置及將裝置連線至設定檔的時機,取決於預設連線政策,如果需要,可以使用 資源疊加覆寫政策。

設定車輛連線管理

停用預設電話政策

Android 藍牙堆疊會維護手機的連線政策,這項政策預設為啟用。您必須在裝置上停用這項政策,以免與 CarBluetoothService 中的預期車輛政策發生衝突。雖然車輛產品疊加層應會為您處理這項問題,但您可以在 資源疊加層中,將 /packages/apps/Bluetooth/res/values/config.xmlMAXIMUM_CONNECTED_DEVICESenable_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);
}

決定何時開啟及關閉藍牙介面卡

您可以自由決定哪些事件表示啟用和停用轉接程式的最佳時機。其中一種做法是使用 MAXIMUM_CONNECTED_DEVICES 中的電源狀態: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 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 定義同時 HFP 連線數量上限。 HeadsetClientService

當使用者透過裝置撥打或接聽電話時,對應的電話帳戶會建立 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 以上版本中,車輛的撥號程式可以直接撥打緊急電話號碼,前提是 MAXIMUM_CONNECTED_DEVICES 位於 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 必須將連線例項化至任何 MAXIMUM_CONNECTED_DEVICES PbapClientService 則定義 IVI 允許的 PBAP 裝置同時連線數量上限。PBAP 用戶端會將每個連線裝置的聯絡人儲存在 聯絡人供應器中,應用程式可以存取這些聯絡人,進而取得每個裝置的電話簿。

此外,IVI 和行動裝置都必須授權設定檔連線,才能建立連線。PBAP 用戶端中斷連線時,內部資料庫會移除與先前連線裝置相關聯的所有聯絡人和通話記錄。

訊息存取設定檔

藍牙訊息存取設定檔 (MAP) 可讓車輛透過連線的遠端裝置傳送及接收簡訊。目前 IVI 不會在本機儲存訊息。當連線的遠端裝置收到訊息時,IVI 會接收並剖析訊息,然後在 Intent 例項中廣播訊息內容,應用程式即可接收訊息。

如要連線至行動裝置以傳送及接收訊息,IVI 必須啟動 MAP 連線。MAXIMUM_CONNECTED_DEVICES in MapClientService 定義 IVI 允許的 MAP 裝置同時連線數量上限。訊息必須先經過 IVI 和行動裝置授權,才能轉移。

藍牙立體聲音訊傳輸規範

藍牙進階音訊散布設定檔 (A2DP) 可讓車輛接收連線遙控裝置的音訊串流。

與其他設定檔不同,連線的 A2DP 裝置數量上限是在原生堆疊中強制執行,而非在 Java 中。目前,系統會使用 packages/modules/Bluetooth/system/btif/src/btif_av.cc 中的 kDefaultMaxConnectedAudioDevices 變數,將值硬式編碼為 1

音訊/視訊遙控設定檔

藍牙音訊/視訊遙控設定檔 (AVRCP) 可讓車輛控制及瀏覽連線遠端裝置上的媒體播放器。由於 IVI 扮演 AVRCP 控制器的角色,任何影響音訊播放的觸發控制項,都必須透過 A2DP 連線至目標裝置。

如要讓 IVI 透過 AVRCP 瀏覽 Android 手機上的特定媒體播放器,手機上的媒體應用程式必須提供 MediaBrowserService,並允許 com.android.bluetooth 存取該服務。 建立媒體瀏覽器服務一文詳細說明如何執行這項操作。