藍牙

Android 提供完整的藍牙實作,可支援許多常見的車內藍牙設定檔。我們也推出許多強化功能,可改善其他裝置和服務的效能和使用體驗。

藍牙連線管理

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

設定車用連線管理

停用預設電話政策

Android 藍牙堆疊會為預設啟用的手機維護連線政策。您必須在裝置上停用這項政策,以免與 CarBluetoothService 中的汽車專用政策產生衝突。雖然 Car 產品重疊層應會為您處理這項問題,但您也可以在 資源重疊層中停用手機政策,方法是將 MAXIMUM_CONNECTED_DEVICES 中的 MAXIMUM_CONNECTED_DEVICES 設為 falseenable_phone_policy /packages/apps/Bluetooth/res/values/config.xml

使用預設的汽車政策

CarBluetoothService 會保留預設設定檔權限。已知裝置和其設定檔重新連線優先順序的清單位於 service/src/com/android/car/BluetoothProfileDeviceManager.java

此外,您也可以在 service/src/com/android/car/BluetoothDeviceConnectionPolicy.java 中找到藍牙連線管理政策。根據預設,這項政策會定義藍牙應連線至綁定裝置的情況,以及應中斷連線的情況。它還會管理汽車專屬的情況,決定何時應開啟或關閉轉接器。

建立自訂的車輛連線管理政策

如果預設的汽車政策無法滿足您的需求,也可以停用該政策,改用自訂政策。您的自訂政策至少負責決定何時啟用和停用藍牙無線網路卡,以及何時連線裝置。您可以使用各種事件來啟用/停用藍牙轉接器,並啟動裝置連線,包括因特定車輛屬性變更而發生的事件。

停用預設的汽車政策

首先,如要使用自訂政策,必須在 資源重疊中將 useDefaultBluetoothConnectionPolicy 設為 false,才能停用預設的汽車政策。這項資源原本是 packages/services/Car/service/res/values/config.xmlMAXIMUM_CONNECTED_DEVICES 的一部分。

啟用及停用藍牙無線網路卡

政策的一項核心功能,就是在適當的時機開啟和關閉藍牙無線網路卡。您可以使用 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 為此值,車內的撥號程式就能直接撥打緊急電話號碼:

<!-- 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 中的 MAXIMUM_CONNECTED_DEVICES 會定義 IVI 允許的同時 PBAP 裝置連線數量上限。PBAP 用戶端會將每個已連結裝置的聯絡資訊儲存在 Contacts Provider 中,應用程式可透過該資料庫取得每部裝置的電話簿。

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

訊息存取權設定檔

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

為了連線至行動裝置,以便傳送及接收訊息,IVI 必須啟動 MAP 連線。 MapClientService 中的 MAXIMUM_CONNECTED_DEVICES 會定義 IVI 允許的同時 MAP 裝置連線數量上限。每個連線都必須經過 IVI 和行動裝置授權,才能傳輸訊息。

藍牙立體聲音訊傳輸規範

藍牙進階音訊傳輸設定檔 (A2DP) 可讓車輛接收來自已連結遙控裝置的音訊串流。

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

音訊和視訊遠端控制設定檔

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

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