Adding implementation for Uwb ranging
Relnote: Initial implementation of the uwb library
Test: /gradlew core:uwb:uwb:cAT
Change-Id: Ic9b835e9e88b01856a2e0d2f31ddee4746e10bee
diff --git a/core/uwb/uwb/api/current.txt b/core/uwb/uwb/api/current.txt
index e6f50d0..0b2add9 100644
--- a/core/uwb/uwb/api/current.txt
+++ b/core/uwb/uwb/api/current.txt
@@ -1 +1,165 @@
// Signature format: 4.0
+package androidx.core.uwb {
+
+ public final class RangingCapabilities {
+ ctor public RangingCapabilities(boolean supportsDistance, boolean supportsAzimuthalAngle, boolean supportsElevationAngle);
+ method public boolean getSupportsAzimuthalAngle();
+ method public boolean getSupportsDistance();
+ method public boolean getSupportsElevationAngle();
+ property public final boolean supportsAzimuthalAngle;
+ property public final boolean supportsDistance;
+ property public final boolean supportsElevationAngle;
+ }
+
+ public final class RangingMeasurement {
+ ctor public RangingMeasurement(float value);
+ method public float getValue();
+ property public final float value;
+ }
+
+ public final class RangingParameters {
+ ctor public RangingParameters(int uwbConfigId, int sessionId, byte[]? sessionKeyInfo, androidx.core.uwb.UwbComplexChannel? complexChannel, java.util.List<androidx.core.uwb.UwbDevice> peerDevices, int updateRate);
+ method public androidx.core.uwb.UwbComplexChannel? getComplexChannel();
+ method public java.util.List<androidx.core.uwb.UwbDevice> getPeerDevices();
+ method public int getSessionId();
+ method public byte[]? getSessionKeyInfo();
+ method public int getUpdateRate();
+ method public int getUwbConfigId();
+ property public final androidx.core.uwb.UwbComplexChannel? complexChannel;
+ property public final java.util.List<androidx.core.uwb.UwbDevice> peerDevices;
+ property public final int sessionId;
+ property public final byte[]? sessionKeyInfo;
+ property public final int updateRate;
+ property public final int uwbConfigId;
+ field public static final androidx.core.uwb.RangingParameters.Companion Companion;
+ field public static final int RANGING_UPDATE_RATE_AUTOMATIC;
+ field public static final int RANGING_UPDATE_RATE_FREQUENT;
+ field public static final int RANGING_UPDATE_RATE_INFREQUENT;
+ }
+
+ public static final class RangingParameters.Companion {
+ }
+
+ public final class RangingPosition {
+ ctor public RangingPosition(androidx.core.uwb.RangingMeasurement distance, androidx.core.uwb.RangingMeasurement? azimuth, androidx.core.uwb.RangingMeasurement? elevation, long elapsedRealtimeNanos);
+ method public androidx.core.uwb.RangingMeasurement? getAzimuth();
+ method public androidx.core.uwb.RangingMeasurement getDistance();
+ method public long getElapsedRealtimeNanos();
+ method public androidx.core.uwb.RangingMeasurement? getElevation();
+ property public final androidx.core.uwb.RangingMeasurement? azimuth;
+ property public final androidx.core.uwb.RangingMeasurement distance;
+ property public final long elapsedRealtimeNanos;
+ property public final androidx.core.uwb.RangingMeasurement? elevation;
+ }
+
+ public abstract class RangingResult {
+ ctor public RangingResult();
+ method public abstract androidx.core.uwb.UwbDevice getDevice();
+ property public abstract androidx.core.uwb.UwbDevice device;
+ }
+
+ public final class RangingResultPeerDisconnected extends androidx.core.uwb.RangingResult {
+ ctor public RangingResultPeerDisconnected(androidx.core.uwb.UwbDevice device);
+ method public androidx.core.uwb.UwbDevice getDevice();
+ property public androidx.core.uwb.UwbDevice device;
+ }
+
+ public final class RangingResultPosition extends androidx.core.uwb.RangingResult {
+ ctor public RangingResultPosition(androidx.core.uwb.UwbDevice device, androidx.core.uwb.RangingPosition position);
+ method public androidx.core.uwb.UwbDevice getDevice();
+ method public androidx.core.uwb.RangingPosition getPosition();
+ property public androidx.core.uwb.UwbDevice device;
+ property public final androidx.core.uwb.RangingPosition position;
+ }
+
+ public final class UwbAddress {
+ ctor public UwbAddress(byte[] address);
+ ctor public UwbAddress(String address);
+ method public byte[] getAddress();
+ property public final byte[] address;
+ field public static final androidx.core.uwb.UwbAddress.Companion Companion;
+ }
+
+ public static final class UwbAddress.Companion {
+ }
+
+ public interface UwbClientSessionScope {
+ method public androidx.core.uwb.UwbAddress getLocalAddress();
+ method public androidx.core.uwb.RangingCapabilities getRangingCapabilities();
+ method public kotlinx.coroutines.flow.Flow<androidx.core.uwb.RangingResult> initSession(androidx.core.uwb.RangingParameters parameters);
+ property public abstract androidx.core.uwb.UwbAddress localAddress;
+ property public abstract androidx.core.uwb.RangingCapabilities rangingCapabilities;
+ }
+
+ public final class UwbComplexChannel {
+ ctor public UwbComplexChannel(int channel, int preambleIndex);
+ method public int getChannel();
+ method public int getPreambleIndex();
+ property public final int channel;
+ property public final int preambleIndex;
+ }
+
+ public interface UwbControleeSessionScope extends androidx.core.uwb.UwbClientSessionScope {
+ }
+
+ public final class UwbDevice {
+ ctor public UwbDevice(androidx.core.uwb.UwbAddress address);
+ method public static androidx.core.uwb.UwbDevice createForAddress(String address);
+ method public static androidx.core.uwb.UwbDevice createForAddress(byte[] address);
+ method public androidx.core.uwb.UwbAddress getAddress();
+ property public final androidx.core.uwb.UwbAddress address;
+ field public static final androidx.core.uwb.UwbDevice.Companion Companion;
+ }
+
+ public static final class UwbDevice.Companion {
+ method public androidx.core.uwb.UwbDevice createForAddress(String address);
+ method public androidx.core.uwb.UwbDevice createForAddress(byte[] address);
+ }
+
+ public interface UwbManager {
+ method public suspend <R> Object? clientSessionScope(kotlin.jvm.functions.Function2<? super androidx.core.uwb.UwbClientSessionScope,? super kotlin.coroutines.Continuation<? super R>,?> sessionHandler, kotlin.coroutines.Continuation<? super R>);
+ method public default static androidx.core.uwb.UwbManager getInstance(android.content.Context context);
+ field public static final androidx.core.uwb.UwbManager.Companion Companion;
+ }
+
+ public static final class UwbManager.Companion {
+ method public androidx.core.uwb.UwbManager getInstance(android.content.Context context);
+ }
+
+}
+
+package androidx.core.uwb.exceptions {
+
+ public class UwbApiException extends java.lang.Exception {
+ ctor public UwbApiException(String message);
+ }
+
+ public final class UwbBackgroundPolicyException extends androidx.core.uwb.exceptions.UwbApiException {
+ ctor public UwbBackgroundPolicyException(String message);
+ }
+
+ public final class UwbHardwareNotAvailableException extends androidx.core.uwb.exceptions.UwbApiException {
+ ctor public UwbHardwareNotAvailableException(String message);
+ }
+
+ public final class UwbRangingAlreadyStartedException extends androidx.core.uwb.exceptions.UwbApiException {
+ ctor public UwbRangingAlreadyStartedException(String message);
+ }
+
+ public final class UwbServiceNotAvailableException extends androidx.core.uwb.exceptions.UwbApiException {
+ ctor public UwbServiceNotAvailableException(String message);
+ }
+
+ public final class UwbSystemCallbackException extends androidx.core.uwb.exceptions.UwbApiException {
+ ctor public UwbSystemCallbackException(String message);
+ }
+
+}
+
+package androidx.core.uwb.helper {
+
+ public final class UwbHelperKt {
+ }
+
+}
+
diff --git a/core/uwb/uwb/api/public_plus_experimental_current.txt b/core/uwb/uwb/api/public_plus_experimental_current.txt
index e6f50d0..0b2add9 100644
--- a/core/uwb/uwb/api/public_plus_experimental_current.txt
+++ b/core/uwb/uwb/api/public_plus_experimental_current.txt
@@ -1 +1,165 @@
// Signature format: 4.0
+package androidx.core.uwb {
+
+ public final class RangingCapabilities {
+ ctor public RangingCapabilities(boolean supportsDistance, boolean supportsAzimuthalAngle, boolean supportsElevationAngle);
+ method public boolean getSupportsAzimuthalAngle();
+ method public boolean getSupportsDistance();
+ method public boolean getSupportsElevationAngle();
+ property public final boolean supportsAzimuthalAngle;
+ property public final boolean supportsDistance;
+ property public final boolean supportsElevationAngle;
+ }
+
+ public final class RangingMeasurement {
+ ctor public RangingMeasurement(float value);
+ method public float getValue();
+ property public final float value;
+ }
+
+ public final class RangingParameters {
+ ctor public RangingParameters(int uwbConfigId, int sessionId, byte[]? sessionKeyInfo, androidx.core.uwb.UwbComplexChannel? complexChannel, java.util.List<androidx.core.uwb.UwbDevice> peerDevices, int updateRate);
+ method public androidx.core.uwb.UwbComplexChannel? getComplexChannel();
+ method public java.util.List<androidx.core.uwb.UwbDevice> getPeerDevices();
+ method public int getSessionId();
+ method public byte[]? getSessionKeyInfo();
+ method public int getUpdateRate();
+ method public int getUwbConfigId();
+ property public final androidx.core.uwb.UwbComplexChannel? complexChannel;
+ property public final java.util.List<androidx.core.uwb.UwbDevice> peerDevices;
+ property public final int sessionId;
+ property public final byte[]? sessionKeyInfo;
+ property public final int updateRate;
+ property public final int uwbConfigId;
+ field public static final androidx.core.uwb.RangingParameters.Companion Companion;
+ field public static final int RANGING_UPDATE_RATE_AUTOMATIC;
+ field public static final int RANGING_UPDATE_RATE_FREQUENT;
+ field public static final int RANGING_UPDATE_RATE_INFREQUENT;
+ }
+
+ public static final class RangingParameters.Companion {
+ }
+
+ public final class RangingPosition {
+ ctor public RangingPosition(androidx.core.uwb.RangingMeasurement distance, androidx.core.uwb.RangingMeasurement? azimuth, androidx.core.uwb.RangingMeasurement? elevation, long elapsedRealtimeNanos);
+ method public androidx.core.uwb.RangingMeasurement? getAzimuth();
+ method public androidx.core.uwb.RangingMeasurement getDistance();
+ method public long getElapsedRealtimeNanos();
+ method public androidx.core.uwb.RangingMeasurement? getElevation();
+ property public final androidx.core.uwb.RangingMeasurement? azimuth;
+ property public final androidx.core.uwb.RangingMeasurement distance;
+ property public final long elapsedRealtimeNanos;
+ property public final androidx.core.uwb.RangingMeasurement? elevation;
+ }
+
+ public abstract class RangingResult {
+ ctor public RangingResult();
+ method public abstract androidx.core.uwb.UwbDevice getDevice();
+ property public abstract androidx.core.uwb.UwbDevice device;
+ }
+
+ public final class RangingResultPeerDisconnected extends androidx.core.uwb.RangingResult {
+ ctor public RangingResultPeerDisconnected(androidx.core.uwb.UwbDevice device);
+ method public androidx.core.uwb.UwbDevice getDevice();
+ property public androidx.core.uwb.UwbDevice device;
+ }
+
+ public final class RangingResultPosition extends androidx.core.uwb.RangingResult {
+ ctor public RangingResultPosition(androidx.core.uwb.UwbDevice device, androidx.core.uwb.RangingPosition position);
+ method public androidx.core.uwb.UwbDevice getDevice();
+ method public androidx.core.uwb.RangingPosition getPosition();
+ property public androidx.core.uwb.UwbDevice device;
+ property public final androidx.core.uwb.RangingPosition position;
+ }
+
+ public final class UwbAddress {
+ ctor public UwbAddress(byte[] address);
+ ctor public UwbAddress(String address);
+ method public byte[] getAddress();
+ property public final byte[] address;
+ field public static final androidx.core.uwb.UwbAddress.Companion Companion;
+ }
+
+ public static final class UwbAddress.Companion {
+ }
+
+ public interface UwbClientSessionScope {
+ method public androidx.core.uwb.UwbAddress getLocalAddress();
+ method public androidx.core.uwb.RangingCapabilities getRangingCapabilities();
+ method public kotlinx.coroutines.flow.Flow<androidx.core.uwb.RangingResult> initSession(androidx.core.uwb.RangingParameters parameters);
+ property public abstract androidx.core.uwb.UwbAddress localAddress;
+ property public abstract androidx.core.uwb.RangingCapabilities rangingCapabilities;
+ }
+
+ public final class UwbComplexChannel {
+ ctor public UwbComplexChannel(int channel, int preambleIndex);
+ method public int getChannel();
+ method public int getPreambleIndex();
+ property public final int channel;
+ property public final int preambleIndex;
+ }
+
+ public interface UwbControleeSessionScope extends androidx.core.uwb.UwbClientSessionScope {
+ }
+
+ public final class UwbDevice {
+ ctor public UwbDevice(androidx.core.uwb.UwbAddress address);
+ method public static androidx.core.uwb.UwbDevice createForAddress(String address);
+ method public static androidx.core.uwb.UwbDevice createForAddress(byte[] address);
+ method public androidx.core.uwb.UwbAddress getAddress();
+ property public final androidx.core.uwb.UwbAddress address;
+ field public static final androidx.core.uwb.UwbDevice.Companion Companion;
+ }
+
+ public static final class UwbDevice.Companion {
+ method public androidx.core.uwb.UwbDevice createForAddress(String address);
+ method public androidx.core.uwb.UwbDevice createForAddress(byte[] address);
+ }
+
+ public interface UwbManager {
+ method public suspend <R> Object? clientSessionScope(kotlin.jvm.functions.Function2<? super androidx.core.uwb.UwbClientSessionScope,? super kotlin.coroutines.Continuation<? super R>,?> sessionHandler, kotlin.coroutines.Continuation<? super R>);
+ method public default static androidx.core.uwb.UwbManager getInstance(android.content.Context context);
+ field public static final androidx.core.uwb.UwbManager.Companion Companion;
+ }
+
+ public static final class UwbManager.Companion {
+ method public androidx.core.uwb.UwbManager getInstance(android.content.Context context);
+ }
+
+}
+
+package androidx.core.uwb.exceptions {
+
+ public class UwbApiException extends java.lang.Exception {
+ ctor public UwbApiException(String message);
+ }
+
+ public final class UwbBackgroundPolicyException extends androidx.core.uwb.exceptions.UwbApiException {
+ ctor public UwbBackgroundPolicyException(String message);
+ }
+
+ public final class UwbHardwareNotAvailableException extends androidx.core.uwb.exceptions.UwbApiException {
+ ctor public UwbHardwareNotAvailableException(String message);
+ }
+
+ public final class UwbRangingAlreadyStartedException extends androidx.core.uwb.exceptions.UwbApiException {
+ ctor public UwbRangingAlreadyStartedException(String message);
+ }
+
+ public final class UwbServiceNotAvailableException extends androidx.core.uwb.exceptions.UwbApiException {
+ ctor public UwbServiceNotAvailableException(String message);
+ }
+
+ public final class UwbSystemCallbackException extends androidx.core.uwb.exceptions.UwbApiException {
+ ctor public UwbSystemCallbackException(String message);
+ }
+
+}
+
+package androidx.core.uwb.helper {
+
+ public final class UwbHelperKt {
+ }
+
+}
+
diff --git a/core/uwb/uwb/api/restricted_current.txt b/core/uwb/uwb/api/restricted_current.txt
index e6f50d0..0b2add9 100644
--- a/core/uwb/uwb/api/restricted_current.txt
+++ b/core/uwb/uwb/api/restricted_current.txt
@@ -1 +1,165 @@
// Signature format: 4.0
+package androidx.core.uwb {
+
+ public final class RangingCapabilities {
+ ctor public RangingCapabilities(boolean supportsDistance, boolean supportsAzimuthalAngle, boolean supportsElevationAngle);
+ method public boolean getSupportsAzimuthalAngle();
+ method public boolean getSupportsDistance();
+ method public boolean getSupportsElevationAngle();
+ property public final boolean supportsAzimuthalAngle;
+ property public final boolean supportsDistance;
+ property public final boolean supportsElevationAngle;
+ }
+
+ public final class RangingMeasurement {
+ ctor public RangingMeasurement(float value);
+ method public float getValue();
+ property public final float value;
+ }
+
+ public final class RangingParameters {
+ ctor public RangingParameters(int uwbConfigId, int sessionId, byte[]? sessionKeyInfo, androidx.core.uwb.UwbComplexChannel? complexChannel, java.util.List<androidx.core.uwb.UwbDevice> peerDevices, int updateRate);
+ method public androidx.core.uwb.UwbComplexChannel? getComplexChannel();
+ method public java.util.List<androidx.core.uwb.UwbDevice> getPeerDevices();
+ method public int getSessionId();
+ method public byte[]? getSessionKeyInfo();
+ method public int getUpdateRate();
+ method public int getUwbConfigId();
+ property public final androidx.core.uwb.UwbComplexChannel? complexChannel;
+ property public final java.util.List<androidx.core.uwb.UwbDevice> peerDevices;
+ property public final int sessionId;
+ property public final byte[]? sessionKeyInfo;
+ property public final int updateRate;
+ property public final int uwbConfigId;
+ field public static final androidx.core.uwb.RangingParameters.Companion Companion;
+ field public static final int RANGING_UPDATE_RATE_AUTOMATIC;
+ field public static final int RANGING_UPDATE_RATE_FREQUENT;
+ field public static final int RANGING_UPDATE_RATE_INFREQUENT;
+ }
+
+ public static final class RangingParameters.Companion {
+ }
+
+ public final class RangingPosition {
+ ctor public RangingPosition(androidx.core.uwb.RangingMeasurement distance, androidx.core.uwb.RangingMeasurement? azimuth, androidx.core.uwb.RangingMeasurement? elevation, long elapsedRealtimeNanos);
+ method public androidx.core.uwb.RangingMeasurement? getAzimuth();
+ method public androidx.core.uwb.RangingMeasurement getDistance();
+ method public long getElapsedRealtimeNanos();
+ method public androidx.core.uwb.RangingMeasurement? getElevation();
+ property public final androidx.core.uwb.RangingMeasurement? azimuth;
+ property public final androidx.core.uwb.RangingMeasurement distance;
+ property public final long elapsedRealtimeNanos;
+ property public final androidx.core.uwb.RangingMeasurement? elevation;
+ }
+
+ public abstract class RangingResult {
+ ctor public RangingResult();
+ method public abstract androidx.core.uwb.UwbDevice getDevice();
+ property public abstract androidx.core.uwb.UwbDevice device;
+ }
+
+ public final class RangingResultPeerDisconnected extends androidx.core.uwb.RangingResult {
+ ctor public RangingResultPeerDisconnected(androidx.core.uwb.UwbDevice device);
+ method public androidx.core.uwb.UwbDevice getDevice();
+ property public androidx.core.uwb.UwbDevice device;
+ }
+
+ public final class RangingResultPosition extends androidx.core.uwb.RangingResult {
+ ctor public RangingResultPosition(androidx.core.uwb.UwbDevice device, androidx.core.uwb.RangingPosition position);
+ method public androidx.core.uwb.UwbDevice getDevice();
+ method public androidx.core.uwb.RangingPosition getPosition();
+ property public androidx.core.uwb.UwbDevice device;
+ property public final androidx.core.uwb.RangingPosition position;
+ }
+
+ public final class UwbAddress {
+ ctor public UwbAddress(byte[] address);
+ ctor public UwbAddress(String address);
+ method public byte[] getAddress();
+ property public final byte[] address;
+ field public static final androidx.core.uwb.UwbAddress.Companion Companion;
+ }
+
+ public static final class UwbAddress.Companion {
+ }
+
+ public interface UwbClientSessionScope {
+ method public androidx.core.uwb.UwbAddress getLocalAddress();
+ method public androidx.core.uwb.RangingCapabilities getRangingCapabilities();
+ method public kotlinx.coroutines.flow.Flow<androidx.core.uwb.RangingResult> initSession(androidx.core.uwb.RangingParameters parameters);
+ property public abstract androidx.core.uwb.UwbAddress localAddress;
+ property public abstract androidx.core.uwb.RangingCapabilities rangingCapabilities;
+ }
+
+ public final class UwbComplexChannel {
+ ctor public UwbComplexChannel(int channel, int preambleIndex);
+ method public int getChannel();
+ method public int getPreambleIndex();
+ property public final int channel;
+ property public final int preambleIndex;
+ }
+
+ public interface UwbControleeSessionScope extends androidx.core.uwb.UwbClientSessionScope {
+ }
+
+ public final class UwbDevice {
+ ctor public UwbDevice(androidx.core.uwb.UwbAddress address);
+ method public static androidx.core.uwb.UwbDevice createForAddress(String address);
+ method public static androidx.core.uwb.UwbDevice createForAddress(byte[] address);
+ method public androidx.core.uwb.UwbAddress getAddress();
+ property public final androidx.core.uwb.UwbAddress address;
+ field public static final androidx.core.uwb.UwbDevice.Companion Companion;
+ }
+
+ public static final class UwbDevice.Companion {
+ method public androidx.core.uwb.UwbDevice createForAddress(String address);
+ method public androidx.core.uwb.UwbDevice createForAddress(byte[] address);
+ }
+
+ public interface UwbManager {
+ method public suspend <R> Object? clientSessionScope(kotlin.jvm.functions.Function2<? super androidx.core.uwb.UwbClientSessionScope,? super kotlin.coroutines.Continuation<? super R>,?> sessionHandler, kotlin.coroutines.Continuation<? super R>);
+ method public default static androidx.core.uwb.UwbManager getInstance(android.content.Context context);
+ field public static final androidx.core.uwb.UwbManager.Companion Companion;
+ }
+
+ public static final class UwbManager.Companion {
+ method public androidx.core.uwb.UwbManager getInstance(android.content.Context context);
+ }
+
+}
+
+package androidx.core.uwb.exceptions {
+
+ public class UwbApiException extends java.lang.Exception {
+ ctor public UwbApiException(String message);
+ }
+
+ public final class UwbBackgroundPolicyException extends androidx.core.uwb.exceptions.UwbApiException {
+ ctor public UwbBackgroundPolicyException(String message);
+ }
+
+ public final class UwbHardwareNotAvailableException extends androidx.core.uwb.exceptions.UwbApiException {
+ ctor public UwbHardwareNotAvailableException(String message);
+ }
+
+ public final class UwbRangingAlreadyStartedException extends androidx.core.uwb.exceptions.UwbApiException {
+ ctor public UwbRangingAlreadyStartedException(String message);
+ }
+
+ public final class UwbServiceNotAvailableException extends androidx.core.uwb.exceptions.UwbApiException {
+ ctor public UwbServiceNotAvailableException(String message);
+ }
+
+ public final class UwbSystemCallbackException extends androidx.core.uwb.exceptions.UwbApiException {
+ ctor public UwbSystemCallbackException(String message);
+ }
+
+}
+
+package androidx.core.uwb.helper {
+
+ public final class UwbHelperKt {
+ }
+
+}
+
diff --git a/core/uwb/uwb/build.gradle b/core/uwb/uwb/build.gradle
index 3793f28..da9b837 100644
--- a/core/uwb/uwb/build.gradle
+++ b/core/uwb/uwb/build.gradle
@@ -24,7 +24,23 @@
dependencies {
api(libs.kotlinStdlib)
- // Add dependencies here
+ api("androidx.annotation:annotation:1.1.0")
+ api("androidx.core:core-ktx:1.2.0")
+ api(libs.kotlinCoroutinesAndroid)
+ implementation(libs.guavaAndroid)
+ implementation('com.google.android.gms:play-services-base:18.0.1')
+ implementation('org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.6.0')
+ implementation('com.google.android.gms:play-services-nearby:18.1.0', {
+ exclude group: "androidx.core"
+ })
+
+ androidTestImplementation(libs.kotlinStdlib)
+ androidTestImplementation(libs.testExtJunit)
+ androidTestImplementation(libs.testCore)
+ androidTestImplementation(libs.testRunner)
+ androidTestImplementation(libs.testRules)
+ androidTestImplementation(libs.truth)
+ androidTestImplementation(libs.espressoCore)
}
androidx {
diff --git a/core/uwb/uwb/src/androidTest/java/androidx/core/uwb/impl/UwbClientSessionScopeImplTest.kt b/core/uwb/uwb/src/androidTest/java/androidx/core/uwb/impl/UwbClientSessionScopeImplTest.kt
new file mode 100644
index 0000000..3fa5e47
--- /dev/null
+++ b/core/uwb/uwb/src/androidTest/java/androidx/core/uwb/impl/UwbClientSessionScopeImplTest.kt
@@ -0,0 +1,298 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.core.uwb.impl
+
+import androidx.core.uwb.RangingParameters
+import androidx.core.uwb.RangingResult
+import androidx.core.uwb.RangingResultPeerDisconnected
+import androidx.core.uwb.RangingResultPosition
+import androidx.core.uwb.UwbDevice
+import androidx.core.uwb.exceptions.UwbRangingAlreadyStartedException
+import androidx.core.uwb.mock.TestUwbClient
+import com.google.android.gms.nearby.uwb.RangingCapabilities
+import com.google.android.gms.nearby.uwb.UwbAddress
+import com.google.android.gms.nearby.uwb.UwbComplexChannel
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.cancellable
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.shareIn
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
+import org.junit.Assert
+import org.junit.Test
+
+class UwbClientSessionScopeImplTest {
+ private val complexChannel = UwbComplexChannel.Builder()
+ .setPreambleIndex(0)
+ .setChannel(0)
+ .build()
+ private val localAddress = UwbAddress(ByteArray(0))
+ private val rangingCapabilities = RangingCapabilities(true, false, false)
+ private val uwbClient = TestUwbClient(complexChannel, localAddress, rangingCapabilities, true)
+ private val uwbClientSessionScopeImpl = UwbClientSessionScopeImpl(
+ uwbClient,
+ androidx.core.uwb.RangingCapabilities(
+ rangingCapabilities.supportsDistance(),
+ rangingCapabilities.supportsAzimuthalAngle(),
+ rangingCapabilities.supportsElevationAngle()),
+ androidx.core.uwb.UwbAddress(localAddress.address))
+ private val uwbDevice = UwbDevice.createForAddress(ByteArray(0))
+ private val rangingParameters = RangingParameters(
+ 0,
+ 0,
+ null,
+ null,
+ listOf(uwbDevice),
+ 0
+ )
+
+ @Test
+ public fun testInitSession_singleConsumer() {
+ val sessionFlow = uwbClientSessionScopeImpl.initSession(rangingParameters)
+ var rangingResult: RangingResult? = null
+ val job = sessionFlow
+ .cancellable()
+ .onEach { rangingResult = it }
+ .launchIn(CoroutineScope(Dispatchers.Main.immediate))
+
+ runBlocking {
+ // wait for the coroutines for UWB to get launched.
+ delay(500)
+ }
+ // a non-null RangingResult should return from the TestUwbClient.
+ if (rangingResult != null) {
+ assertThat(rangingResult is RangingResultPosition).isTrue()
+ } else {
+ job.cancel()
+ Assert.fail()
+ }
+
+ // cancel and wait for the job to terminate.
+ job.cancel()
+ runBlocking {
+ job.join()
+ }
+ // StopRanging should have been called after the coroutine scope completed.
+ assertThat(uwbClient.stopRangingCalled).isTrue()
+ }
+
+ @Test
+ public fun testInitSession_multipleSharedConsumers() {
+ var passed1 = false
+ var passed2 = false
+ val sharedFlow = uwbClientSessionScopeImpl.initSession(rangingParameters)
+ .shareIn(CoroutineScope(Dispatchers.Main.immediate), SharingStarted.WhileSubscribed(),
+ replay = 1)
+ val job = CoroutineScope(Dispatchers.Main.immediate).launch {
+ sharedFlow
+ .onEach {
+ if (it is RangingResultPosition) {
+ passed1 = true
+ }
+ }
+ .collect()
+ }
+ val job2 = CoroutineScope(Dispatchers.Main.immediate).launch {
+ sharedFlow
+ .onEach {
+ if (it is RangingResultPosition) {
+ passed2 = true
+ }
+ }
+ .collect()
+ }
+
+ runBlocking {
+ // wait for coroutines for flow to start.
+ delay(500)
+ }
+
+ // a non-null RangingResult should return from the TestUwbClient.
+ assertThat(passed1).isTrue()
+ assertThat(passed2).isTrue()
+
+ // cancel and wait for the first job to terminate.
+ job.cancel()
+ runBlocking {
+ job.join()
+ }
+ // StopRanging should not have been called because not all consumers have finished.
+ assertThat(uwbClient.stopRangingCalled).isFalse()
+
+ // cancel and wait for the second job to terminate.
+ job2.cancel()
+ runBlocking {
+ job2.join()
+ }
+ // StopRanging should have been called because all consumers have finished.
+ assertThat(uwbClient.stopRangingCalled).isTrue()
+ }
+
+ @Test
+ public fun testInitSession_singleConsumer_disconnectPeerDevice() {
+ val sessionFlow = uwbClientSessionScopeImpl.initSession(rangingParameters)
+ var peerDisconnected = false
+ val job = CoroutineScope(Dispatchers.Main.immediate).launch {
+ sessionFlow
+ .cancellable()
+ .onEach {
+ if (it is RangingResultPeerDisconnected) {
+ peerDisconnected = true
+ }
+ }
+ .collect()
+ }
+
+ runBlocking {
+ // wait for coroutines for flow to start.
+ delay(500)
+ uwbClient.disconnectPeer(com.google.android.gms.nearby.uwb.UwbDevice.createForAddress(
+ uwbDevice.address.address))
+
+ // wait for rangingResults to get filled.
+ delay(500)
+ }
+
+ // a peer disconnected event should have occurred.
+ assertThat(peerDisconnected).isTrue()
+
+ // cancel and wait for the job to terminate.
+ job.cancel()
+ runBlocking {
+ job.join()
+ }
+ // StopRanging should have been called after the coroutine scope completed.
+ assertThat(uwbClient.stopRangingCalled).isTrue()
+ }
+
+ @Test
+ public fun testInitSession_multipleSharedConsumers_disconnectPeerDevice() {
+ val sharedFlow = uwbClientSessionScopeImpl.initSession(rangingParameters)
+ .shareIn(CoroutineScope(Dispatchers.Main.immediate), SharingStarted.WhileSubscribed())
+
+ var peerDisconnected = false
+ var peerDisconnected2 = false
+ val job = CoroutineScope(Dispatchers.Main.immediate).launch {
+ sharedFlow
+ .onEach {
+ if (it is RangingResultPeerDisconnected) {
+ peerDisconnected = true
+ }
+ }
+ .collect()
+ }
+ val job2 = CoroutineScope(Dispatchers.Main.immediate).launch {
+ sharedFlow
+ .onEach {
+ if (it is RangingResultPeerDisconnected) {
+ peerDisconnected2 = true
+ }
+ }
+ .collect()
+ }
+
+ runBlocking {
+ // wait for coroutines for flow to start.
+ delay(500)
+ uwbClient.disconnectPeer(com.google.android.gms.nearby.uwb.UwbDevice.createForAddress(
+ uwbDevice.address.address))
+
+ // wait for rangingResults to get filled.
+ delay(500)
+ }
+
+ // a peer disconnected event should have occurred.
+ assertThat(peerDisconnected).isTrue()
+ assertThat(peerDisconnected2).isTrue()
+
+ // cancel and wait for the job to terminate.
+ job.cancel()
+ runBlocking {
+ job.join()
+ }
+ // StopRanging should not have been called because not all consumers have finished.
+ assertThat(uwbClient.stopRangingCalled).isFalse()
+
+ // cancel and wait for the job to terminate.
+ job2.cancel()
+ runBlocking {
+ job2.join()
+ }
+ // StopRanging should have been called because all consumers have finished.
+ assertThat(uwbClient.stopRangingCalled).isTrue()
+ }
+
+ @Test
+ public fun testInitSession_multipleSessions_throwsUwbApiException() {
+ val sessionFlow = uwbClientSessionScopeImpl.initSession(rangingParameters)
+ val sessionFlow2 = uwbClientSessionScopeImpl.initSession(rangingParameters)
+
+ val job = CoroutineScope(Dispatchers.Main.immediate).launch {
+ sessionFlow.collect()
+ }
+ runBlocking {
+ // wait for coroutines for flow to start.
+ delay(500)
+ }
+ val job2 = CoroutineScope(Dispatchers.Main.immediate).launch {
+ try {
+ sessionFlow2.collect()
+ Assert.fail()
+ } catch (e: UwbRangingAlreadyStartedException) {
+ // verified the exception was thrown.
+ }
+ }
+ job.cancel()
+ job2.cancel()
+ }
+
+ @Test
+ public fun testInitSession_reusingSession_throwsUwbApiException() {
+ val sessionFlow = uwbClientSessionScopeImpl.initSession(rangingParameters)
+
+ val job = CoroutineScope(Dispatchers.Main.immediate).launch {
+ sessionFlow.collect()
+ }
+ runBlocking {
+ // wait for coroutines for flow to start.
+ delay(500)
+ }
+ // cancel and wait for the job to terminate.
+ job.cancel()
+ runBlocking {
+ job.join()
+ }
+ // StopRanging should not have been called because not all consumers have finished.
+ assertThat(uwbClient.stopRangingCalled).isTrue()
+ val job2 = CoroutineScope(Dispatchers.Main.immediate).launch {
+ try {
+ // Reuse the same session after it was closed.
+ sessionFlow.collect()
+ Assert.fail()
+ } catch (e: UwbRangingAlreadyStartedException) {
+ // verified the exception was thrown.
+ }
+ }
+ job2.cancel()
+ }
+}
\ No newline at end of file
diff --git a/core/uwb/uwb/src/androidTest/java/androidx/core/uwb/mock/TestUwbClient.kt b/core/uwb/uwb/src/androidTest/java/androidx/core/uwb/mock/TestUwbClient.kt
new file mode 100644
index 0000000..afe7418
--- /dev/null
+++ b/core/uwb/uwb/src/androidTest/java/androidx/core/uwb/mock/TestUwbClient.kt
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.core.uwb.mock
+
+import com.google.android.gms.common.api.ApiException
+import com.google.android.gms.common.api.Status
+import com.google.android.gms.common.api.internal.ApiKey
+import com.google.android.gms.nearby.uwb.RangingCapabilities
+import com.google.android.gms.nearby.uwb.RangingMeasurement
+import com.google.android.gms.nearby.uwb.RangingParameters
+import com.google.android.gms.nearby.uwb.RangingPosition
+import com.google.android.gms.nearby.uwb.RangingSessionCallback
+import com.google.android.gms.nearby.uwb.UwbAddress
+import com.google.android.gms.nearby.uwb.UwbClient
+import com.google.android.gms.nearby.uwb.UwbComplexChannel
+import com.google.android.gms.nearby.uwb.UwbDevice
+import com.google.android.gms.nearby.uwb.UwbStatusCodes
+import com.google.android.gms.nearby.uwb.zze
+import com.google.android.gms.tasks.Task
+import com.google.android.gms.tasks.Tasks
+
+/** A default implementation of [UwbClient] used in testing. */
+class TestUwbClient(
+ val complexChannel: UwbComplexChannel,
+ val localAddress: UwbAddress,
+ val rangingCapabilities: RangingCapabilities,
+ val isAvailable: Boolean
+) : UwbClient {
+ var stopRangingCalled = false
+ private set
+ private lateinit var callback: RangingSessionCallback
+ private var startedRanging = false
+ companion object {
+ val rangingPosition = RangingPosition(
+ RangingMeasurement(1, 1.0F), null, null, 20)
+ }
+ override fun getApiKey(): ApiKey<zze> {
+ TODO("Not yet implemented")
+ }
+
+ override fun getComplexChannel(): Task<UwbComplexChannel> {
+ return Tasks.forResult(complexChannel)
+ }
+
+ override fun getLocalAddress(): Task<UwbAddress> {
+ return Tasks.forResult(localAddress)
+ }
+
+ override fun getRangingCapabilities(): Task<RangingCapabilities> {
+ return Tasks.forResult(rangingCapabilities)
+ }
+
+ override fun isAvailable(): Task<Boolean> {
+ return Tasks.forResult(isAvailable)
+ }
+
+ override fun startRanging(
+ parameters: RangingParameters,
+ sessionCallback: RangingSessionCallback
+ ): Task<Void> {
+ if (startedRanging) {
+ throw ApiException(Status(UwbStatusCodes.RANGING_ALREADY_STARTED))
+ }
+ callback = sessionCallback
+ val peer = parameters.peerDevices.first()
+ callback.onRangingResult(peer, rangingPosition)
+ startedRanging = true
+ return Tasks.forResult(null)
+ }
+
+ override fun stopRanging(callback: RangingSessionCallback): Task<Void> {
+ if (stopRangingCalled) {
+ throw RuntimeException("Stop Ranging has already been called.")
+ }
+ stopRangingCalled = true
+ return Tasks.forResult(null)
+ }
+
+ fun disconnectPeer(device: UwbDevice) {
+ callback.onRangingSuspended(device, 0)
+ }
+}
\ No newline at end of file
diff --git a/core/uwb/uwb/src/main/java/androidx/core/uwb/RangingCapabilities.kt b/core/uwb/uwb/src/main/java/androidx/core/uwb/RangingCapabilities.kt
new file mode 100644
index 0000000..1366a64
--- /dev/null
+++ b/core/uwb/uwb/src/main/java/androidx/core/uwb/RangingCapabilities.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.core.uwb
+
+/**
+ * Describes UWB ranging capabilities for the current device.
+ * @property supportsDistance - Whether distance ranging is supported
+ * @property supportsAzimuthalAngle - Whether azimuthal angle of arrival is supported
+ * @property supportsElevationAngle - Whether elevation angle of arrival is supported
+ **/
+class RangingCapabilities(
+ val supportsDistance: Boolean,
+ val supportsAzimuthalAngle: Boolean,
+ val supportsElevationAngle: Boolean
+)
diff --git a/core/uwb/uwb/src/main/java/androidx/core/uwb/RangingMeasurement.kt b/core/uwb/uwb/src/main/java/androidx/core/uwb/RangingMeasurement.kt
new file mode 100644
index 0000000..424669c
--- /dev/null
+++ b/core/uwb/uwb/src/main/java/androidx/core/uwb/RangingMeasurement.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.core.uwb
+
+/** Measurement providing the value and confidence of the ranging.
+ *
+ * @property value the value of this measurement.
+ */
+class RangingMeasurement(val value: Float)
diff --git a/core/uwb/uwb/src/main/java/androidx/core/uwb/RangingParameters.kt b/core/uwb/uwb/src/main/java/androidx/core/uwb/RangingParameters.kt
new file mode 100644
index 0000000..a6aa775
--- /dev/null
+++ b/core/uwb/uwb/src/main/java/androidx/core/uwb/RangingParameters.kt
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.core.uwb
+
+/**
+ * Set of parameters which should be passed to the UWB chip to start ranging.
+ *
+ * @property uwbConfigId
+ * The UWB configuration ID. One ID specifies one fixed set of pre-defined parameters.
+ *
+ * @property sessionId
+ * The ID of the ranging session. If the value is SESSION_ID_UNSET (0), it will
+ * be created from the hash of controller address and complex channel values.
+ *
+ * The same session IDs should be used at both ends (Controller and controlee).
+ *
+ * @property sessionKeyInfo
+ * The session key info to use for the ranging.
+ * If the profile uses STATIC
+ * STS, this byte array is 8-byte long with first two bytes as
+ * Vendor_ID and next six bytes as STATIC_STS_IV.
+ *
+ * The same session keys should be used at both ends (Controller and controlee).
+ *
+ * @property complexChannel
+ * Optional. If device type is ROLE_CONTROLEE then complex channel should be set.
+ *
+ * @property peerDevices
+ * The peers to perform ranging with. If using unicast, length should be 1.
+ *
+ * @property updateRate
+ * The update rate of the ranging data
+ */
+class RangingParameters(
+ val uwbConfigId: Int,
+ val sessionId: Int,
+ val sessionKeyInfo: ByteArray?,
+ val complexChannel: UwbComplexChannel?,
+ val peerDevices: List<UwbDevice>,
+ val updateRate: Int
+) {
+
+ companion object {
+
+ /**
+ * Pre-defined unicast STATIC STS DS-TWR ranging, deferred mode, ranging
+ * interval 240 ms.
+ *
+ * <p> Typical use case: device tracking tags
+ */
+ @JvmField
+ internal val UWB_CONFIG_ID_1 = 1
+
+ /**
+ * Pre-defined one-to-many STATIC STS DS-TWR ranging, deferred mode, ranging
+ * interval 200 ms.
+ *
+ * <p> Typical use case: smart phone interacts with many smart devices
+ */
+ @JvmField
+ internal val UWB_CONFIG_ID_2 = 2
+
+ /** Same as CONFIG_ID_1, except AoA data is not reported. */
+ @JvmField
+ internal val UWB_CONFIG_ID_3 = 3
+
+ /**
+ * When the screen is on, the reporting interval is hundreds of milliseconds.
+ * When the screen is off, the reporting interval is a few seconds.
+ */
+ @JvmField
+ val RANGING_UPDATE_RATE_AUTOMATIC = 1
+
+ /**
+ * The reporting interval is the same as in the AUTOMATIC screen-off case. The
+ * The power consumption is optimized by turning off the radio between ranging
+ * reports. (The implementation is hardware and software dependent and it may
+ * change between different versions.)
+ */
+ @JvmField
+ val RANGING_UPDATE_RATE_INFREQUENT = 2
+
+ /**
+ * The reporting interval is the same as in the AUTOMATIC screen-on case.
+ *
+ * The actual reporting interval is UwbConfigId related. Different
+ * configuration may use different values. (The default reporting interval at INFREQUENT mode is 4 seconds)
+ */
+ @JvmField
+ val RANGING_UPDATE_RATE_FREQUENT = 3
+ }
+}
diff --git a/core/uwb/uwb/src/main/java/androidx/core/uwb/RangingPosition.kt b/core/uwb/uwb/src/main/java/androidx/core/uwb/RangingPosition.kt
new file mode 100644
index 0000000..5ae737b
--- /dev/null
+++ b/core/uwb/uwb/src/main/java/androidx/core/uwb/RangingPosition.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.core.uwb
+
+/**
+ * Position of a device during ranging.
+ *
+ * @property distance
+ * The line-of-sight distance in meters of the ranging device, or null if not
+ * available.
+ *
+ * @property azimuth
+ * The azimuth angle in degrees of the ranging device, or null if not available.
+ * The range is [-90, 90].
+ *
+ * @property elevation
+ * The elevation angle in degrees of the ranging device, or null if not
+ * available. The range is [-90, 90].
+ *
+ * @property elapsedRealtimeNanos
+ * The elapsed realtime in nanos from when the system booted up to this position
+ * measurement.
+ */
+class RangingPosition(
+ val distance: RangingMeasurement,
+ val azimuth: RangingMeasurement?,
+ val elevation: RangingMeasurement?,
+ val elapsedRealtimeNanos: Long
+)
diff --git a/core/uwb/uwb/src/main/java/androidx/core/uwb/RangingResult.kt b/core/uwb/uwb/src/main/java/androidx/core/uwb/RangingResult.kt
new file mode 100644
index 0000000..e43c336
--- /dev/null
+++ b/core/uwb/uwb/src/main/java/androidx/core/uwb/RangingResult.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.core.uwb
+
+/** A data class for ranging result update. */
+abstract class RangingResult {
+ abstract val device: UwbDevice
+}
+
+/** A ranging result with the device position update. */
+class RangingResultPosition(
+ override val device: UwbDevice,
+ val position: RangingPosition
+) : RangingResult()
+
+/** A ranging result with peer disconnected status update. */
+class RangingResultPeerDisconnected(override val device: UwbDevice) : RangingResult()
\ No newline at end of file
diff --git a/core/uwb/uwb/src/main/java/androidx/core/uwb/UwbAddress.kt b/core/uwb/uwb/src/main/java/androidx/core/uwb/UwbAddress.kt
new file mode 100644
index 0000000..f1db3ca
--- /dev/null
+++ b/core/uwb/uwb/src/main/java/androidx/core/uwb/UwbAddress.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.core.uwb
+
+import com.google.common.io.BaseEncoding
+
+/**
+ * Represents a UWB address.
+ *
+ * @property address the device address (eg, MAC address).
+ */
+class UwbAddress(val address: ByteArray) {
+
+ /** @throws an [IllegalArgumentException] if address is invalid. */
+ constructor(address: String) : this(BASE_16_SEPARATOR.decode(address))
+
+ companion object {
+ private val BASE_16_SEPARATOR: BaseEncoding = BaseEncoding.base16().withSeparator(":", 2)
+ }
+
+ /** Checks that two UwbAddresses are equal. */
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (javaClass != other?.javaClass) return false
+
+ other as UwbAddress
+
+ if (!address.contentEquals(other.address)) return false
+
+ return true
+ }
+
+ /** Returns the hashcode. */
+ override fun hashCode(): Int {
+ return address.contentHashCode()
+ }
+
+ /** Returns the string format of [UwbAddress]. */
+ override fun toString(): String {
+ return BASE_16_SEPARATOR.encode(address)
+ }
+}
diff --git a/core/uwb/uwb/src/main/java/androidx/core/uwb/UwbClientSessionScope.kt b/core/uwb/uwb/src/main/java/androidx/core/uwb/UwbClientSessionScope.kt
new file mode 100644
index 0000000..0151426
--- /dev/null
+++ b/core/uwb/uwb/src/main/java/androidx/core/uwb/UwbClientSessionScope.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.core.uwb
+
+import kotlinx.coroutines.flow.Flow
+
+/** Interface for client session that is established between nearby UWB devices. */
+interface UwbClientSessionScope {
+ /**
+ * Returns a flow of [RangingResult]. Consuming the flow will initiate the UWB ranging and only
+ * one flow can be initiated. To consume the flow from multiple consumers,
+ * convert the flow to a SharedFlow.
+ *
+ * @throws [UwbRangingAlreadyStartedException] if a new flow was consumed again after the UWB
+ * ranging is already initiated.
+ *
+ * @throws [UwbSystemCallbackException] if the backend UWB system has resulted in an error.
+ *
+ * @throws [SecurityException] if ranging does not have the
+ * android.permission.UWB_RANGING permission. Apps must
+ * have requested and been granted this permission before calling this method.
+ *
+ * @throws [IllegalArgumentException] if the client starts a controlee session
+ * without setting complex channel and peer address.
+ */
+ fun initSession(parameters: RangingParameters): Flow<RangingResult>
+
+ /** Returns the [RangingCapabilities] which the device supports. */
+ val rangingCapabilities: RangingCapabilities
+
+ /**
+ * A local address can only be used for a single ranging session. After
+ * a ranging session is ended, a new address will be allocated.
+ *
+ * Ranging session duration may also be limited to prevent addresses
+ * from being used for too long. In this case, your ranging session would be
+ * suspended and clients would need to exchange the new address with their peer
+ * before starting again.
+ */
+ val localAddress: UwbAddress
+}
\ No newline at end of file
diff --git a/core/uwb/uwb/src/main/java/androidx/core/uwb/UwbComplexChannel.kt b/core/uwb/uwb/src/main/java/androidx/core/uwb/UwbComplexChannel.kt
new file mode 100644
index 0000000..644dd5b
--- /dev/null
+++ b/core/uwb/uwb/src/main/java/androidx/core/uwb/UwbComplexChannel.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.core.uwb
+
+/**
+ * Represents the channel which a UWB device is currently active on.
+ *
+ * @property channel the current channel for the device.
+ * @property preambleIndex the current preamble index for the device.
+ */
+class UwbComplexChannel(val channel: Int, val preambleIndex: Int)
diff --git a/core/uwb/uwb/src/main/java/androidx/core/uwb/UwbControleeSessionScope.kt b/core/uwb/uwb/src/main/java/androidx/core/uwb/UwbControleeSessionScope.kt
new file mode 100644
index 0000000..5e02a8e
--- /dev/null
+++ b/core/uwb/uwb/src/main/java/androidx/core/uwb/UwbControleeSessionScope.kt
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.core.uwb
+
+/** Interface for controlee client session that is established between nearby UWB devices. */
+interface UwbControleeSessionScope : UwbClientSessionScope
\ No newline at end of file
diff --git a/core/uwb/uwb/src/main/java/androidx/core/uwb/UwbDevice.kt b/core/uwb/uwb/src/main/java/androidx/core/uwb/UwbDevice.kt
new file mode 100644
index 0000000..da8a982
--- /dev/null
+++ b/core/uwb/uwb/src/main/java/androidx/core/uwb/UwbDevice.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.core.uwb
+
+/**
+ * Represents a UWB device.
+ *
+ * @property address the device address (e.g., MAC address).
+ */
+class UwbDevice(val address: UwbAddress) {
+
+ companion object {
+ /**
+ * Creates a new UwbDevice for a given address.
+ *
+ * @throws an [IllegalArgumentException] if address is invalid.
+ */
+ @JvmStatic
+ fun createForAddress(address: String): UwbDevice {
+ return UwbDevice(UwbAddress(address))
+ }
+
+ /** Creates a new UwbDevice for a given address. */
+ @JvmStatic
+ fun createForAddress(address: ByteArray): UwbDevice {
+ return UwbDevice(UwbAddress(address))
+ }
+ }
+}
diff --git a/core/uwb/uwb/src/main/java/androidx/core/uwb/UwbManager.kt b/core/uwb/uwb/src/main/java/androidx/core/uwb/UwbManager.kt
new file mode 100644
index 0000000..cf21af3
--- /dev/null
+++ b/core/uwb/uwb/src/main/java/androidx/core/uwb/UwbManager.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.core.uwb
+
+import android.content.Context
+import androidx.core.uwb.impl.UwbManagerImpl
+
+/**
+ * Interface for getting UWB capabilities and interacting with nearby UWB devices to perform
+ * ranging.
+ */
+interface UwbManager {
+ companion object {
+
+ /** Creates a new UwbManager that is used for creating controlee client sessions. */
+ @JvmStatic
+ fun getInstance(context: Context): UwbManager {
+ return UwbManagerImpl(context)
+ }
+ }
+
+ /**
+ * Establishes a new [UwbClientSessionScope] that tracks the lifecycle of a UWB connection.
+ *
+ * @throws [UwbServiceNotAvailableException] if the UWB is turned off.
+ * @throws [UwbHardwareNotAvailableException] if the hardware is not available on the device.
+ */
+ suspend fun <R> clientSessionScope(sessionHandler: suspend UwbClientSessionScope.() -> R): R
+}
\ No newline at end of file
diff --git a/core/uwb/uwb/src/main/java/androidx/core/uwb/exceptions/UwbApiException.kt b/core/uwb/uwb/src/main/java/androidx/core/uwb/exceptions/UwbApiException.kt
new file mode 100644
index 0000000..0dc12f7
--- /dev/null
+++ b/core/uwb/uwb/src/main/java/androidx/core/uwb/exceptions/UwbApiException.kt
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.core.uwb.exceptions
+
+/** Exception class for Uwb service errors. */
+open class UwbApiException(message: String) : Exception(message)
diff --git a/core/uwb/uwb/src/main/java/androidx/core/uwb/exceptions/UwbBackgroundPolicyException.kt b/core/uwb/uwb/src/main/java/androidx/core/uwb/exceptions/UwbBackgroundPolicyException.kt
new file mode 100644
index 0000000..1d8d7e2
--- /dev/null
+++ b/core/uwb/uwb/src/main/java/androidx/core/uwb/exceptions/UwbBackgroundPolicyException.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.core.uwb.exceptions
+
+/**
+ * Calls to the UWB service fail for background systems. Only foreground
+ * systems can call the UWB service.
+ */
+class UwbBackgroundPolicyException(message: String) : UwbApiException(message)
\ No newline at end of file
diff --git a/core/uwb/uwb/src/main/java/androidx/core/uwb/exceptions/UwbHardwareNotAvailableException.kt b/core/uwb/uwb/src/main/java/androidx/core/uwb/exceptions/UwbHardwareNotAvailableException.kt
new file mode 100644
index 0000000..72ad22d
--- /dev/null
+++ b/core/uwb/uwb/src/main/java/androidx/core/uwb/exceptions/UwbHardwareNotAvailableException.kt
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.core.uwb.exceptions
+
+/** The uwb hardware is not available on the device. */
+class UwbHardwareNotAvailableException(message: String) : UwbApiException(message)
\ No newline at end of file
diff --git a/core/uwb/uwb/src/main/java/androidx/core/uwb/exceptions/UwbRangingAlreadyStartedException.kt b/core/uwb/uwb/src/main/java/androidx/core/uwb/exceptions/UwbRangingAlreadyStartedException.kt
new file mode 100644
index 0000000..8a300b0
--- /dev/null
+++ b/core/uwb/uwb/src/main/java/androidx/core/uwb/exceptions/UwbRangingAlreadyStartedException.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.core.uwb.exceptions
+
+/**
+ * The ranging has already started for the [UwbClientSessionScope], but still received another
+ * request to start ranging. You cannot reuse a [UwbClientSessionScope] for multiple ranging
+ * sessions. To have multiple consumers for a single ranging session, use a [SharedFlow].
+ */
+class UwbRangingAlreadyStartedException(message: String) : UwbApiException(message)
\ No newline at end of file
diff --git a/core/uwb/uwb/src/main/java/androidx/core/uwb/exceptions/UwbServiceNotAvailableException.kt b/core/uwb/uwb/src/main/java/androidx/core/uwb/exceptions/UwbServiceNotAvailableException.kt
new file mode 100644
index 0000000..ad1db5b
--- /dev/null
+++ b/core/uwb/uwb/src/main/java/androidx/core/uwb/exceptions/UwbServiceNotAvailableException.kt
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.core.uwb.exceptions
+
+/** The service was not available on the device. */
+class UwbServiceNotAvailableException(message: String) : UwbApiException(message)
\ No newline at end of file
diff --git a/core/uwb/uwb/src/main/java/androidx/core/uwb/exceptions/UwbSystemCallbackException.kt b/core/uwb/uwb/src/main/java/androidx/core/uwb/exceptions/UwbSystemCallbackException.kt
new file mode 100644
index 0000000..32546ff
--- /dev/null
+++ b/core/uwb/uwb/src/main/java/androidx/core/uwb/exceptions/UwbSystemCallbackException.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.core.uwb.exceptions
+
+/**
+ * Unusual failures happened in UWB system callback, such as stopping
+ * ranging or removing a known controlee failed.
+ */
+class UwbSystemCallbackException(message: String) : UwbApiException(message)
\ No newline at end of file
diff --git a/core/uwb/uwb/src/main/java/androidx/core/uwb/helper/UwbHelper.kt b/core/uwb/uwb/src/main/java/androidx/core/uwb/helper/UwbHelper.kt
new file mode 100644
index 0000000..9c5a93e
--- /dev/null
+++ b/core/uwb/uwb/src/main/java/androidx/core/uwb/helper/UwbHelper.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.core.uwb.helper
+
+import android.content.Context
+import androidx.core.uwb.exceptions.UwbHardwareNotAvailableException
+import androidx.core.uwb.exceptions.UwbRangingAlreadyStartedException
+import androidx.core.uwb.exceptions.UwbServiceNotAvailableException
+import androidx.core.uwb.exceptions.UwbSystemCallbackException
+import com.google.android.gms.common.api.ApiException
+import com.google.android.gms.nearby.uwb.UwbStatusCodes
+
+internal const val UWB_FEATURE = "android.hardware.uwb"
+
+/** Returns whether the Uwb System Feature is available on the device. */
+internal fun isSystemFeatureAvailable(context: Context): Boolean {
+ return context.packageManager.hasSystemFeature(UWB_FEATURE)
+}
+
+/** Checks if the uwb system feature is supported and throws an UwbApiException otherwise. */
+internal fun checkSystemFeature(context: Context) {
+ if (!isSystemFeatureAvailable(context)) {
+ throw UwbHardwareNotAvailableException("UWB Hardware is not available on this device.")
+ }
+}
+
+internal fun handleApiException(e: ApiException) {
+ when (e.statusCode) {
+ UwbStatusCodes.INVALID_API_CALL ->
+ throw IllegalArgumentException("Illegal api call was received.")
+ UwbStatusCodes.MISSING_PERMISSION_UWB_RANGING ->
+ throw SecurityException("Missing permission to start ranging.")
+ UwbStatusCodes.RANGING_ALREADY_STARTED ->
+ throw UwbRangingAlreadyStartedException("Ranging has already started for the" +
+ " clientSessionScope.")
+ UwbStatusCodes.SERVICE_NOT_AVAILABLE ->
+ throw UwbServiceNotAvailableException("UWB Service is not available.")
+ UwbStatusCodes.UWB_SYSTEM_CALLBACK_FAILURE ->
+ throw UwbSystemCallbackException("UWB system has failed to deliver ")
+ else ->
+ throw RuntimeException("Unexpected error. This indicates that the library is not " +
+ "up-to-date with the service backend.")
+ }
+}
diff --git a/core/uwb/uwb/src/main/java/androidx/core/uwb/impl/UwbClientSessionScopeImpl.kt b/core/uwb/uwb/src/main/java/androidx/core/uwb/impl/UwbClientSessionScopeImpl.kt
new file mode 100644
index 0000000..1dada71
--- /dev/null
+++ b/core/uwb/uwb/src/main/java/androidx/core/uwb/impl/UwbClientSessionScopeImpl.kt
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.core.uwb.impl
+
+import android.util.Log
+import androidx.core.uwb.RangingCapabilities
+import androidx.core.uwb.RangingMeasurement
+import androidx.core.uwb.RangingParameters
+import androidx.core.uwb.RangingResultPeerDisconnected
+import androidx.core.uwb.RangingResultPosition
+import androidx.core.uwb.UwbAddress
+import androidx.core.uwb.UwbControleeSessionScope
+import androidx.core.uwb.exceptions.UwbRangingAlreadyStartedException
+import com.google.android.gms.common.api.ApiException
+import com.google.android.gms.nearby.uwb.RangingPosition
+import com.google.android.gms.nearby.uwb.RangingSessionCallback
+import com.google.android.gms.nearby.uwb.UwbClient
+import com.google.android.gms.nearby.uwb.UwbComplexChannel
+import com.google.android.gms.nearby.uwb.UwbDevice
+import kotlinx.coroutines.flow.callbackFlow
+import androidx.core.uwb.helper.handleApiException
+import kotlinx.coroutines.channels.awaitClose
+
+internal class UwbClientSessionScopeImpl(
+ private val uwbClient: UwbClient,
+ override val rangingCapabilities: RangingCapabilities,
+ override val localAddress: UwbAddress
+) : UwbControleeSessionScope {
+ companion object {
+ private const val TAG = "UwbClientSessionScope"
+ }
+ private var sessionStarted = false
+
+ override fun initSession(parameters: RangingParameters) = callbackFlow {
+ if (sessionStarted) {
+ throw UwbRangingAlreadyStartedException("Ranging has already started. To initiate " +
+ "a new ranging session, create a new client session scope.")
+ }
+
+ val configId = when (parameters.uwbConfigId) {
+ RangingParameters.UWB_CONFIG_ID_1 ->
+ com.google.android.gms.nearby.uwb.RangingParameters.UwbConfigId.CONFIG_ID_1
+ RangingParameters.UWB_CONFIG_ID_3 ->
+ com.google.android.gms.nearby.uwb.RangingParameters.UwbConfigId.CONFIG_ID_3
+ else ->
+ com.google.android.gms.nearby.uwb.RangingParameters.UwbConfigId.UNKNOWN
+ }
+ val updateRate = when (parameters.updateRate) {
+ RangingParameters.RANGING_UPDATE_RATE_AUTOMATIC ->
+ com.google.android.gms.nearby.uwb.RangingParameters.RangingUpdateRate.AUTOMATIC
+ RangingParameters.RANGING_UPDATE_RATE_FREQUENT ->
+ com.google.android.gms.nearby.uwb.RangingParameters.RangingUpdateRate.FREQUENT
+ RangingParameters.RANGING_UPDATE_RATE_INFREQUENT ->
+ com.google.android.gms.nearby.uwb.RangingParameters.RangingUpdateRate.INFREQUENT
+ else ->
+ com.google.android.gms.nearby.uwb.RangingParameters.RangingUpdateRate.UNKNOWN
+ }
+ val parametersBuilder = com.google.android.gms.nearby.uwb.RangingParameters.Builder()
+ .setSessionId(parameters.sessionId)
+ .setUwbConfigId(configId)
+ .setRangingUpdateRate(updateRate)
+ .setSessionKeyInfo(parameters.sessionKeyInfo)
+ .setUwbConfigId(parameters.uwbConfigId)
+ .setComplexChannel(
+ parameters.complexChannel?.let {
+ UwbComplexChannel.Builder()
+ .setChannel(it.channel)
+ .setPreambleIndex(it.preambleIndex)
+ .build()
+ })
+ for (peer in parameters.peerDevices) {
+ parametersBuilder.addPeerDevice(UwbDevice.createForAddress(peer.address.address))
+ }
+ val callback =
+ object : RangingSessionCallback {
+ override fun onRangingInitialized(device: UwbDevice) {
+ Log.i(TAG, "Started UWB ranging.")
+ }
+
+ override fun onRangingResult(device: UwbDevice, position: RangingPosition) {
+ trySend(
+ RangingResultPosition(
+ androidx.core.uwb.UwbDevice(UwbAddress(device.address.address)),
+ androidx.core.uwb.RangingPosition(
+ RangingMeasurement(position.distance.value),
+ position.azimuth?.let {
+ RangingMeasurement(it.value)
+ },
+ position.elevation?.let {
+ RangingMeasurement(it.value)
+ },
+ position.elapsedRealtimeNanos
+ )
+ )
+ )
+ }
+
+ override fun onRangingSuspended(device: UwbDevice, reason: Int) {
+ trySend(
+ RangingResultPeerDisconnected(
+ androidx.core.uwb.UwbDevice(UwbAddress(device.address.address))
+ )
+ )
+ }
+ }
+
+ try {
+ uwbClient.startRanging(parametersBuilder.build(), callback)
+ sessionStarted = true
+ } catch (e: ApiException) {
+ handleApiException(e)
+ }
+
+ awaitClose {
+ try {
+ uwbClient.stopRanging(callback)
+ } catch (e: ApiException) {
+ handleApiException(e)
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/core/uwb/uwb/src/main/java/androidx/core/uwb/impl/UwbManagerImpl.kt b/core/uwb/uwb/src/main/java/androidx/core/uwb/impl/UwbManagerImpl.kt
new file mode 100644
index 0000000..09de8dc
--- /dev/null
+++ b/core/uwb/uwb/src/main/java/androidx/core/uwb/impl/UwbManagerImpl.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.core.uwb.impl
+
+import android.content.Context
+import androidx.core.uwb.RangingCapabilities
+import androidx.core.uwb.UwbAddress
+import androidx.core.uwb.UwbClientSessionScope
+import androidx.core.uwb.UwbManager
+import com.google.android.gms.common.api.ApiException
+import com.google.android.gms.nearby.Nearby
+import kotlinx.coroutines.tasks.await
+import androidx.core.uwb.helper.checkSystemFeature
+import androidx.core.uwb.helper.handleApiException
+
+internal class UwbManagerImpl(val context: Context) : UwbManager {
+ override suspend fun <R> clientSessionScope(
+ sessionHandler: suspend UwbClientSessionScope.() -> R
+ ): R {
+ // Check whether UWB hardware is available on the device.
+ checkSystemFeature(context)
+ val uwbClient = Nearby.getUwbControleeClient(context)
+ val localAddress: com.google.android.gms.nearby.uwb.UwbAddress
+ val rangingCapabilities: com.google.android.gms.nearby.uwb.RangingCapabilities
+ try {
+ localAddress = uwbClient.localAddress.await()
+ rangingCapabilities = uwbClient.rangingCapabilities.await()
+ } catch (e: ApiException) {
+ handleApiException(e)
+ throw RuntimeException("Unexpected error. This indicates that the library is not " +
+ "up-to-date with the service backend.")
+ }
+ return UwbClientSessionScopeImpl(
+ uwbClient,
+ RangingCapabilities(
+ rangingCapabilities.supportsDistance(),
+ rangingCapabilities.supportsAzimuthalAngle(),
+ rangingCapabilities.supportsElevationAngle()),
+ UwbAddress(localAddress.address)
+ ).sessionHandler()
+ }
+}
\ No newline at end of file