裝置之間的距離

Android 16 推出 Ranging 模組,提供統一且標準化的介面,方便裝置間進行精確測距。您可以使用這個 API 介面測量對等裝置的距離和位置,不必個別處理每項測距技術。

Ranging 模組支援下列技術:

測距功能和可用性

RangingManager 類別會向應用程式提供本機裝置支援的測距技術相關資訊,以及每項技術的可用性和功能。應用程式可以註冊 Callback,接收任何支援技術的可用性或功能異動更新。

裝置角色

參與測距工作階段的裝置必須是發起者回應者。發起端裝置會與一或多個回應端裝置啟動測距工作階段。回應器裝置一次只能回應一個啟動器的測距要求。您可以使用 RangingPreference 類別,在測距工作階段中指定特定裝置的角色。

測距工作階段類型

在裝置之間啟動測距工作階段時,通常需要建立頻外 (OOB) 資料傳輸,才能交換工作階段的參數。

Ranging 模組可以為您處理 OOB 協商,但同時也支援自訂 OOB 實作。

圖 1. 工作階段類型的 OOB 流程。

預設 OOB 實作方式

在這種工作階段類型 (RANGING_SESSION_OOB) 中,Ranging 模組會處理 OOB 協商,以啟動測距工作階段。系統會根據應用程式提供的測距偏好設定選取合適的參數,並根據兩部裝置支援的技術,使用適當的技術。這個工作階段類型使用標準化的 OOB specification

Ranging 模組只會定義用於與對等裝置互動的 OOB 資料格式和序列。不會處理對等裝置探索或連線建立作業。

自訂 OOB 實作

在這種工作階段類型 (RANGING_SESSION_RAW) 中,應用程式會略過 Ranging 模組的 OOB 流程,並處理自己的 OOB 協商和參數。也就是說,應用程式必須判斷對等裝置支援哪些技術、協商測距參數,然後開始測距工作階段。

測距偏好設定

使用 RangingPreference 物件,指定範圍工作階段的所選參數。此時你應該可以執行下列操作:

  • 裝置角色。這表示裝置是啟動器還是回應器。
  • 測距設定。RangingConfig 物件會指定測距工作階段類型,以及啟動測距工作階段所需的其他參數。
  • 工作階段設定:SessionConfig 物件會指定要在測距工作階段強制執行的參數,例如測量限制、感應器融合、地理圍欄設定等。

測距權限

測距模組需要新的統一權限 (android.permission.RANGING),才能存取目前和日後的所有測距技術。這項權限位於 NEARBY_DEVICES_PERMISSIONS 清單中。

<uses-permission android:name="android.permission.RANGING" />

限制

測距模組可能會因多種原因限制測距,包括:

  • 第三方應用程式只能使用超寬頻執行背景測距,且僅限支援的裝置。不得使用其他技術在背景測距。
  • 裝置的並行測距工作階段數量已達上限,因此無法進行測距。
  • 如果系統健康狀態不佳 (例如電池、效能或記憶體問題),測距功能可能會受到限制。

此外,Ranging 模組也有下列已知限制:

  • Ranging 模組僅支援將測距資料傳送至同層級裝置,以供超寬頻使用。如果是其他技術,Ranging 模組只會將測距資料傳送至發起端裝置。
  • 測距模組僅支援在原始測距模式中動態新增裝置,且僅適用於超寬頻。
  • 對於預設 OOB 實作,Ranging 模組不支援一對多超寬頻工作階段。如果傳遞多個裝置控制代碼,模組會為每個支援超寬頻的對等互連裝置建立一對一的會期。

進行測距工作階段

如要使用 Ranging 模組進行測距工作階段,請按照下列步驟操作:

  1. 確認所有裝置都搭載 Android 16 以上版本。
  2. 在應用程式資訊清單中要求 android.permission.RANGING 權限
  3. 評估測距技術的功能和可用性。
  4. 探索用於測距作業的對等互連裝置。
  5. 使用「測距工作階段類型」一節所述的任一工作階段類型,建立頻帶外交換的連線。
  6. 啟動測距功能,並持續取得測距資料。
  7. 終止測距工作階段。

以下程式碼範例會針對啟動器和回應器角色,示範這些步驟。

Kotlin

class RangingApp {

    // Starts a ranging session on the initiator side.
    fun startRangingInitiator(
        context: Context,
        deviceHandle: DeviceHandle,
        executor: Executor,
        callback: RangingSessionCallback
    ) {

        // Get the RangingManager which is the entry point for ranging module.
        val manager = context.getSystemService(RangingManager::class.java)

        // Create a new RangingSession using the provided executor and callback.
        val session = manager.createRangingSession(executor, callback)

        // Create an OobInitiatorRangingConfig, which specifies the ranging parameters for
        // the initiator role.
        val config = OobInitiatorRangingConfig.Builder()
            .setFastestRangingInterval(Duration.ofMillis(100))
            .setSlowestRangingInterval(Duration.ofMillis(5000))
            .setRangingMode(RANGING_MODE_AUTO)
            .setSecurityLevel(SECURITY_LEVEL_BASIC)
            .addDeviceHandle(deviceHandle)
            .build()

        // Create a RangingPreference, which specifies the role (initiator) and
        // configuration for the ranging session.
        val preference =
            RangingPreference.Builder(DEVICE_ROLE_INITIATOR, config).build()

        // Start ranging session.
        session.start(preference)

        // If successful, the ranging data will be sent through callback#onResults

        // Stop ranging session
        session.stop()
    }

    // Starts a ranging session on the responder side.
    fun startRangingResponder(
        context: Context,
        deviceHandle: DeviceHandle,
        executor: Executor,
        callback: RangingSessionCallback
    ) {

        // Get the RangingManager which is the entry point for ranging module.
        val manager = context.getSystemService(RangingManager::class.java)

        // Create a new RangingSession using the provided executor and callback.
        val session = manager.createRangingSession(executor, callback)

        // Create an OobResponderRangingConfig, which specifies the ranging parameters for
        // the responder role.
        val config = OobResponderRangingConfig.Builder(deviceHandle).build()

        // Create a RangingPreference, which specifies the role (responder) and
        // configuration for the ranging session.
        val preference =
            RangingPreference.Builder(DEVICE_ROLE_RESPONDER, config).build()

        // Start the ranging session.
        session.start(preference)

        // Stop the ranging session
        session.stop()
    }
}

Java

public class RangingApp {

    // Starts a ranging session on the initiator side.
    void startRangingInitiator(Context context, DeviceHandle deviceHandle, Executor executor, RangingSessionCallback callback) {

        // Get the RangingManager which is the entry point for ranging module.
        RangingManager manager = context.getSystemService(RangingManager.class);

        // Create a new RangingSession using the provided executor and callback.
        RangingSession session = manager.createRangingSession(executor, callback);

        // Create an OobInitiatorRangingConfig, which specifies the ranging parameters for
        // the initiator role.
        OobInitiatorRangingConfig config = new OobInitiatorRangingConfig.Builder()
                .setFastestRangingInterval(Duration.ofMillis(100))
                .setSlowestRangingInterval(Duration.ofMillis(5000))
                .setRangingMode(RANGING_MODE_AUTO)
                .setSecurityLevel(SECURITY_LEVEL_BASIC)
                .addDeviceHandle(deviceHandle)
                .build();

        // Create a RangingPreference, which specifies the role (initiator) and
        // configuration for the ranging session.
        RangingPreference preference =
                new RangingPreference.Builder(DEVICE_ROLE_INITIATOR, config).build();

        // Start ranging session.
        session.start(preference);

        // If successful, the ranging data will be sent through callback#onResults

        // Stop ranging session
        session.stop();

    }

    // Starts a ranging session on the responder side.
    void startRangingResponder(Context context,  DeviceHandle deviceHandle, Executor executor, RangingSessionCallback callback) {

        // Get the RangingManager which is the entry point for ranging module.
        RangingManager manager = context.getSystemService(RangingManager.class);

        // Create a new RangingSession using the provided executor and callback.
        RangingSession session = manager.createRangingSession(executor, callback);

        // Create an OobResponderRangingConfig, which specifies the ranging parameters for
        // the responder role.
        OobResponderRangingConfig config = new OobResponderRangingConfig.Builder(  deviceHandle).build();

        // Create a RangingPreference, which specifies the role (responder) and
        // configuration for the ranging session.
        RangingPreference preference =
                new RangingPreference.Builder(DEVICE_ROLE_RESPONDER, config).build();

        // Start the ranging session.
        session.start(preference);

        // Stop the ranging session
        session.stop();
    }
}

與 iOS 裝置的 UWB 互通性

測距模組支援使用超寬頻 (UWB) 與 iOS 裝置互通。如要使用 iOS 裝置測距,您必須自訂 OOB 實作,並使用符合 Apple Nearby Interaction Accessory Protocol 的特定參數設定測距工作階段。如需參考,請參閱範例應用程式

建立 RangingPreference 時,請使用 RawRangingDeviceUwbRangingParams 指定設定。下列參數對 iOS 互通性至關重要:

  • 設定 ID:使用 UwbRangingParams.CONFIG_UNICAST_DS_TWR
  • 工作階段金鑰資訊:提供包含供應商 ID 和靜態 STS IV 的位元組陣列。
  • 複雜通道:設定通道號碼和前置碼索引,與 iOS 裝置的設定相符。

下列程式碼片段示範如何為與 iOS 回應器進行測距的發起端裝置建立 RangingPreference

Kotlin

// Create UwbRangingParams with iOS-specific configuration
val uwbRangingParams = UwbRangingParams.Builder(
    sessionId,
    UwbRangingParams.CONFIG_UNICAST_DS_TWR,
    sourceAddress,
    destinationAddress
)
    .setComplexChannel(
        UwbComplexChannel.Builder()
            .setChannel(channelNumber)
            .setPreambleIndex(preambleIndex)
            .build()
    )
    .setRangingUpdateRate(updateRate)
    .setSessionKeyInfo(sessionKeyInfo) // Vendor ID + STS IV
    .setSlotDuration(slotDuration)
    .build()

// Create RawRangingDevice
val rawRangingDevice = RawRangingDevice.Builder()
    .setRangingDevice(RangingDevice.Builder().build())
    .setUwbRangingParams(uwbRangingParams)
    .build()

// Create SessionConfig
val sessionConfig = SessionConfig.Builder()
    .setAngleOfArrivalNeeded(true)
    .setDataNotificationConfig(DataNotificationConfig.Builder()
        .setNotificationConfigType(DataNotificationConfig.NOTIFICATION_CONFIG_ENABLE)
        .build())
    .build()

// Create RangingPreference for the initiator
val preference = RangingPreference.Builder(
    RangingPreference.DEVICE_ROLE_INITIATOR,
    RawInitiatorRangingConfig.Builder()
        .addRawRangingDevice(rawRangingDevice)
        .build()
)
    .setSessionConfig(sessionConfig)
    .build()

Java

// Create UwbRangingParams with iOS-specific configuration
UwbRangingParams uwbRangingParams = new UwbRangingParams.Builder(
        sessionId,
        UwbRangingParams.CONFIG_UNICAST_DS_TWR,
        sourceAddress,
        destinationAddress)
        .setComplexChannel(new UwbComplexChannel.Builder()
                .setChannel(channelNumber)
                .setPreambleIndex(preambleIndex)
                .build())
        .setRangingUpdateRate(updateRate)
        .setSessionKeyInfo(sessionKeyInfo) // Vendor ID + STS IV
        .setSlotDuration(slotDuration)
        .build();

// Create RawRangingDevice
RawRangingDevice rawRangingDevice = new RawRangingDevice.Builder()
        .setRangingDevice(new RangingDevice.Builder().build())
        .setUwbRangingParams(uwbRangingParams)
        .build();

// Create SessionConfig
SessionConfig sessionConfig = new SessionConfig.Builder()
        .setAngleOfArrivalNeeded(true)
        .setDataNotificationConfig(new DataNotificationConfig.Builder()
                .setNotificationConfigType(DataNotificationConfig.NOTIFICATION_CONFIG_ENABLE)
                .build())
        .build();

// Create RangingPreference for the initiator
RangingPreference preference = new RangingPreference.Builder(
        RangingPreference.DEVICE_ROLE_INITIATOR,
        new RawInitiatorRangingConfig.Builder()
                .addRawRangingDevice(rawRangingDevice)
                .build())
        .setSessionConfig(sessionConfig)
        .build();

UWB Downlink-TDoA API

如要瞭解 UWB DL-TDoA API,請參閱「Android 17 新功能」。

範例應用程式

如需如何使用 Ranging 模組的端對端範例,請參閱 AOSP 中的範例應用程式。這個範例應用程式涵蓋 Ranging 模組支援的所有測距技術,並包含兩種支援的會期類型流程。

與 iOS 裝置的 UWB 互通性

Android 範例應用程式支援使用 iOS 範例應用程式啟動 UWB 測距工作階段。

圖 2. Android 和 iOS UWB 使用情形。