summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--framework/jarjar-rules.txt4
-rw-r--r--framework/java/android/uwb/UwbManager.java8
-rw-r--r--indev_uwb_adaptation/jni/src/api.rs10
-rw-r--r--indev_uwb_adaptation/jni/src/callback.rs39
-rw-r--r--indev_uwb_adaptation/jni/src/context.rs2
-rw-r--r--indev_uwb_adaptation/jni/src/object_mapping.rs71
-rw-r--r--service/java/com/android/server/uwb/UwbConfigStore.java4
-rw-r--r--service/java/com/android/server/uwb/UwbCountryCode.java2
-rw-r--r--service/java/com/android/server/uwb/UwbInjector.java10
-rw-r--r--service/java/com/android/server/uwb/UwbMetrics.java9
-rw-r--r--service/java/com/android/server/uwb/UwbServiceCore.java76
-rw-r--r--service/java/com/android/server/uwb/UwbServiceImpl.java10
-rw-r--r--service/java/com/android/server/uwb/UwbSessionManager.java239
-rw-r--r--service/java/com/android/server/uwb/UwbSessionNotificationManager.java13
-rw-r--r--service/java/com/android/server/uwb/UwbSettingsStore.java4
-rw-r--r--service/java/com/android/server/uwb/UwbShellCommand.java1
-rw-r--r--service/java/com/android/server/uwb/UwbTestUtils.java40
-rw-r--r--service/java/com/android/server/uwb/advertisement/UwbAdvertiseManager.java5
-rw-r--r--service/java/com/android/server/uwb/correction/UwbFilterEngine.java209
-rw-r--r--service/java/com/android/server/uwb/correction/filtering/IFilter.java75
-rw-r--r--service/java/com/android/server/uwb/correction/filtering/IPositionFilter.java66
-rw-r--r--service/java/com/android/server/uwb/correction/filtering/MAFilter.java237
-rw-r--r--service/java/com/android/server/uwb/correction/filtering/MARotationFilter.java99
-rw-r--r--service/java/com/android/server/uwb/correction/filtering/PositionFilterImpl.java135
-rw-r--r--service/java/com/android/server/uwb/correction/filtering/Sample.java50
-rw-r--r--service/java/com/android/server/uwb/correction/math/AoAVector.java34
-rw-r--r--service/java/com/android/server/uwb/correction/math/MathHelper.java4
-rw-r--r--service/java/com/android/server/uwb/correction/math/Matrix.java2
-rw-r--r--service/java/com/android/server/uwb/correction/math/Pose.java10
-rw-r--r--service/java/com/android/server/uwb/correction/math/Quaternion.java10
-rw-r--r--service/java/com/android/server/uwb/correction/math/SphericalVector.java18
-rw-r--r--service/java/com/android/server/uwb/correction/math/Vector3.java8
-rw-r--r--service/java/com/android/server/uwb/correction/pose/GyroPoseSource.java151
-rw-r--r--service/java/com/android/server/uwb/correction/pose/IPoseSource.java95
-rw-r--r--service/java/com/android/server/uwb/correction/pose/IntegPoseSource.java176
-rw-r--r--service/java/com/android/server/uwb/correction/pose/PoseEventListener.java33
-rw-r--r--service/java/com/android/server/uwb/correction/pose/PoseSourceBase.java135
-rw-r--r--service/java/com/android/server/uwb/correction/pose/RotationPoseSource.java121
-rw-r--r--service/java/com/android/server/uwb/correction/pose/SixDOFPoseSource.java134
-rw-r--r--service/java/com/android/server/uwb/correction/primers/AoAPrimer.java59
-rw-r--r--service/java/com/android/server/uwb/correction/primers/ElevationPrimer.java76
-rw-r--r--service/java/com/android/server/uwb/correction/primers/FovPrimer.java88
-rw-r--r--service/java/com/android/server/uwb/correction/primers/IPrimer.java42
-rw-r--r--service/java/com/android/server/uwb/data/UwbUciConstants.java12
-rw-r--r--service/java/com/android/server/uwb/jni/NativeUwbManager.java8
-rw-r--r--service/java/com/android/server/uwb/params/FiraEncoder.java51
-rw-r--r--service/java/com/android/server/uwb/params/TlvBuffer.java14
-rw-r--r--service/java/com/android/server/uwb/util/LruList.java103
-rwxr-xr-xservice/java/com/android/server/uwb/util/UwbUtil.java21
-rw-r--r--service/support_lib/src/com/google/uwb/support/ccc/CccOpenRangingParams.java14
-rw-r--r--service/support_lib/src/com/google/uwb/support/ccc/CccParams.java8
-rw-r--r--service/support_lib/src/com/google/uwb/support/fira/FiraOpenSessionParams.java56
-rw-r--r--service/support_lib/src/com/google/uwb/support/fira/FiraParams.java27
-rw-r--r--service/support_lib/test/CccTests.java1
-rw-r--r--service/support_lib/test/FiraTests.java12
-rw-r--r--service/tests/src/com/android/server/uwb/UwbConfigurationManagerTest.java3
-rw-r--r--service/tests/src/com/android/server/uwb/UwbServiceCoreTest.java120
-rw-r--r--service/tests/src/com/android/server/uwb/UwbSessionManagerTest.java345
-rw-r--r--service/tests/src/com/android/server/uwb/UwbSessionNotificationManagerTest.java8
-rw-r--r--service/tests/src/com/android/server/uwb/advertisement/UwbAdvertiseManagerTest.java4
-rw-r--r--service/tests/src/com/android/server/uwb/correction/TestHelpers.java3
-rw-r--r--service/tests/src/com/android/server/uwb/correction/UwbFilterEngineTest.java123
-rw-r--r--service/tests/src/com/android/server/uwb/correction/filtering/MAFilterTest.java94
-rw-r--r--service/tests/src/com/android/server/uwb/correction/filtering/MARotationFilterTest.java54
-rw-r--r--service/tests/src/com/android/server/uwb/correction/filtering/NullFilter.java65
-rw-r--r--service/tests/src/com/android/server/uwb/correction/math/AoAVectorTest.java6
-rw-r--r--service/tests/src/com/android/server/uwb/correction/math/MathHelperTest.java2
-rw-r--r--service/tests/src/com/android/server/uwb/correction/math/QuaternionTest.java4
-rw-r--r--service/tests/src/com/android/server/uwb/correction/math/SphericalVectorTest.java6
-rw-r--r--service/tests/src/com/android/server/uwb/correction/math/Vector3Tests.java74
-rw-r--r--service/tests/src/com/android/server/uwb/correction/pose/NullPoseSource.java64
-rw-r--r--service/tests/src/com/android/server/uwb/correction/primers/AoAPrimerTest.java67
-rw-r--r--service/tests/src/com/android/server/uwb/correction/primers/ElevationPrimerTest.java83
-rw-r--r--service/tests/src/com/android/server/uwb/correction/primers/FoVPrimerTest.java110
-rw-r--r--service/tests/src/com/android/server/uwb/correction/primers/NullPrimer.java54
-rw-r--r--service/tests/src/com/android/server/uwb/params/FiraEncoderTest.java19
-rw-r--r--service/uci/jni/Android.bp3
-rw-r--r--service/uci/jni/src/notification_manager_android.rs212
-rw-r--r--service/uci/jni/src/uci_jni_android_new.rs197
-rw-r--r--tests/cts/hostsidetests/multidevices/uwb/lib/uwb_ranging_params.py6
-rw-r--r--tests/cts/hostsidetests/multidevices/uwb/ranging_test.py3
-rw-r--r--tests/cts/hostsidetests/multidevices/uwb/snippet/UwbManagerSnippet.java26
-rw-r--r--tests/cts/hostsidetests/multidevices/uwb/uwb_manager_test.py3
-rw-r--r--tests/cts/tests/Android.bp1
-rw-r--r--tests/cts/tests/src/android/uwb/cts/UwbManagerTest.java1
85 files changed, 4193 insertions, 508 deletions
diff --git a/framework/jarjar-rules.txt b/framework/jarjar-rules.txt
index 375d9c6b..589e47b2 100644
--- a/framework/jarjar-rules.txt
+++ b/framework/jarjar-rules.txt
@@ -1,6 +1,8 @@
## used by service-uwb ##
# Statically included annotations.
rule androidx.annotation.** com.android.x.uwb.@0
+# Statically linked cbor library
+rule co.nstant.in.cbor.** com.android.x.uwb.@0
# Statically included module utils.
rule com.android.modules.utils.** com.android.x.uwb.@0
# Statically included HAL stubs.
@@ -12,5 +14,7 @@ rule com.google.uwb.** com.android.x.uwb.@0
rule com.android.internal.util.** com.android.x.uwb.@0
# Use our statically linked protobuf library
rule com.google.protobuf.** com.android.x.uwb.@0
+# Statically linked bouncy castle library
+rule org.bouncycastle.** com.android.x.uwb.@0
## used by both framework-uwb and service-uwb ##
diff --git a/framework/java/android/uwb/UwbManager.java b/framework/java/android/uwb/UwbManager.java
index 1b22c2e3..154b9fbd 100644
--- a/framework/java/android/uwb/UwbManager.java
+++ b/framework/java/android/uwb/UwbManager.java
@@ -382,7 +382,7 @@ public final class UwbManager {
@NonNull RangingReport rangingReport);
/**
- * Invoked when requesting a check on pointed target.
+ * Invoked to check pointed target decision by Oem.
*
* @param pointedTargetBundle pointed target params
* @return Oem pointed status
@@ -981,13 +981,15 @@ public final class UwbManager {
public static final int MESSAGE_TYPE_COMMAND = 1;
/**
* @hide
- * Message Type value reserved for testing.
+ * Message Type for C-APDU (Command - Application Protocol Data Unit),
+ * used for communication with secure component.
*/
public static final int MESSAGE_TYPE_TEST_1 = 4;
/**
* @hide
- * Message Type value reserved for testing.
+ * Message Type for R-APDU (Response - Application Protocol Data Unit),
+ * used for communication with secure component.
*/
public static final int MESSAGE_TYPE_TEST_2 = 5;
diff --git a/indev_uwb_adaptation/jni/src/api.rs b/indev_uwb_adaptation/jni/src/api.rs
index d7e6150f..1c1d7dd0 100644
--- a/indev_uwb_adaptation/jni/src/api.rs
+++ b/indev_uwb_adaptation/jni/src/api.rs
@@ -17,8 +17,8 @@
//! Internally after the UWB core service is instantiated, the pointer to the service is saved
//! on the calling Java side.
use jni::objects::{GlobalRef, JObject, JValue};
-use jni::signature::JavaType;
-use jni::sys::{jboolean, jbyte, jbyteArray, jint, jlong, jobject};
+use jni::signature::ReturnType;
+use jni::sys::{jboolean, jbyte, jbyteArray, jint, jlong, jobject, jvalue};
use jni::JNIEnv;
use log::{debug, error};
use num_traits::FromPrimitive;
@@ -425,7 +425,7 @@ fn get_power_stats(ctx: JniContext) -> Result<jobject> {
let power_stats = uwb_service.android_get_power_stats()?;
let ps_jni = PowerStatsJni::try_from(PowerStatsWithEnv::new(ctx.env, power_stats))?;
- Ok(ps_jni.jni_context.obj.into_inner())
+ Ok(ps_jni.jni_context.obj.into_raw())
}
fn get_uwb_service(ctx: JniContext) -> Result<&mut UwbServiceWrapper> {
@@ -471,8 +471,8 @@ fn get_class_loader_obj(env: &JNIEnv) -> Result<GlobalRef> {
let class_loader = env.call_method_unchecked(
uwb_service_core_class,
get_class_loader_method,
- JavaType::Object("java/lang/ClassLoader".into()),
- &[JValue::Void],
+ ReturnType::Object,
+ &[jvalue::from(JValue::Void)],
)?;
let class_loader_jobject = class_loader.l()?;
Ok(env.new_global_ref(class_loader_jobject)?)
diff --git a/indev_uwb_adaptation/jni/src/callback.rs b/indev_uwb_adaptation/jni/src/callback.rs
index 706af6fe..f0b974ba 100644
--- a/indev_uwb_adaptation/jni/src/callback.rs
+++ b/indev_uwb_adaptation/jni/src/callback.rs
@@ -20,6 +20,7 @@ use std::collections::HashMap;
use jni::objects::{GlobalRef, JClass, JMethodID, JObject, JValue};
use jni::signature::TypeSignature;
+use jni::sys::jvalue;
use jni::{AttachGuard, JavaVM};
use log::error;
@@ -57,7 +58,7 @@ pub struct UwbServiceCallbackImpl {
env: AttachGuard<'static>,
class_loader_obj: GlobalRef,
callback_obj: GlobalRef,
- jmethod_id_map: HashMap<String, JMethodID<'static>>,
+ jmethod_id_map: HashMap<String, JMethodID>,
jclass_map: HashMap<String, GlobalRef>,
}
@@ -77,6 +78,8 @@ impl UwbServiceCallbackImpl {
}
fn find_local_class(&mut self, class_name: &str) -> Result<GlobalRef> {
+ let class_name_jobject = *self.env.new_string(class_name)?;
+
let jclass = match self.jclass_map.get(class_name) {
Some(jclass) => jclass.clone(),
None => {
@@ -86,7 +89,7 @@ impl UwbServiceCallbackImpl {
self.class_loader_obj.as_obj(),
"findClass",
"(Ljava/lang/String;)Ljava/lang/Class;",
- &[JValue::Object(JObject::from(self.env.new_string(class_name)?))],
+ &[JValue::Object(class_name_jobject)],
)?
.l()?;
@@ -99,7 +102,7 @@ impl UwbServiceCallbackImpl {
Ok(jclass)
}
- fn cached_jni_call(&mut self, name: &str, sig: &str, args: &[JValue]) -> Result<()> {
+ fn cached_jni_call(&mut self, name: &str, sig: &str, args: &[jvalue]) -> Result<()> {
let type_signature = TypeSignature::from_str(sig)?;
if type_signature.args.len() != args.len() {
return Err(Error::Jni(jni::errors::Error::InvalidArgList(type_signature)));
@@ -126,8 +129,11 @@ impl UwbServiceCallbackImpl {
impl UwbServiceCallback for UwbServiceCallbackImpl {
fn on_service_reset(&mut self, success: bool) {
- let result =
- self.cached_jni_call("onServiceResetReceived", "(Z)V", &[JValue::Bool(success as u8)]);
+ let result = self.cached_jni_call(
+ "onServiceResetReceived",
+ "(Z)V",
+ &[jvalue::from(JValue::Bool(success as u8))],
+ );
result_helper("on_service_reset", result);
}
@@ -135,7 +141,7 @@ impl UwbServiceCallback for UwbServiceCallbackImpl {
let result = self.cached_jni_call(
"onDeviceStatusNotificationReceived",
"(I)V",
- &[JValue::Int(state as i32)],
+ &[jvalue::from(JValue::Int(state as i32))],
);
result_helper("on_uci_device_status_changed", result);
}
@@ -150,9 +156,9 @@ impl UwbServiceCallback for UwbServiceCallbackImpl {
"onSessionStatusNotificationReceived",
"(JII)V",
&[
- JValue::Long(session_id as i64),
- JValue::Int(session_state as i32),
- JValue::Int(reason_code as i32),
+ jvalue::from(JValue::Long(session_id as i64)),
+ jvalue::from(JValue::Int(session_state as i32)),
+ jvalue::from(JValue::Int(reason_code as i32)),
],
);
result_helper("on_session_state_changed", result);
@@ -198,7 +204,10 @@ impl UwbServiceCallback for UwbServiceCallbackImpl {
let result = self.cached_jni_call(
"onRangeDataNotificationReceived",
"(JLcom/android/server/uwb/data/UwbRangingData;)V",
- &[JValue::Long(session_id as i64), JValue::Object(uwb_raning_data_jobject)],
+ &[
+ jvalue::from(JValue::Long(session_id as i64)),
+ jvalue::from(JValue::Object(uwb_raning_data_jobject)),
+ ],
);
result_helper("on_range_data_received", result);
}
@@ -218,13 +227,17 @@ impl UwbServiceCallback for UwbServiceCallbackImpl {
error!("UWB Service Callback: Failed to set byte array: {:?}", err);
return;
}
+
+ // Safety: payload_jbyte_array safely instantiated above.
+ let payload_jobject = unsafe { JObject::from_raw(payload_jbyte_array) };
+
let result = self.cached_jni_call(
"onVendorUciNotificationReceived",
"(II[B)V",
&[
- JValue::Int(gid as i32),
- JValue::Int(oid as i32),
- JValue::Object(JObject::from(payload_jbyte_array)),
+ jvalue::from(JValue::Int(gid as i32)),
+ jvalue::from(JValue::Int(oid as i32)),
+ jvalue::from(JValue::Object(payload_jobject)),
],
);
diff --git a/indev_uwb_adaptation/jni/src/context.rs b/indev_uwb_adaptation/jni/src/context.rs
index 97f9b1ec..315eba7c 100644
--- a/indev_uwb_adaptation/jni/src/context.rs
+++ b/indev_uwb_adaptation/jni/src/context.rs
@@ -43,7 +43,7 @@ impl<'a> JniContext<'a> {
pub fn byte_arr_getter(&self, method: &str) -> Result<Vec<u8>, jni::errors::Error> {
let val_obj = self.env.call_method(self.obj, method, "()[B", &[])?.l()?;
- self.env.convert_byte_array(val_obj.into_inner())
+ self.env.convert_byte_array(val_obj.into_raw())
}
pub fn object_getter(
diff --git a/indev_uwb_adaptation/jni/src/object_mapping.rs b/indev_uwb_adaptation/jni/src/object_mapping.rs
index e6dcc8d9..e7b21322 100644
--- a/indev_uwb_adaptation/jni/src/object_mapping.rs
+++ b/indev_uwb_adaptation/jni/src/object_mapping.rs
@@ -370,10 +370,10 @@ impl<'a> FiraControleeParamsJni<'a> {
let env = self.jni_context.env;
let addr_arr =
self.jni_context.object_getter("getAddressList", "[android/uwb/UwbAddress;")?;
- let addr_len = env.get_array_length(addr_arr.into_inner())?;
+ let addr_len = env.get_array_length(addr_arr.into_raw())?;
let subs_arr = self.jni_context.object_getter("getSubSessionIdList", "[I")?;
- let subs_len = env.get_array_length(subs_arr.into_inner())?;
+ let subs_len = env.get_array_length(subs_arr.into_raw())?;
if addr_len != subs_len {
return Err(Error::Parse(format!(
@@ -386,10 +386,10 @@ impl<'a> FiraControleeParamsJni<'a> {
let size: usize = addr_len.try_into().unwrap();
let mut subs_arr_vec = vec![0i32; size];
- env.get_int_array_region(subs_arr.into_inner(), 0, &mut subs_arr_vec)?;
+ env.get_int_array_region(subs_arr.into_raw(), 0, &mut subs_arr_vec)?;
for (i, sub_session) in subs_arr_vec.iter().enumerate() {
- let uwb_address_obj = env.get_object_array_element(addr_arr.into_inner(), i as i32)?;
+ let uwb_address_obj = env.get_object_array_element(addr_arr.into_raw(), i as i32)?;
let uwb_address: u16 = UwbAddressJni::new(env, uwb_address_obj).try_into()?;
controlees
.push(Controlee { short_address: uwb_address, subsession_id: *sub_session as u32 });
@@ -495,27 +495,27 @@ pub struct UwbRangingDataJni<'a> {
impl<'a> TryFrom<SessionRangeDataWithEnv<'a>> for UwbRangingDataJni<'a> {
type Error = Error;
fn try_from(data_obj: SessionRangeDataWithEnv<'a>) -> Result<Self> {
- let (mac_address_indicator, measurements_size) = match data_obj
- .session_range_data
- .ranging_measurements
- {
- RangingMeasurements::ShortAddressTwoWay(ref m) => {
- (MacAddressIndicator::ShortAddress, m.len())
- }
- RangingMeasurements::ExtendedAddressTwoWay(ref m) => {
- (MacAddressIndicator::ExtendedAddress, m.len())
- }
- RangingMeasurements::ShortDltdoa(ref m) => (MacAddressIndicator::ShortAddress, m.len()),
- RangingMeasurements::ExtendedDltdoa(ref m) => {
- (MacAddressIndicator::ExtendedAddress, m.len())
- }
- RangingMeasurements::ShortAddressOwrAoa(ref m) => {
- (MacAddressIndicator::ShortAddress, m.len())
- }
- RangingMeasurements::ExtendedAddressOwrAoa(ref m) => {
- (MacAddressIndicator::ExtendedAddress, m.len())
- }
- };
+ let (mac_address_indicator, measurements_size) =
+ match data_obj.session_range_data.ranging_measurements {
+ RangingMeasurements::ShortAddressTwoWay(ref m) => {
+ (MacAddressIndicator::ShortAddress, m.len())
+ }
+ RangingMeasurements::ExtendedAddressTwoWay(ref m) => {
+ (MacAddressIndicator::ExtendedAddress, m.len())
+ }
+ RangingMeasurements::ShortAddressDltdoa(ref m) => {
+ (MacAddressIndicator::ShortAddress, m.len())
+ }
+ RangingMeasurements::ExtendedAddressDltdoa(ref m) => {
+ (MacAddressIndicator::ExtendedAddress, m.len())
+ }
+ RangingMeasurements::ShortAddressOwrAoa(ref m) => {
+ (MacAddressIndicator::ShortAddress, m.len())
+ }
+ RangingMeasurements::ExtendedAddressOwrAoa(ref m) => {
+ (MacAddressIndicator::ExtendedAddress, m.len())
+ }
+ };
let measurements_jni = UwbTwoWayMeasurementJni::try_from(RangingMeasurementsWithEnv::new(
data_obj.env,
data_obj.uwb_two_way_measurement_jclass,
@@ -524,6 +524,9 @@ impl<'a> TryFrom<SessionRangeDataWithEnv<'a>> for UwbRangingDataJni<'a> {
let raw_notification_jbytearray =
data_obj.env.byte_array_from_slice(&data_obj.session_range_data.raw_ranging_data)?;
// TODO(b/246678053): Check on using OwrAoa measurement class here.
+
+ // Safety: raw_notification_jbytearray safely instantiated above.
+ let raw_notification_jobject = unsafe { JObject::from_raw(raw_notification_jbytearray) };
let ranging_data_jni = data_obj.env.new_object(
data_obj.uwb_ranging_data_jclass,
"(JJIJIII[Lcom/android/server/uwb/data/UwbTwoWayMeasurement;[B)V",
@@ -536,7 +539,7 @@ impl<'a> TryFrom<SessionRangeDataWithEnv<'a>> for UwbRangingDataJni<'a> {
JValue::Int(mac_address_indicator as i32),
JValue::Int(measurements_size as i32),
JValue::Object(measurements_jni.jni_context.obj),
- JValue::Object(JObject::from(raw_notification_jbytearray)),
+ JValue::Object(raw_notification_jobject),
],
)?;
@@ -654,11 +657,14 @@ impl<'a> TryFrom<RangingMeasurementsWithEnv<'a>> for UwbTwoWayMeasurementJni<'a>
_ => todo!(),
};
let address_jbytearray = measurements_obj.env.new_byte_array(byte_arr_size)?;
+
+ // Safety: address_jbytearray safely instantiated above.
+ let address_jobject = unsafe { JObject::from_raw(address_jbytearray) };
let zero_initiated_measurement_jobject = measurements_obj.env.new_object(
measurements_obj.uwb_two_way_measurement_jclass,
"([BIIIIIIIIIIIII)V",
&[
- JValue::Object(JObject::from(address_jbytearray)),
+ JValue::Object(address_jobject),
JValue::Int(0),
JValue::Int(0),
JValue::Int(0),
@@ -688,11 +694,16 @@ impl<'a> TryFrom<RangingMeasurementsWithEnv<'a>> for UwbTwoWayMeasurementJni<'a>
0,
mac_address_bytes.as_slice(),
)?;
+
+ // Safety: mac_address_bytes_jbytearray safely instantiated above.
+ let mac_address_bytes_jobject =
+ unsafe { JObject::from_raw(mac_address_bytes_jbytearray) };
+
let measurement_jobject = measurements_obj.env.new_object(
measurements_obj.uwb_two_way_measurement_jclass,
"([BIIIIIIIIIIIII)V",
&[
- JValue::Object(JObject::from(mac_address_bytes_jbytearray)),
+ JValue::Object(mac_address_bytes_jobject),
JValue::Int(measurement.status as i32),
JValue::Int(measurement.nlos as i32),
JValue::Int(measurement.distance as i32),
@@ -715,8 +726,10 @@ impl<'a> TryFrom<RangingMeasurementsWithEnv<'a>> for UwbTwoWayMeasurementJni<'a>
)?;
}
+ // Safety: measurements_array_jobject safely instantiated above.
+ let measurements_jobject = unsafe { JObject::from_raw(measurements_array_jobject) };
Ok(UwbTwoWayMeasurementJni {
- jni_context: JniContext::new(measurements_obj.env, measurements_array_jobject.into()),
+ jni_context: JniContext::new(measurements_obj.env, measurements_jobject),
})
}
}
diff --git a/service/java/com/android/server/uwb/UwbConfigStore.java b/service/java/com/android/server/uwb/UwbConfigStore.java
index 4082621b..a0df3f6b 100644
--- a/service/java/com/android/server/uwb/UwbConfigStore.java
+++ b/service/java/com/android/server/uwb/UwbConfigStore.java
@@ -758,7 +758,7 @@ public class UwbConfigStore {
* Dump the local log buffer and other internal state of UwbConfigManager.
*/
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- pw.println("Dump of UwbConfigStore");
+ pw.println("---- Dump of UwbConfigStore ----");
pw.println("UwbConfigStore - Store File Begin ----");
Stream.of(mSharedStores, mUserStores)
.filter(Objects::nonNull)
@@ -777,7 +777,7 @@ public class UwbConfigStore {
pw.print(", ");
pw.println("File Name: " + STORE_ID_TO_FILE_NAME.get(storeData.getStoreFileId()));
}
- pw.println("UwbConfigStore - Store Data End ----");
+ pw.println("---- Dump of UwbConfigStore ----");
}
/**
diff --git a/service/java/com/android/server/uwb/UwbCountryCode.java b/service/java/com/android/server/uwb/UwbCountryCode.java
index 7fbb0621..5c7c0848 100644
--- a/service/java/com/android/server/uwb/UwbCountryCode.java
+++ b/service/java/com/android/server/uwb/UwbCountryCode.java
@@ -331,6 +331,7 @@ public class UwbCountryCode {
* Method to dump the current state of this UwbCountryCode object.
*/
public synchronized void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println("---- Dump of UwbCountryCode ----");
pw.println("DefaultCountryCode(system property): "
+ mUwbInjector.getOemDefaultCountryCode());
pw.println("mOverrideCountryCode: " + mOverrideCountryCode);
@@ -339,5 +340,6 @@ public class UwbCountryCode {
pw.println("mWifiCountryTimestamp: " + mWifiCountryTimestamp);
pw.println("mCountryCode: " + mCountryCode);
pw.println("mCountryCodeUpdatedTimestamp: " + mCountryCodeUpdatedTimestamp);
+ pw.println("---- Dump of UwbCountryCode ----");
}
}
diff --git a/service/java/com/android/server/uwb/UwbInjector.java b/service/java/com/android/server/uwb/UwbInjector.java
index d9e8e886..25c5dd00 100644
--- a/service/java/com/android/server/uwb/UwbInjector.java
+++ b/service/java/com/android/server/uwb/UwbInjector.java
@@ -92,6 +92,8 @@ public class UwbInjector {
private final SystemBuildProperties mSystemBuildProperties;
private final UwbDiagnostics mUwbDiagnostics;
+ private final UwbSessionManager mUwbSessionManager;
+
public UwbInjector(@NonNull UwbContext context) {
// Create UWB service thread.
HandlerThread uwbHandlerThread = new HandlerThread("UwbService");
@@ -121,14 +123,14 @@ public class UwbInjector {
UwbSessionNotificationManager uwbSessionNotificationManager =
new UwbSessionNotificationManager(this);
UwbAdvertiseManager uwbAdvertiseManager = new UwbAdvertiseManager(this);
- UwbSessionManager uwbSessionManager =
+ mUwbSessionManager =
new UwbSessionManager(uwbConfigurationManager, mNativeUwbManager, mUwbMetrics,
uwbAdvertiseManager, uwbSessionNotificationManager, this,
mContext.getSystemService(AlarmManager.class),
mContext.getSystemService(ActivityManager.class),
mLooper);
mUwbService = new UwbServiceCore(mContext, mNativeUwbManager, mUwbMetrics,
- mUwbCountryCode, uwbSessionManager, uwbConfigurationManager, this, mLooper);
+ mUwbCountryCode, mUwbSessionManager, uwbConfigurationManager, this, mLooper);
mSystemBuildProperties = new SystemBuildProperties();
mUwbDiagnostics = new UwbDiagnostics(mContext, this, mSystemBuildProperties);
}
@@ -192,6 +194,10 @@ public class UwbInjector {
return mUwbDiagnostics;
}
+ public UwbSessionManager getUwbSessionManager() {
+ return mUwbSessionManager;
+ }
+
/**
* Create a UwbShellCommand instance.
*/
diff --git a/service/java/com/android/server/uwb/UwbMetrics.java b/service/java/com/android/server/uwb/UwbMetrics.java
index 4c2471dc..b12e6de8 100644
--- a/service/java/com/android/server/uwb/UwbMetrics.java
+++ b/service/java/com/android/server/uwb/UwbMetrics.java
@@ -533,24 +533,25 @@ public class UwbMetrics {
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
synchronized (mLock) {
pw.println("---- Dump of UwbMetrics ----");
- pw.println("---- mRangingSessionList ----");
+ pw.println("-- mRangingSessionList --");
for (RangingSessionStats stats: mRangingSessionList) {
pw.println(stats.toString());
}
- pw.println("---- mOpenedSessionMap ----");
+ pw.println("-- mOpenedSessionMap --");
for (int i = 0; i < mOpenedSessionMap.size(); i++) {
pw.println(mOpenedSessionMap.valueAt(i).toString());
}
- pw.println("---- mRangingReportList ----");
+ pw.println("-- mRangingReportList --");
for (RangingReportEvent event: mRangingReportList) {
pw.println(event.toString());
}
pw.println("mNumApps=" + mNumApps);
- pw.println("---- Device operation success/error count ----");
+ pw.println("-- Device operation success/error count --");
pw.println("mNumDeviceInitSuccess = " + mNumDeviceInitSuccess);
pw.println("mNumDeviceInitFailure = " + mNumDeviceInitFailure);
pw.println("mNumDeviceStatusError = " + mNumDeviceStatusError);
pw.println("mNumUciGenericError = " + mNumUciGenericError);
+ pw.println("---- Dump of UwbMetrics ----");
}
}
}
diff --git a/service/java/com/android/server/uwb/UwbServiceCore.java b/service/java/com/android/server/uwb/UwbServiceCore.java
index b62aa701..853f5356 100644
--- a/service/java/com/android/server/uwb/UwbServiceCore.java
+++ b/service/java/com/android/server/uwb/UwbServiceCore.java
@@ -25,13 +25,12 @@ import static com.google.uwb.support.fira.FiraParams.MULTICAST_LIST_UPDATE_ACTIO
import android.content.AttributionSource;
import android.content.Context;
-import android.os.Binder;
import android.os.Handler;
-import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.PersistableBundle;
import android.os.PowerManager;
+import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.util.Log;
import android.util.Pair;
@@ -71,7 +70,6 @@ import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@@ -95,7 +93,8 @@ public class UwbServiceCore implements INativeUwbManager.DeviceNotification,
@VisibleForTesting
public static final int TASK_NOTIFY_ADAPTER_STATE = 4;
- private static final int WATCHDOG_MS = 10000;
+ @VisibleForTesting
+ public static final int WATCHDOG_MS = 10000;
private static final int SEND_VENDOR_CMD_TIMEOUT_MS = 10000;
@VisibleForTesting
public static final int TASK_NOTIFY_ADAPTER_STATE_MESSAGE_DELAY_MS = 15000;
@@ -105,8 +104,8 @@ public class UwbServiceCore implements INativeUwbManager.DeviceNotification,
private final PowerManager.WakeLock mUwbWakeLock;
private final Context mContext;
- // TODO: Use RemoteCallbackList instead.
- private final ConcurrentHashMap<Integer, AdapterInfo> mAdapterMap = new ConcurrentHashMap<>();
+ private final RemoteCallbackList<IUwbAdapterStateCallbacks>
+ mAdapterStateCallbacksList = new RemoteCallbackList<>();
private final EnableDisableTask mEnableDisableTask;
private final UwbSessionManager mSessionManager;
@@ -269,13 +268,19 @@ public class UwbServiceCore implements INativeUwbManager.DeviceNotification,
// TODO(b/244443764): Consider checking on the current adapter state and returning if it's
// the same, to avoid sending extra onAdapterStateChanged() notifications. Currently this
// will happen when UWB is toggled on and a valid country code is already set.
- for (AdapterInfo adapter : mAdapterMap.values()) {
+ if (mAdapterStateCallbacksList.getRegisteredCallbackCount() == 0) {
+ return;
+ }
+ final int count = mAdapterStateCallbacksList.beginBroadcast();
+ for (int i = 0; i < count; i++) {
try {
- adapter.getAdapterStateCallbacks().onAdapterStateChanged(adapterState, reason);
+ mAdapterStateCallbacksList.getBroadcastItem(i)
+ .onAdapterStateChanged(adapterState, reason);
} catch (RemoteException e) {
Log.e(TAG, "onAdapterStateChanged is failed");
}
}
+ mAdapterStateCallbacksList.finishBroadcast();
}
int getAdapterStateFromDeviceState(int deviceState) {
@@ -326,17 +331,12 @@ public class UwbServiceCore implements INativeUwbManager.DeviceNotification,
public void registerAdapterStateCallbacks(IUwbAdapterStateCallbacks adapterStateCallbacks)
throws RemoteException {
- AdapterInfo adapter = new AdapterInfo(Binder.getCallingPid(), adapterStateCallbacks);
- mAdapterMap.put(Binder.getCallingPid(), adapter);
- adapter.getBinder().linkToDeath(adapter, 0);
+ mAdapterStateCallbacksList.register(adapterStateCallbacks);
adapterStateCallbacks.onAdapterStateChanged(getAdapterState(), mLastStateChangedReason);
}
public void unregisterAdapterStateCallbacks(IUwbAdapterStateCallbacks callbacks) {
- int pid = Binder.getCallingPid();
- AdapterInfo adapter = mAdapterMap.get(pid);
- adapter.getBinder().unlinkToDeath(adapter, 0);
- mAdapterMap.remove(pid);
+ mAdapterStateCallbacksList.unregister(callbacks);
}
public void registerVendorExtensionCallback(IUwbVendorUciCallback callbacks) {
@@ -412,6 +412,7 @@ public class UwbServiceCore implements INativeUwbManager.DeviceNotification,
throw new IllegalStateException("Uwb is not enabled");
}
int sessionId = 0;
+ int sessionType = 0;
if (UuidBundleWrapper.isUuidBundle(params)) {
UuidBundleWrapper uuidBundleWrapper = UuidBundleWrapper.fromBundle(params);
@@ -436,14 +437,16 @@ public class UwbServiceCore implements INativeUwbManager.DeviceNotification,
}
FiraOpenSessionParams firaOpenSessionParams = builder.build();
sessionId = firaOpenSessionParams.getSessionId();
+ sessionType = firaOpenSessionParams.getSessionType();
mSessionManager.initSession(attributionSource, sessionHandle, sessionId,
- firaOpenSessionParams.getProtocolName(),
+ (byte) sessionType, firaOpenSessionParams.getProtocolName(),
firaOpenSessionParams, rangingCallbacks, chipId);
} else if (CccParams.isCorrectProtocol(params)) {
CccOpenRangingParams cccOpenRangingParams = CccOpenRangingParams.fromBundle(params);
sessionId = cccOpenRangingParams.getSessionId();
+ sessionType = cccOpenRangingParams.getSessionType();
mSessionManager.initSession(attributionSource, sessionHandle, sessionId,
- cccOpenRangingParams.getProtocolName(),
+ (byte) sessionType, cccOpenRangingParams.getProtocolName(),
cccOpenRangingParams, rangingCallbacks, chipId);
} else {
Log.e(TAG, "openRanging - Wrong parameters");
@@ -752,7 +755,9 @@ public class UwbServiceCore implements INativeUwbManager.DeviceNotification,
countryCode);
}
} finally {
- mUwbWakeLock.release();
+ if (mUwbWakeLock.isHeld()) {
+ mUwbWakeLock.release();
+ }
watchDog.cancel();
}
} catch (Exception e) {
@@ -786,7 +791,9 @@ public class UwbServiceCore implements INativeUwbManager.DeviceNotification,
getAdapterStateFromDeviceState(UwbUciConstants.DEVICE_STATE_OFF),
getReasonFromDeviceState(UwbUciConstants.DEVICE_STATE_OFF));
} finally {
- mUwbWakeLock.release();
+ if (mUwbWakeLock.isHeld()) {
+ mUwbWakeLock.release();
+ }
watchDog.cancel();
}
}
@@ -856,32 +863,6 @@ public class UwbServiceCore implements INativeUwbManager.DeviceNotification,
}
}
- class AdapterInfo implements IBinder.DeathRecipient {
- private final IBinder mIBinder;
- private IUwbAdapterStateCallbacks mAdapterStateCallbacks;
- private int mPid;
-
- AdapterInfo(int pid, IUwbAdapterStateCallbacks adapterStateCallbacks) {
- mIBinder = adapterStateCallbacks.asBinder();
- mAdapterStateCallbacks = adapterStateCallbacks;
- mPid = pid;
- }
-
- public IUwbAdapterStateCallbacks getAdapterStateCallbacks() {
- return mAdapterStateCallbacks;
- }
-
- public IBinder getBinder() {
- return mIBinder;
- }
-
- @Override
- public void binderDied() {
- mIBinder.unlinkToDeath(this, 0);
- mAdapterMap.remove(mPid);
- }
- }
-
private void takBugReportAfterDeviceError(String bugTitle) {
if (mUwbInjector.getDeviceConfigFacade().isDeviceErrorBugreportEnabled()) {
mUwbInjector.getUwbDiagnostics().takeBugReport(bugTitle);
@@ -889,14 +870,15 @@ public class UwbServiceCore implements INativeUwbManager.DeviceNotification,
}
/**
- * Dump the UWB service status
+ * Dump the UWB session manager debug info
*/
public synchronized void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.println("---- Dump of UwbServiceCore ----");
for (String chipId : mUwbInjector.getMultichipData().getChipIds()) {
- pw.println("device state = " + getDeviceStateString(mChipIdToStateMap.get(chipId))
+ pw.println("Device state = " + getDeviceStateString(mChipIdToStateMap.get(chipId))
+ " for chip id = " + chipId);
}
pw.println("mLastStateChangedReason = " + mLastStateChangedReason);
+ pw.println("---- Dump of UwbServiceCore ----");
}
}
diff --git a/service/java/com/android/server/uwb/UwbServiceImpl.java b/service/java/com/android/server/uwb/UwbServiceImpl.java
index 96d15ec8..b9f1990d 100644
--- a/service/java/com/android/server/uwb/UwbServiceImpl.java
+++ b/service/java/com/android/server/uwb/UwbServiceImpl.java
@@ -103,15 +103,22 @@ public class UwbServiceImpl extends IUwbAdapter.Stub {
return;
}
mUwbSettingsStore.dump(fd, pw, args);
+ pw.println();
mUwbInjector.getUwbMetrics().dump(fd, pw, args);
+ pw.println();
mUwbServiceCore.dump(fd, pw, args);
+ pw.println();
+ mUwbInjector.getUwbSessionManager().dump(fd, pw, args);
+ pw.println();
mUwbInjector.getUwbCountryCode().dump(fd, pw, args);
+ pw.println();
mUwbInjector.getUwbConfigStore().dump(fd, pw, args);
+ pw.println();
dumpPowerStats(fd, pw, args);
}
private void dumpPowerStats(FileDescriptor fd, PrintWriter pw, String[] args) {
- pw.println("---- powerStats ----");
+ pw.println("---- PowerStats ----");
try {
PersistableBundle bundle = getSpecificationInfo(null);
GenericSpecificationParams params = GenericSpecificationParams.fromBundle(bundle);
@@ -128,6 +135,7 @@ public class UwbServiceImpl extends IUwbAdapter.Stub {
pw.println("Exception while getting power stats.");
e.printStackTrace(pw);
}
+ pw.println("---- PowerStats ----");
}
private void enforceUwbPrivilegedPermission() {
diff --git a/service/java/com/android/server/uwb/UwbSessionManager.java b/service/java/com/android/server/uwb/UwbSessionManager.java
index 4c097ee6..bfb7096a 100644
--- a/service/java/com/android/server/uwb/UwbSessionManager.java
+++ b/service/java/com/android/server/uwb/UwbSessionManager.java
@@ -68,6 +68,7 @@ import com.android.server.uwb.jni.INativeUwbManager;
import com.android.server.uwb.jni.NativeUwbManager;
import com.android.server.uwb.proto.UwbStatsLog;
import com.android.server.uwb.util.ArrayUtils;
+import com.android.server.uwb.util.LruList;
import com.android.server.uwb.util.UwbUtil;
import com.google.uwb.support.base.Params;
@@ -84,14 +85,18 @@ import com.google.uwb.support.generic.GenericSpecificationParams;
import com.google.uwb.support.oemextension.AdvertisePointedTarget;
import com.google.uwb.support.oemextension.SessionStatus;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
import java.nio.ByteBuffer;
import java.util.ArrayList;
-import java.util.Arrays;
+import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.SortedMap;
+import java.util.TreeMap;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
@@ -127,8 +132,8 @@ public class UwbSessionManager implements INativeUwbManager.SessionNotification
// TODO: don't expose the internal field for testing.
@VisibleForTesting
final ConcurrentHashMap<Integer, UwbSession> mSessionTable = new ConcurrentHashMap();
- final ConcurrentHashMap<Long, ReceivedDataInfo> mReceivedDataMap =
- new ConcurrentHashMap<Long, ReceivedDataInfo>();
+ // Used for storing recently closed sessions for debugging purposes.
+ final LruList<UwbSession> mDbgRecentlyClosedSessions = new LruList<>(5);
final ConcurrentHashMap<Integer, List<UwbSession>> mNonPrivilegedUidToFiraSessionsTable =
new ConcurrentHashMap();
private final ActivityManager mActivityManager;
@@ -260,6 +265,12 @@ public class UwbSessionManager implements INativeUwbManager.SessionNotification
Log.d(TAG, "onDataReceived - address: " + UwbUtil.toHexString(address)
+ ", Data: " + UwbUtil.toHexString(data));
+ UwbSession uwbSession = getUwbSession((int) sessionId);
+ if (uwbSession == null) {
+ Log.e(TAG, "onDataReceived(): Received data for unknown sessionId = " + sessionId);
+ return;
+ }
+
// Size of address in the UCI Packet for DATA_MESSAGE_RCV is always expected to be 8
// (EXTENDED_ADDRESS_BYTE_LENGTH). It can contain the MacAddress in short format however
// (2 LSB with MacAddress, 6 MSB zeroed out).
@@ -278,10 +289,12 @@ public class UwbSessionManager implements INativeUwbManager.SessionNotification
info.sourceEndPoint = sourceEndPoint;
info.destEndPoint = destEndPoint;
info.payload = data;
- mReceivedDataMap.put(longAddress, info);
+
+ uwbSession.addReceivedDataInfo(info);
}
- private static final class ReceivedDataInfo {
+ @VisibleForTesting
+ static final class ReceivedDataInfo {
public long sessionId;
public int status;
public long sequenceNum;
@@ -370,16 +383,6 @@ public class UwbSessionManager implements INativeUwbManager.SessionNotification
}
}
- private byte getSessionType(String protocolName) {
- byte sessionType = UwbUciConstants.SESSION_TYPE_RANGING;
- if (protocolName.equals(FiraParams.PROTOCOL_NAME)) {
- sessionType = UwbUciConstants.SESSION_TYPE_RANGING;
- } else if (protocolName.equals(CccParams.PROTOCOL_NAME)) {
- sessionType = UwbUciConstants.SESSION_TYPE_CCC;
- }
- return sessionType;
- }
-
private int setAppConfigurations(UwbSession uwbSession) {
int status = mConfigurationManager.setAppConfigurations(uwbSession.getSessionId(),
uwbSession.getParams(), uwbSession.getChipId());
@@ -396,13 +399,13 @@ public class UwbSessionManager implements INativeUwbManager.SessionNotification
}
public synchronized void initSession(AttributionSource attributionSource,
- SessionHandle sessionHandle, int sessionId, String protocolName, Params params,
- IUwbRangingCallbacks rangingCallbacks, String chipId)
+ SessionHandle sessionHandle, int sessionId, byte sessionType, String protocolName,
+ Params params, IUwbRangingCallbacks rangingCallbacks, String chipId)
throws RemoteException {
- Log.i(TAG, "initSession() - sessionId: " + sessionId
- + ", sessionHandle: " + sessionHandle);
+ Log.i(TAG, "initSession() - sessionId: " + sessionId + ", sessionHandle: " + sessionHandle
+ + ", sessionType: " + sessionType);
UwbSession uwbSession = createUwbSession(attributionSource, sessionHandle, sessionId,
- protocolName, params, rangingCallbacks, chipId);
+ sessionType, protocolName, params, rangingCallbacks, chipId);
// Check the attribution source chain to ensure that there are no 3p apps which are not in
// fg which can receive the ranging results.
AttributionSource nonPrivilegedAppAttrSource =
@@ -445,8 +448,6 @@ public class UwbSessionManager implements INativeUwbManager.SessionNotification
return;
}
- byte sessionType = getSessionType(protocolName);
-
try {
uwbSession.getBinder().linkToDeath(uwbSession, 0);
} catch (RemoteException e) {
@@ -470,10 +471,10 @@ public class UwbSessionManager implements INativeUwbManager.SessionNotification
// TODO: use UwbInjector.
@VisibleForTesting
UwbSession createUwbSession(AttributionSource attributionSource, SessionHandle sessionHandle,
- int sessionId, String protocolName, Params params,
+ int sessionId, byte sessionType, String protocolName, Params params,
IUwbRangingCallbacks iUwbRangingCallbacks, String chipId) {
- return new UwbSession(attributionSource, sessionHandle, sessionId, protocolName, params,
- iUwbRangingCallbacks, chipId);
+ return new UwbSession(attributionSource, sessionHandle, sessionId, sessionType,
+ protocolName, params, iUwbRangingCallbacks, chipId);
}
public synchronized void deInitSession(SessionHandle sessionHandle) {
@@ -595,30 +596,18 @@ public class UwbSessionManager implements INativeUwbManager.SessionNotification
UwbOwrAoaMeasurement uwbOwrAoaMeasurement = rangingData.getRangingOwrAoaMeasure();
mAdvertiseManager.updateAdvertiseTarget(uwbOwrAoaMeasurement);
- byte[] macAddress = getValidMacAddressFromOwrAoaMeasurement(
+ byte[] macAddressBytes = getValidMacAddressFromOwrAoaMeasurement(
rangingData, uwbOwrAoaMeasurement);
- if (macAddress == null) {
+ if (macAddressBytes == null) {
Log.i(TAG, "OwR Aoa UwbSession: Invalid MacAddress for remote device");
return;
}
- uwbSession.setRemoteMacAddress(macAddress);
- // Get any application payload data received in this OWR AOA ranging session and notify it.
- ReceivedDataInfo receivedDataInfo = getReceivedDataInfo(macAddress);
- if (receivedDataInfo == null) {
- return;
- }
-
- UwbSession uwbSessionFromReceivedData = getUwbSession((int) receivedDataInfo.sessionId);
- if (uwbSessionFromReceivedData != uwbSession) {
- return;
- }
-
- boolean advertisePointingResult = mAdvertiseManager.isPointedTarget(macAddress);
+ boolean advertisePointingResult = mAdvertiseManager.isPointedTarget(macAddressBytes);
if (mUwbInjector.getUwbServiceCore().isOemExtensionCbRegistered()) {
try {
PersistableBundle pointedTargetBundle = new AdvertisePointedTarget.Builder()
- .setMacAddress(macAddress)
+ .setMacAddress(macAddressBytes)
.setAdvertisePointingResult(advertisePointingResult)
.build()
.toBundle();
@@ -633,9 +622,22 @@ public class UwbSessionManager implements INativeUwbManager.SessionNotification
}
if (advertisePointingResult) {
- UwbAddress uwbAddress = UwbAddress.fromBytes(macAddress);
- mSessionNotificationManager.onDataReceived(
- uwbSession, uwbAddress, new PersistableBundle(), receivedDataInfo.payload);
+ // Use a loop to notify all the received application data payload(s) (in sequence number
+ // order) for this OWR AOA ranging session.
+ long macAddress = macAddressByteArrayToLong(macAddressBytes);
+ UwbAddress uwbAddress = UwbAddress.fromBytes(macAddressBytes);
+
+ List<ReceivedDataInfo> receivedDataInfoList = uwbSession.getAllReceivedDataInfo(
+ macAddress);
+ if (receivedDataInfoList.isEmpty()) {
+ Log.i(TAG, "OwR Aoa UwbSession: Application Payload data not found for"
+ + " MacAddress = " + UwbUtil.toHexString(macAddress));
+ return;
+ }
+
+ receivedDataInfoList.stream().forEach(r ->
+ mSessionNotificationManager.onDataReceived(
+ uwbSession, uwbAddress, new PersistableBundle(), r.payload));
mAdvertiseManager.removeAdvertiseTarget(macAddress);
}
}
@@ -652,13 +654,6 @@ public class UwbSessionManager implements INativeUwbManager.SessionNotification
return null;
}
- /** Get any received data for the given device MacAddress */
- @VisibleForTesting
- public ReceivedDataInfo getReceivedDataInfo(byte[] macAddress) {
- // Convert the macAddress to a long as the address could be in short or extended format.
- return mReceivedDataMap.get(macAddressByteArrayToLong(macAddress));
- }
-
public boolean isExistedSession(SessionHandle sessionHandle) {
return (getSessionId(sessionHandle) != null);
}
@@ -861,18 +856,18 @@ public class UwbSessionManager implements INativeUwbManager.SessionNotification
removeFromNonPrivilegedUidToFiraSessionTableIfNecessary(uwbSession);
removeAdvertiserData(uwbSession);
mSessionTable.remove(uwbSession.getSessionId());
+ mDbgRecentlyClosedSessions.add(uwbSession);
}
}
private void removeAdvertiserData(UwbSession uwbSession) {
- byte[] remoteMacAddress = uwbSession.getRemoteMacAddress();
- if (remoteMacAddress != null) {
+ for (long remoteMacAddress : uwbSession.getRemoteMacAddressList()) {
mAdvertiseManager.removeAdvertiseTarget(remoteMacAddress);
}
}
void addToNonPrivilegedUidToFiraSessionTableIfNecessary(@NonNull UwbSession uwbSession) {
- if (getSessionType(uwbSession.getProtocolName()) == UwbUciConstants.SESSION_TYPE_RANGING) {
+ if (uwbSession.getSessionType() == UwbUciConstants.SESSION_TYPE_RANGING) {
AttributionSource nonPrivilegedAppAttrSource =
uwbSession.getAnyNonPrivilegedAppInAttributionSource();
if (nonPrivilegedAppAttrSource != null) {
@@ -886,7 +881,7 @@ public class UwbSessionManager implements INativeUwbManager.SessionNotification
}
void removeFromNonPrivilegedUidToFiraSessionTableIfNecessary(@NonNull UwbSession uwbSession) {
- if (getSessionType(uwbSession.getProtocolName()) == UwbUciConstants.SESSION_TYPE_RANGING) {
+ if (uwbSession.getSessionType() == UwbUciConstants.SESSION_TYPE_RANGING) {
AttributionSource nonPrivilegedAppAttrSource =
uwbSession.getAnyNonPrivilegedAppInAttributionSource();
if (nonPrivilegedAppAttrSource != null) {
@@ -1015,7 +1010,7 @@ public class UwbSessionManager implements INativeUwbManager.SessionNotification
uwbSession.setOperationType(OPERATION_TYPE_INIT_SESSION);
status = mNativeUwbManager.initSession(
uwbSession.getSessionId(),
- getSessionType(uwbSession.getParams().getProtocolName()),
+ uwbSession.getSessionType(),
uwbSession.getChipId());
if (status != UwbUciConstants.STATUS_CODE_OK) {
return status;
@@ -1425,20 +1420,21 @@ public class UwbSessionManager implements INativeUwbManager.SessionNotification
return sendDataStatus;
}
- // TODO(b/246678053): Check on the usage of sequenceNum field, is it used
- // for ordering the data payload packets by host or firmware ?
- int sequenceNum = 1;
+ // Get the UCI sequence number for this data packet.
+ byte sequenceNum = uwbSession.getDataSndSequenceNumber();
sendDataStatus = mNativeUwbManager.sendData(
uwbSession.getSessionId(), sendDataInfo.remoteDeviceAddress.toBytes(),
UwbUciConstants.UWB_DESTINATION_END_POINT_HOST, sequenceNum,
- sendDataInfo.data);
- Log.d(TAG, "MSG_SESSION_SEND_DATA status: " + sendDataStatus);
+ sendDataInfo.data, uwbSession.getChipId());
+ Log.d(TAG, "MSG_SESSION_SEND_DATA status: " + sendDataStatus
+ + " for data packet sequence number: " + sequenceNum);
if (sendDataStatus == UwbUciConstants.STATUS_CODE_OK) {
mSessionNotificationManager.onDataSent(
uwbSession, sendDataInfo.remoteDeviceAddress,
sendDataInfo.params);
+ uwbSession.incrementDataSndSequenceNumber();
} else {
mSessionNotificationManager.onDataSendFailed(
uwbSession, sendDataInfo.remoteDeviceAddress, sendDataStatus,
@@ -1516,7 +1512,7 @@ public class UwbSessionManager implements INativeUwbManager.SessionNotification
private final AttributionSource mAttributionSource;
private final SessionHandle mSessionHandle;
private final int mSessionId;
- private byte[] mRemoteMacAddress;
+ private final byte mSessionType;
private final IUwbRangingCallbacks mIUwbRangingCallbacks;
private final String mProtocolName;
private final IBinder mIBinder;
@@ -1532,16 +1528,29 @@ public class UwbSessionManager implements INativeUwbManager.SessionNotification
private boolean mHasNonPrivilegedFgApp = false;
private @FiraParams.RangeDataNtfConfig Integer mOrigRangeDataNtfConfig;
private long mRangingErrorStreakTimeoutMs = RANGING_RESULT_ERROR_NO_TIMEOUT;
+ // Use a Map<RemoteMacAddress, SortedMap<SequenceNumber, ReceivedDataInfo>> to store all
+ // the Application payload data packets received in this (active) UWB Session.
+ // - The outer key (RemoteMacAddress) is used to identify the Advertiser device that sends
+ // the data (there can be multiple advertisers in the same UWB session).
+ // - The inner key (SequenceNumber) is used to ensure we don't store duplicate packets,
+ // and notify them to the higher layers in-order.
+ // TODO(b/246678053): Change the type of SequenceNumber from Long to Integer everywhere.
+ private final ConcurrentHashMap<Long, SortedMap<Long, ReceivedDataInfo>>
+ mReceivedDataInfoMap;
+
+ // Store the UCI sequence number for the next Data packet (to be sent to UWBS).
+ private byte mDataSndSequenceNumber;
@VisibleForTesting
public List<UwbControlee> mControleeList;
UwbSession(AttributionSource attributionSource, SessionHandle sessionHandle, int sessionId,
- String protocolName, Params params, IUwbRangingCallbacks iUwbRangingCallbacks,
- String chipId) {
+ byte sessionType, String protocolName, Params params,
+ IUwbRangingCallbacks iUwbRangingCallbacks, String chipId) {
this.mAttributionSource = attributionSource;
this.mSessionHandle = sessionHandle;
this.mSessionId = sessionId;
+ this.mSessionType = sessionType;
this.mProtocolName = protocolName;
this.mIUwbRangingCallbacks = iUwbRangingCallbacks;
this.mIBinder = iUwbRangingCallbacks.asBinder();
@@ -1562,6 +1571,9 @@ public class UwbSessionManager implements INativeUwbManager.SessionNotification
mRangingErrorStreakTimeoutMs = firaParams
.getRangingErrorStreakTimeoutMs();
}
+
+ this.mReceivedDataInfoMap = new ConcurrentHashMap<>();
+ this.mDataSndSequenceNumber = 0;
}
private boolean isPrivilegedApp(int uid, String packageName) {
@@ -1594,6 +1606,51 @@ public class UwbSessionManager implements INativeUwbManager.SessionNotification
}
/**
+ * Store a ReceivedDataInfo for the UwbSession. If we already have stored data from the
+ * same advertiser and with the same sequence number, this is a no-op.
+ */
+ public void addReceivedDataInfo(ReceivedDataInfo receivedDataInfo) {
+ SortedMap<Long, ReceivedDataInfo> innerMap = mReceivedDataInfoMap.get(
+ receivedDataInfo.address);
+ if (innerMap == null) {
+ innerMap = new TreeMap<>();
+ mReceivedDataInfoMap.put(receivedDataInfo.address, innerMap);
+ }
+ innerMap.putIfAbsent(receivedDataInfo.sequenceNum, receivedDataInfo);
+ }
+
+ /**
+ * Return all the ReceivedDataInfo from the given remote device, in sequence number order.
+ * This method also removes the returned packets from the Map, so the same packet will
+ * not be returned again (in a future call).
+ */
+ public List<ReceivedDataInfo> getAllReceivedDataInfo(long macAddress) {
+ SortedMap<Long, ReceivedDataInfo> innerMap = mReceivedDataInfoMap.get(macAddress);
+ if (innerMap == null) {
+ // No stored ReceivedDataInfo(s) for the address.
+ return List.of();
+ }
+
+ List<ReceivedDataInfo> receivedDataInfoList = new ArrayList<>(innerMap.values());
+ innerMap.clear();
+ return receivedDataInfoList;
+ }
+
+ /**
+ * Get the UCI sequence number for the next Data packet to be sent to the UWBS.
+ */
+ public byte getDataSndSequenceNumber() {
+ return mDataSndSequenceNumber;
+ }
+
+ /**
+ * Increment the UCI sequence number for the next Data packet to be sent to the UWBS.
+ */
+ public void incrementDataSndSequenceNumber() {
+ mDataSndSequenceNumber++;
+ }
+
+ /**
* Adds a Controlee to the session. This should only be called to reflect
* the state of the native UWB interface.
* @param address The UWB address of the Controlee to add.
@@ -1624,6 +1681,10 @@ public class UwbSessionManager implements INativeUwbManager.SessionNotification
return this.mSessionId;
}
+ public byte getSessionType() {
+ return this.mSessionType;
+ }
+
public String getChipId() {
return this.mChipId;
}
@@ -1705,12 +1766,8 @@ public class UwbSessionManager implements INativeUwbManager.SessionNotification
this.mSessionState = state;
}
- public byte[] getRemoteMacAddress() {
- return mRemoteMacAddress;
- }
-
- public void setRemoteMacAddress(byte[] remoteMacAddress) {
- this.mRemoteMacAddress = Arrays.copyOf(remoteMacAddress, remoteMacAddress.length);
+ public Set<Long> getRemoteMacAddressList() {
+ return mReceivedDataInfoMap.keySet();
}
public void setMulticastListUpdateStatus(
@@ -1780,7 +1837,6 @@ public class UwbSessionManager implements INativeUwbManager.SessionNotification
}
}
-
/**
* Starts a timer to detect if the app that started the UWB session is in the background
* for longer than {@link UwbSession#mNonPrivilegedBgTimeoutMs }.
@@ -1866,6 +1922,18 @@ public class UwbSessionManager implements INativeUwbManager.SessionNotification
}
}
}
+
+ @Override
+ public String toString() {
+ return "UwbSession: { Session Id: " + getSessionId()
+ + ", Handle: " + getSessionHandle()
+ + ", Protocol: " + getProtocolName()
+ + ", State: " + getSessionState()
+ + ", Data Send Sequence Number: " + getDataSndSequenceNumber()
+ + ", Params: " + getParams()
+ + ", AttributionSource: " + getAttributionSource()
+ + " }";
+ }
}
// TODO: refactor the async operation flow.
@@ -1883,4 +1951,31 @@ public class UwbSessionManager implements INativeUwbManager.SessionNotification
notify();
}
}
-} \ No newline at end of file
+
+ /**
+ * Dump the UWB session manager debug info
+ */
+ public synchronized void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println("---- Dump of UwbSessionManager ----");
+ pw.println("Active sessions: ");
+ for (Map.Entry<Integer, UwbSession> entry : mSessionTable.entrySet()) {
+ UwbSession uwbSession = entry.getValue();
+ pw.println(uwbSession);
+ }
+ pw.println("Recently closed sessions: ");
+ for (UwbSession uwbSession: mDbgRecentlyClosedSessions.getEntries()) {
+ pw.println(uwbSession);
+ }
+ List<Integer> nonPrivilegedSessionIds =
+ mNonPrivilegedUidToFiraSessionsTable.entrySet()
+ .stream()
+ .map(e -> e.getValue()
+ .stream()
+ .map(UwbSession::getSessionId)
+ .collect(Collectors.toList()))
+ .flatMap(Collection::stream)
+ .collect(Collectors.toList());
+ pw.println("Non Privileged Fira Session Ids: " + nonPrivilegedSessionIds);
+ pw.println("---- Dump of UwbSessionManager ----");
+ }
+}
diff --git a/service/java/com/android/server/uwb/UwbSessionNotificationManager.java b/service/java/com/android/server/uwb/UwbSessionNotificationManager.java
index 0c095dd8..4e278e86 100644
--- a/service/java/com/android/server/uwb/UwbSessionNotificationManager.java
+++ b/service/java/com/android/server/uwb/UwbSessionNotificationManager.java
@@ -68,15 +68,22 @@ public class UwbSessionNotificationManager {
+ sessionHandle);
return;
}
-
- RangingReport rangingReport = getRangingReport(rangingData, uwbSession.getProtocolName(),
- uwbSession.getParams(), mUwbInjector.getElapsedSinceBootNanos());
+ RangingReport rangingReport = null;
+ try {
+ rangingReport = getRangingReport(rangingData,
+ uwbSession.getProtocolName(),
+ uwbSession.getParams(), mUwbInjector.getElapsedSinceBootNanos());
+ } catch (Exception e) {
+ Log.e(TAG, "getRangingReport Failed.");
+ e.printStackTrace();
+ }
if (mUwbInjector.getUwbServiceCore().isOemExtensionCbRegistered()) {
try {
rangingReport = mUwbInjector.getUwbServiceCore().getOemExtensionCallback()
.onRangingReportReceived(rangingReport);
} catch (RemoteException e) {
+ Log.e(TAG, "UwbInjector - onRangingReportReceived : Failed.");
e.printStackTrace();
}
}
diff --git a/service/java/com/android/server/uwb/UwbSettingsStore.java b/service/java/com/android/server/uwb/UwbSettingsStore.java
index 24e59727..3abf5d8b 100644
--- a/service/java/com/android/server/uwb/UwbSettingsStore.java
+++ b/service/java/com/android/server/uwb/UwbSettingsStore.java
@@ -282,11 +282,11 @@ public class UwbSettingsStore {
* Dump output for debugging.
*/
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- pw.println();
- pw.println("Dump of " + TAG);
+ pw.println("---- Dump of UwbSettingsStore ----");
synchronized (mLock) {
pw.println("Settings: " + mSettings);
}
+ pw.println("---- Dump of UwbSettingsStore ----");
}
/**
diff --git a/service/java/com/android/server/uwb/UwbShellCommand.java b/service/java/com/android/server/uwb/UwbShellCommand.java
index af5b0c3d..d147d19f 100644
--- a/service/java/com/android/server/uwb/UwbShellCommand.java
+++ b/service/java/com/android/server/uwb/UwbShellCommand.java
@@ -142,6 +142,7 @@ public class UwbShellCommand extends BasicShellCommandHandler {
new FiraOpenSessionParams.Builder()
.setProtocolVersion(FiraParams.PROTOCOL_VERSION_1_1)
.setSessionId(1)
+ .setSessionType(FiraParams.SESSION_TYPE_RANGING)
.setChannelNumber(9)
.setDeviceType(RANGING_DEVICE_TYPE_CONTROLLER)
.setDeviceRole(RANGING_DEVICE_ROLE_INITIATOR)
diff --git a/service/java/com/android/server/uwb/UwbTestUtils.java b/service/java/com/android/server/uwb/UwbTestUtils.java
index c5358841..d0ae15fe 100644
--- a/service/java/com/android/server/uwb/UwbTestUtils.java
+++ b/service/java/com/android/server/uwb/UwbTestUtils.java
@@ -45,16 +45,23 @@ import com.google.uwb.support.oemextension.RangingReportMetadata;
public class UwbTestUtils {
public static final int TEST_SESSION_ID = 7;
public static final int TEST_SESSION_ID_2 = 8;
+ public static final byte TEST_SESSION_TYPE = FiraParams.SESSION_TYPE_RANGING;
public static final byte[] PEER_SHORT_MAC_ADDRESS = {0x35, 0x37};
+ public static final long PEER_SHORT_MAC_ADDRESS_LONG = 0x3735L;
public static final byte[] PEER_EXTENDED_SHORT_MAC_ADDRESS =
{0x35, 0x37, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+ public static final long PEER_EXTENDED_SHORT_MAC_ADDRESS_LONG = 0x3735L;
public static final byte[] PEER_EXTENDED_MAC_ADDRESS =
{0x12, 0x14, 0x16, 0x18, 0x31, 0x33, 0x35, 0x37};
+ public static final long PEER_EXTENDED_MAC_ADDRESS_LONG = 0x3735333118161412L;
public static final byte[] PEER_EXTENDED_MAC_ADDRESS_2 =
{0x2, 0x4, 0x6, 0x8, 0x1, 0x3, 0x5, 0x7};
+ public static final long PEER_EXTENDED_MAC_ADDRESS_2_LONG = 0x0705030108060402L;
public static final byte[] PEER_BAD_MAC_ADDRESS = {0x12, 0x14, 0x16, 0x18};
public static final UwbAddress PEER_EXTENDED_UWB_ADDRESS = UwbAddress.fromBytes(
PEER_EXTENDED_MAC_ADDRESS);
+ public static final UwbAddress PEER_EXTENDED_UWB_ADDRESS_2 = UwbAddress.fromBytes(
+ PEER_EXTENDED_MAC_ADDRESS_2);
public static final UwbAddress PEER_SHORT_UWB_ADDRESS = UwbAddress.fromBytes(
PEER_SHORT_MAC_ADDRESS);
public static final PersistableBundle PERSISTABLE_BUNDLE = new PersistableBundle();
@@ -72,6 +79,7 @@ public class UwbTestUtils {
private static final int TEST_DISTANCE = 101;
private static final float TEST_AOA_AZIMUTH = 67;
private static final int TEST_AOA_AZIMUTH_FOM = 50;
+ private static final int TEST_BAD_AOA_AZIMUTH_FOM = 150;
private static final float TEST_AOA_ELEVATION = 37;
private static final int TEST_AOA_ELEVATION_FOM = 90;
private static final float TEST_AOA_DEST_AZIMUTH = 67;
@@ -101,11 +109,22 @@ public class UwbTestUtils {
/** Build UwbRangingData for all Ranging Measurement Type(s). */
public static UwbRangingData generateRangingData(
int rangingMeasurementType, int macAddressingMode, int rangingStatus) {
+ byte[] macAddress = (macAddressingMode == MAC_ADDRESSING_MODE_SHORT)
+ ? PEER_SHORT_MAC_ADDRESS : PEER_EXTENDED_MAC_ADDRESS;
+ return generateRangingData(
+ rangingMeasurementType, macAddressingMode, macAddress, rangingStatus);
+ }
+
+ /** Build UwbRangingData for all Ranging Measurement Type(s). */
+ public static UwbRangingData generateRangingData(
+ int rangingMeasurementType, int macAddressingMode, byte[] macAddress,
+ int rangingStatus) {
switch (rangingMeasurementType) {
case RANGING_MEASUREMENT_TYPE_TWO_WAY:
return generateTwoWayMeasurementRangingData(rangingStatus);
case RANGING_MEASUREMENT_TYPE_OWR_AOA:
- return generateOwrAoaMeasurementRangingData(macAddressingMode, rangingStatus);
+ return generateOwrAoaMeasurementRangingData(
+ macAddressingMode, macAddress, rangingStatus);
case RANGING_MEASUREMENT_TYPE_DL_TDOA:
return generateDlTDoAMeasurementRangingData(macAddressingMode, rangingStatus);
default:
@@ -130,10 +149,8 @@ public class UwbTestUtils {
}
private static UwbRangingData generateOwrAoaMeasurementRangingData(
- int macAddressingMode, int rangingStatus) {
+ int macAddressingMode, byte[] macAddress, int rangingStatus) {
final int noOfRangingMeasures = 1;
- byte[] macAddress = (macAddressingMode == MAC_ADDRESSING_MODE_SHORT)
- ? PEER_SHORT_MAC_ADDRESS : PEER_EXTENDED_MAC_ADDRESS;
final UwbOwrAoaMeasurement uwbOwrAoaMeasurement = new UwbOwrAoaMeasurement(
macAddress, rangingStatus, TEST_LOS,
TEST_FRAME_SEQUENCE_NUMBER, TEST_BLOCK_IDX,
@@ -145,6 +162,21 @@ public class UwbTestUtils {
TEST_RAW_NTF_DATA);
}
+ /** Generate an OWR ranging data with a bad AoA Azimuth FOM */
+ public static UwbRangingData generateBadOwrAoaMeasurementRangingData(
+ int macAddressingMode, byte[] macAddress) {
+ final int noOfRangingMeasures = 1;
+ final UwbOwrAoaMeasurement uwbOwrAoaMeasurement = new UwbOwrAoaMeasurement(
+ macAddress, TEST_STATUS, TEST_LOS,
+ TEST_FRAME_SEQUENCE_NUMBER, TEST_BLOCK_IDX,
+ convertFloatToQFormat(TEST_AOA_AZIMUTH, 9, 7), TEST_BAD_AOA_AZIMUTH_FOM,
+ convertFloatToQFormat(TEST_AOA_ELEVATION, 9, 7), TEST_AOA_ELEVATION_FOM);
+ return new UwbRangingData(TEST_SEQ_COUNTER, TEST_SESSION_ID,
+ TEST_RCR_INDICATION, TEST_CURR_RANGING_INTERVAL, RANGING_MEASUREMENT_TYPE_OWR_AOA,
+ macAddressingMode, noOfRangingMeasures, uwbOwrAoaMeasurement,
+ TEST_RAW_NTF_DATA);
+ }
+
private static UwbRangingData generateDlTDoAMeasurementRangingData(
int macAddressingMode, int rangingStatus) {
final int noOfRangingMeasures = 1;
diff --git a/service/java/com/android/server/uwb/advertisement/UwbAdvertiseManager.java b/service/java/com/android/server/uwb/advertisement/UwbAdvertiseManager.java
index 0355f604..6f4085dd 100644
--- a/service/java/com/android/server/uwb/advertisement/UwbAdvertiseManager.java
+++ b/service/java/com/android/server/uwb/advertisement/UwbAdvertiseManager.java
@@ -85,8 +85,7 @@ public class UwbAdvertiseManager {
/**
* Remove all the stored AdvertiseTarget data for the given device.
*/
- public void removeAdvertiseTarget(byte[] macAddressBytes) {
- long macAddress = macAddressByteArrayToLong(macAddressBytes);
+ public void removeAdvertiseTarget(long macAddress) {
mAdvertiseTargetMap.remove(macAddress);
}
@@ -113,7 +112,7 @@ public class UwbAdvertiseManager {
}
if (!isWithinTimeThreshold(uwbAdvertiseTarget)) {
- removeAdvertiseTarget(macAddressBytes);
+ removeAdvertiseTarget(macAddress);
}
}
diff --git a/service/java/com/android/server/uwb/correction/UwbFilterEngine.java b/service/java/com/android/server/uwb/correction/UwbFilterEngine.java
new file mode 100644
index 00000000..4617f8a9
--- /dev/null
+++ b/service/java/com/android/server/uwb/correction/UwbFilterEngine.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 2023 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 com.android.server.uwb.correction;
+
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.server.uwb.correction.filtering.IPositionFilter;
+import com.android.server.uwb.correction.math.Pose;
+import com.android.server.uwb.correction.math.SphericalVector;
+import com.android.server.uwb.correction.pose.IPoseSource;
+import com.android.server.uwb.correction.primers.IPrimer;
+
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Consumes raw UWB values and outputs filtered UWB values. See the {@link UwbFilterEngine.Builder}
+ * for how it is configured.
+ */
+public class UwbFilterEngine implements AutoCloseable {
+ public static final boolean ENABLE_BIG_LOG = false;
+ @NonNull private final List<IPrimer> mPrimers;
+ @Nullable private final IPositionFilter mFilter;
+ @Nullable private final IPoseSource mPoseSource;
+
+ /**
+ * The last UWB reading, after priming or filtering, depending on which facilities
+ * are available. If computation fails or is not possible (ie - filter or primer is not
+ * configured), the computation function will return this.
+ */
+ @Nullable private SphericalVector mLastInputState;
+
+ private boolean mClosed;
+
+ private UwbFilterEngine(
+ @NonNull List<IPrimer> primers,
+ @Nullable IPoseSource poseSource,
+ @Nullable IPositionFilter filter) {
+ this.mPrimers = primers;
+ this.mPoseSource = poseSource;
+ this.mFilter = filter;
+ }
+
+ /**
+ * Updates the engine with the latest UWB data.
+ * @param position The raw position produced by the UWB hardware.
+ */
+ public void add(@NonNull SphericalVector.Sparse position) {
+ add(position, Instant.now());
+ }
+
+ /**
+ * Updates the engine with the latest UWB data.
+ * @param position The raw position produced by the UWB hardware.
+ * @param instant The instant at which the UWB value was received.
+ */
+ public void add(@NonNull SphericalVector.Sparse position, Instant instant) {
+ StringBuilder bigLog = ENABLE_BIG_LOG ? new StringBuilder(position.toString()) : null;
+ Objects.requireNonNull(position);
+ Objects.requireNonNull(instant);
+
+ SphericalVector prediction = compute(instant);
+
+ for (IPrimer primer: mPrimers) {
+ position = primer.prime(position, prediction, mPoseSource);
+ if (bigLog != null) {
+ bigLog.append(" ->")
+ .append(primer.getClass().getSimpleName()).append("=")
+ .append(position);
+ }
+ }
+ if (!position.isComplete()) {
+ // Primers did not fully prime the position vector.
+ // This is not okay unless the triangulation filter is implemented to produce
+ // these missing values.
+ }
+ mLastInputState = position.vector;
+ if (mFilter != null) {
+ mFilter.updatePose(mPoseSource, instant);
+ mFilter.add(position.vector, instant);
+ if (bigLog != null) {
+ bigLog.append(" : filtered=")
+ .append(mFilter.compute());
+ }
+ }
+ if (bigLog != null) {
+ Log.d("RAW", bigLog.toString());
+ }
+ }
+
+ /**
+ * Computes the most probably UWB location as of now.
+ *
+ * @return A SphericalVector representing the most likely UWB location.
+ */
+ @Nullable
+ public SphericalVector compute() {
+ return compute(Instant.now());
+ }
+
+ /**
+ * Computes the most probable UWB location as of the given instant.
+ * @param instant The time for which to compute the UWB location. This should be at or after
+ * the most recent UWB sample.
+ * @return A SphericalVector representing the most likely UWB location.
+ */
+ @Nullable
+ public SphericalVector compute(Instant instant) {
+ if (mFilter != null) {
+ mFilter.updatePose(mPoseSource, instant);
+ return mFilter.compute(instant);
+ }
+ return mLastInputState;
+ }
+
+ /**
+ * Gets the current device pose.
+ */
+ @NonNull
+ public Pose getPose() {
+ Pose pose = null;
+ if (mPoseSource != null) {
+ pose = mPoseSource.getPose();
+ }
+ if (pose == null) {
+ pose = Pose.IDENTITY;
+ }
+ return pose;
+ }
+
+ /**
+ * Frees or closes all resources consumed by this object.
+ */
+ @Override
+ public void close() {
+ if (!mClosed) {
+ mClosed = true;
+ }
+ }
+
+ /**
+ * Builder for a {@link UwbFilterEngine}.
+ */
+ public static class Builder {
+ @Nullable private IPositionFilter mFilter;
+ @Nullable private IPoseSource mPoseSource;
+ @NonNull private final ArrayList<IPrimer> mPrimers = new ArrayList<>();
+
+ /**
+ * Sets the filter this UWB filter engine will use. If not provided, no filtering will
+ * occur.
+ * @param filter The position filter to use.
+ * @return This builder.
+ */
+ public Builder setFilter(IPositionFilter filter) {
+ this.mFilter = filter;
+ return this;
+ }
+
+ /**
+ * Sets the pose source the UWB filter engine will use. If not set, no pose processing
+ * will occur.
+ * @param poseSource Any pose source.
+ * @return This builder.
+ */
+ public Builder setPoseSource(IPoseSource poseSource) {
+ this.mPoseSource = poseSource;
+ return this;
+ }
+
+ /**
+ * Adds a primer to the list of primers the engine will use. The primers will execute
+ * in the order in which {@link #addPrimer(IPrimer)} was called.
+ * @param primer The primer to add.
+ * @return This builder.
+ */
+ public Builder addPrimer(@NonNull IPrimer primer) {
+ Objects.requireNonNull(primer);
+ this.mPrimers.add(primer);
+ return this;
+ }
+
+ /**
+ * Builds a UWB filter engine based on the calls made to the builder.
+ * @return the constructed UWB filter engine.
+ */
+ public UwbFilterEngine build() {
+ return new UwbFilterEngine(mPrimers, mPoseSource, mFilter);
+ }
+ }
+}
diff --git a/service/java/com/android/server/uwb/correction/filtering/IFilter.java b/service/java/com/android/server/uwb/correction/filtering/IFilter.java
new file mode 100644
index 00000000..7413a1ae
--- /dev/null
+++ b/service/java/com/android/server/uwb/correction/filtering/IFilter.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2023 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 com.android.server.uwb.correction.filtering;
+
+import androidx.annotation.NonNull;
+
+import java.time.Instant;
+
+/**
+ * Interface for a filter.
+ */
+public interface IFilter {
+ /**
+ * Adds a value to the filter.
+ * @param value The value to add to the filter.
+ * The timestamp defaults to now.
+ */
+ default void add(float value) {
+ add(value, Instant.now());
+ }
+
+ /**
+ * Adds a value to the filter.
+ * @param value The value to add to the filter.
+ * @param instant When the value occurred, used to determine the latency introduced by
+ * the filter. Note that this has no effect on the order in which the filter operates
+ * on values.
+ */
+ void add(float value, @NonNull Instant instant);
+
+ /**
+ * Alters the state of the filter such that it anticipates a change by the given amount.
+ * For example, if the filter is working with distance, and the distance of the next
+ * reading is expected to increase by 1 meter, 'shift' should be 1.
+ * @param shift How much to alter the filter state.
+ */
+ void compensate(float shift);
+
+ /**
+ * Gets a sample object with the result from the last computation. The sample's time is
+ * the average time of the samples that created the result, effectively describing the
+ * latency introduced by the filter.
+ * @return The result from the last computation.
+ */
+ @NonNull
+ Sample getResult();
+
+ /**
+ * Gets a sample object with the result from the provided time. The returned sample's time is
+ * the closest the filter can provide to the given time.
+ * The default behavior is to return the latest available result, which is likely to be
+ * older than the requested time (see {@link #getResult()}).
+ * This must be overridden in order to support predicting filters like a Kalman filter or an
+ * extrapolating median/average filter.
+ * @param when The preferred time of the predicted sample.
+ * @return The result from the computation.
+ */
+ @NonNull
+ default Sample getResult(Instant when) {
+ return getResult();
+ }
+}
diff --git a/service/java/com/android/server/uwb/correction/filtering/IPositionFilter.java b/service/java/com/android/server/uwb/correction/filtering/IPositionFilter.java
new file mode 100644
index 00000000..091bb8d1
--- /dev/null
+++ b/service/java/com/android/server/uwb/correction/filtering/IPositionFilter.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2023 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 com.android.server.uwb.correction.filtering;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.server.uwb.correction.math.SphericalVector;
+import com.android.server.uwb.correction.pose.IPoseSource;
+
+import java.time.Instant;
+
+/**
+ * Interface for a filter that operates on a UwbPosition.
+ */
+public interface IPositionFilter {
+ /**
+ * Adds a value to the filter.
+ * @param position The value to add to the filter.
+ * The timestamp defaults to now.
+ */
+ default void add(@NonNull SphericalVector position) {
+ add(position, Instant.now());
+ }
+
+ /**
+ * Adds a value to the filter.
+ * @param value The value to add to the filter.
+ * @param instant When the value occurred, used to determine the latency introduced by
+ * the filter. Note that this has no effect on the order in which the filter operates
+ * on values.
+ */
+ void add(@NonNull SphericalVector value, @NonNull Instant instant);
+
+ /**
+ * Computes a predicted UWB position based on the new pose.
+ */
+ default SphericalVector compute() {
+ return compute(Instant.now());
+ }
+
+ /**
+ * Computes a predicted UWB position based on the new pose.
+ * @param instant The instant for which the UWB prediction should be computed.
+ */
+ SphericalVector compute(@NonNull Instant instant);
+
+ /**
+ * Updates the filter history to account for changes to the pose.
+ * @param poseSource The pose source from which to get the latest pose.
+ */
+ void updatePose(@Nullable IPoseSource poseSource, @NonNull Instant instant);
+}
diff --git a/service/java/com/android/server/uwb/correction/filtering/MAFilter.java b/service/java/com/android/server/uwb/correction/filtering/MAFilter.java
new file mode 100644
index 00000000..eba0b4f1
--- /dev/null
+++ b/service/java/com/android/server/uwb/correction/filtering/MAFilter.java
@@ -0,0 +1,237 @@
+/*
+ * Copyright (C) 2023 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 com.android.server.uwb.correction.filtering;
+
+import androidx.annotation.NonNull;
+
+import java.time.Instant;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * A Median, Average filter. The filter has an adjustable median window and
+ * the configured percentage of non-outliers are averaged.
+ */
+public class MAFilter implements IFilter {
+ /**
+ * The maximum allowed filter size.
+ */
+ public static final int MAX_FILTER = 255;
+ private static final String TAG = "MAEFilter";
+
+ private int mWindowSize;
+ private float mCut;
+ @NonNull
+ private final ArrayDeque<Sample> mWindow = new ArrayDeque<>();
+ @NonNull
+ private Sample mResult = new Sample(0F, Instant.now());
+
+ /**
+ * Creates a new instance of the MAFilter class.
+ * @param windowSize The maximum number of samples to store in the moving window.
+ * @param cut What percentage of non-outliers are to be averaged, from 0 to 1. See
+ * {@link #setCut(float)} for more information.
+ */
+ public MAFilter(int windowSize, float cut) {
+ setWindowSize(windowSize);
+ setCut(cut);
+ }
+
+ /**
+ * Gets the size of the median window.
+ * @return A count of samples.
+ */
+ public int getWindowSize() {
+ return mWindowSize;
+ }
+
+ /**
+ * Sets the size of the median window; how many samples are considered when producing a filtered
+ * result. Must be between 1 and {@link #MAX_FILTER}.
+ * @param value The number of samples to set as the maximum window size.
+ */
+ public void setWindowSize(int value) {
+ if (value <= 0 || value > MAX_FILTER) {
+ throw new IllegalArgumentException(
+ "Value is out of range; must be between 1 and " + MAX_FILTER + " inclusive.");
+ }
+ mWindowSize = value;
+ }
+
+ /**
+ * Gets the size of the median cut.
+ * @return A value from 0-1 that describes what percentage of values in the window will be
+ * kept and averaged.
+ */
+ public float getCut() {
+ return mCut;
+ }
+
+ /**
+ * Sets the size of the median cut. A value of 0 is a perfect median, taking only the
+ * center value(s). A value of 1 is a perfect average. A value of 0.25 discards 75% of the
+ * outliers and averages the 25% remaining values.
+ * @param value A value 0-1 that describes what median percentage of the window to average.
+ */
+ public void setCut(float value) {
+ if (value < 0 || value > 1) {
+ throw new IllegalArgumentException(
+ "Value is out of range; must be between 0 and 1 inclusive");
+ }
+ mCut = value;
+ }
+
+ /**
+ * Gets a sample object with the result from the last computation. The sample's time is
+ * the average time of the samples that created the result, effectively describing the
+ * latency introduced by the filter.
+ * @return The result from the last computation.
+ */
+ @NonNull
+ public Sample getResult() {
+ return mResult;
+ }
+
+ /**
+ * Adds a value to the filter.
+ * @param value The value to add to the filter.
+ * @param instant When the value occurred, used to determine the latency introduced by
+ * the filter. Note that this has no effect on the order in which the filter operates
+ * on values. Defaults to now.
+ */
+ @Override
+ public void add(float value, @NonNull Instant instant) {
+ Objects.requireNonNull(instant);
+ mWindow.addLast(new Sample(value, instant));
+ while (mWindow.size() > mWindowSize) {
+ mWindow.removeFirst();
+ }
+ mResult = compute();
+ }
+
+ /**
+ * Rewrites all sample values based on the selector.
+ * @param selector The interface containing the function that selects the new sample values.
+ */
+ protected void remap(RemapFunction selector) {
+ mWindow.forEach(s -> s.value = selector.run(s.value));
+ mResult = new Sample(selector.run(mResult.value), mResult.instant);
+ }
+
+ /**
+ * Alters the state of the filter such that it anticipates a change by the given amount.
+ * For example, if the filter is working with distance, and the distance of the next
+ * reading is expected to increase by 1 meter, 'shift' should be 1.
+ * @param shift How much to alter the filter state.
+ */
+ @Override
+ public void compensate(float shift) {
+ remap(s -> s + shift);
+ }
+
+ /**
+ * Performs the median and average component and returns a new sample.
+ * The sample's instant indicates the sourced data's center time, approximating how much
+ * latency was introduced by the filter.
+ */
+ private Sample compute() {
+ int count = mWindow.size();
+ if (count == 0) {
+ throw new IllegalStateException("The filter is empty.");
+ }
+ if (count == 1) {
+ return mWindow.getFirst();
+ }
+ List<Sample> sorted = sortSamples(mWindow);
+
+ if (mCut == 1F) {
+ // 100% of a median cut is just an average.
+
+ // Note that this comes AFTER the sort. MARotationFilter's averaging routine
+ // requires that samples are sorted, as it sorts in a special way to respect angle
+ // rollover.
+ return averageSortedSamples(sorted);
+ }
+
+ int throwAway = Math.round(count * (1 - mCut) / 2);
+ if (2 * throwAway >= count) {
+ // At least 2 samples if count is even or 1 sample if count is odd
+ throwAway--;
+ }
+
+ return averageSortedSamples(sorted.subList(throwAway, count - throwAway));
+ }
+
+ /**
+ * Creates a new, sorted list containing the provided samples. Sorting is based on the sample
+ * value.
+ * @param list A list of samples to sort.
+ * @return A new list, of the same samples, sorted by value.
+ */
+ protected List<Sample> sortSamples(Collection<Sample> list) {
+ ArrayList<Sample> sorted = new ArrayList<>(list);
+ Collections.sort(sorted);
+ return sorted;
+ }
+
+ /**
+ * Averages a list of samples.
+ * @param samples The list of samples.
+ * @return A sample containing the average value and time of the samples in the list.
+ */
+ protected Sample averageSortedSamples(Collection<Sample> samples) {
+ int size = samples.size();
+ if (size == 0) {
+ return null; // Average can't be computed.
+ }
+ float valueSum = 0F;
+
+ // Using a relevant epoch keeps the values small and therefore decreases the risk of
+ // overflow.
+ long instantEpoch = samples.stream().findFirst().get().instant.toEpochMilli();
+ long instantSum = 0;
+ for (Sample s: samples) {
+ if (s == null) {
+ // there should never be a null. It's not worth decrementing size and checking for
+ // size == 0 again.lis
+ return null; // Average can't be computed.
+ }
+ valueSum += s.value;
+ instantSum += s.instant.toEpochMilli() - instantEpoch;
+ }
+ float avg = valueSum / size;
+ return new Sample(
+ avg,
+ Instant.ofEpochMilli(instantEpoch + instantSum / size));
+ }
+
+ /**
+ * This interface can be used to implement a remapper - a function that changes historical data
+ * in the filter in order to compensate for aspects of pose changes.
+ */
+ public interface RemapFunction {
+ /**
+ * Performs a change to a data point in a filter.
+ * @param value The value that needs compensation from a pose change.
+ * @return The new, pose-compensated value.
+ */
+ float run(float value);
+ }
+}
diff --git a/service/java/com/android/server/uwb/correction/filtering/MARotationFilter.java b/service/java/com/android/server/uwb/correction/filtering/MARotationFilter.java
new file mode 100644
index 00000000..d404d218
--- /dev/null
+++ b/service/java/com/android/server/uwb/correction/filtering/MARotationFilter.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2023 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 com.android.server.uwb.correction.filtering;
+
+import static com.android.server.uwb.correction.math.MathHelper.F_PI;
+
+import com.android.server.uwb.correction.math.MathHelper;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * A median and average filter that operates identically to {@link MAFilter}, but uses the radians
+ * circular number system, wherein numbers refer to points on a circle such that +PI and -PI are
+ * the same, and averages refer to the average angle on a circle rather than the average of their
+ * linear numerical values.
+ */
+public class MARotationFilter extends MAFilter {
+ public MARotationFilter(int windowSize, float cut) {
+ super(windowSize, cut);
+ }
+
+ /**
+ * Creates a naive average of the given samples. Both the value and instant of the samples are
+ * averaged. This will probably not produce a desired result if the samples are normalized
+ * to roll over at +/-PI rad. Use {@link #sortSamples(Collection)} to shift the roll over
+ * to a more desired location.
+ * @param samples The list of samples.
+ * @return The average of the samples, normalized within +/-PI.
+ */
+ @Override
+ protected Sample averageSortedSamples(Collection<Sample> samples) {
+ Sample result = super.averageSortedSamples(samples);
+ return new Sample(MathHelper.normalizeRadians(result.value), result.instant);
+ }
+
+ /**
+ * Rewrites all sample values based on the selector.
+ * @param selector The interface containing the remapping function.
+ */
+ public void remap(RemapFunction selector) {
+ super.remap(v -> MathHelper.normalizeRadians(selector.run(v)));
+ }
+
+ /**
+ * Changes the input list so that angles can be sorted, averaged and compared, even if
+ * they are on either side of the +/-180 divide. Note that this will return angles
+ * higher than 180 degrees.
+ * The input values must be between -180 and +180.
+ * Creating a sorted list, this finds the largest "gap" between angles and assumes that
+ * angles on either side of that gap represent the upper and lower bounds of what needs to
+ * be averaged. It then adds 360 to the values to the left of the gap and rearranges
+ * to make the list is sorted again.
+ * (Degrees are used for the explanation - this function actually operates on radians)
+ */
+ @Override
+ protected List<Sample> sortSamples(Collection<Sample> list) {
+ List<Sample> sorted = super.sortSamples(list);
+ int size = sorted.size();
+ if (size < 2) {
+ return sorted;
+ }
+
+ // The initial gap to check, maybe not the biggest, is the gap on either side of +/-180,
+ // which is at the index 0.
+ int largestGapIndex = 0;
+ float largestGapSize =
+ (sorted.get(size - 1).value - sorted.get(0).value + 2 * F_PI) % F_PI;
+ for (int i = 1; i < size; i++) {
+ float diff = sorted.get(i).value - sorted.get(i - 1).value;
+ if (diff > largestGapSize) {
+ largestGapSize = diff;
+ largestGapIndex = i;
+ }
+ }
+ for (int i = 0; i < largestGapIndex; i++) {
+ sorted.set(
+ i,
+ new Sample(sorted.get(i).value + 2 * F_PI, sorted.get(i).instant)
+ );
+ }
+ Collections.rotate(sorted, -largestGapIndex);
+ return sorted;
+ }
+}
diff --git a/service/java/com/android/server/uwb/correction/filtering/PositionFilterImpl.java b/service/java/com/android/server/uwb/correction/filtering/PositionFilterImpl.java
new file mode 100644
index 00000000..aab30b32
--- /dev/null
+++ b/service/java/com/android/server/uwb/correction/filtering/PositionFilterImpl.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2023 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 com.android.server.uwb.correction.filtering;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.server.uwb.correction.math.Pose;
+import com.android.server.uwb.correction.math.SphericalVector;
+import com.android.server.uwb.correction.math.Vector3;
+import com.android.server.uwb.correction.pose.IPoseSource;
+
+import java.time.Instant;
+import java.util.Objects;
+
+/**
+ * An implementation of a combined azimuth, distance and elevation filter, which can be shifted
+ * as the pose changes.
+ * A filter that operates on X/Y/Z may be faster, but it would not support filtering differently
+ * for angle and distance.
+ */
+public class PositionFilterImpl implements IPositionFilter {
+ @NonNull private final IFilter mAzimuthFilter;
+ @NonNull private final IFilter mElevationFilter;
+ @NonNull private final IFilter mDistanceFilter;
+ private Pose mLastPose;
+
+ public PositionFilterImpl(
+ @NonNull IFilter azimuthFilter,
+ @NonNull IFilter elevationFilter,
+ @NonNull IFilter distanceFilter) {
+ Objects.requireNonNull(azimuthFilter);
+ Objects.requireNonNull(elevationFilter);
+ Objects.requireNonNull(distanceFilter);
+ this.mAzimuthFilter = azimuthFilter;
+ this.mElevationFilter = elevationFilter;
+ this.mDistanceFilter = distanceFilter;
+ }
+
+ /**
+ * Adds a value to the filter.
+ *
+ * @param value The value to add to the filter.
+ * @param instant When the value occurred, used to determine the latency introduced by
+ * the filter. Note that this has no effect on the order in which the filter
+ * operates
+ */
+ @Override
+ public void add(@NonNull SphericalVector value, @NonNull Instant instant) {
+ Objects.requireNonNull(value);
+ Objects.requireNonNull(instant);
+ mAzimuthFilter.add(value.azimuth, instant);
+ mElevationFilter.add(value.elevation, instant);
+ mDistanceFilter.add(value.distance, instant);
+ }
+
+ /**
+ * Computes a predicted UWB position based on the new pose.
+ *
+ * @param instant The instant for which the UWB prediction should be computed.
+ */
+ @Override
+ public SphericalVector compute(@NonNull Instant instant) {
+ Objects.requireNonNull(instant);
+ // Cartesian extrapolation would happen here, such as target movement.
+ // Spherical extrapolation can happen in the filter because it operates on
+ // spherical values.
+
+ return SphericalVector.fromRadians(
+ mAzimuthFilter.getResult(instant).value,
+ mElevationFilter.getResult(instant).value,
+ mDistanceFilter.getResult(instant).value
+ );
+ }
+
+ /**
+ * Updates the filter history to account for changes to the pose. Note that the entire
+ * pose source object is provided, so that its capabilities can be assessed as a part
+ * of the computation.
+ *
+ * @param poseSource The pose source that has the new pose.
+ */
+ @Override
+ public void updatePose(@Nullable IPoseSource poseSource, @NonNull Instant instant) {
+ if (poseSource == null) {
+ return;
+ }
+ Pose newPose = poseSource.getPose();
+ if (mLastPose != null && newPose != null && newPose != mLastPose) {
+ Pose deltaPose = Pose.compose(newPose.inverted(), mLastPose);
+ updatePoseFromDelta(deltaPose, compute(instant));
+ }
+ mLastPose = newPose;
+ }
+
+ /**
+ * Applies compensations to the azimuth, elevation and distance filters based on how the
+ * pose changed, and how the last-known position of the tag would be affected.
+ *
+ * @param deltaPose A relative transform describing how the pose changed.
+ * @param estimate The last known location of the UWB signal.
+ */
+ private void updatePoseFromDelta(@NonNull Pose deltaPose, @NonNull SphericalVector estimate) {
+ // This conversion (Spherical -> Cartesian -> transform -> Spherical) is the best
+ // I have for right now. At the expense of readability, this could more efficiently
+ // transform spherical coordinates if performance is a problem.
+
+ // Last known position of tag, relative to camera as of previous pose.
+ Vector3 vecFromOldCam = estimate.toCartesian();
+
+ // Convert to position of tag, relative to camera after the pose changed.
+ Vector3 vecFromNewCam = deltaPose.transformPoint(vecFromOldCam);
+
+ // New azimuth, elevation and distance based on this new tag position.
+ SphericalVector newEstimate = SphericalVector.fromCartesian(vecFromNewCam);
+
+ // Adjust the filters to represent this new estimation.
+ mAzimuthFilter.compensate(newEstimate.azimuth - estimate.azimuth);
+ mElevationFilter.compensate(newEstimate.elevation - estimate.elevation);
+ mDistanceFilter.compensate(newEstimate.distance - estimate.distance);
+ }
+}
diff --git a/service/java/com/android/server/uwb/correction/filtering/Sample.java b/service/java/com/android/server/uwb/correction/filtering/Sample.java
new file mode 100644
index 00000000..68614867
--- /dev/null
+++ b/service/java/com/android/server/uwb/correction/filtering/Sample.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2023 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 com.android.server.uwb.correction.filtering;
+
+import androidx.annotation.NonNull;
+
+import java.time.Instant;
+
+/**
+ * Represents a data sample and when it was acquired.
+ */
+public class Sample implements Comparable<Sample> {
+ public float value;
+ public Instant instant;
+
+ /**
+ * Creates a new instance of the Sample class.
+ * @param value The value of the sample.
+ * @param instant The time at which the value was relevant.
+ */
+ Sample(float value, Instant instant) {
+ this.value = value;
+ this.instant = instant;
+ }
+
+ /**
+ * Compares this sample to another, ignoring the time of the samples.
+ * @param other The other sample to compare to.
+ * @return a negative integer, zero, or a positive integer as this object
+ * is less than, equal to, or greater than the specified object.
+ * @throws NullPointerException if the specified object is null
+ */
+ @Override
+ public int compareTo(@NonNull Sample other) {
+ return Float.compare(value, other.value);
+ }
+}
diff --git a/service/java/com/android/server/uwb/correction/math/AoAVector.java b/service/java/com/android/server/uwb/correction/math/AoAVector.java
index 85e62a1d..01010d8d 100644
--- a/service/java/com/android/server/uwb/correction/math/AoAVector.java
+++ b/service/java/com/android/server/uwb/correction/math/AoAVector.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2023 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.
@@ -42,18 +42,18 @@ import java.util.Objects;
/**
* Represents a point in space as distance, azimuth and elevation.
* This uses OpenGL's right-handed coordinate system, where the origin is facing in the
- * -Z direction. Increasing azimuth rotates around Y and increases X. Increasing
- * elevation rotates around X and increases Y.
+ * -Z direction. Increasing azimuth rotates around Y and increases X. Increasing
+ * elevation rotates around X and increases Y.
*
* Note that this is NOT quite a spherical vector. It represents angles seen by AoA antennas.
* In this implementation, azimuth and elevation are treated the same. Therefore, for example:
- * Very "up" or "down" targets will have an azimuth near 0, because the signal will arrive at
- * both AoA antennas at nearly the same time.
+ * Very "up" or "down" targets will have an azimuth near 0, because the signal will arrive at
+ * both AoA antennas at nearly the same time.
* In a spherical vector, azimuth is computed exclusively from the horizontal plane and treated
- * independently of the vertical axis, but elevation is computed along the plane of the azimuth.
+ * independently of the vertical axis, but elevation is computed along the plane of the azimuth.
* This also means that there are some angles that are impossible. For example, something with
- * a 90deg azimuth (directly right of the phone) cannot possibly be viewed by the elevation
- * antennas from any angle other than 0deg.
+ * a 90deg azimuth (directly right of the phone) cannot possibly be viewed by the elevation
+ * antennas from any angle other than 0deg.
*/
@Immutable
public final class AoAVector {
@@ -64,8 +64,8 @@ public final class AoAVector {
/**
* Creates a AoAVector from the azimuth, elevation and distance of a viewpoint that is
- * facing into the -Z axis. Illegal azimuth and elevation combinations will be scaled away
- * from +/-90deg such that they are legal.
+ * facing into the -Z axis. Illegal azimuth and elevation combinations will be scaled away
+ * from +/-90deg such that they are legal.
*
* @param azimuth The angle along the X axis, around the Y axis.
* @param elevation The angle along the Y axis, around the X axis.
@@ -76,7 +76,7 @@ public final class AoAVector {
float ae = abs(elevation);
if (ae > F_HALF_PI) {
// Normalize elevation to be only +/-90 - if it's outside that, mirror and bound the
- // elevation and flip the azimuth.
+ // elevation and flip the azimuth.
elevation = (F_PI - ae) * signum(elevation);
azimuth += F_PI;
}
@@ -97,11 +97,11 @@ public final class AoAVector {
float scaleFactor = angleSum / (F_HALF_PI);
if (scaleFactor > 1) {
// The combination of degrees isn't possible - for example, the azimuth suggests that
- // the target is exactly 90deg to the right, and yet elevation is non-zero.
+ // the target is exactly 90deg to the right, and yet elevation is non-zero.
// The elevation and azimuth will be scaled down until they are within
- // legal limits. This will create a bias away from 90-degree readings.
- // Note that azimuth will be corrected to higher than 90deg if it was originally
- // above 90deg.
+ // legal limits. This will create a bias away from 90-degree readings.
+ // Note that azimuth will be corrected to higher than 90deg if it was originally
+ // above 90deg.
elevation /= scaleFactor;
azimuth = backFacing ? (F_PI * signum(azimuth) - laz / scaleFactor) : (azimuth
/ scaleFactor);
@@ -159,7 +159,7 @@ public final class AoAVector {
/**
* Produces an AoA vector from a cartesian vector, converting X, Y and Z values to
- * azimuth, elevation and distance.
+ * azimuth, elevation and distance.
*
* @param position The cartesian representation to convert.
* @return An equivalent AoA vector representation.
@@ -172,7 +172,7 @@ public final class AoAVector {
/**
* Produces a AoA vector from a cartesian vector, converting X, Y and Z values to
- * azimuth, elevation and distance.
+ * azimuth, elevation and distance.
*
* @param x The cartesian x-coordinate to convert.
* @param y The cartesian y-coordinate to convert.
diff --git a/service/java/com/android/server/uwb/correction/math/MathHelper.java b/service/java/com/android/server/uwb/correction/math/MathHelper.java
index be5f79de..dae0e2f0 100644
--- a/service/java/com/android/server/uwb/correction/math/MathHelper.java
+++ b/service/java/com/android/server/uwb/correction/math/MathHelper.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2023 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.
@@ -54,7 +54,7 @@ public final class MathHelper {
/**
* Converts degrees that may be outside +/-180 to an equivalent rotation value between
- * -180 (excl) and 180 (incl).
+ * -180 (excl) and 180 (incl).
* @param deg The degrees to normalize
* @return A value above -180 and up to 180 that has an equivalent angle to the input.
*/
diff --git a/service/java/com/android/server/uwb/correction/math/Matrix.java b/service/java/com/android/server/uwb/correction/math/Matrix.java
index 7e0746f6..bf64b799 100644
--- a/service/java/com/android/server/uwb/correction/math/Matrix.java
+++ b/service/java/com/android/server/uwb/correction/math/Matrix.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2023 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.
diff --git a/service/java/com/android/server/uwb/correction/math/Pose.java b/service/java/com/android/server/uwb/correction/math/Pose.java
index 4e99ccab..a15b243e 100644
--- a/service/java/com/android/server/uwb/correction/math/Pose.java
+++ b/service/java/com/android/server/uwb/correction/math/Pose.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2023 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.
@@ -45,7 +45,7 @@ public class Pose {
*
* @param translation a {@code float[3]} representing the translation vector
* @param rotation a {@code float[4]} representing the rotation quaternion following the
- * Hamilton convention.
+ * Hamilton convention.
* @throws IllegalArgumentException if translation and rotation lengths are wrong.
*/
public Pose(float[] translation, float[] rotation) {
@@ -99,9 +99,9 @@ public class Pose {
}
/**
- * Transforms the provided point by the pose. This converts a point relative to the pose into
- * a world-relative point. To convert from a world-relative point to a pose-relative point,
- * use pose.inverted().transformPoint(point)
+ * Transforms the provided point by the pose. This converts a point relative to the pose into
+ * a world-relative point. To convert from a world-relative point to a pose-relative point,
+ * use pose.inverted().transformPoint(point)
*/
public Vector3 transformPoint(Vector3 point) {
return rotation.rotateVector(point).add(translation);
diff --git a/service/java/com/android/server/uwb/correction/math/Quaternion.java b/service/java/com/android/server/uwb/correction/math/Quaternion.java
index efbd8356..f649911d 100644
--- a/service/java/com/android/server/uwb/correction/math/Quaternion.java
+++ b/service/java/com/android/server/uwb/correction/math/Quaternion.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2023 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.
@@ -36,8 +36,8 @@ import java.util.Locale;
* Represents an orientation in 3D space.
*
* This uses OpenGL's right-handed coordinate system, where the origin is facing in the
- * -Z direction. Angle operations such as {@link Quaternion#yawPitchRoll(float, float, float)}
- * assume these operations relative to a quaternion facing in the -Z direction.
+ * -Z direction. Angle operations such as {@link Quaternion#yawPitchRoll(float, float, float)}
+ * assume these operations relative to a quaternion facing in the -Z direction.
*
* +Y
* | -Z
@@ -48,8 +48,8 @@ import java.util.Locale;
* -Y
*
* Yaw, pitch and roll direction can be determined by "grabbing" the axis you're rotating with
- * your right hand, orienting your thumb to point in the positive direction. Your fingers' curl
- * direction indicates the rotation created by positive numbers.
+ * your right hand, orienting your thumb to point in the positive direction. Your fingers' curl
+ * direction indicates the rotation created by positive numbers.
*/
@SuppressWarnings("UnaryPlus")
@Immutable
diff --git a/service/java/com/android/server/uwb/correction/math/SphericalVector.java b/service/java/com/android/server/uwb/correction/math/SphericalVector.java
index acb6412d..c580a881 100644
--- a/service/java/com/android/server/uwb/correction/math/SphericalVector.java
+++ b/service/java/com/android/server/uwb/correction/math/SphericalVector.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2023 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.
@@ -41,8 +41,8 @@ import java.util.Objects;
/**
* Represents a point in space represented as distance, azimuth and elevation.
* This uses OpenGL's right-handed coordinate system, where the origin is facing in the
- * -Z direction. Increasing azimuth rotates around Y and increases X. Increasing
- * elevation rotates around X and increases Y.
+ * -Z direction. Increasing azimuth rotates around Y and increases X. Increasing
+ * elevation rotates around X and increases Y.
*/
@Immutable
public class SphericalVector {
@@ -52,7 +52,7 @@ public class SphericalVector {
/**
* Creates a SphericalVector from the azimuth, elevation and distance of a viewpoint that is
- * facing into the -Z axis.
+ * facing into the -Z axis.
*
* @param azimuth The angle along the X axis, around the Y axis.
* @param elevation The angle along the Y axis, around the X axis.
@@ -63,7 +63,7 @@ public class SphericalVector {
float ae = abs(elevation);
if (ae > F_HALF_PI) {
// Normalize elevation to be only +/-90 - if it's outside that, mirror and bound the
- // elevation and flip the azimuth.
+ // elevation and flip the azimuth.
elevation = (F_PI - ae) * signum(elevation);
azimuth += F_PI;
}
@@ -119,7 +119,7 @@ public class SphericalVector {
/**
* Produces a SphericalVector from a cartesian vector, converting X, Y and Z values to
- * azimuth, elevation and distance.
+ * azimuth, elevation and distance.
*
* @param position The cartesian representation to convert.
* @return An equivalent spherical vector representation.
@@ -132,7 +132,7 @@ public class SphericalVector {
/**
* Produces a spherical vector from a cartesian vector, converting X, Y and Z values to
- * azimuth, elevation and distance.
+ * azimuth, elevation and distance.
*
* @param x The cartesian x-coordinate to convert.
* @param y The cartesian y-coordinate to convert.
@@ -208,7 +208,7 @@ public class SphericalVector {
/**
* Converts this SphericalVector to an equivalent sparse Spherical Vector that has all 3
- * components.
+ * components.
*
* @return An equivalent {@link Sparse}.
*/
@@ -218,7 +218,7 @@ public class SphericalVector {
/**
* Converts this SphericalVector to an equivalent sparse Spherical Vector, with the specified
- * presence or absence of values.
+ * presence or absence of values.
*
* @param hasAzimuth True if the vector includes azimuth.
* @param hasElevation True if the vector includes elevation.
diff --git a/service/java/com/android/server/uwb/correction/math/Vector3.java b/service/java/com/android/server/uwb/correction/math/Vector3.java
index 894db9db..6ea3367d 100644
--- a/service/java/com/android/server/uwb/correction/math/Vector3.java
+++ b/service/java/com/android/server/uwb/correction/math/Vector3.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2023 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.
@@ -131,8 +131,8 @@ public class Vector3 {
/**
* Gets the square of the length of the vector. When performing length comparisons,
- * it is more optimal to compare against a squared length to avoid having to perform
- * a sqrt.
+ * it is more optimal to compare against a squared length to avoid having to perform
+ * a sqrt.
* @return The square of the length of the vector.
*/
public float lengthSquared() {
@@ -206,7 +206,7 @@ public class Vector3 {
/**
* Converts a vector expressed in radians (ie - yaw, pitch, roll), into degrees. Primarily
- * used as a convenience to display data to a user.
+ * used as a convenience to display data to a user.
* @return A Vector3 multiplied by 180/PI.
*/
public Vector3 toDegrees() {
diff --git a/service/java/com/android/server/uwb/correction/pose/GyroPoseSource.java b/service/java/com/android/server/uwb/correction/pose/GyroPoseSource.java
new file mode 100644
index 00000000..b80bc736
--- /dev/null
+++ b/service/java/com/android/server/uwb/correction/pose/GyroPoseSource.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2023 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 com.android.server.uwb.correction.pose;
+
+import android.content.Context;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+
+import com.android.server.uwb.correction.math.MathHelper;
+import com.android.server.uwb.correction.math.Pose;
+import com.android.server.uwb.correction.math.Quaternion;
+import com.android.server.uwb.correction.math.Vector3;
+
+import java.security.InvalidParameterException;
+import java.time.Instant;
+import java.util.EnumSet;
+import java.util.Objects;
+
+/**
+ * Provides poses from the phone's gyro, which provides relative changes to yaw, pitch and roll.
+ * Positional changes are not supported. Note that this pose source has many limitations,
+ * particularly because it drifts and has no sense of down. It is likely to produce weak results
+ * as the phone rotates, and non-elevation phones cannot estimate elevation because there is no
+ * absolute sense of pitch from this pose source.
+ *
+ * The only reason to use this class is to save a very, very marginal amount of power by not using
+ * the accelerometer (to make pitch and roll absolute) and the magnetometer (to make yaw absolute),
+ * or if those sensors do not exist.
+ *
+ * Consider using the {@link RotationPoseSource}.
+ */
+public class GyroPoseSource extends PoseSourceBase implements SensorEventListener {
+ private static final String TAG = "GyroPoseSource";
+ private final SensorManager mSensorManager;
+ private final Sensor mSensor;
+ private final int mIntervalUs;
+ private final int mIntervalMs;
+
+ float mAbsoluteYaw = 0;
+ float mAbsolutePitch = 0;
+ float mAbsoluteRoll = 0;
+
+ private long mLastUpdate;
+
+ /**
+ * Creates a new instance of the GyroPoseSource
+ * @param intervalMs How frequently to update the pose.
+ */
+ public GyroPoseSource(@NonNull Context context, int intervalMs)
+ throws UnsupportedOperationException {
+ Objects.requireNonNull(context);
+ if (intervalMs < MIN_INTERVAL_MS || intervalMs > MAX_INTERVAL_MS) {
+ throw new InvalidParameterException("Invalid interval.");
+ }
+ mSensorManager = context.getSystemService(SensorManager.class);
+ mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE);
+ if (mSensor == null) {
+ throw new UnsupportedOperationException("Device does not support the gyroscope.");
+ }
+ this.mIntervalMs = intervalMs;
+ mIntervalUs = intervalMs * 1000;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected void start() {
+ mSensorManager.registerListener(this, mSensor, mIntervalUs);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected void stop() {
+ mSensorManager.unregisterListener(this);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onSensorChanged(SensorEvent event) {
+ // Yaw changes are relative to the phone, but rotation vector yaw changes are relative to
+ // to the world.
+ // Pitch and roll might just spin forever due to drift; this filter might actually
+ // be more useful if it only produced yaw values.
+ // Further, because the data is accumulated as YPR instead of a Quaternion, there may
+ // be strange gimbal side-effects.
+ if (event.sensor.getType() == Sensor.TYPE_GYROSCOPE) {
+ long now = Instant.now().toEpochMilli();
+ long timeSpan = now - mLastUpdate;
+
+ mLastUpdate = now;
+ if (timeSpan > mIntervalMs * 2L) {
+ // Keep a limit on how long we'll integrate motion.
+ timeSpan = mIntervalMs;
+ }
+
+ // Compute az/el absolute change over the course of the sample.
+ float yaw = event.values[1] * (timeSpan / 1000F);
+ float pitch = event.values[0] * (timeSpan / 1000F);
+ float roll = event.values[2] * (timeSpan / 1000F);
+
+ mAbsoluteYaw = MathHelper.normalizeRadians(mAbsoluteYaw + yaw);
+ mAbsolutePitch = MathHelper.normalizeRadians(mAbsolutePitch + pitch);
+ mAbsoluteRoll = MathHelper.normalizeRadians(mAbsoluteRoll + roll);
+
+ publish(new Pose(
+ Vector3.ORIGIN,
+ Quaternion.yawPitchRoll(mAbsoluteYaw, mAbsolutePitch, mAbsoluteRoll)
+ ));
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onAccuracyChanged(Sensor sensor, int accuracy) {
+ Log.d(TAG, "onAccuracyChanged() $sensor");
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ @NonNull
+ public EnumSet<Capabilities> getCapabilities() {
+ return Capabilities.ROTATION;
+ }
+}
diff --git a/service/java/com/android/server/uwb/correction/pose/IPoseSource.java b/service/java/com/android/server/uwb/correction/pose/IPoseSource.java
new file mode 100644
index 00000000..274f2aed
--- /dev/null
+++ b/service/java/com/android/server/uwb/correction/pose/IPoseSource.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2023 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 com.android.server.uwb.correction.pose;
+
+import androidx.annotation.NonNull;
+
+import com.android.server.uwb.correction.math.Pose;
+
+import java.util.EnumSet;
+
+/**
+ * Provides pose update information and a way for subscribers to listen for them.
+ */
+public interface IPoseSource extends AutoCloseable {
+ /** The shortest practical update interval for a pose source. */
+ int MIN_INTERVAL_MS = 1000 / 60; // 60Hz
+
+ /** The longest practical update interval for a pose source. */
+ int MAX_INTERVAL_MS = 10000; // 0.1Hz.
+
+ /**
+ * A set of all possible pose source capabilities.
+ */
+ enum Capabilities {
+ YAW, PITCH, ROLL, X, Y, Z,
+ /**
+ * Indicates that a pitch and roll of 0 means that the phone is upright. If this flag
+ * is not present, pitch and roll changes are only relative.
+ */
+ UPRIGHT;
+
+ public static final EnumSet<Capabilities> ALL = EnumSet.allOf(Capabilities.class);
+ public static final EnumSet<Capabilities> NONE = EnumSet.noneOf(Capabilities.class);
+ public static final EnumSet<Capabilities> ROTATION = EnumSet.of(
+ Capabilities.YAW,
+ Capabilities.PITCH,
+ Capabilities.ROLL
+ );
+ public static final EnumSet<Capabilities> UPRIGHT_ROTATION = EnumSet.of(
+ Capabilities.YAW,
+ Capabilities.PITCH,
+ Capabilities.ROLL,
+ Capabilities.UPRIGHT);
+ public static final EnumSet<Capabilities> TRANSLATION = EnumSet.of(
+ Capabilities.X,
+ Capabilities.Y,
+ Capabilities.Z);
+ }
+
+ /**
+ * Stops the pose sensing and removes all listeners.
+ */
+ @Override
+ void close();
+
+ /**
+ * Registers a listener for the pose updates.
+ * @param listener The PoseEventListener that will be notified when the pose changes.
+ */
+ void registerListener(@NonNull PoseEventListener listener);
+
+ /**
+ * Unregisters a listener from the pose updates.
+ * @param listener The PoseEventListener that will no longer be notified when the pose changes.
+ * @return True if successfully removed. Note that a listener may be prematurely removed if it
+ * has thrown an error.
+ */
+ boolean unregisterListener(@NonNull PoseEventListener listener);
+
+ /**
+ * Gets the current pose.
+ * @return The current pose. May be null.
+ */
+ Pose getPose();
+
+ /**
+ * Gets the capabilities of this pose source.
+ * @return An EnumSet of Capabilities.
+ */
+ @NonNull
+ EnumSet<Capabilities> getCapabilities();
+}
diff --git a/service/java/com/android/server/uwb/correction/pose/IntegPoseSource.java b/service/java/com/android/server/uwb/correction/pose/IntegPoseSource.java
new file mode 100644
index 00000000..7dec772f
--- /dev/null
+++ b/service/java/com/android/server/uwb/correction/pose/IntegPoseSource.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2023 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 com.android.server.uwb.correction.pose;
+
+import static com.android.server.uwb.correction.math.MathHelper.F_HALF_PI;
+
+import static java.lang.Math.abs;
+import static java.lang.Math.min;
+import static java.lang.Math.signum;
+
+import android.content.Context;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+
+import com.android.server.uwb.correction.math.Pose;
+import com.android.server.uwb.correction.math.Quaternion;
+import com.android.server.uwb.correction.math.Vector3;
+
+import java.security.InvalidParameterException;
+import java.time.Instant;
+import java.util.EnumSet;
+import java.util.Objects;
+
+/**
+ * Provides poses by double-integrating the accelerometer. It is hilariously bad with ordinary
+ * accelerometers.
+ * The rotation sensor is used for rotation.
+ *
+ * Use this pose source if your device has a military-grade accelerometer, or build upon this
+ * class to research better double-integration technology such as AI-based double integration, or
+ * ARCore pose tracking.
+ */
+public class IntegPoseSource extends PoseSourceBase implements SensorEventListener {
+ private static final String TAG = "IntegPoseSource";
+ private final SensorManager mSensorManager;
+ private final Sensor mRotationSensor;
+ private final Sensor mAccelSensor;
+ private final int mInterval;
+ private Vector3 mAccelCal = new Vector3(0, 0, 0);
+ private Vector3 mPosition = new Vector3(0, 0, 0);
+ private Vector3 mSpeed = new Vector3(0, 0, 0);
+ private long mLastUpdate;
+
+ /** how much drift from origin before position is reset to the origin. In meters. */
+ final int mPosResetDistance = 20;
+ final float mSpeedDamp = 0.95F; // Speed coefficient (1=no slowing, 0.999F=some slowing)
+ final float mPosDamp = 0.999F; // Position recentering (1=no recentering, 0.999F=some)
+ final float mCalRate = 0.002F; // How quickly calibration adjusts to accel changes.
+
+ // The local system is oriented with Y up. The Android rotation vector has Z up. Pitching down
+ // will correct this.
+ private final Quaternion mRotator = Quaternion.yawPitchRoll(0, -F_HALF_PI, 0);
+
+ /**
+ * Creates a new instance of the IntegPoseSource
+ * @param intervalMs How frequently to update the pose.
+ */
+ public IntegPoseSource(@NonNull Context context, int intervalMs) {
+ Objects.requireNonNull(context);
+ if (intervalMs < MIN_INTERVAL_MS || intervalMs > MAX_INTERVAL_MS) {
+ throw new InvalidParameterException("Invalid interval.");
+ }
+ mSensorManager = context.getSystemService(SensorManager.class);
+ mRotationSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR);
+ if (mRotationSensor == null) {
+ throw new UnsupportedOperationException(
+ "Device does not support the required rotation vector sensors.");
+ }
+ mAccelSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_LINEAR_ACCELERATION);
+ if (mAccelSensor == null) {
+ throw new UnsupportedOperationException(
+ "Device does not support the required linear acceleration sensors."
+ );
+ }
+ mInterval = intervalMs * 1000;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected void start() {
+ mSensorManager.registerListener(this, mRotationSensor, mInterval);
+ mSensorManager.registerListener(this, mAccelSensor, mInterval);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected void stop() {
+ mSensorManager.unregisterListener(this);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onSensorChanged(SensorEvent event) {
+ if (event.sensor.getType() == Sensor.TYPE_LINEAR_ACCELERATION) {
+ if (mLastUpdate == 0) {
+ mLastUpdate = Instant.now().toEpochMilli();
+ return;
+ }
+ long now = Instant.now().toEpochMilli();
+ float dur = (now - mLastUpdate) / 1000.0F;
+ mLastUpdate = now;
+ Vector3 accel = new Vector3(
+ event.values[0] - mAccelCal.x,
+ event.values[1] - mAccelCal.y,
+ event.values[2] - mAccelCal.z
+ );
+ mAccelCal = new Vector3(
+ mAccelCal.x + min(abs(accel.x), mCalRate) * signum(accel.x),
+ mAccelCal.y + min(abs(accel.y), mCalRate) * signum(accel.y),
+ mAccelCal.z + min(abs(accel.z), mCalRate) * signum(accel.z)
+ );
+ mSpeed = new Vector3(
+ (mSpeed.x + accel.x * dur) * mSpeedDamp,
+ (mSpeed.y + accel.y * dur) * mSpeedDamp,
+ (mSpeed.z + accel.z * dur) * mSpeedDamp
+ );
+ mPosition = new Vector3(
+ (mPosition.x + mSpeed.x * dur) * mPosDamp,
+ (mPosition.y + mSpeed.y * dur) * mPosDamp,
+ (mPosition.z + mSpeed.z * dur) * mPosDamp
+ );
+ if (mPosition.lengthSquared() > mPosResetDistance * mPosResetDistance) {
+ mPosition = Vector3.ORIGIN;
+ }
+ } else if (event.sensor.getType() == Sensor.TYPE_ROTATION_VECTOR) {
+ Quaternion base = new Quaternion(
+ event.values[0],
+ event.values[1],
+ event.values[2],
+ event.values[3]
+ );
+ publish(new Pose(mPosition, Quaternion.multiply(mRotator, base)));
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onAccuracyChanged(Sensor sensor, int accuracy) {
+ Log.d(TAG, "onAccuracyChanged() $sensor");
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ @NonNull
+ public EnumSet<Capabilities> getCapabilities() {
+ return Capabilities.ALL;
+ }
+}
diff --git a/service/java/com/android/server/uwb/correction/pose/PoseEventListener.java b/service/java/com/android/server/uwb/correction/pose/PoseEventListener.java
new file mode 100644
index 00000000..3447988b
--- /dev/null
+++ b/service/java/com/android/server/uwb/correction/pose/PoseEventListener.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 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 com.android.server.uwb.correction.pose;
+
+import androidx.annotation.NonNull;
+
+import com.android.server.uwb.correction.math.Pose;
+
+/**
+ * Used for receiving notifications from a PoseSource when there is new pose data.
+ */
+public interface PoseEventListener {
+ /**
+ * Called when there is an update to the device's pose. The origin is arbitrary, but
+ * position could be relative to the starting position, and rotation could be relative
+ * to magnetic north and the direction of gravity.
+ * @param pose The new location and orientation of the device.
+ */
+ void onPoseChanged(@NonNull Pose pose);
+}
diff --git a/service/java/com/android/server/uwb/correction/pose/PoseSourceBase.java b/service/java/com/android/server/uwb/correction/pose/PoseSourceBase.java
new file mode 100644
index 00000000..e446114b
--- /dev/null
+++ b/service/java/com/android/server/uwb/correction/pose/PoseSourceBase.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2023 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 com.android.server.uwb.correction.pose;
+
+import android.util.Log;
+
+import androidx.annotation.GuardedBy;
+import androidx.annotation.NonNull;
+
+import com.android.server.uwb.correction.math.Pose;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * Optional base implementation for a PoseSource. Provides help to register listeners and
+ * publishing.
+ */
+public abstract class PoseSourceBase implements IPoseSource {
+ private final Lock mLockObject = new ReentrantLock();
+ @GuardedBy("mLockObject")
+ private final List<PoseEventListener> mListeners;
+ private static final String TAG = "PoseSourceBase";
+ private final AtomicReference<Pose> mPose = new AtomicReference<>();
+
+ public PoseSourceBase() {
+ mListeners = new ArrayList<>();
+ }
+
+ /**
+ * Starts the pose source. Called by the {@link PoseSourceBase} when the first
+ * listener subscribes.
+ */
+ protected abstract void start();
+
+ /**
+ * Stops the pose source. Called by the {@link PoseSourceBase} when the last
+ * listener unsubscribes.
+ */
+ protected abstract void stop();
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void close() {
+
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void registerListener(@NonNull PoseEventListener listener) {
+ Objects.requireNonNull(listener);
+ mLockObject.lock();
+ try {
+ mListeners.add(listener);
+ if (mListeners.size() == 1) {
+ start(); // Run inside the lock to make sure starts and stops are sequential.
+ }
+ } finally {
+ mLockObject.unlock();
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean unregisterListener(@NonNull PoseEventListener listener) {
+ Objects.requireNonNull(listener);
+ mLockObject.lock();
+ try {
+ boolean removed = mListeners.remove(listener);
+ if (removed && mListeners.size() == 0) {
+ stop(); // Run inside the lock to make sure starts and stops are sequential.
+ }
+ return removed;
+ } finally {
+ mLockObject.unlock();
+ }
+ }
+
+ /**
+ * Publishes the pose to all listeners.
+ *
+ * @param pose The updated device pose.
+ */
+ protected void publish(@NonNull Pose pose) {
+ Objects.requireNonNull(pose);
+ ArrayList<PoseEventListener> listeners;
+ mLockObject.lock();
+ try {
+ // Copy snapshot to minimize lock time and allow changes to listeners while
+ // we report pose changes.
+ listeners = new ArrayList<>(this.mListeners);
+ } finally {
+ mLockObject.unlock();
+ }
+ this.mPose.set(pose);
+ for (int i = 0; i < listeners.size(); i++) {
+ try {
+ listeners.get(i).onPoseChanged(pose);
+ } catch (Exception ex) {
+ Log.e(TAG, ex.toString());
+
+ // Remove the listener, so it doesn't become a persistent problem.
+ listeners.remove(i--);
+ }
+ }
+ }
+
+ @Override
+ public Pose getPose() {
+ return mPose.get();
+ }
+}
diff --git a/service/java/com/android/server/uwb/correction/pose/RotationPoseSource.java b/service/java/com/android/server/uwb/correction/pose/RotationPoseSource.java
new file mode 100644
index 00000000..45ef7825
--- /dev/null
+++ b/service/java/com/android/server/uwb/correction/pose/RotationPoseSource.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2023 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 com.android.server.uwb.correction.pose;
+
+import static com.android.server.uwb.correction.math.MathHelper.F_HALF_PI;
+
+import android.content.Context;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+
+import com.android.server.uwb.correction.math.Pose;
+import com.android.server.uwb.correction.math.Quaternion;
+import com.android.server.uwb.correction.math.Vector3;
+
+import java.security.InvalidParameterException;
+import java.util.EnumSet;
+import java.util.Objects;
+
+/**
+ * Provides poses from the phone's rotation vector, which provides yaw, pitch and roll,
+ * oriented where +Y is north and +Z is skyward. Positional changes are not supported.
+ * The output value is rotated to the local system's orientation, where +Y is skyward.
+ *
+ * This pose source is very reliable and available on almost all phones, but provides no
+ * positioning.
+ */
+public class RotationPoseSource extends PoseSourceBase implements SensorEventListener {
+ private static final String TAG = "RotationPoseSource";
+ private final SensorManager mSensorManager;
+ private final Sensor mSensor;
+ private final int mInterval;
+
+ // The local system is oriented with Y up. The Android rotation vector has Z up. Pitching down
+ // will correct this.
+ private final Quaternion mRotator = Quaternion.yawPitchRoll(0, -F_HALF_PI, 0);
+
+ /**
+ * Creates a new instance of the RotationPoseSource
+ * @param intervalMs How frequently to update the pose.
+ */
+ public RotationPoseSource(@NonNull Context context, int intervalMs) {
+ Objects.requireNonNull(context);
+ if (intervalMs < MIN_INTERVAL_MS || intervalMs > MAX_INTERVAL_MS) {
+ throw new InvalidParameterException("Invalid interval.");
+ }
+ mSensorManager = context.getSystemService(SensorManager.class);
+ mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR);
+ if (mSensor == null) {
+ throw new UnsupportedOperationException("Device does not support the rotation vector.");
+ }
+ mInterval = intervalMs * 1000;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected void start() {
+ mSensorManager.registerListener(this, mSensor, mInterval);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected void stop() {
+ mSensorManager.unregisterListener(this);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onSensorChanged(SensorEvent event) {
+ if (event.sensor.getType() == Sensor.TYPE_ROTATION_VECTOR) {
+ // The rotation vector is a quaternion oriented to gravity and geomagnetic north.
+ Quaternion base = new Quaternion(
+ event.values[0],
+ event.values[1],
+ event.values[2],
+ event.values[3]
+ );
+ publish(new Pose(Vector3.ORIGIN, Quaternion.multiply(mRotator, base)));
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onAccuracyChanged(Sensor sensor, int accuracy) {
+ Log.d(TAG, "onAccuracyChanged() $sensor");
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ @NonNull
+ public EnumSet<Capabilities> getCapabilities() {
+ return Capabilities.UPRIGHT_ROTATION;
+ }
+}
diff --git a/service/java/com/android/server/uwb/correction/pose/SixDOFPoseSource.java b/service/java/com/android/server/uwb/correction/pose/SixDOFPoseSource.java
new file mode 100644
index 00000000..ac80feaf
--- /dev/null
+++ b/service/java/com/android/server/uwb/correction/pose/SixDOFPoseSource.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2023 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 com.android.server.uwb.correction.pose;
+
+import static com.android.server.uwb.correction.math.MathHelper.F_HALF_PI;
+
+import android.content.Context;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+
+import com.android.server.uwb.correction.math.Pose;
+import com.android.server.uwb.correction.math.Quaternion;
+import com.android.server.uwb.correction.math.Vector3;
+
+import java.security.InvalidParameterException;
+import java.util.EnumSet;
+import java.util.Objects;
+
+/**
+ * Provides poses from the device's 6DOF fused sensor, which provides a full position and rotation
+ * relative to an arbitrary origin.
+ *
+ * This virtual sensor is usually only implemented in purpose-built spatial-tracking systems such as
+ * Google Glass and Meta Quest. It can be power-hungry.
+ *
+ * Functionally this resembles ARCore's pose tracking, but relieves the difficulty of having to get
+ * ARCore pose updates from the user application.
+ */
+public class SixDOFPoseSource extends PoseSourceBase implements SensorEventListener {
+ private static final String TAG = "SixDOFPoseSource";
+ private final SensorManager mSensorManager;
+ private final Sensor mSensor;
+ private final int mInterval;
+
+ // The local system is oriented with Y up. The Android rotation vector has Z up. Pitching down
+ // will correct this.
+ private final Quaternion mRotator = Quaternion.yawPitchRoll(0, -F_HALF_PI, 0);
+
+ /**
+ * Creates a new instance of the FusionPoseSource.
+ * @param intervalMs How frequently to update the pose.
+ */
+ public SixDOFPoseSource(@NonNull Context context, int intervalMs) {
+ Objects.requireNonNull(context);
+ if (intervalMs < MIN_INTERVAL_MS || intervalMs > MAX_INTERVAL_MS) {
+ throw new InvalidParameterException("Invalid interval.");
+ }
+ mSensorManager = context.getSystemService(SensorManager.class);
+ mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_POSE_6DOF);
+ if (mSensor == null) {
+ throw new UnsupportedOperationException(
+ "Device does not support the Pose 6DOF sensor."
+ );
+ }
+ mInterval = intervalMs * 1000;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected void start() {
+ mSensorManager.registerListener(this, mSensor, mInterval);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected void stop() {
+ mSensorManager.unregisterListener(this);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onSensorChanged(SensorEvent event) {
+ if (event.sensor.getType() == Sensor.TYPE_POSE_6DOF) {
+ // The rotation vector is a quaternion oriented to gravity and geomagnetic north.
+ // See https://developer.android.com/reference/android/hardware/Sensor#TYPE_POSE_6DOF
+
+ // The local system is oriented with Y up. The Android position vector has Z up.
+ Vector3 position = new Vector3(
+ event.values[4],
+ event.values[6], // Y and Z swapped
+ event.values[5]
+ );
+
+ Quaternion rotation = new Quaternion(
+ event.values[0],
+ event.values[1],
+ event.values[2],
+ event.values[3]
+ );
+ publish(new Pose(position, Quaternion.multiply(mRotator, rotation)));
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onAccuracyChanged(Sensor sensor, int accuracy) {
+ Log.d(TAG, "onAccuracyChanged() $sensor");
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ @NonNull
+ public EnumSet<Capabilities> getCapabilities() {
+ return Capabilities.ALL;
+ }
+}
diff --git a/service/java/com/android/server/uwb/correction/primers/AoAPrimer.java b/service/java/com/android/server/uwb/correction/primers/AoAPrimer.java
new file mode 100644
index 00000000..9a67d068
--- /dev/null
+++ b/service/java/com/android/server/uwb/correction/primers/AoAPrimer.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2023 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 com.android.server.uwb.correction.primers;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.server.uwb.correction.math.AoAVector;
+import com.android.server.uwb.correction.math.SphericalVector;
+import com.android.server.uwb.correction.pose.IPoseSource;
+
+/**
+ * Converts a PDoA azimuth value to a spherical coordinate azimuth by accounting for elevation.
+ * See {@link AoAVector} for information on the difference.
+ * This primer is needed on hardware that does not support elevation, after the ElevationPrimer,
+ * so that the estimated elevation can be used to perform the PDoA-to-azimuth conversion.
+ * This primer is also needed on hardware that supports elevation, but with firmware that does
+ * not perform the PDoA-to-azimuth conversion.
+ */
+public class AoAPrimer implements IPrimer {
+ /**
+ * Applies corrections to a raw position.
+ *
+ * @param input The original UWB reading.
+ * @param prediction A prediction of where the signal probably came from.
+ * @param poseSource A pose source that may indicate phone orientation.
+ * @return A replacement value for the UWB input that has been corrected for the situation.
+ */
+ @Override
+ public SphericalVector.Sparse prime(
+ @NonNull SphericalVector.Sparse input,
+ @Nullable SphericalVector prediction,
+ @Nullable IPoseSource poseSource) {
+ if (input.hasElevation && input.hasAzimuth) {
+ // Reinterpret the SphericalVector as an AoAVector, then convert it to a
+ // SphericalVector.
+ return AoAVector.fromRadians(
+ input.vector.azimuth,
+ input.vector.elevation,
+ input.vector.distance)
+ .toSphericalVector()
+ .toSparse(true, true, input.hasDistance);
+ }
+ return input;
+ }
+}
diff --git a/service/java/com/android/server/uwb/correction/primers/ElevationPrimer.java b/service/java/com/android/server/uwb/correction/primers/ElevationPrimer.java
new file mode 100644
index 00000000..6203c727
--- /dev/null
+++ b/service/java/com/android/server/uwb/correction/primers/ElevationPrimer.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2023 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 com.android.server.uwb.correction.primers;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.server.uwb.correction.math.Pose;
+import com.android.server.uwb.correction.math.SphericalVector;
+import com.android.server.uwb.correction.pose.IPoseSource;
+import com.android.server.uwb.correction.pose.IPoseSource.Capabilities;
+
+/**
+ * Applies a default pose-based elevation to a UWB reading. A basic "assumption" about what the
+ * elevation might be helps improve the quality of pose-based azimuth compensations, and may
+ * provide a more understandable UWB location guess to the user.
+ * Recommended for hardware that does not support elevation. This should execute before the
+ * AoAPrimer in the primer execution order.
+ */
+public class ElevationPrimer implements IPrimer {
+ /**
+ * Applies a default pose-based elevation to a UWB reading that doesn't have one.
+ *
+ * @param input The original UWB reading.
+ * @param prediction A prediction of where the signal probably came from.
+ * @param poseSource A pose source that may indicate phone orientation.
+ * @return A replacement value for the UWB vector that has been corrected for the situation.
+ */
+ @Override
+ public SphericalVector.Sparse prime(
+ @NonNull SphericalVector.Sparse input,
+ @Nullable SphericalVector prediction,
+ @Nullable IPoseSource poseSource) {
+ // Early exit: If there is already an elevation, we won't try to fill it in.
+ if (input.hasElevation) {
+ return input;
+ }
+
+ SphericalVector.Sparse position = input;
+ if (poseSource != null
+ && poseSource.getCapabilities().contains(Capabilities.UPRIGHT)
+ ) {
+ Pose pose = poseSource.getPose();
+ if (pose != null) {
+ // The pose source knows which way is upright, so if we don't have
+ // an AoA elevation, we'll assume that elevation is level with the phone.
+ // i.e. If the phone pitches down, the elevation would appear up.
+
+ position = new SphericalVector.Sparse(
+ SphericalVector.fromRadians(
+ input.vector.azimuth,
+ -pose.rotation.toYawPitchRoll().y, // -Pitch becomes our assumed elevation
+ input.vector.distance
+ ),
+ input.hasAzimuth,
+ true,
+ input.hasDistance
+ );
+ }
+ }
+ return position;
+ }
+}
diff --git a/service/java/com/android/server/uwb/correction/primers/FovPrimer.java b/service/java/com/android/server/uwb/correction/primers/FovPrimer.java
new file mode 100644
index 00000000..035df1cd
--- /dev/null
+++ b/service/java/com/android/server/uwb/correction/primers/FovPrimer.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2023 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 com.android.server.uwb.correction.primers;
+
+import static com.android.server.uwb.correction.math.MathHelper.F_PI;
+
+import static java.lang.Math.abs;
+import static java.lang.Math.cos;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.server.uwb.correction.math.SphericalVector;
+import com.android.server.uwb.correction.pose.IPoseSource;
+
+/**
+ * Limits the field view of incoming UWB readings by replacing angles outside the defined limits
+ * with predicted angles (which are based on the last-known-good angle combined with pose changes).
+ *
+ * Most UWB hardware suffers from accuracy issues beyond a certain azimuth or elevation, and
+ * conversely will produce erroneous steep angles when there are issues with the signal.
+ *
+ * This implementation imposes a double-cone-shaped FOV, meaning that the device can see a circular
+ * area in front and behind the phone. Other primers can limit the view to forward-only if
+ * necessary.
+ */
+public class FovPrimer implements IPrimer {
+ private final double mCosFov;
+
+ /**
+ * Creates a new instance of the FovPrimer class.
+ * @param fov The field-of-view to impose on hardware coordinates.
+ */
+ public FovPrimer(float fov) {
+ if (fov > F_PI) {
+ fov = F_PI;
+ }
+ this.mCosFov = cos(fov);
+ }
+
+ /**
+ * Applies corrections to a raw position.
+ * @param input The original UWB reading.
+ * @param prediction A prediction of where the signal probably came from.
+ * @param poseSource A pose source that may indicate phone orientation.
+ * @return A replacement value for the UWB input that has been corrected for the situation.
+ */
+ @Override
+ public SphericalVector.Sparse prime(
+ @NonNull SphericalVector.Sparse input,
+ @Nullable SphericalVector prediction,
+ @Nullable IPoseSource poseSource) {
+ if (prediction == null) {
+ return input;
+ }
+
+ float azimuth = input.hasAzimuth ? input.vector.azimuth : prediction.azimuth;
+ float elevation = input.hasElevation ? input.vector.elevation : prediction.elevation;
+
+ // Compute the absolute cartesian Z-value of the az/el vector, ignoring distance,
+ // as an indicator of the position's relation to the FOV.
+ double zValue = abs(cos(elevation) * cos(azimuth));
+
+ // Faster equivalent to acos(zValue) < mFov
+ if (zValue < mCosFov) {
+ return SphericalVector.fromRadians(
+ prediction.azimuth,
+ prediction.elevation,
+ input.vector.distance
+ ).toSparse(true, true, input.hasDistance);
+ }
+
+ return input;
+ }
+}
diff --git a/service/java/com/android/server/uwb/correction/primers/IPrimer.java b/service/java/com/android/server/uwb/correction/primers/IPrimer.java
new file mode 100644
index 00000000..516b79b0
--- /dev/null
+++ b/service/java/com/android/server/uwb/correction/primers/IPrimer.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2023 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 com.android.server.uwb.correction.primers;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.server.uwb.correction.math.SphericalVector;
+import com.android.server.uwb.correction.pose.IPoseSource;
+
+/**
+ * Given known data about a UWB reading, applies corrections that correct for nonlinearities,
+ * missing data or other hardware limitations.
+ */
+public interface IPrimer {
+ /**
+ * Applies corrections to a raw position.
+ *
+ * @param input The original UWB reading.
+ * @param prediction A prediction of where the signal probably came from.
+ * @param poseSource A pose source that may indicate phone orientation.
+ * @return A replacement value for the UWB input that has been corrected for the situation.
+ */
+ SphericalVector.Sparse prime(
+ @NonNull SphericalVector.Sparse input,
+ @Nullable SphericalVector prediction,
+ @Nullable IPoseSource poseSource
+ );
+}
diff --git a/service/java/com/android/server/uwb/data/UwbUciConstants.java b/service/java/com/android/server/uwb/data/UwbUciConstants.java
index f43b9e06..a6cf5b31 100644
--- a/service/java/com/android/server/uwb/data/UwbUciConstants.java
+++ b/service/java/com/android/server/uwb/data/UwbUciConstants.java
@@ -15,10 +15,10 @@
*/
package com.android.server.uwb.data;
-import static android.hardware.uwb.fira_android.UwbVendorSessionInitSessionType.CCC;
import static android.hardware.uwb.fira_android.UwbVendorStatusCodes.STATUS_ERROR_CCC_LIFECYCLE;
import static android.hardware.uwb.fira_android.UwbVendorStatusCodes.STATUS_ERROR_CCC_SE_BUSY;
+import com.google.uwb.support.ccc.CccParams;
import com.google.uwb.support.fira.FiraParams;
public class UwbUciConstants {
@@ -35,10 +35,12 @@ public class UwbUciConstants {
/**
* Table 13: Control Messages to Initialize UWB session
*/
- public static final byte SESSION_TYPE_RANGING = 0x00;
- public static final byte SESSION_TYPE_DATA_TRANSFER = 0x01;
- public static final byte SESSION_TYPE_CCC = (byte) CCC;
- public static final byte SESSION_TYPE_DEVICE_TEST_MODE = (byte) 0xD0;
+ public static final byte SESSION_TYPE_RANGING = FiraParams.SESSION_TYPE_RANGING;
+ public static final byte SESSION_TYPE_DATA_TRANSFER =
+ FiraParams.SESSION_TYPE_RANGING_AND_IN_BAND_DATA;
+ public static final byte SESSION_TYPE_CCC = (byte) CccParams.SESSION_TYPE_CCC;
+ public static final byte SESSION_TYPE_DEVICE_TEST_MODE =
+ (byte) FiraParams.SESSION_TYPE_DEVICE_TEST_MODE;
/**
* Table 14: Control Messages to De-Initialize UWB session - SESSION_STATUS_NTF
diff --git a/service/java/com/android/server/uwb/jni/NativeUwbManager.java b/service/java/com/android/server/uwb/jni/NativeUwbManager.java
index 4643b72e..abaaf0c9 100644
--- a/service/java/com/android/server/uwb/jni/NativeUwbManager.java
+++ b/service/java/com/android/server/uwb/jni/NativeUwbManager.java
@@ -403,9 +403,10 @@ public class NativeUwbManager {
* Send payload data to a remote device in a UWB ranging session.
*/
public byte sendData(
- int sessionId, byte[] address, byte destEndPoint, int sequenceNum, byte[] appData) {
+ int sessionId, byte[] address, byte destEndPoint, byte sequenceNum, byte[] appData,
+ String chipId) {
synchronized (mNativeLock) {
- return nativeSendData(sessionId, address, destEndPoint, sequenceNum, appData);
+ return nativeSendData(sessionId, address, destEndPoint, sequenceNum, appData, chipId);
}
}
@@ -427,9 +428,8 @@ public class NativeUwbManager {
}
}
- // TODO(b/259487023): no native implementation
private native byte nativeSendData(int sessionId, byte[] address, byte destEndPoint,
- int sequenceNum, byte[] appData);
+ byte sequenceNum, byte[] appData, String chipId);
private native long nativeDispatcherNew(Object[] chipIds);
diff --git a/service/java/com/android/server/uwb/params/FiraEncoder.java b/service/java/com/android/server/uwb/params/FiraEncoder.java
index 770b97c7..f45dc635 100644
--- a/service/java/com/android/server/uwb/params/FiraEncoder.java
+++ b/service/java/com/android/server/uwb/params/FiraEncoder.java
@@ -47,7 +47,7 @@ public class FiraEncoder extends TlvEncoder {
return null;
}
- private static boolean hasAoaBoundInRangeDataNfConfig(int rangeDataNtfConfig) {
+ private static boolean hasAoaBoundInRangeDataNtfConfig(int rangeDataNtfConfig) {
return rangeDataNtfConfig == RANGE_DATA_NTF_CONFIG_ENABLE_AOA_LEVEL_TRIG
|| rangeDataNtfConfig == RANGE_DATA_NTF_CONFIG_ENABLE_PROXIMITY_AOA_LEVEL_TRIG
|| rangeDataNtfConfig == RANGE_DATA_NTF_CONFIG_ENABLE_AOA_EDGE_TRIG
@@ -103,8 +103,8 @@ public class FiraEncoder extends TlvEncoder {
.putByte(ConfigParam.KEY_ROTATION_RATE, (byte) params.getKeyRotationRate())
.putByte(ConfigParam.SESSION_PRIORITY, (byte) params.getSessionPriority())
.putByte(ConfigParam.MAC_ADDRESS_MODE, (byte) params.getMacAddressMode())
- .putByteArray(ConfigParam.VENDOR_ID,
- TlvUtil.getReverseBytes(params.getVendorId()))
+ .putByteArray(ConfigParam.VENDOR_ID, params.getVendorId() != null
+ ? TlvUtil.getReverseBytes(params.getVendorId()) : null)
.putByteArray(ConfigParam.STATIC_STS_IV,
params.getStaticStsIV())
.putByte(ConfigParam.NUMBER_OF_STS_SEGMENTS, (byte) params.getStsSegmentCount())
@@ -148,23 +148,23 @@ public class FiraEncoder extends TlvEncoder {
.putByte(ConfigParam.NUM_AOA_ELEVATION_MEASUREMENTS,
(byte) params.getNumOfMsrmtFocusOnAoaElevation());
}
- if (hasAoaBoundInRangeDataNfConfig(params.getRangeDataNtfConfig())) {
- tlvBufferBuilder.putByteArray(ConfigParam.RANGE_DATA_NTF_AOA_BOUND, new byte[]{
+ if (hasAoaBoundInRangeDataNtfConfig(params.getRangeDataNtfConfig())) {
+ tlvBufferBuilder.putShortArray(ConfigParam.RANGE_DATA_NTF_AOA_BOUND, new short[]{
// TODO (b/235355249): Verify this conversion. This is using AOA value
// in UwbTwoWayMeasurement to external RangingMeasurement conversion as
// reference.
- (byte) UwbUtil.twos_compliment(UwbUtil.convertFloatToQFormat(
+ (short) UwbUtil.twos_compliment(UwbUtil.convertFloatToQFormat(
UwbUtil.radianTodegree(
- params.getRangeDataNtfAoaAzimuthLower()), 9, 7), 8),
- (byte) UwbUtil.twos_compliment(UwbUtil.convertFloatToQFormat(
+ params.getRangeDataNtfAoaAzimuthLower()), 9, 7), 16),
+ (short) UwbUtil.twos_compliment(UwbUtil.convertFloatToQFormat(
UwbUtil.radianTodegree(
- params.getRangeDataNtfAoaAzimuthUpper()), 9, 7), 8),
- (byte) UwbUtil.twos_compliment(UwbUtil.convertFloatToQFormat(
+ params.getRangeDataNtfAoaAzimuthUpper()), 9, 7), 16),
+ (short) UwbUtil.twos_compliment(UwbUtil.convertFloatToQFormat(
UwbUtil.radianTodegree(
- params.getRangeDataNtfAoaElevationLower()), 9, 7), 8),
- (byte) UwbUtil.twos_compliment(UwbUtil.convertFloatToQFormat(
+ params.getRangeDataNtfAoaElevationLower()), 9, 7), 16),
+ (short) UwbUtil.twos_compliment(UwbUtil.convertFloatToQFormat(
UwbUtil.radianTodegree(
- params.getRangeDataNtfAoaElevationLower()), 9, 7), 8),
+ params.getRangeDataNtfAoaElevationUpper()), 9, 7), 16),
});
}
if (params.isRssiReportingEnabled()) {
@@ -180,9 +180,9 @@ public class FiraEncoder extends TlvEncoder {
tlvBufferBuilder.putByteArray(ConfigParam.CAP_SIZE_RANGE, params.getCapSize());
}
if (params.getDeviceRole() == FiraParams.RANGING_DEVICE_UT_TAG) {
- tlvBufferBuilder.putLong(ConfigParam.UL_TDOA_TX_INTERVAL,
+ tlvBufferBuilder.putInt(ConfigParam.UL_TDOA_TX_INTERVAL,
params.getUlTdoaTxIntervalMs());
- tlvBufferBuilder.putLong(ConfigParam.UL_TDOA_RANDOM_WINDOW,
+ tlvBufferBuilder.putInt(ConfigParam.UL_TDOA_RANDOM_WINDOW,
params.getUlTdoaRandomWindowMs());
tlvBufferBuilder.putByteArray(ConfigParam.UL_TDOA_DEVICE_ID, getUlTdoaDeviceId(
params.getUlTdoaDeviceIdType(), params.getUlTdoaDeviceId()));
@@ -191,7 +191,6 @@ public class FiraEncoder extends TlvEncoder {
}
return tlvBufferBuilder.build();
}
-
private byte[] getUlTdoaDeviceId(int ulTdoaDeviceIdType, byte[] ulTdoaDeviceId) {
if (ulTdoaDeviceIdType == FiraParams.UL_TDOA_DEVICE_ID_NONE) {
// Device ID not included
@@ -235,7 +234,7 @@ public class FiraEncoder extends TlvEncoder {
(short) rangeDataProximityFar.intValue());
}
- if (rangeDataNtfConfig != null && hasAoaBoundInRangeDataNfConfig(rangeDataNtfConfig)) {
+ if (rangeDataNtfConfig != null && hasAoaBoundInRangeDataNtfConfig(rangeDataNtfConfig)) {
if ((rangeDataAoaAzimuthLower != null && rangeDataAoaAzimuthUpper != null)
|| (rangeDataAoaElevationLower != null && rangeDataAoaElevationUpper != null)) {
rangeDataAoaAzimuthLower = rangeDataAoaAzimuthLower != null
@@ -250,22 +249,22 @@ public class FiraEncoder extends TlvEncoder {
rangeDataAoaElevationUpper = rangeDataAoaElevationUpper != null
? rangeDataAoaElevationUpper
: FiraParams.RANGE_DATA_NTF_AOA_ELEVATION_UPPER_DEFAULT;
- tlvBuilder.putByteArray(ConfigParam.RANGE_DATA_NTF_AOA_BOUND, new byte[]{
+ tlvBuilder.putShortArray(ConfigParam.RANGE_DATA_NTF_AOA_BOUND, new short[]{
// TODO (b/235355249): Verify this conversion. This is using AOA value
// in UwbTwoWayMeasurement to external RangingMeasurement conversion as
// reference.
- (byte) UwbUtil.twos_compliment(UwbUtil.convertFloatToQFormat(
+ (short) UwbUtil.twos_compliment(UwbUtil.convertFloatToQFormat(
UwbUtil.radianTodegree(
- rangeDataAoaAzimuthLower.floatValue()), 9, 7), 8),
- (byte) UwbUtil.twos_compliment(UwbUtil.convertFloatToQFormat(
+ rangeDataAoaAzimuthLower.floatValue()), 9, 7), 16),
+ (short) UwbUtil.twos_compliment(UwbUtil.convertFloatToQFormat(
UwbUtil.radianTodegree(
- rangeDataAoaAzimuthUpper.floatValue()), 9, 7), 8),
- (byte) UwbUtil.twos_compliment(UwbUtil.convertFloatToQFormat(
+ rangeDataAoaAzimuthUpper.floatValue()), 9, 7), 16),
+ (short) UwbUtil.twos_compliment(UwbUtil.convertFloatToQFormat(
UwbUtil.radianTodegree(
- rangeDataAoaElevationLower.floatValue()), 9, 7), 8),
- (byte) UwbUtil.twos_compliment(UwbUtil.convertFloatToQFormat(
+ rangeDataAoaElevationLower.floatValue()), 9, 7), 16),
+ (short) UwbUtil.twos_compliment(UwbUtil.convertFloatToQFormat(
UwbUtil.radianTodegree(
- rangeDataAoaElevationUpper.floatValue()), 9, 7), 8),
+ rangeDataAoaElevationUpper.floatValue()), 9, 7), 16),
});
}
}
diff --git a/service/java/com/android/server/uwb/params/TlvBuffer.java b/service/java/com/android/server/uwb/params/TlvBuffer.java
index 167c7f71..3d7ff339 100644
--- a/service/java/com/android/server/uwb/params/TlvBuffer.java
+++ b/service/java/com/android/server/uwb/params/TlvBuffer.java
@@ -81,6 +81,20 @@ public class TlvBuffer {
return this;
}
+ public TlvBuffer.Builder putShortArray(int tagType, short[] sArray) {
+ if (sArray == null) return this;
+ return putShortArray(tagType, sArray.length, sArray);
+ }
+
+ public TlvBuffer.Builder putShortArray(int tagType, int length, short[] sArray) {
+ addHeader(tagType, length * Short.BYTES);
+ for (int i = 0; i < length; i++) {
+ this.mBuffer.put(TlvUtil.getLeBytes(sArray[i]));
+ }
+ this.mNoOfParams++;
+ return this;
+ }
+
public TlvBuffer.Builder putInt(int tagType, int data) {
addHeader(tagType, Integer.BYTES);
this.mBuffer.put(TlvUtil.getLeBytes(data));
diff --git a/service/java/com/android/server/uwb/util/LruList.java b/service/java/com/android/server/uwb/util/LruList.java
new file mode 100644
index 00000000..1b5f483b
--- /dev/null
+++ b/service/java/com/android/server/uwb/util/LruList.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2023 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 com.android.server.uwb.util;
+
+import android.annotation.NonNull;
+
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * Utility class for keeping a relatively small List of values, sorted by most recently added
+ * first.
+ *
+ * Copied from {@code packages/modules/Wifi/service/java/com/android/server/wifi/util/LruList.java}
+ * @param <E>
+ */
+public class LruList<E> {
+ private int mSize;
+ private LinkedList<E> mLinkedList;
+
+ /**
+ * Creates a new LruList capped by maxSize.
+ * @param maxSize max allowed size of the LruList
+ */
+ public LruList(int maxSize) {
+ mSize = maxSize;
+ mLinkedList = new LinkedList<E>();
+ }
+
+ /**
+ * Add an entry. If the entry already exists then it will be moved to the front. Otherwise,
+ * a new entry will be added.
+ * If this operation makes the LruList exceed the max allowed size, then the least recently
+ * added entry will be removed.
+ * @param entry
+ */
+ public void add(@NonNull E entry) {
+ if (entry == null) {
+ return;
+ }
+ int index = mLinkedList.indexOf(entry);
+ if (index >= 0) {
+ mLinkedList.remove(index);
+ }
+ mLinkedList.addFirst(entry);
+ while (mLinkedList.size() > mSize) {
+ mLinkedList.removeLast();
+ }
+ }
+
+ /**
+ * Remove an entry from list.
+ */
+ public void remove(@NonNull E entry) {
+ if (entry == null) {
+ return;
+ }
+ int index = mLinkedList.indexOf(entry);
+ if (index < 0) {
+ return;
+ }
+ mLinkedList.remove(index);
+ }
+
+ /**
+ * Returns the list of entries sorted by most recently added entries first.
+ * @return
+ */
+ public @NonNull List<E> getEntries() {
+ return new ArrayList<E>(mLinkedList);
+ }
+
+ /**
+ * Gets the number of entries in this LruList.
+ */
+ public int size() {
+ return mLinkedList.size();
+ }
+
+ /**
+ * Get the index in the list of the input entry.
+ * If not in the list will return -1.
+ * If in the list, smaller index is more recently added.
+ */
+ public int indexOf(E entry) {
+ return mLinkedList.indexOf(entry);
+ }
+}
diff --git a/service/java/com/android/server/uwb/util/UwbUtil.java b/service/java/com/android/server/uwb/util/UwbUtil.java
index d1f94cb1..30645678 100755
--- a/service/java/com/android/server/uwb/util/UwbUtil.java
+++ b/service/java/com/android/server/uwb/util/UwbUtil.java
@@ -40,6 +40,7 @@ public final class UwbUtil {
return sb.toString();
}
+ /** Convert the given int to a 4-byte hex-string */
public static String toHexString(int var) {
byte[] byteArray = new byte[4];
byteArray[0] = (byte) (var & 0xff);
@@ -54,6 +55,26 @@ public final class UwbUtil {
return sb.toString();
}
+ /** Convert the given long to an 8-byte hex-string */
+ public static String toHexString(long var) {
+ byte[] byteArray = new byte[8];
+ byteArray[0] = (byte) (var & 0xff);
+ byteArray[1] = (byte) ((var >> 8) & 0xff);
+ byteArray[2] = (byte) ((var >> 16) & 0xff);
+ byteArray[3] = (byte) ((var >> 24) & 0xff);
+ byteArray[4] = (byte) ((var >> 32) & 0xff);
+ byteArray[5] = (byte) ((var >> 40) & 0xff);
+ byteArray[6] = (byte) ((var >> 48) & 0xff);
+ byteArray[7] = (byte) ((var >> 56) & 0xff);
+
+ StringBuilder sb = new StringBuilder();
+ for (byte b : byteArray) {
+ sb.append(HEXCHARS[(b >> 4) & 0xF]);
+ sb.append(HEXCHARS[b & 0xF]);
+ }
+ return sb.toString();
+ }
+
public static byte[] getByteArray(String valueString) {
int len = valueString.length();
byte[] data = new byte[len / 2];
diff --git a/service/support_lib/src/com/google/uwb/support/ccc/CccOpenRangingParams.java b/service/support_lib/src/com/google/uwb/support/ccc/CccOpenRangingParams.java
index f0323b16..cdc76d37 100644
--- a/service/support_lib/src/com/google/uwb/support/ccc/CccOpenRangingParams.java
+++ b/service/support_lib/src/com/google/uwb/support/ccc/CccOpenRangingParams.java
@@ -42,6 +42,7 @@ public class CccOpenRangingParams extends CccParams {
private static final String KEY_UWB_CONFIG = "uwb_config";
private static final String KEY_PULSE_SHAPE_COMBO = "pulse_shape_combo";
private static final String KEY_SESSION_ID = "session_id";
+ private static final String KEY_SESSION_TYPE = "session_type";
private static final String KEY_RAN_MULTIPLIER = "ran_multiplier";
private static final String KEY_CHANNEL = "channel";
private static final String KEY_NUM_CHAPS_PER_SLOT = "num_chaps_per_slot";
@@ -55,6 +56,7 @@ public class CccOpenRangingParams extends CccParams {
@UwbConfig private final int mUwbConfig;
private final CccPulseShapeCombo mPulseShapeCombo;
private final int mSessionId;
+ @SessionType private final int mSessionType;
private final int mRanMultiplier;
@Channel private final int mChannel;
private final int mNumChapsPerSlot;
@@ -69,6 +71,7 @@ public class CccOpenRangingParams extends CccParams {
@UwbConfig int uwbConfig,
CccPulseShapeCombo pulseShapeCombo,
int sessionId,
+ @SessionType int sessionType,
int ranMultiplier,
@Channel int channel,
int numChapsPerSlot,
@@ -81,6 +84,7 @@ public class CccOpenRangingParams extends CccParams {
mUwbConfig = uwbConfig;
mPulseShapeCombo = pulseShapeCombo;
mSessionId = sessionId;
+ mSessionType = sessionType;
mRanMultiplier = ranMultiplier;
mChannel = channel;
mNumChapsPerSlot = numChapsPerSlot;
@@ -103,6 +107,7 @@ public class CccOpenRangingParams extends CccParams {
bundle.putInt(KEY_UWB_CONFIG, mUwbConfig);
bundle.putString(KEY_PULSE_SHAPE_COMBO, mPulseShapeCombo.toString());
bundle.putInt(KEY_SESSION_ID, mSessionId);
+ bundle.putInt(KEY_SESSION_TYPE, mSessionType);
bundle.putInt(KEY_RAN_MULTIPLIER, mRanMultiplier);
bundle.putInt(KEY_CHANNEL, mChannel);
bundle.putInt(KEY_NUM_CHAPS_PER_SLOT, mNumChapsPerSlot);
@@ -166,6 +171,11 @@ public class CccOpenRangingParams extends CccParams {
return mSessionId;
}
+ @SessionType
+ public int getSessionType() {
+ return mSessionType;
+ }
+
@IntRange(from = 0, to = 255)
public int getRanMultiplier() {
return mRanMultiplier;
@@ -209,6 +219,7 @@ public class CccOpenRangingParams extends CccParams {
@UwbConfig private RequiredParam<Integer> mUwbConfig = new RequiredParam<>();
private RequiredParam<CccPulseShapeCombo> mPulseShapeCombo = new RequiredParam<>();
private RequiredParam<Integer> mSessionId = new RequiredParam<>();
+ @SessionType private int mSessionType = CccParams.SESSION_TYPE_CCC;
private RequiredParam<Integer> mRanMultiplier = new RequiredParam<>();
@Channel private RequiredParam<Integer> mChannel = new RequiredParam<>();
@ChapsPerSlot private RequiredParam<Integer> mNumChapsPerSlot = new RequiredParam<>();
@@ -228,6 +239,7 @@ public class CccOpenRangingParams extends CccParams {
mUwbConfig.set(builder.mUwbConfig.get());
mPulseShapeCombo.set(builder.mPulseShapeCombo.get());
mSessionId.set(builder.mSessionId.get());
+ mSessionType = builder.mSessionType;
mRanMultiplier.set(builder.mRanMultiplier.get());
mChannel.set(builder.mChannel.get());
mNumChapsPerSlot.set(builder.mNumChapsPerSlot.get());
@@ -243,6 +255,7 @@ public class CccOpenRangingParams extends CccParams {
mUwbConfig.set(params.mUwbConfig);
mPulseShapeCombo.set(params.mPulseShapeCombo);
mSessionId.set(params.mSessionId);
+ mSessionType = params.mSessionType;
mRanMultiplier.set(params.mRanMultiplier);
mChannel.set(params.mChannel);
mNumChapsPerSlot.set(params.mNumChapsPerSlot);
@@ -319,6 +332,7 @@ public class CccOpenRangingParams extends CccParams {
mUwbConfig.get(),
mPulseShapeCombo.get(),
mSessionId.get(),
+ mSessionType,
mRanMultiplier.get(),
mChannel.get(),
mNumChapsPerSlot.get(),
diff --git a/service/support_lib/src/com/google/uwb/support/ccc/CccParams.java b/service/support_lib/src/com/google/uwb/support/ccc/CccParams.java
index ae718787..b241b235 100644
--- a/service/support_lib/src/com/google/uwb/support/ccc/CccParams.java
+++ b/service/support_lib/src/com/google/uwb/support/ccc/CccParams.java
@@ -200,4 +200,12 @@ public abstract class CccParams extends Params {
public static final int PROTOCOL_ERROR_SE_BUSY = 1;
public static final int PROTOCOL_ERROR_LIFECYCLE = 2;
public static final int PROTOCOL_ERROR_NOT_FOUND = 3;
+
+ /** Session Type */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(
+ value = {SESSION_TYPE_CCC})
+ public @interface SessionType {}
+
+ public static final int SESSION_TYPE_CCC = 160;
}
diff --git a/service/support_lib/src/com/google/uwb/support/fira/FiraOpenSessionParams.java b/service/support_lib/src/com/google/uwb/support/fira/FiraOpenSessionParams.java
index dd8d1322..ff1df5de 100644
--- a/service/support_lib/src/com/google/uwb/support/fira/FiraOpenSessionParams.java
+++ b/service/support_lib/src/com/google/uwb/support/fira/FiraOpenSessionParams.java
@@ -45,6 +45,7 @@ public class FiraOpenSessionParams extends FiraParams {
private final FiraProtocolVersion mProtocolVersion;
private final int mSessionId;
+ @SessionType private final int mSessionType;
@RangingDeviceType private final int mDeviceType;
@RangingDeviceRole private final int mDeviceRole;
@RangingRoundUsage private final int mRangingRoundUsage;
@@ -125,8 +126,8 @@ public class FiraOpenSessionParams extends FiraParams {
private final int mNumOfMsrmtFocusOnAoaAzimuth;
private final int mNumOfMsrmtFocusOnAoaElevation;
private final Long mRangingErrorStreakTimeoutMs;
- private final long mUlTdoaTxIntervalMs;
- private final long mUlTdoaRandomWindowMs;
+ private final int mUlTdoaTxIntervalMs;
+ private final int mUlTdoaRandomWindowMs;
@UlTdoaDeviceIdType private final int mUlTdoaDeviceIdType;
@Nullable private final byte[] mUlTdoaDeviceId;
@UlTdoaTxTimestampType private final int mUlTdoaTxTimestampType;
@@ -136,6 +137,7 @@ public class FiraOpenSessionParams extends FiraParams {
private static final String KEY_PROTOCOL_VERSION = "protocol_version";
private static final String KEY_SESSION_ID = "session_id";
+ private static final String KEY_SESSION_TYPE = "session_type";
private static final String KEY_DEVICE_TYPE = "device_type";
private static final String KEY_DEVICE_ROLE = "device_role";
private static final String KEY_RANGING_ROUND_USAGE = "ranging_round_usage";
@@ -219,6 +221,7 @@ public class FiraOpenSessionParams extends FiraParams {
private FiraOpenSessionParams(
FiraProtocolVersion protocolVersion,
int sessionId,
+ @SessionType int sessionType,
@RangingDeviceType int deviceType,
@RangingDeviceRole int deviceRole,
@RangingRoundUsage int rangingRoundUsage,
@@ -279,13 +282,14 @@ public class FiraOpenSessionParams extends FiraParams {
int numOfMsrmtFocusOnAoaAzimuth,
int numOfMsrmtFocusOnAoaElevation,
Long rangingErrorStreakTimeoutMs,
- long ulTdoaTxIntervalMs,
- long ulTdoaRandomWindowMs,
+ int ulTdoaTxIntervalMs,
+ int ulTdoaRandomWindowMs,
int ulTdoaDeviceIdType,
@Nullable byte[] ulTdoaDeviceId,
int ulTdoaTxTimestampType) {
mProtocolVersion = protocolVersion;
mSessionId = sessionId;
+ mSessionType = sessionType;
mDeviceType = deviceType;
mDeviceRole = deviceRole;
mRangingRoundUsage = rangingRoundUsage;
@@ -362,6 +366,11 @@ public class FiraOpenSessionParams extends FiraParams {
return mSessionId;
}
+ @SessionType
+ public int getSessionType() {
+ return mSessionType;
+ }
+
@RangingDeviceType
public int getDeviceType() {
return mDeviceType;
@@ -629,11 +638,11 @@ public class FiraOpenSessionParams extends FiraParams {
return mRangingErrorStreakTimeoutMs;
}
- public long getUlTdoaTxIntervalMs() {
+ public int getUlTdoaTxIntervalMs() {
return mUlTdoaTxIntervalMs;
}
- public long getUlTdoaRandomWindowMs() {
+ public int getUlTdoaRandomWindowMs() {
return mUlTdoaRandomWindowMs;
}
@@ -680,6 +689,7 @@ public class FiraOpenSessionParams extends FiraParams {
PersistableBundle bundle = super.toBundle();
bundle.putString(KEY_PROTOCOL_VERSION, mProtocolVersion.toString());
bundle.putInt(KEY_SESSION_ID, mSessionId);
+ bundle.putInt(KEY_SESSION_TYPE, mSessionType);
bundle.putInt(KEY_DEVICE_TYPE, mDeviceType);
bundle.putInt(KEY_DEVICE_ROLE, mDeviceRole);
bundle.putInt(KEY_RANGING_ROUND_USAGE, mRangingRoundUsage);
@@ -760,8 +770,8 @@ public class FiraOpenSessionParams extends FiraParams {
bundle.putInt(KEY_NUM_OF_MSRMT_FOCUS_ON_AOA_AZIMUTH, mNumOfMsrmtFocusOnAoaAzimuth);
bundle.putInt(KEY_NUM_OF_MSRMT_FOCUS_ON_AOA_ELEVATION, mNumOfMsrmtFocusOnAoaElevation);
bundle.putLong(RANGING_ERROR_STREAK_TIMEOUT_MS, mRangingErrorStreakTimeoutMs);
- bundle.putLong(UL_TDOA_TX_INTERVAL, mUlTdoaTxIntervalMs);
- bundle.putLong(UL_TDOA_RANDOM_WINDOW, mUlTdoaRandomWindowMs);
+ bundle.putInt(UL_TDOA_TX_INTERVAL, mUlTdoaTxIntervalMs);
+ bundle.putInt(UL_TDOA_RANDOM_WINDOW, mUlTdoaRandomWindowMs);
bundle.putInt(UL_TDOA_DEVICE_ID_TYPE, mUlTdoaDeviceIdType);
bundle.putIntArray(UL_TDOA_DEVICE_ID, byteArrayToIntArray(mUlTdoaDeviceId));
bundle.putInt(UL_TDOA_TX_TIMESTAMP_TYPE, mUlTdoaTxTimestampType);
@@ -802,6 +812,7 @@ public class FiraOpenSessionParams extends FiraParams {
FiraProtocolVersion.fromString(
requireNonNull(bundle.getString(KEY_PROTOCOL_VERSION))))
.setSessionId(bundle.getInt(KEY_SESSION_ID))
+ .setSessionType(bundle.getInt(KEY_SESSION_TYPE, FiraParams.SESSION_TYPE_RANGING))
.setDeviceType(bundle.getInt(KEY_DEVICE_TYPE))
.setDeviceRole(bundle.getInt(KEY_DEVICE_ROLE))
.setRangingRoundUsage(bundle.getInt(KEY_RANGING_ROUND_USAGE))
@@ -810,8 +821,7 @@ public class FiraOpenSessionParams extends FiraParams {
.setDestAddressList(destAddressList)
// Changed from int to long. Look for int value, if long value not found to
// maintain backwards compatibility.
- .setInitiationTimeMs(bundle.getLong(
- KEY_INITIATION_TIME_MS, bundle.getInt(KEY_INITIATION_TIME_MS)))
+ .setInitiationTimeMs(bundle.getLong(KEY_INITIATION_TIME_MS))
.setSlotDurationRstu(bundle.getInt(KEY_SLOT_DURATION_RSTU))
.setSlotsPerRangingRound(bundle.getInt(KEY_SLOTS_PER_RANGING_ROUND))
.setRangingIntervalMs(bundle.getInt(KEY_RANGING_INTERVAL_MS))
@@ -881,8 +891,8 @@ public class FiraOpenSessionParams extends FiraParams {
bundle.getInt(KEY_NUM_OF_MSRMT_FOCUS_ON_AOA_ELEVATION))
.setRangingErrorStreakTimeoutMs(bundle
.getLong(RANGING_ERROR_STREAK_TIMEOUT_MS, 30_000L))
- .setUlTdoaTxIntervalMs(bundle.getLong(UL_TDOA_TX_INTERVAL))
- .setUlTdoaRandomWindowMs(bundle.getLong(UL_TDOA_RANDOM_WINDOW))
+ .setUlTdoaTxIntervalMs(bundle.getInt(UL_TDOA_TX_INTERVAL))
+ .setUlTdoaRandomWindowMs(bundle.getInt(UL_TDOA_RANDOM_WINDOW))
.setUlTdoaDeviceIdType(bundle.getInt(UL_TDOA_DEVICE_ID_TYPE))
.setUlTdoaDeviceId(intArrayToByteArray(bundle.getIntArray(UL_TDOA_DEVICE_ID)))
.setUlTdoaTxTimestampType(bundle.getInt(UL_TDOA_TX_TIMESTAMP_TYPE))
@@ -898,6 +908,8 @@ public class FiraOpenSessionParams extends FiraParams {
private final RequiredParam<FiraProtocolVersion> mProtocolVersion = new RequiredParam<>();
private final RequiredParam<Integer> mSessionId = new RequiredParam<>();
+ @SessionType
+ private int mSessionType = FiraParams.SESSION_TYPE_RANGING;
private final RequiredParam<Integer> mDeviceType = new RequiredParam<>();
private final RequiredParam<Integer> mDeviceRole = new RequiredParam<>();
@@ -1045,10 +1057,10 @@ public class FiraOpenSessionParams extends FiraParams {
/** UCI spec default: +180 (No upper-bound filtering) */
private double mRangeDataNtfAoaAzimuthUpper = RANGE_DATA_NTF_AOA_AZIMUTH_UPPER_DEFAULT;
- /** UCI spec default: -180 (No low-bound filtering) */
+ /** UCI spec default: -90 (No low-bound filtering) */
private double mRangeDataNtfAoaElevationLower = RANGE_DATA_NTF_AOA_ELEVATION_LOWER_DEFAULT;
- /** UCI spec default: +180 (No upper-bound filtering) */
+ /** UCI spec default: +90 (No upper-bound filtering) */
private double mRangeDataNtfAoaElevationUpper = RANGE_DATA_NTF_AOA_ELEVATION_UPPER_DEFAULT;
/** UCI spec default: RESULT_REPORT_CONFIG bit 0 is 1 */
@@ -1075,10 +1087,10 @@ public class FiraOpenSessionParams extends FiraParams {
private long mRangingErrorStreakTimeoutMs = 30_000L;
/** Ul-TDoA Tx Interval in Milliseconds */
- private long mUlTdoaTxIntervalMs = 2_000L;
+ private int mUlTdoaTxIntervalMs = 2000;
/** Ul-TDoA Random Window in Milliseconds */
- private long mUlTdoaRandomWindowMs = 0;
+ private int mUlTdoaRandomWindowMs = 0;
/** Ul-TDoA Device ID type */
@UlTdoaDeviceIdType private int mUlTdoaDeviceIdType = UL_TDOA_DEVICE_ID_NONE;
@@ -1094,6 +1106,7 @@ public class FiraOpenSessionParams extends FiraParams {
public Builder(@NonNull Builder builder) {
mProtocolVersion.set(builder.mProtocolVersion.get());
mSessionId.set(builder.mSessionId.get());
+ mSessionType = builder.mSessionType;
mDeviceType.set(builder.mDeviceType.get());
mDeviceRole.set(builder.mDeviceRole.get());
mRangingRoundUsage = builder.mRangingRoundUsage;
@@ -1166,6 +1179,7 @@ public class FiraOpenSessionParams extends FiraParams {
public Builder(@NonNull FiraOpenSessionParams params) {
mProtocolVersion.set(params.mProtocolVersion);
mSessionId.set(params.mSessionId);
+ mSessionType = params.mSessionType;
mDeviceType.set(params.mDeviceType);
mDeviceRole.set(params.mDeviceRole);
mRangingRoundUsage = params.mRangingRoundUsage;
@@ -1245,6 +1259,11 @@ public class FiraOpenSessionParams extends FiraParams {
return this;
}
+ public FiraOpenSessionParams.Builder setSessionType(@SessionType int sessionType) {
+ mSessionType = sessionType;
+ return this;
+ }
+
public FiraOpenSessionParams.Builder setDeviceType(@RangingDeviceType int deviceType) {
mDeviceType.set(deviceType);
return this;
@@ -1577,13 +1596,13 @@ public class FiraOpenSessionParams extends FiraParams {
}
public FiraOpenSessionParams.Builder setUlTdoaTxIntervalMs(
- long ulTdoaTxIntervalMs) {
+ int ulTdoaTxIntervalMs) {
mUlTdoaTxIntervalMs = ulTdoaTxIntervalMs;
return this;
}
public FiraOpenSessionParams.Builder setUlTdoaRandomWindowMs(
- long ulTdoaRandomWindowMs) {
+ int ulTdoaRandomWindowMs) {
mUlTdoaRandomWindowMs = ulTdoaRandomWindowMs;
return this;
}
@@ -1754,6 +1773,7 @@ public class FiraOpenSessionParams extends FiraParams {
return new FiraOpenSessionParams(
mProtocolVersion.get(),
mSessionId.get(),
+ mSessionType,
mDeviceType.get(),
mDeviceRole.get(),
mRangingRoundUsage,
diff --git a/service/support_lib/src/com/google/uwb/support/fira/FiraParams.java b/service/support_lib/src/com/google/uwb/support/fira/FiraParams.java
index 2ce48bf0..615fa712 100644
--- a/service/support_lib/src/com/google/uwb/support/fira/FiraParams.java
+++ b/service/support_lib/src/com/google/uwb/support/fira/FiraParams.java
@@ -673,8 +673,8 @@ public abstract class FiraParams extends Params {
public static final int RANGE_DATA_NTF_PROXIMITY_FAR_DEFAULT = 20000;
public static final double RANGE_DATA_NTF_AOA_AZIMUTH_LOWER_DEFAULT = -Math.PI;
public static final double RANGE_DATA_NTF_AOA_AZIMUTH_UPPER_DEFAULT = Math.PI;
- public static final double RANGE_DATA_NTF_AOA_ELEVATION_LOWER_DEFAULT = -Math.PI;
- public static final double RANGE_DATA_NTF_AOA_ELEVATION_UPPER_DEFAULT = Math.PI;
+ public static final double RANGE_DATA_NTF_AOA_ELEVATION_LOWER_DEFAULT = -Math.PI / 2;
+ public static final double RANGE_DATA_NTF_AOA_ELEVATION_UPPER_DEFAULT = Math.PI / 2;
public enum AoaCapabilityFlag implements FlagEnum {
HAS_AZIMUTH_SUPPORT(1),
@@ -1002,6 +1002,29 @@ public abstract class FiraParams extends Params {
public static final int KEY_LENGTH_256_BITS_NOT_SUPPORTED = 0;
public static final int KEY_LENGTH_256_BITS_SUPPORTED = 1;
+ /**
+ * Session Type (for SESSION_INIT_CMD)
+ */
+ @IntDef(
+ value = {
+ SESSION_TYPE_RANGING,
+ SESSION_TYPE_RANGING_AND_IN_BAND_DATA,
+ SESSION_TYPE_DATA_TRANSFER,
+ SESSION_TYPE_RANGING_ONLY_PHASE,
+ SESSION_TYPE_IN_BAND_DATA_PHASE,
+ SESSION_TYPE_RANGING_WITH_DATA_PHASE,
+ SESSION_TYPE_DEVICE_TEST_MODE,
+ })
+ public @interface SessionType{}
+
+ public static final int SESSION_TYPE_RANGING = 0;
+ public static final int SESSION_TYPE_RANGING_AND_IN_BAND_DATA = 1;
+ public static final int SESSION_TYPE_DATA_TRANSFER = 2;
+ public static final int SESSION_TYPE_RANGING_ONLY_PHASE = 3;
+ public static final int SESSION_TYPE_IN_BAND_DATA_PHASE = 4;
+ public static final int SESSION_TYPE_RANGING_WITH_DATA_PHASE = 5;
+ public static final int SESSION_TYPE_DEVICE_TEST_MODE = 0xD0;
+
// Helper functions
protected static UwbAddress longToUwbAddress(long value, int length) {
ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES);
diff --git a/service/support_lib/test/CccTests.java b/service/support_lib/test/CccTests.java
index e9a0fde6..d5431cf0 100644
--- a/service/support_lib/test/CccTests.java
+++ b/service/support_lib/test/CccTests.java
@@ -83,6 +83,7 @@ public class CccTests {
assertEquals(
params.getPulseShapeCombo().getResponderTx(), pulseShapeCombo.getResponderTx());
assertEquals(params.getSessionId(), sessionId);
+ assertEquals(params.getSessionType(), CccParams.SESSION_TYPE_CCC);
assertEquals(params.getRanMultiplier(), ranMultiplier);
assertEquals(params.getChannel(), channel);
assertEquals(params.getNumChapsPerSlot(), chapsPerSlot);
diff --git a/service/support_lib/test/FiraTests.java b/service/support_lib/test/FiraTests.java
index 48ec79ef..833b2c4f 100644
--- a/service/support_lib/test/FiraTests.java
+++ b/service/support_lib/test/FiraTests.java
@@ -35,6 +35,7 @@ import static com.google.uwb.support.fira.FiraParams.RANGING_DEVICE_TYPE_CONTROL
import static com.google.uwb.support.fira.FiraParams.RANGING_DEVICE_TYPE_CONTROLLER;
import static com.google.uwb.support.fira.FiraParams.RANGING_ROUND_USAGE_SS_TWR_DEFERRED_MODE;
import static com.google.uwb.support.fira.FiraParams.RFRAME_CONFIG_SP1;
+import static com.google.uwb.support.fira.FiraParams.SESSION_TYPE_RANGING;
import static com.google.uwb.support.fira.FiraParams.SFD_ID_VALUE_3;
import static com.google.uwb.support.fira.FiraParams.STATE_CHANGE_REASON_CODE_ERROR_INVALID_RANGING_INTERVAL;
import static com.google.uwb.support.fira.FiraParams.STATUS_CODE_ERROR_ADDRESS_ALREADY_PRESENT;
@@ -79,6 +80,7 @@ public class FiraTests {
public void testOpenSessionParams() {
FiraProtocolVersion protocolVersion = FiraParams.PROTOCOL_VERSION_1_1;
int sessionId = 10;
+ int sessionType = SESSION_TYPE_RANGING;
int deviceType = RANGING_DEVICE_TYPE_CONTROLEE;
int deviceRole = RANGING_DEVICE_ROLE_INITIATOR;
int rangingRoundUsage = RANGING_ROUND_USAGE_SS_TWR_DEFERRED_MODE;
@@ -130,7 +132,7 @@ public class FiraTests {
double rangeDataNtfAoaAzimuthLower = -0.5;
double rangeDataNtfAoaAzimuthUpper = +1.5;
double rangeDataNtfAoaElevationLower = -1.5;
- double rangeDataNtfAoaElevationUpper = +2.5;
+ double rangeDataNtfAoaElevationUpper = +1.2;
boolean hasTimeOfFlightReport = true;
boolean hasAngleOfArrivalAzimuthReport = true;
boolean hasAngleOfArrivalElevationReport = true;
@@ -139,8 +141,8 @@ public class FiraTests {
int numOfMsrmtFocusOnRange = 1;
int numOfMsrmtFocusOnAoaAzimuth = 2;
int numOfMsrmtFocusOnAoaElevation = 3;
- long ulTdoaTxIntervalMs = 1_000L;
- long ulTdoaRandomWindowMS = 100;
+ int ulTdoaTxIntervalMs = 1_000;
+ int ulTdoaRandomWindowMS = 100;
int ulTdoaDeviceIdType = UL_TDOA_DEVICE_ID_16_BIT;
byte[] ulTdoaDeviceId = new byte[] {(byte) 0x0C, (byte) 0x0B};
int ulTdoaTxTimestampType = TX_TIMESTAMP_40_BIT;
@@ -149,6 +151,7 @@ public class FiraTests {
new FiraOpenSessionParams.Builder()
.setProtocolVersion(protocolVersion)
.setSessionId(sessionId)
+ .setSessionType(sessionType)
.setDeviceType(deviceType)
.setDeviceRole(deviceRole)
.setRangingRoundUsage(rangingRoundUsage)
@@ -213,6 +216,7 @@ public class FiraTests {
assertEquals(params.getProtocolVersion(), protocolVersion);
assertEquals(params.getSessionId(), sessionId);
+ assertEquals(params.getSessionType(), sessionType);
assertEquals(params.getDeviceType(), deviceType);
assertEquals(params.getDeviceRole(), deviceRole);
assertEquals(params.getRangingRoundUsage(), rangingRoundUsage);
@@ -442,7 +446,7 @@ public class FiraTests {
double rangeDataAoaAzimuthLower = -0.5;
double rangeDataAoaAzimuthUpper = +1.5;
double rangeDataAoaElevationLower = -1.5;
- double rangeDataAoaElevationUpper = +2.5;
+ double rangeDataAoaElevationUpper = +1.2;
int[] subSessionIdList = new int[] {3, 4};
FiraRangingReconfigureParams params =
diff --git a/service/tests/src/com/android/server/uwb/UwbConfigurationManagerTest.java b/service/tests/src/com/android/server/uwb/UwbConfigurationManagerTest.java
index d6cffe4c..a5b6c049 100644
--- a/service/tests/src/com/android/server/uwb/UwbConfigurationManagerTest.java
+++ b/service/tests/src/com/android/server/uwb/UwbConfigurationManagerTest.java
@@ -31,6 +31,7 @@ import static com.google.uwb.support.fira.FiraParams.RANGING_DEVICE_ROLE_INITIAT
import static com.google.uwb.support.fira.FiraParams.RANGING_DEVICE_TYPE_CONTROLEE;
import static com.google.uwb.support.fira.FiraParams.RANGING_ROUND_USAGE_SS_TWR_DEFERRED_MODE;
import static com.google.uwb.support.fira.FiraParams.RFRAME_CONFIG_SP1;
+import static com.google.uwb.support.fira.FiraParams.SESSION_TYPE_RANGING;
import static com.google.uwb.support.fira.FiraParams.SFD_ID_VALUE_3;
import static com.google.uwb.support.fira.FiraParams.STS_CONFIG_DYNAMIC_FOR_CONTROLEE_INDIVIDUAL_KEY;
import static com.google.uwb.support.fira.FiraParams.STS_LENGTH_128_SYMBOLS;
@@ -143,6 +144,7 @@ public class UwbConfigurationManagerTest {
private FiraOpenSessionParams getFiraParams() {
FiraProtocolVersion protocolVersion = FiraParams.PROTOCOL_VERSION_1_1;
int sessionId = 10;
+ int sessionType = SESSION_TYPE_RANGING;
int deviceType = RANGING_DEVICE_TYPE_CONTROLEE;
int deviceRole = RANGING_DEVICE_ROLE_INITIATOR;
int rangingRoundUsage = RANGING_ROUND_USAGE_SS_TWR_DEFERRED_MODE;
@@ -204,6 +206,7 @@ public class UwbConfigurationManagerTest {
new FiraOpenSessionParams.Builder()
.setProtocolVersion(protocolVersion)
.setSessionId(sessionId)
+ .setSessionType(sessionType)
.setDeviceType(deviceType)
.setDeviceRole(deviceRole)
.setRangingRoundUsage(rangingRoundUsage)
diff --git a/service/tests/src/com/android/server/uwb/UwbServiceCoreTest.java b/service/tests/src/com/android/server/uwb/UwbServiceCoreTest.java
index abc7b34c..5d780fe0 100644
--- a/service/tests/src/com/android/server/uwb/UwbServiceCoreTest.java
+++ b/service/tests/src/com/android/server/uwb/UwbServiceCoreTest.java
@@ -31,6 +31,7 @@ import static com.google.uwb.support.fira.FiraParams.RANGE_DATA_NTF_CONFIG_ENABL
import static com.google.uwb.support.fira.FiraParams.RANGING_DEVICE_ROLE_RESPONDER;
import static com.google.uwb.support.fira.FiraParams.RANGING_DEVICE_TYPE_CONTROLLER;
import static com.google.uwb.support.fira.FiraParams.RANGING_ROUND_USAGE_SS_TWR_DEFERRED_MODE;
+import static com.google.uwb.support.fira.FiraParams.SESSION_TYPE_RANGING;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
@@ -105,7 +106,9 @@ import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.mockito.MockitoSession;
+import org.mockito.invocation.InvocationOnMock;
import org.mockito.quality.Strictness;
+import org.mockito.stubbing.Answer;
import java.util.Arrays;
import java.util.List;
@@ -136,6 +139,7 @@ public class UwbServiceCoreTest {
new FiraOpenSessionParams.Builder()
.setProtocolVersion(FiraParams.PROTOCOL_VERSION_1_1)
.setSessionId(1)
+ .setSessionType(SESSION_TYPE_RANGING)
.setDeviceType(RANGING_DEVICE_TYPE_CONTROLLER)
.setDeviceRole(RANGING_DEVICE_ROLE_RESPONDER)
.setDeviceAddress(UwbAddress.fromBytes(new byte[] { 0x4, 0x6}))
@@ -172,6 +176,7 @@ public class UwbServiceCoreTest {
@Mock private UwbInjector mUwbInjector;
@Mock DeviceConfigFacade mDeviceConfigFacade;
@Mock private ProfileManager mProfileManager;
+ @Mock private PowerManager.WakeLock mUwbWakeLock;
@Mock private Resources mResources;
private TestLooper mTestLooper;
@@ -187,7 +192,7 @@ public class UwbServiceCoreTest {
mTestLooper = new TestLooper();
PowerManager powerManager = mock(PowerManager.class);
when(powerManager.newWakeLock(anyInt(), anyString()))
- .thenReturn(mock(PowerManager.WakeLock.class));
+ .thenReturn(mUwbWakeLock);
when(mContext.getSystemService(PowerManager.class)).thenReturn(powerManager);
when(mUwbInjector.getDeviceConfigFacade()).thenReturn(mDeviceConfigFacade);
UwbMultichipData uwbMultichipData = setUpMultichipDataForOneChip();
@@ -386,6 +391,41 @@ public class UwbServiceCoreTest {
verifyNoMoreInteractions(mNativeUwbManager, mUwbCountryCode, cb);
}
+ // Test the UWB stack enable when the NativeUwbManager.doInitialize() is delayed such that the
+ // watchdog timer expiry happens.
+ @Test
+ public void testEnableWhenInitializeDelayed() throws Exception {
+ IUwbAdapterStateCallbacks cb = mock(IUwbAdapterStateCallbacks.class);
+ when(cb.asBinder()).thenReturn(mock(IBinder.class));
+ mUwbServiceCore.registerAdapterStateCallbacks(cb);
+ verify(cb).onAdapterStateChanged(UwbManager.AdapterStateCallback.STATE_DISABLED,
+ StateChangeReason.SYSTEM_BOOT);
+
+ // Setup doInitialize() to take long time, such that the WatchDog thread times out.
+ when(mNativeUwbManager.doInitialize()).thenAnswer(new Answer<Boolean>() {
+ public Boolean answer(InvocationOnMock invocation) throws Throwable {
+ // Return success but too late, so this result shouldn't matter.
+ Thread.sleep(UwbServiceCore.WATCHDOG_MS + 1000);
+ return true;
+ }
+ });
+ when(mUwbCountryCode.getCountryCode()).thenReturn("US");
+ when(mUwbCountryCode.setCountryCode(anyBoolean())).thenReturn(true);
+
+ // Setup the wakelock to be checked twice (once from the watchdog thread after expiry, and
+ // second time from handleEnable()).
+ when(mUwbWakeLock.isHeld()).thenReturn(true).thenReturn(false);
+
+ mUwbServiceCore.setEnabled(true);
+ mTestLooper.dispatchAll();
+
+ verify(mNativeUwbManager).doInitialize();
+ verify(mUwbWakeLock, times(1)).acquire();
+ verify(mUwbWakeLock, times(2)).isHeld();
+ verify(mUwbWakeLock, times(1)).release();
+ verify(cb).onAdapterStateChanged(UwbManager.AdapterStateCallback.STATE_ENABLED_INACTIVE,
+ StateChangeReason.SYSTEM_POLICY);
+ }
@Test
public void testDisable() throws Exception {
@@ -407,6 +447,47 @@ public class UwbServiceCoreTest {
StateChangeReason.SYSTEM_POLICY);
}
+ // Test the UWB stack disable when the NativeUwbManager.doDeinitialize() is delayed such that
+ // the watchdog timer expiry happens.
+ @Test
+ public void testDisableWhenInitializeDelayed() throws Exception {
+ IUwbAdapterStateCallbacks cb = mock(IUwbAdapterStateCallbacks.class);
+ when(cb.asBinder()).thenReturn(mock(IBinder.class));
+ mUwbServiceCore.registerAdapterStateCallbacks(cb);
+ verify(cb).onAdapterStateChanged(UwbManager.AdapterStateCallback.STATE_DISABLED,
+ StateChangeReason.SYSTEM_BOOT);
+
+ // Enable first
+ enableUwbWithCountryCode();
+ verify(cb).onAdapterStateChanged(UwbManager.AdapterStateCallback.STATE_ENABLED_INACTIVE,
+ StateChangeReason.SYSTEM_POLICY);
+
+ clearInvocations(mUwbWakeLock);
+
+ // Setup doDeinitialize() to take long time, such that the WatchDog thread times out.
+ when(mNativeUwbManager.doDeinitialize()).thenAnswer(new Answer<Boolean>() {
+ public Boolean answer(InvocationOnMock invocation) throws Throwable {
+ // Return success but too late, so this result shouldn't matter.
+ Thread.sleep(UwbServiceCore.WATCHDOG_MS + 1000);
+ return true;
+ }
+ });
+
+ // Setup the wakelock to be checked twice (once from the watchdog thread after expiry, and
+ // second time from handleDisable()).
+ when(mUwbWakeLock.isHeld()).thenReturn(true).thenReturn(false);
+
+ // Disable UWB.
+ mUwbServiceCore.setEnabled(false);
+ mTestLooper.dispatchAll();
+
+ verify(mNativeUwbManager).doDeinitialize();
+ verify(mUwbWakeLock, times(1)).acquire();
+ verify(mUwbWakeLock, times(2)).isHeld();
+ verify(mUwbWakeLock, times(1)).release();
+ verify(cb).onAdapterStateChanged(UwbManager.AdapterStateCallback.STATE_DISABLED,
+ StateChangeReason.SYSTEM_POLICY);
+ }
@Test
public void testDisableWhenAlreadyDisabled() throws Exception {
@@ -547,7 +628,8 @@ public class UwbServiceCoreTest {
verify(mUwbSessionManager).initSession(
eq(attributionSource),
- eq(sessionHandle), eq(params.getSessionId()), eq(FiraParams.PROTOCOL_NAME),
+ eq(sessionHandle), eq(params.getSessionId()), eq((byte) params.getSessionType()),
+ eq(FiraParams.PROTOCOL_NAME),
argThat(p -> ((FiraOpenSessionParams) p).getSessionId() == params.getSessionId()),
eq(cb), eq(TEST_DEFAULT_CHIP_ID));
@@ -566,7 +648,8 @@ public class UwbServiceCoreTest {
verify(mUwbSessionManager).initSession(
eq(attributionSource),
- eq(sessionHandle), eq(params.getSessionId()), eq(CccParams.PROTOCOL_NAME),
+ eq(sessionHandle), eq(params.getSessionId()), eq((byte) params.getSessionType()),
+ eq(CccParams.PROTOCOL_NAME),
argThat(p -> ((CccOpenRangingParams) p).getSessionId() == params.getSessionId()),
eq(cb), eq(TEST_DEFAULT_CHIP_ID));
}
@@ -903,6 +986,37 @@ public class UwbServiceCoreTest {
}
@Test
+ public void testMultipleDeviceStateCallbacks() throws Exception {
+ IUwbAdapterStateCallbacks cb1 = mock(IUwbAdapterStateCallbacks.class);
+ when(cb1.asBinder()).thenReturn(mock(IBinder.class));
+ IUwbAdapterStateCallbacks cb2 = mock(IUwbAdapterStateCallbacks.class);
+ when(cb2.asBinder()).thenReturn(mock(IBinder.class));
+
+ mUwbServiceCore.registerAdapterStateCallbacks(cb1);
+ verify(cb1).onAdapterStateChanged(UwbManager.AdapterStateCallback.STATE_DISABLED,
+ StateChangeReason.SYSTEM_BOOT);
+
+ mUwbServiceCore.registerAdapterStateCallbacks(cb2);
+ verify(cb2).onAdapterStateChanged(UwbManager.AdapterStateCallback.STATE_DISABLED,
+ StateChangeReason.SYSTEM_BOOT);
+
+ enableUwbWithCountryCode();
+ verify(cb1).onAdapterStateChanged(UwbManager.AdapterStateCallback.STATE_ENABLED_INACTIVE,
+ StateChangeReason.SYSTEM_POLICY);
+ verify(cb2).onAdapterStateChanged(UwbManager.AdapterStateCallback.STATE_ENABLED_INACTIVE,
+ StateChangeReason.SYSTEM_POLICY);
+
+ when(mUwbCountryCode.getCountryCode()).thenReturn("US");
+ mUwbServiceCore.onDeviceStatusNotificationReceived(UwbUciConstants.DEVICE_STATE_ACTIVE,
+ TEST_DEFAULT_CHIP_ID);
+ mTestLooper.dispatchAll();
+ verify(cb1).onAdapterStateChanged(UwbManager.AdapterStateCallback.STATE_ENABLED_ACTIVE,
+ StateChangeReason.SESSION_STARTED);
+ verify(cb2).onAdapterStateChanged(UwbManager.AdapterStateCallback.STATE_ENABLED_ACTIVE,
+ StateChangeReason.SESSION_STARTED);
+ }
+
+ @Test
public void testDeviceStateCallback_invalidChipId() throws Exception {
IUwbAdapterStateCallbacks cb = mock(IUwbAdapterStateCallbacks.class);
when(cb.asBinder()).thenReturn(mock(IBinder.class));
diff --git a/service/tests/src/com/android/server/uwb/UwbSessionManagerTest.java b/service/tests/src/com/android/server/uwb/UwbSessionManagerTest.java
index ca230446..f8f10b6d 100644
--- a/service/tests/src/com/android/server/uwb/UwbSessionManagerTest.java
+++ b/service/tests/src/com/android/server/uwb/UwbSessionManagerTest.java
@@ -24,14 +24,20 @@ import static com.android.server.uwb.UwbTestUtils.DATA_PAYLOAD;
import static com.android.server.uwb.UwbTestUtils.PEER_BAD_MAC_ADDRESS;
import static com.android.server.uwb.UwbTestUtils.PEER_EXTENDED_MAC_ADDRESS;
import static com.android.server.uwb.UwbTestUtils.PEER_EXTENDED_MAC_ADDRESS_2;
+import static com.android.server.uwb.UwbTestUtils.PEER_EXTENDED_MAC_ADDRESS_2_LONG;
+import static com.android.server.uwb.UwbTestUtils.PEER_EXTENDED_MAC_ADDRESS_LONG;
import static com.android.server.uwb.UwbTestUtils.PEER_EXTENDED_SHORT_MAC_ADDRESS;
+import static com.android.server.uwb.UwbTestUtils.PEER_EXTENDED_SHORT_MAC_ADDRESS_LONG;
import static com.android.server.uwb.UwbTestUtils.PEER_EXTENDED_UWB_ADDRESS;
+import static com.android.server.uwb.UwbTestUtils.PEER_EXTENDED_UWB_ADDRESS_2;
import static com.android.server.uwb.UwbTestUtils.PEER_SHORT_MAC_ADDRESS;
+import static com.android.server.uwb.UwbTestUtils.PEER_SHORT_MAC_ADDRESS_LONG;
import static com.android.server.uwb.UwbTestUtils.PEER_SHORT_UWB_ADDRESS;
import static com.android.server.uwb.UwbTestUtils.PERSISTABLE_BUNDLE;
import static com.android.server.uwb.UwbTestUtils.RANGING_MEASUREMENT_TYPE_UNDEFINED;
import static com.android.server.uwb.UwbTestUtils.TEST_SESSION_ID;
import static com.android.server.uwb.UwbTestUtils.TEST_SESSION_ID_2;
+import static com.android.server.uwb.UwbTestUtils.TEST_SESSION_TYPE;
import static com.android.server.uwb.data.UwbUciConstants.MAC_ADDRESSING_MODE_EXTENDED;
import static com.android.server.uwb.data.UwbUciConstants.MAC_ADDRESSING_MODE_SHORT;
import static com.android.server.uwb.data.UwbUciConstants.RANGING_DEVICE_ROLE_ADVERTISER;
@@ -45,9 +51,9 @@ import static com.google.common.truth.Truth.assertThat;
import static com.google.uwb.support.fira.FiraParams.PROTOCOL_NAME;
import static com.google.uwb.support.fira.FiraParams.RangeDataNtfConfigCapabilityFlag.HAS_RANGE_DATA_NTF_CONFIG_DISABLE;
import static com.google.uwb.support.fira.FiraParams.RangeDataNtfConfigCapabilityFlag.HAS_RANGE_DATA_NTF_CONFIG_ENABLE;
+import static com.google.uwb.support.fira.FiraParams.SESSION_TYPE_RANGING;
+import static com.google.uwb.support.fira.FiraParams.STATUS_CODE_OK;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyByte;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -56,6 +62,7 @@ import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isA;
import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
@@ -64,6 +71,7 @@ import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
@@ -141,7 +149,9 @@ public class UwbSessionManagerTest {
private static final UwbAddress UWB_DEST_ADDRESS_3 =
UwbAddress.fromBytes(new byte[] {(byte) 0x07, (byte) 0x08 });
private static final int TEST_RANGING_INTERVAL_MS = 200;
- private static final int DATA_SEQUENCE_NUM = 1;
+ private static final byte DATA_SEQUENCE_NUM = 0;
+ private static final byte DATA_SEQUENCE_NUM_1 = 2;
+
private static final int SOURCE_END_POINT = 100;
private static final int DEST_END_POINT = 200;
private static final int HANDLE_ID = 12;
@@ -222,26 +232,42 @@ public class UwbSessionManagerTest {
@Test
public void onDataReceived_extendedMacAddressFormat() {
+ UwbSession mockUwbSession = mock(UwbSession.class);
+ when(mockUwbSession.getWaitObj()).thenReturn(mock(WaitObj.class));
+ doReturn(mockUwbSession)
+ .when(mUwbSessionManager).getUwbSession(eq(TEST_SESSION_ID));
+
mUwbSessionManager.onDataReceived(TEST_SESSION_ID, UwbUciConstants.STATUS_CODE_OK,
DATA_SEQUENCE_NUM, PEER_EXTENDED_MAC_ADDRESS, SOURCE_END_POINT, DEST_END_POINT,
DATA_PAYLOAD);
- assertNotNull(mUwbSessionManager.getReceivedDataInfo(PEER_EXTENDED_MAC_ADDRESS));
+ verify(mockUwbSession).addReceivedDataInfo(isA(UwbSessionManager.ReceivedDataInfo.class));
}
@Test
public void onDataReceived_unsupportedMacAddressLength() {
+ UwbSession mockUwbSession = mock(UwbSession.class);
+ when(mockUwbSession.getWaitObj()).thenReturn(mock(WaitObj.class));
+ doReturn(mockUwbSession)
+ .when(mUwbSessionManager).getUwbSession(eq(TEST_SESSION_ID));
+
mUwbSessionManager.onDataReceived(TEST_SESSION_ID, UwbUciConstants.STATUS_CODE_OK,
DATA_SEQUENCE_NUM, PEER_BAD_MAC_ADDRESS, SOURCE_END_POINT, DEST_END_POINT,
DATA_PAYLOAD);
- assertNull(mUwbSessionManager.getReceivedDataInfo(PEER_BAD_MAC_ADDRESS));
+ verify(mockUwbSession, never()).addReceivedDataInfo(
+ isA(UwbSessionManager.ReceivedDataInfo.class));
}
@Test
public void onDataReceived_shortMacAddressFormat() {
+ UwbSession mockUwbSession = mock(UwbSession.class);
+ when(mockUwbSession.getWaitObj()).thenReturn(mock(WaitObj.class));
+ doReturn(mockUwbSession)
+ .when(mUwbSessionManager).getUwbSession(eq(TEST_SESSION_ID));
+
mUwbSessionManager.onDataReceived(TEST_SESSION_ID, UwbUciConstants.STATUS_CODE_OK,
DATA_SEQUENCE_NUM, PEER_EXTENDED_SHORT_MAC_ADDRESS, SOURCE_END_POINT,
DEST_END_POINT, DATA_PAYLOAD);
- assertNotNull(mUwbSessionManager.getReceivedDataInfo(PEER_EXTENDED_SHORT_MAC_ADDRESS));
+ verify(mockUwbSession).addReceivedDataInfo(isA(UwbSessionManager.ReceivedDataInfo.class));
}
@Test
@@ -296,6 +322,7 @@ public class UwbSessionManagerTest {
mUwbSessionManager.onDataReceived(TEST_SESSION_ID, UwbUciConstants.STATUS_CODE_OK,
DATA_SEQUENCE_NUM, PEER_EXTENDED_MAC_ADDRESS, SOURCE_END_POINT, DEST_END_POINT,
DATA_PAYLOAD);
+ verify(mockUwbSession).addReceivedDataInfo(isA(UwbSessionManager.ReceivedDataInfo.class));
// Next call onRangeDataNotificationReceived() to process the RANGE_DATA_NTF.
UwbRangingData uwbRangingData = UwbTestUtils.generateRangingData(
@@ -305,6 +332,8 @@ public class UwbSessionManagerTest {
RANGING_DEVICE_ROLE_OBSERVER, Optional.of(ROUND_USAGE_OWR_AOA_MEASUREMENT));
when(mockUwbSession.getParams()).thenReturn(firaParams);
when(mUwbAdvertiseManager.isPointedTarget(PEER_EXTENDED_MAC_ADDRESS)).thenReturn(true);
+ when(mockUwbSession.getAllReceivedDataInfo(PEER_EXTENDED_MAC_ADDRESS_LONG))
+ .thenReturn(List.of(buildReceivedDataInfo(PEER_EXTENDED_MAC_ADDRESS_LONG)));
mUwbSessionManager.onRangeDataNotificationReceived(uwbRangingData);
verify(mUwbSessionNotificationManager)
@@ -313,7 +342,7 @@ public class UwbSessionManagerTest {
verify(mUwbSessionNotificationManager)
.onDataReceived(eq(mockUwbSession), eq(PEER_EXTENDED_UWB_ADDRESS),
isA(PersistableBundle.class), eq(DATA_PAYLOAD));
- verify(mUwbAdvertiseManager).removeAdvertiseTarget(PEER_EXTENDED_MAC_ADDRESS);
+ verify(mUwbAdvertiseManager).removeAdvertiseTarget(PEER_EXTENDED_MAC_ADDRESS_LONG);
verify(mUwbMetrics).logRangingResult(anyInt(), eq(uwbRangingData));
}
@@ -338,6 +367,7 @@ public class UwbSessionManagerTest {
mUwbSessionManager.onDataReceived(TEST_SESSION_ID, UwbUciConstants.STATUS_CODE_OK,
DATA_SEQUENCE_NUM, PEER_EXTENDED_SHORT_MAC_ADDRESS,
SOURCE_END_POINT, DEST_END_POINT, DATA_PAYLOAD);
+ verify(mockUwbSession).addReceivedDataInfo(isA(UwbSessionManager.ReceivedDataInfo.class));
// Next call onRangeDataNotificationReceived() to process the RANGE_DATA_NTF.
UwbRangingData uwbRangingData = UwbTestUtils.generateRangingData(
@@ -347,6 +377,8 @@ public class UwbSessionManagerTest {
RANGING_DEVICE_ROLE_OBSERVER, Optional.of(ROUND_USAGE_OWR_AOA_MEASUREMENT));
when(mockUwbSession.getParams()).thenReturn(firaParams);
when(mUwbAdvertiseManager.isPointedTarget(PEER_SHORT_MAC_ADDRESS)).thenReturn(true);
+ when(mockUwbSession.getAllReceivedDataInfo(PEER_EXTENDED_SHORT_MAC_ADDRESS_LONG))
+ .thenReturn(List.of(buildReceivedDataInfo(PEER_EXTENDED_SHORT_MAC_ADDRESS_LONG)));
mUwbSessionManager.onRangeDataNotificationReceived(uwbRangingData);
verify(mUwbSessionNotificationManager)
@@ -355,10 +387,80 @@ public class UwbSessionManagerTest {
verify(mUwbSessionNotificationManager)
.onDataReceived(eq(mockUwbSession), eq(PEER_SHORT_UWB_ADDRESS),
isA(PersistableBundle.class), eq(DATA_PAYLOAD));
- verify(mUwbAdvertiseManager).removeAdvertiseTarget(PEER_SHORT_MAC_ADDRESS);
+ verify(mUwbAdvertiseManager).removeAdvertiseTarget(PEER_SHORT_MAC_ADDRESS_LONG);
verify(mUwbMetrics).logRangingResult(anyInt(), eq(uwbRangingData));
}
+ // Test scenario for receiving Application payload data followed by a RANGE_DATA_NTF with an
+ // OWR Aoa Measurement, from Multiple advertiser devices in a UWB session.
+ @Test
+ public void onRangeDataNotificationReceived_owrAoa_success_multipleAdvertisers() {
+ UwbSession mockUwbSession = mock(UwbSession.class);
+ when(mockUwbSession.getWaitObj()).thenReturn(mock(WaitObj.class));
+ doReturn(mockUwbSession)
+ .when(mUwbSessionManager).getUwbSession(eq(TEST_SESSION_ID));
+
+ // First call onDataReceived() to get the application payload data.
+ mUwbSessionManager.onDataReceived(TEST_SESSION_ID, UwbUciConstants.STATUS_CODE_OK,
+ DATA_SEQUENCE_NUM, PEER_EXTENDED_MAC_ADDRESS, SOURCE_END_POINT, DEST_END_POINT,
+ DATA_PAYLOAD);
+ mUwbSessionManager.onDataReceived(TEST_SESSION_ID, UwbUciConstants.STATUS_CODE_OK,
+ DATA_SEQUENCE_NUM_1, PEER_EXTENDED_MAC_ADDRESS, SOURCE_END_POINT, DEST_END_POINT,
+ DATA_PAYLOAD);
+
+ mUwbSessionManager.onDataReceived(TEST_SESSION_ID, UwbUciConstants.STATUS_CODE_OK,
+ DATA_SEQUENCE_NUM, PEER_EXTENDED_MAC_ADDRESS_2, SOURCE_END_POINT, DEST_END_POINT,
+ DATA_PAYLOAD);
+ mUwbSessionManager.onDataReceived(TEST_SESSION_ID, UwbUciConstants.STATUS_CODE_OK,
+ DATA_SEQUENCE_NUM_1, PEER_EXTENDED_MAC_ADDRESS_2, SOURCE_END_POINT, DEST_END_POINT,
+ DATA_PAYLOAD);
+
+ verify(mockUwbSession, times(4)).addReceivedDataInfo(
+ isA(UwbSessionManager.ReceivedDataInfo.class));
+
+ // Next call onRangeDataNotificationReceived() to process the RANGE_DATA_NTF.
+ UwbRangingData uwbRangingData1 = UwbTestUtils.generateRangingData(
+ RANGING_MEASUREMENT_TYPE_OWR_AOA, MAC_ADDRESSING_MODE_EXTENDED,
+ PEER_EXTENDED_MAC_ADDRESS, UwbUciConstants.STATUS_CODE_OK);
+ UwbRangingData uwbRangingData2 = UwbTestUtils.generateRangingData(
+ RANGING_MEASUREMENT_TYPE_OWR_AOA, MAC_ADDRESSING_MODE_EXTENDED,
+ PEER_EXTENDED_MAC_ADDRESS_2, UwbUciConstants.STATUS_CODE_OK);
+ Params firaParams = setupFiraParams(
+ RANGING_DEVICE_ROLE_OBSERVER, Optional.of(ROUND_USAGE_OWR_AOA_MEASUREMENT));
+ when(mockUwbSession.getParams()).thenReturn(firaParams);
+ when(mUwbAdvertiseManager.isPointedTarget(PEER_EXTENDED_MAC_ADDRESS)).thenReturn(true);
+ when(mUwbAdvertiseManager.isPointedTarget(PEER_EXTENDED_MAC_ADDRESS_2)).thenReturn(true);
+ when(mockUwbSession.getAllReceivedDataInfo(PEER_EXTENDED_MAC_ADDRESS_LONG))
+ .thenReturn(List.of(
+ buildReceivedDataInfo(PEER_EXTENDED_MAC_ADDRESS_LONG, DATA_SEQUENCE_NUM),
+ buildReceivedDataInfo(
+ PEER_EXTENDED_MAC_ADDRESS_LONG, DATA_SEQUENCE_NUM_1)));
+ when(mockUwbSession.getAllReceivedDataInfo(PEER_EXTENDED_MAC_ADDRESS_2_LONG))
+ .thenReturn(List.of(
+ buildReceivedDataInfo(PEER_EXTENDED_MAC_ADDRESS_2_LONG, DATA_SEQUENCE_NUM),
+ buildReceivedDataInfo(
+ PEER_EXTENDED_MAC_ADDRESS_2_LONG, DATA_SEQUENCE_NUM_1)));
+ mUwbSessionManager.onRangeDataNotificationReceived(uwbRangingData1);
+ mUwbSessionManager.onRangeDataNotificationReceived(uwbRangingData2);
+
+ verify(mUwbSessionNotificationManager)
+ .onRangingResult(eq(mockUwbSession), eq(uwbRangingData1));
+ verify(mUwbSessionNotificationManager)
+ .onRangingResult(eq(mockUwbSession), eq(uwbRangingData2));
+ verify(mUwbAdvertiseManager).updateAdvertiseTarget(uwbRangingData1.mRangingOwrAoaMeasure);
+ verify(mUwbAdvertiseManager).updateAdvertiseTarget(uwbRangingData2.mRangingOwrAoaMeasure);
+ verify(mUwbSessionNotificationManager, times(2))
+ .onDataReceived(eq(mockUwbSession), eq(PEER_EXTENDED_UWB_ADDRESS),
+ isA(PersistableBundle.class), eq(DATA_PAYLOAD));
+ verify(mUwbSessionNotificationManager, times(2))
+ .onDataReceived(eq(mockUwbSession), eq(PEER_EXTENDED_UWB_ADDRESS_2),
+ isA(PersistableBundle.class), eq(DATA_PAYLOAD));
+ verify(mUwbAdvertiseManager).removeAdvertiseTarget(PEER_EXTENDED_MAC_ADDRESS_LONG);
+ verify(mUwbAdvertiseManager).removeAdvertiseTarget(PEER_EXTENDED_MAC_ADDRESS_2_LONG);
+ verify(mUwbMetrics).logRangingResult(anyInt(), eq(uwbRangingData1));
+ verify(mUwbMetrics).logRangingResult(anyInt(), eq(uwbRangingData2));
+ }
+
@Test
public void onRangeDataNotificationReceived_owrAoa_CheckPointedTarget_Failed()
throws RemoteException {
@@ -395,7 +497,7 @@ public class UwbSessionManagerTest {
verify(mUwbSessionNotificationManager, never())
.onDataReceived(eq(mockUwbSession), eq(PEER_SHORT_UWB_ADDRESS),
isA(PersistableBundle.class), eq(DATA_PAYLOAD));
- verify(mUwbAdvertiseManager, never()).removeAdvertiseTarget(PEER_SHORT_MAC_ADDRESS);
+ verify(mUwbAdvertiseManager, never()).removeAdvertiseTarget(PEER_SHORT_MAC_ADDRESS_LONG);
verify(mUwbMetrics).logRangingResult(anyInt(), eq(uwbRangingData));
}
@@ -434,6 +536,7 @@ public class UwbSessionManagerTest {
mUwbSessionManager.onDataReceived(TEST_SESSION_ID, UwbUciConstants.STATUS_CODE_OK,
DATA_SEQUENCE_NUM, PEER_EXTENDED_MAC_ADDRESS, SOURCE_END_POINT, DEST_END_POINT,
DATA_PAYLOAD);
+ verify(mockUwbSession).addReceivedDataInfo(isA(UwbSessionManager.ReceivedDataInfo.class));
// Next call onRangeDataNotificationReceived() to process the RANGE_DATA_NTF.
mUwbSessionManager.onRangeDataNotificationReceived(uwbRangingData);
@@ -458,6 +561,7 @@ public class UwbSessionManagerTest {
mUwbSessionManager.onDataReceived(TEST_SESSION_ID, UwbUciConstants.STATUS_CODE_OK,
DATA_SEQUENCE_NUM, PEER_EXTENDED_MAC_ADDRESS, SOURCE_END_POINT, DEST_END_POINT,
DATA_PAYLOAD);
+ verify(mockUwbSession).addReceivedDataInfo(isA(UwbSessionManager.ReceivedDataInfo.class));
// Next call onRangeDataNotificationReceived() to process the RANGE_DATA_NTF (with an
// incorrect RangingRoundUsage value).
@@ -486,6 +590,7 @@ public class UwbSessionManagerTest {
mUwbSessionManager.onDataReceived(TEST_SESSION_ID, UwbUciConstants.STATUS_CODE_OK,
DATA_SEQUENCE_NUM, PEER_EXTENDED_MAC_ADDRESS, SOURCE_END_POINT, DEST_END_POINT,
DATA_PAYLOAD);
+ verify(mockUwbSession).addReceivedDataInfo(isA(UwbSessionManager.ReceivedDataInfo.class));
// Next call onRangeDataNotificationReceived() to process the RANGE_DATA_NTF.
Params firaParams = setupFiraParams(
@@ -514,14 +619,15 @@ public class UwbSessionManagerTest {
Params firaParams = setupFiraParams(
RANGING_DEVICE_ROLE_OBSERVER, Optional.of(ROUND_USAGE_OWR_AOA_MEASUREMENT));
when(mockUwbSession.getParams()).thenReturn(firaParams);
+ when(mUwbAdvertiseManager.isPointedTarget(PEER_EXTENDED_MAC_ADDRESS)).thenReturn(true);
+ when(mockUwbSession.getAllReceivedDataInfo(PEER_EXTENDED_MAC_ADDRESS_LONG))
+ .thenReturn(List.of());
mUwbSessionManager.onRangeDataNotificationReceived(uwbRangingData);
verify(mUwbSessionNotificationManager)
.onRangingResult(eq(mockUwbSession), eq(uwbRangingData));
verify(mUwbAdvertiseManager).updateAdvertiseTarget(uwbRangingData.mRangingOwrAoaMeasure);
verify(mUwbMetrics).logRangingResult(anyInt(), eq(uwbRangingData));
-
- verify(mUwbAdvertiseManager, never()).isPointedTarget(isA(byte[].class));
verifyZeroInteractions(mUwbSessionNotificationManager);
}
@@ -540,19 +646,21 @@ public class UwbSessionManagerTest {
mUwbSessionManager.onDataReceived(TEST_SESSION_ID, UwbUciConstants.STATUS_CODE_OK,
DATA_SEQUENCE_NUM, PEER_EXTENDED_MAC_ADDRESS_2, SOURCE_END_POINT, DEST_END_POINT,
DATA_PAYLOAD);
+ verify(mockUwbSession).addReceivedDataInfo(isA(UwbSessionManager.ReceivedDataInfo.class));
// Next call onRangeDataNotificationReceived() to process the RANGE_DATA_NTF.
Params firaParams = setupFiraParams(
RANGING_DEVICE_ROLE_OBSERVER, Optional.of(ROUND_USAGE_OWR_AOA_MEASUREMENT));
when(mockUwbSession.getParams()).thenReturn(firaParams);
+ when(mUwbAdvertiseManager.isPointedTarget(PEER_EXTENDED_MAC_ADDRESS)).thenReturn(true);
+ when(mockUwbSession.getAllReceivedDataInfo(PEER_EXTENDED_MAC_ADDRESS_LONG))
+ .thenReturn(List.of());
mUwbSessionManager.onRangeDataNotificationReceived(uwbRangingData);
verify(mUwbSessionNotificationManager)
.onRangingResult(eq(mockUwbSession), eq(uwbRangingData));
verify(mUwbAdvertiseManager).updateAdvertiseTarget(uwbRangingData.mRangingOwrAoaMeasure);
verify(mUwbMetrics).logRangingResult(anyInt(), eq(uwbRangingData));
-
- verify(mUwbAdvertiseManager, never()).isPointedTarget(isA(byte[].class));
verifyZeroInteractions(mUwbSessionNotificationManager);
}
@@ -565,30 +673,33 @@ public class UwbSessionManagerTest {
when(mockUwbSession.getWaitObj()).thenReturn(mock(WaitObj.class));
doReturn(mockUwbSession)
.when(mUwbSessionManager).getUwbSession(eq(TEST_SESSION_ID));
+ UwbSession mockUwbSession2 = mock(UwbSession.class);
+ when(mockUwbSession2.getWaitObj()).thenReturn(mock(WaitObj.class));
+ doReturn(mockUwbSession2)
+ .when(mUwbSessionManager).getUwbSession(eq(TEST_SESSION_ID_2));
// onDataReceived() called for a different UwbSessionID, which should be equivalent to it
// not being called.
mUwbSessionManager.onDataReceived(TEST_SESSION_ID_2, UwbUciConstants.STATUS_CODE_OK,
DATA_SEQUENCE_NUM, PEER_EXTENDED_MAC_ADDRESS, SOURCE_END_POINT, DEST_END_POINT,
DATA_PAYLOAD);
+ verify(mockUwbSession2).addReceivedDataInfo(isA(UwbSessionManager.ReceivedDataInfo.class));
- // Next call onRangeDataNotificationReceived() to process the RANGE_DATA_NTF.
- UwbSession mockUwbSession2 = mock(UwbSession.class);
- when(mockUwbSession2.getWaitObj()).thenReturn(mock(WaitObj.class));
- doReturn(mockUwbSession2)
- .when(mUwbSessionManager).getUwbSession(eq(TEST_SESSION_ID_2));
-
+ // Next call onRangeDataNotificationReceived() to process the RANGE_DATA_NTF. Setup such
+ // that there is no ReceivedDataInfo returned for the UwbSession (to simulate the test
+ // scenario).
Params firaParams = setupFiraParams(
RANGING_DEVICE_ROLE_OBSERVER, Optional.of(ROUND_USAGE_OWR_AOA_MEASUREMENT));
when(mockUwbSession.getParams()).thenReturn(firaParams);
+ when(mUwbAdvertiseManager.isPointedTarget(PEER_EXTENDED_MAC_ADDRESS)).thenReturn(true);
+ when(mockUwbSession.getAllReceivedDataInfo(PEER_EXTENDED_MAC_ADDRESS_LONG))
+ .thenReturn(List.of());
mUwbSessionManager.onRangeDataNotificationReceived(uwbRangingData);
verify(mUwbSessionNotificationManager)
.onRangingResult(eq(mockUwbSession), eq(uwbRangingData));
verify(mUwbAdvertiseManager).updateAdvertiseTarget(uwbRangingData.mRangingOwrAoaMeasure);
verify(mUwbMetrics).logRangingResult(anyInt(), eq(uwbRangingData));
-
- verify(mUwbAdvertiseManager, never()).isPointedTarget(isA(byte[].class));
verifyZeroInteractions(mUwbSessionNotificationManager);
}
@@ -605,6 +716,7 @@ public class UwbSessionManagerTest {
mUwbSessionManager.onDataReceived(TEST_SESSION_ID, UwbUciConstants.STATUS_CODE_OK,
DATA_SEQUENCE_NUM, PEER_EXTENDED_MAC_ADDRESS, SOURCE_END_POINT, DEST_END_POINT,
DATA_PAYLOAD);
+ verify(mockUwbSession).addReceivedDataInfo(isA(UwbSessionManager.ReceivedDataInfo.class));
// Setup isPointedTarget() to return false.
when(mUwbAdvertiseManager.isPointedTarget(PEER_EXTENDED_MAC_ADDRESS)).thenReturn(false);
@@ -613,7 +725,7 @@ public class UwbSessionManagerTest {
verify(mUwbSessionNotificationManager)
.onRangingResult(eq(mockUwbSession), eq(uwbRangingData));
verify(mUwbMetrics).logRangingResult(anyInt(), eq(uwbRangingData));
- verify(mUwbAdvertiseManager, never()).removeAdvertiseTarget(isA(byte[].class));
+ verify(mUwbAdvertiseManager, never()).removeAdvertiseTarget(isA(Long.class));
verifyZeroInteractions(mUwbSessionNotificationManager);
}
@@ -677,7 +789,7 @@ public class UwbSessionManagerTest {
doReturn(true).when(mUwbSessionManager).isExistedSession(anyInt());
mUwbSessionManager.initSession(ATTRIBUTION_SOURCE, mock(SessionHandle.class),
- TEST_SESSION_ID, "any", mock(Params.class), mockRangingCallbacks,
+ TEST_SESSION_ID, TEST_SESSION_TYPE, "any", mock(Params.class), mockRangingCallbacks,
TEST_CHIP_ID);
verify(mockRangingCallbacks).onRangingOpenFailed(
@@ -692,7 +804,7 @@ public class UwbSessionManagerTest {
IUwbRangingCallbacks mockRangingCallbacks = mock(IUwbRangingCallbacks.class);
mUwbSessionManager.initSession(ATTRIBUTION_SOURCE, mock(SessionHandle.class),
- TEST_SESSION_ID, "any", mock(Params.class), mockRangingCallbacks,
+ TEST_SESSION_ID, TEST_SESSION_TYPE, "any", mock(Params.class), mockRangingCallbacks,
TEST_CHIP_ID);
verify(mockRangingCallbacks).onRangingOpenFailed(any(), anyInt(), any());
@@ -709,15 +821,16 @@ public class UwbSessionManagerTest {
IBinder mockBinder = mock(IBinder.class);
UwbSession uwbSession = spy(
mUwbSessionManager.new UwbSession(ATTRIBUTION_SOURCE, mockSessionHandle,
- TEST_SESSION_ID, FiraParams.PROTOCOL_NAME, mockParams,
+ TEST_SESSION_ID, TEST_SESSION_TYPE, FiraParams.PROTOCOL_NAME, mockParams,
mockRangingCallbacks, TEST_CHIP_ID));
doReturn(mockBinder).when(uwbSession).getBinder();
doReturn(uwbSession).when(mUwbSessionManager).createUwbSession(any(), any(), anyInt(),
- anyString(), any(), any(), anyString());
+ anyByte(), anyString(), any(), any(), anyString());
doThrow(new RemoteException()).when(mockBinder).linkToDeath(any(), anyInt());
mUwbSessionManager.initSession(ATTRIBUTION_SOURCE, mockSessionHandle, TEST_SESSION_ID,
- FiraParams.PROTOCOL_NAME, mockParams, mockRangingCallbacks, TEST_CHIP_ID);
+ TEST_SESSION_TYPE, FiraParams.PROTOCOL_NAME, mockParams, mockRangingCallbacks,
+ TEST_CHIP_ID);
verify(uwbSession).binderDied();
verify(mockRangingCallbacks).onRangingOpenFailed(any(), anyInt(), any());
@@ -737,14 +850,15 @@ public class UwbSessionManagerTest {
UwbSession uwbSession = spy(
mUwbSessionManager.new UwbSession(ATTRIBUTION_SOURCE, mockSessionHandle,
- TEST_SESSION_ID, FiraParams.PROTOCOL_NAME, mockParams,
+ TEST_SESSION_ID, TEST_SESSION_TYPE, FiraParams.PROTOCOL_NAME, mockParams,
mockRangingCallbacks, TEST_CHIP_ID));
doReturn(mockBinder).when(uwbSession).getBinder();
doReturn(uwbSession).when(mUwbSessionManager).createUwbSession(any(), any(), anyInt(),
- anyString(), any(), any(), anyString());
+ anyByte(), anyString(), any(), any(), anyString());
mUwbSessionManager.initSession(ATTRIBUTION_SOURCE, mockSessionHandle, TEST_SESSION_ID,
- FiraParams.PROTOCOL_NAME, mockParams, mockRangingCallbacks, TEST_CHIP_ID);
+ TEST_SESSION_TYPE, FiraParams.PROTOCOL_NAME, mockParams, mockRangingCallbacks,
+ TEST_CHIP_ID);
verify(uwbSession, never()).binderDied();
verify(mockRangingCallbacks, never()).onRangingOpenFailed(any(), anyInt(), any());
@@ -767,14 +881,15 @@ public class UwbSessionManagerTest {
UwbSession uwbSession = spy(
mUwbSessionManager.new UwbSession(ATTRIBUTION_SOURCE, mockSessionHandle,
- TEST_SESSION_ID, FiraParams.PROTOCOL_NAME, mockParams,
+ TEST_SESSION_ID, TEST_SESSION_TYPE, FiraParams.PROTOCOL_NAME, mockParams,
mockRangingCallbacks, TEST_CHIP_ID));
doReturn(mockBinder).when(uwbSession).getBinder();
doReturn(uwbSession).when(mUwbSessionManager).createUwbSession(any(), any(), anyInt(),
- anyString(), any(), any(), anyString());
+ anyByte(), anyString(), any(), any(), anyString());
mUwbSessionManager.initSession(ATTRIBUTION_SOURCE, mockSessionHandle, TEST_SESSION_ID,
- FiraParams.PROTOCOL_NAME, mockParams, mockRangingCallbacks, TEST_CHIP_ID);
+ TEST_SESSION_TYPE, FiraParams.PROTOCOL_NAME, mockParams, mockRangingCallbacks,
+ TEST_CHIP_ID);
assertThat(uwbSession.getControleeList().size() == 1
&& uwbSession.getControleeList().get(0).getUwbAddress().equals(UWB_DEST_ADDRESS))
@@ -888,12 +1003,13 @@ public class UwbSessionManagerTest {
// Setup the UwbSession to have the peer device's MacAddress stored (which happens when
// a valid RANGE_DATA_NTF with an OWR AoA Measurement is received).
- doReturn(PEER_EXTENDED_MAC_ADDRESS).when(mockUwbSession).getRemoteMacAddress();
+ doReturn(Set.of(PEER_EXTENDED_MAC_ADDRESS_LONG)).when(mockUwbSession)
+ .getRemoteMacAddressList();
mUwbSessionManager.stopRanging(mock(SessionHandle.class));
mTestLooper.dispatchNext();
- verify(mUwbAdvertiseManager).removeAdvertiseTarget(PEER_EXTENDED_MAC_ADDRESS);
+ verify(mUwbAdvertiseManager).removeAdvertiseTarget(PEER_EXTENDED_MAC_ADDRESS_LONG);
}
@Test
@@ -1083,11 +1199,11 @@ public class UwbSessionManagerTest {
Params params = setupFiraParams();
UwbSession uwbSession = spy(
mUwbSessionManager.new UwbSession(attributionSource, mockSessionHandle,
- TEST_SESSION_ID, FiraParams.PROTOCOL_NAME, params, mockRangingCallbacks,
- TEST_CHIP_ID));
+ TEST_SESSION_ID, TEST_SESSION_TYPE, FiraParams.PROTOCOL_NAME, params,
+ mockRangingCallbacks, TEST_CHIP_ID));
doReturn(mockBinder).when(uwbSession).getBinder();
doReturn(uwbSession).when(mUwbSessionManager).createUwbSession(any(), any(), anyInt(),
- anyString(), any(), any(), anyString());
+ anyByte(), anyString(), any(), any(), anyString());
doReturn(mock(WaitObj.class)).when(uwbSession).getWaitObj();
return uwbSession;
@@ -1107,6 +1223,7 @@ public class UwbSessionManagerTest {
UWB_DEST_ADDRESS))
.setProtocolVersion(new FiraProtocolVersion(1, 0))
.setSessionId(10)
+ .setSessionType(SESSION_TYPE_RANGING)
.setDeviceType(FiraParams.RANGING_DEVICE_TYPE_CONTROLLER)
.setDeviceRole(deviceRole)
.setMultiNodeMode(FiraParams.MULTI_NODE_MODE_UNICAST)
@@ -1145,11 +1262,11 @@ public class UwbSessionManagerTest {
IBinder mockBinder = mock(IBinder.class);
UwbSession uwbSession = spy(
mUwbSessionManager.new UwbSession(ATTRIBUTION_SOURCE, mockSessionHandle,
- TEST_SESSION_ID, CccParams.PROTOCOL_NAME, params, mockRangingCallbacks,
- TEST_CHIP_ID));
+ TEST_SESSION_ID, TEST_SESSION_TYPE, CccParams.PROTOCOL_NAME, params,
+ mockRangingCallbacks, TEST_CHIP_ID));
doReturn(mockBinder).when(uwbSession).getBinder();
doReturn(uwbSession).when(mUwbSessionManager).createUwbSession(any(), any(), anyInt(),
- anyString(), any(), any(), anyString());
+ anyByte(), anyString(), any(), any(), anyString());
doReturn(mock(WaitObj.class)).when(uwbSession).getWaitObj();
return uwbSession;
@@ -1168,7 +1285,7 @@ public class UwbSessionManagerTest {
mUwbSessionManager.initSession(ATTRIBUTION_SOURCE, uwbSession.getSessionHandle(),
- TEST_SESSION_ID, FiraParams.PROTOCOL_NAME,
+ TEST_SESSION_ID, TEST_SESSION_TYPE, FiraParams.PROTOCOL_NAME,
uwbSession.getParams(), uwbSession.getIUwbRangingCallbacks(), TEST_CHIP_ID);
mTestLooper.dispatchAll();
@@ -1193,7 +1310,7 @@ public class UwbSessionManagerTest {
mUwbSessionManager.initSession(ATTRIBUTION_SOURCE, uwbSession.getSessionHandle(),
- TEST_SESSION_ID, FiraParams.PROTOCOL_NAME,
+ TEST_SESSION_ID, TEST_SESSION_TYPE, FiraParams.PROTOCOL_NAME,
uwbSession.getParams(), uwbSession.getIUwbRangingCallbacks(), TEST_CHIP_ID);
mTestLooper.dispatchAll();
@@ -1217,7 +1334,7 @@ public class UwbSessionManagerTest {
mUwbSessionManager.initSession(ATTRIBUTION_SOURCE, uwbSession.getSessionHandle(),
- TEST_SESSION_ID, FiraParams.PROTOCOL_NAME,
+ TEST_SESSION_ID, TEST_SESSION_TYPE, FiraParams.PROTOCOL_NAME,
uwbSession.getParams(), uwbSession.getIUwbRangingCallbacks(), TEST_CHIP_ID);
mTestLooper.dispatchAll();
@@ -1241,7 +1358,7 @@ public class UwbSessionManagerTest {
mUwbSessionManager.initSession(ATTRIBUTION_SOURCE, uwbSession.getSessionHandle(),
- TEST_SESSION_ID, FiraParams.PROTOCOL_NAME,
+ TEST_SESSION_ID, TEST_SESSION_TYPE, FiraParams.PROTOCOL_NAME,
uwbSession.getParams(), uwbSession.getIUwbRangingCallbacks(), TEST_CHIP_ID);
mTestLooper.dispatchAll();
@@ -1265,7 +1382,7 @@ public class UwbSessionManagerTest {
mUwbSessionManager.initSession(ATTRIBUTION_SOURCE, uwbSession.getSessionHandle(),
- TEST_SESSION_ID, FiraParams.PROTOCOL_NAME,
+ TEST_SESSION_ID, TEST_SESSION_TYPE, FiraParams.PROTOCOL_NAME,
uwbSession.getParams(), uwbSession.getIUwbRangingCallbacks(), TEST_CHIP_ID);
mTestLooper.dispatchAll();
@@ -1289,7 +1406,7 @@ public class UwbSessionManagerTest {
mUwbSessionManager.initSession(ATTRIBUTION_SOURCE, uwbSession.getSessionHandle(),
- TEST_SESSION_ID, FiraParams.PROTOCOL_NAME,
+ TEST_SESSION_ID, TEST_SESSION_TYPE, FiraParams.PROTOCOL_NAME,
uwbSession.getParams(), uwbSession.getIUwbRangingCallbacks(), TEST_CHIP_ID);
mTestLooper.dispatchAll();
@@ -1308,7 +1425,7 @@ public class UwbSessionManagerTest {
UwbSession uwbSession = setUpUwbSessionForExecution(ATTRIBUTION_SOURCE);
mUwbSessionManager.initSession(ATTRIBUTION_SOURCE, uwbSession.getSessionHandle(),
- TEST_SESSION_ID, FiraParams.PROTOCOL_NAME,
+ TEST_SESSION_ID, TEST_SESSION_TYPE, FiraParams.PROTOCOL_NAME,
uwbSession.getParams(), uwbSession.getIUwbRangingCallbacks(), TEST_CHIP_ID);
// OPEN_RANGING message scheduled.
@@ -1323,7 +1440,7 @@ public class UwbSessionManagerTest {
UwbSession uwbSession = setUpUwbSessionForExecution(ATTRIBUTION_SOURCE);
mUwbSessionManager.initSession(ATTRIBUTION_SOURCE, uwbSession.getSessionHandle(),
- TEST_SESSION_ID, FiraParams.PROTOCOL_NAME,
+ TEST_SESSION_ID, TEST_SESSION_TYPE, FiraParams.PROTOCOL_NAME,
uwbSession.getParams(), uwbSession.getIUwbRangingCallbacks(), TEST_CHIP_ID);
verify(uwbSession.getIUwbRangingCallbacks()).onRangingOpenFailed(
@@ -1347,7 +1464,7 @@ public class UwbSessionManagerTest {
UwbSession uwbSession = setUpUwbSessionForExecution(attributionSource);
mUwbSessionManager.initSession(attributionSource, uwbSession.getSessionHandle(),
- TEST_SESSION_ID, FiraParams.PROTOCOL_NAME,
+ TEST_SESSION_ID, TEST_SESSION_TYPE, FiraParams.PROTOCOL_NAME,
uwbSession.getParams(), uwbSession.getIUwbRangingCallbacks(), TEST_CHIP_ID);
return uwbSession;
}
@@ -1461,7 +1578,7 @@ public class UwbSessionManagerTest {
.build();
UwbSession uwbSession = setUpUwbSessionForExecution(attributionSource);
mUwbSessionManager.initSession(attributionSource, uwbSession.getSessionHandle(),
- TEST_SESSION_ID, FiraParams.PROTOCOL_NAME,
+ TEST_SESSION_ID, TEST_SESSION_TYPE, FiraParams.PROTOCOL_NAME,
uwbSession.getParams(), uwbSession.getIUwbRangingCallbacks(), TEST_CHIP_ID);
verify(uwbSession.getIUwbRangingCallbacks()).onRangingOpenFailed(
@@ -1473,7 +1590,7 @@ public class UwbSessionManagerTest {
private UwbSession prepareExistingUwbSession() throws Exception {
UwbSession uwbSession = setUpUwbSessionForExecution(ATTRIBUTION_SOURCE);
mUwbSessionManager.initSession(ATTRIBUTION_SOURCE, uwbSession.getSessionHandle(),
- TEST_SESSION_ID, FiraParams.PROTOCOL_NAME,
+ TEST_SESSION_ID, TEST_SESSION_TYPE, FiraParams.PROTOCOL_NAME,
uwbSession.getParams(), uwbSession.getIUwbRangingCallbacks(), TEST_CHIP_ID);
mTestLooper.nextMessage(); // remove the OPEN_RANGING msg;
@@ -1485,7 +1602,7 @@ public class UwbSessionManagerTest {
private UwbSession prepareExistingCccUwbSession() throws Exception {
UwbSession uwbSession = setUpCccUwbSessionForExecution();
mUwbSessionManager.initSession(ATTRIBUTION_SOURCE, uwbSession.getSessionHandle(),
- TEST_SESSION_ID, CccParams.PROTOCOL_NAME,
+ TEST_SESSION_ID, TEST_SESSION_TYPE, CccParams.PROTOCOL_NAME,
uwbSession.getParams(), uwbSession.getIUwbRangingCallbacks(), TEST_CHIP_ID);
mTestLooper.nextMessage(); // remove the OPEN_RANGING msg;
@@ -1713,6 +1830,42 @@ public class UwbSessionManagerTest {
}
@Test
+ public void session_receivedDataInfo() throws Exception {
+ UwbSession uwbSession = prepareExistingUwbSession();
+
+ // Setup the UwbSession to have multiple data packets (being received) for multiple remote
+ // devices. This includes some duplicate packets (same sequence number from same remote
+ // device), which should be ignored.
+ UwbSessionManager.ReceivedDataInfo deviceOnePacketOne = buildReceivedDataInfo(
+ PEER_EXTENDED_MAC_ADDRESS_LONG, DATA_SEQUENCE_NUM);
+ UwbSessionManager.ReceivedDataInfo deviceOnePacketTwo = buildReceivedDataInfo(
+ PEER_EXTENDED_MAC_ADDRESS_LONG, DATA_SEQUENCE_NUM_1);
+ UwbSessionManager.ReceivedDataInfo deviceTwoPacketOne = buildReceivedDataInfo(
+ PEER_EXTENDED_MAC_ADDRESS_2_LONG, DATA_SEQUENCE_NUM);
+ UwbSessionManager.ReceivedDataInfo deviceTwoPacketTwo = buildReceivedDataInfo(
+ PEER_EXTENDED_MAC_ADDRESS_2_LONG, DATA_SEQUENCE_NUM_1);
+
+ uwbSession.addReceivedDataInfo(deviceOnePacketOne);
+ uwbSession.addReceivedDataInfo(deviceOnePacketTwo);
+ uwbSession.addReceivedDataInfo(deviceOnePacketOne);
+
+ uwbSession.addReceivedDataInfo(deviceTwoPacketOne);
+ uwbSession.addReceivedDataInfo(deviceTwoPacketTwo);
+ uwbSession.addReceivedDataInfo(deviceTwoPacketOne);
+
+ // Verify that the first call to getAllReceivedDataInfo() for a device returns all it's
+ // received packets, and the second call receives an empty list.
+ assertThat(uwbSession.getAllReceivedDataInfo(PEER_EXTENDED_MAC_ADDRESS_LONG)).isEqualTo(
+ List.of(deviceOnePacketOne, deviceOnePacketTwo));
+ assertThat(uwbSession.getAllReceivedDataInfo(PEER_EXTENDED_MAC_ADDRESS_LONG)).isEqualTo(
+ List.of());
+ assertThat(uwbSession.getAllReceivedDataInfo(PEER_EXTENDED_MAC_ADDRESS_2_LONG)).isEqualTo(
+ List.of(deviceTwoPacketOne, deviceTwoPacketTwo));
+ assertThat(uwbSession.getAllReceivedDataInfo(PEER_EXTENDED_MAC_ADDRESS_2_LONG)).isEqualTo(
+ List.of());
+ }
+
+ @Test
public void execStartCccRanging_success() throws Exception {
UwbSession uwbSession = prepareExistingCccUwbSession();
// set up for start ranging
@@ -1822,7 +1975,8 @@ public class UwbSessionManagerTest {
when(mNativeUwbManager.sendData(eq(TEST_SESSION_ID),
eq(PEER_EXTENDED_UWB_ADDRESS.toBytes()),
eq(UwbUciConstants.UWB_DESTINATION_END_POINT_HOST), eq(DATA_SEQUENCE_NUM),
- eq(DATA_PAYLOAD))).thenReturn((byte) UwbUciConstants.STATUS_CODE_OK);
+ eq(DATA_PAYLOAD), eq(TEST_CHIP_ID)))
+ .thenReturn((byte) UwbUciConstants.STATUS_CODE_OK);
mUwbSessionManager.sendData(uwbSession.getSessionHandle(), PEER_EXTENDED_UWB_ADDRESS,
PERSISTABLE_BUNDLE, DATA_PAYLOAD);
@@ -1831,12 +1985,52 @@ public class UwbSessionManagerTest {
verify(mNativeUwbManager).sendData(eq(TEST_SESSION_ID),
eq(PEER_EXTENDED_UWB_ADDRESS.toBytes()),
eq(UwbUciConstants.UWB_DESTINATION_END_POINT_HOST), eq(DATA_SEQUENCE_NUM),
- eq(DATA_PAYLOAD));
+ eq(DATA_PAYLOAD), eq(TEST_CHIP_ID));
verify(mUwbSessionNotificationManager).onDataSent(
eq(uwbSession), eq(PEER_EXTENDED_UWB_ADDRESS), eq(PERSISTABLE_BUNDLE));
}
@Test
+ public void sendData_success_sequenceNumberRollover() throws Exception {
+ UwbSession uwbSession = prepareExistingUwbSession();
+
+ // Setup the UwbSession to start ranging (and move it to active state).
+ doReturn(UwbUciConstants.UWB_SESSION_STATE_IDLE).when(uwbSession).getSessionState();
+ when(mNativeUwbManager.startRanging(eq(TEST_SESSION_ID), anyString()))
+ .thenReturn((byte) UwbUciConstants.STATUS_CODE_OK);
+
+ mUwbSessionManager.startRanging(
+ uwbSession.getSessionHandle(), uwbSession.getParams());
+ mTestLooper.dispatchAll();
+ doReturn(UwbUciConstants.UWB_SESSION_STATE_ACTIVE).when(uwbSession).getSessionState();
+
+ clearInvocations(mNativeUwbManager);
+
+ // Send 257 data packets on the UWB session, so that the UCI sequence number rolls over,
+ // back to 0.
+ when(mNativeUwbManager.sendData(anyInt(), any(), anyByte(), anyByte(), any(), anyString()))
+ .thenReturn((byte) UwbUciConstants.STATUS_CODE_OK);
+
+ for (int i = 0; i <= 256; i++) {
+ mUwbSessionManager.sendData(uwbSession.getSessionHandle(), PEER_EXTENDED_UWB_ADDRESS,
+ PERSISTABLE_BUNDLE, DATA_PAYLOAD);
+ mTestLooper.dispatchNext();
+ }
+
+ // Verify that there are 257 calls to mNativeUwbManager.sendData(), with the important
+ // thing here being that there should be 2 calls for sequence_number = 0, and 1 call for all
+ // the other sequence number values [1-255].
+ for (int i = 0; i < 256; i++) {
+ int expectedCount = (i == 0) ? 2 : 1;
+ verify(mNativeUwbManager, times(expectedCount)).sendData(eq(TEST_SESSION_ID),
+ eq(PEER_EXTENDED_UWB_ADDRESS.toBytes()),
+ eq(UwbUciConstants.UWB_DESTINATION_END_POINT_HOST), eq((byte) i),
+ eq(DATA_PAYLOAD), eq(TEST_CHIP_ID));
+ }
+ verifyNoMoreInteractions(mNativeUwbManager);
+ }
+
+ @Test
public void sendData_missingSessionHandle() throws Exception {
mUwbSessionManager.sendData(
null /* sessionHandle */, PEER_EXTENDED_UWB_ADDRESS, PERSISTABLE_BUNDLE,
@@ -1846,7 +2040,7 @@ public class UwbSessionManagerTest {
verify(mNativeUwbManager, never()).sendData(
eq(TEST_SESSION_ID), eq(PEER_EXTENDED_UWB_ADDRESS.toBytes()),
eq(UwbUciConstants.UWB_DESTINATION_END_POINT_HOST), eq(DATA_SEQUENCE_NUM),
- eq(DATA_PAYLOAD));
+ eq(DATA_PAYLOAD), eq(TEST_CHIP_ID));
verify(mUwbSessionNotificationManager).onDataSendFailed(
eq(null), eq(PEER_EXTENDED_UWB_ADDRESS),
eq(UwbUciConstants.STATUS_CODE_ERROR_SESSION_NOT_EXIST), eq(PERSISTABLE_BUNDLE));
@@ -1865,7 +2059,7 @@ public class UwbSessionManagerTest {
verify(mNativeUwbManager, never()).sendData(
eq(TEST_SESSION_ID), eq(PEER_EXTENDED_UWB_ADDRESS.toBytes()),
eq(UwbUciConstants.UWB_DESTINATION_END_POINT_HOST), eq(DATA_SEQUENCE_NUM),
- eq(DATA_PAYLOAD));
+ eq(DATA_PAYLOAD), eq(TEST_CHIP_ID));
verify(mUwbSessionNotificationManager).onDataSendFailed(
eq(null), eq(PEER_EXTENDED_UWB_ADDRESS),
eq(UwbUciConstants.STATUS_CODE_ERROR_SESSION_NOT_EXIST), eq(PERSISTABLE_BUNDLE));
@@ -1885,7 +2079,7 @@ public class UwbSessionManagerTest {
verify(mNativeUwbManager, never()).sendData(
eq(TEST_SESSION_ID), eq(PEER_EXTENDED_UWB_ADDRESS.toBytes()),
eq(UwbUciConstants.UWB_DESTINATION_END_POINT_HOST), eq(DATA_SEQUENCE_NUM),
- eq(DATA_PAYLOAD));
+ eq(DATA_PAYLOAD), eq(TEST_CHIP_ID));
verify(mUwbSessionNotificationManager).onDataSendFailed(
eq(uwbSession), eq(PEER_EXTENDED_UWB_ADDRESS),
eq(UwbUciConstants.STATUS_CODE_FAILED), eq(PERSISTABLE_BUNDLE));
@@ -1913,7 +2107,7 @@ public class UwbSessionManagerTest {
verify(mNativeUwbManager, never()).sendData(
eq(TEST_SESSION_ID), eq(PEER_EXTENDED_UWB_ADDRESS.toBytes()),
eq(UwbUciConstants.UWB_DESTINATION_END_POINT_HOST), eq(DATA_SEQUENCE_NUM),
- eq(DATA_PAYLOAD));
+ eq(DATA_PAYLOAD), eq(TEST_CHIP_ID));
verify(mUwbSessionNotificationManager).onDataSendFailed(
eq(uwbSession), eq(PEER_EXTENDED_UWB_ADDRESS),
eq(UwbUciConstants.STATUS_CODE_INVALID_PARAM), eq(PERSISTABLE_BUNDLE));
@@ -1941,7 +2135,7 @@ public class UwbSessionManagerTest {
verify(mNativeUwbManager, never()).sendData(
eq(TEST_SESSION_ID), eq(PEER_EXTENDED_UWB_ADDRESS.toBytes()),
eq(UwbUciConstants.UWB_DESTINATION_END_POINT_HOST), eq(DATA_SEQUENCE_NUM),
- eq(DATA_PAYLOAD));
+ eq(DATA_PAYLOAD), eq(TEST_CHIP_ID));
verify(mUwbSessionNotificationManager).onDataSendFailed(
eq(uwbSession), eq(null),
eq(UwbUciConstants.STATUS_CODE_INVALID_PARAM), eq(PERSISTABLE_BUNDLE));
@@ -1965,7 +2159,8 @@ public class UwbSessionManagerTest {
when(mNativeUwbManager.sendData(eq(TEST_SESSION_ID),
eq(PEER_EXTENDED_UWB_ADDRESS.toBytes()),
eq(UwbUciConstants.UWB_DESTINATION_END_POINT_HOST), eq(DATA_SEQUENCE_NUM),
- eq(DATA_PAYLOAD))).thenReturn((byte) UwbUciConstants.STATUS_CODE_FAILED);
+ eq(DATA_PAYLOAD), eq(TEST_CHIP_ID)))
+ .thenReturn((byte) UwbUciConstants.STATUS_CODE_FAILED);
mUwbSessionManager.sendData(uwbSession.getSessionHandle(), PEER_EXTENDED_UWB_ADDRESS,
PERSISTABLE_BUNDLE, DATA_PAYLOAD);
@@ -1974,7 +2169,7 @@ public class UwbSessionManagerTest {
verify(mNativeUwbManager).sendData(eq(TEST_SESSION_ID),
eq(PEER_EXTENDED_UWB_ADDRESS.toBytes()),
eq(UwbUciConstants.UWB_DESTINATION_END_POINT_HOST), eq(DATA_SEQUENCE_NUM),
- eq(DATA_PAYLOAD));
+ eq(DATA_PAYLOAD), eq(TEST_CHIP_ID));
verify(mUwbSessionNotificationManager).onDataSendFailed(
eq(uwbSession), eq(PEER_EXTENDED_UWB_ADDRESS),
eq(UwbUciConstants.STATUS_CODE_FAILED), eq(PERSISTABLE_BUNDLE));
@@ -2417,7 +2612,8 @@ public class UwbSessionManagerTest {
// Setup the UwbSession to have the peer device's MacAddress stored (which happens when
// a valid RANGE_DATA_NTF with an OWR AoA Measurement is received).
- doReturn(PEER_EXTENDED_MAC_ADDRESS).when(mockUwbSession).getRemoteMacAddress();
+ doReturn(Set.of(PEER_EXTENDED_MAC_ADDRESS_LONG)).when(mockUwbSession)
+ .getRemoteMacAddressList();
// Call deInitSession().
IBinder mockBinder = mock(IBinder.class);
@@ -2430,7 +2626,7 @@ public class UwbSessionManagerTest {
mTestLooper.dispatchNext();
- verify(mUwbAdvertiseManager).removeAdvertiseTarget(PEER_EXTENDED_MAC_ADDRESS);
+ verify(mUwbAdvertiseManager).removeAdvertiseTarget(PEER_EXTENDED_MAC_ADDRESS_LONG);
}
@Test
@@ -2470,7 +2666,7 @@ public class UwbSessionManagerTest {
verify(mUwbSessionNotificationManager).onRangingClosed(
eq(uwbSession), eq(UwbUciConstants.STATUS_CODE_FAILED));
- verify(mUwbAdvertiseManager, never()).removeAdvertiseTarget(isA(byte[].class));
+ verify(mUwbAdvertiseManager, never()).removeAdvertiseTarget(isA(Long.class));
verify(mUwbMetrics).logRangingCloseEvent(
eq(uwbSession), eq(UwbUciConstants.STATUS_CODE_FAILED));
assertThat(mUwbSessionManager.getSessionCount()).isEqualTo(0);
@@ -2558,7 +2754,7 @@ public class UwbSessionManagerTest {
eq(uwbSession), eq(UwbUciConstants.STATUS_CODE_OK));
assertThat(mUwbSessionManager.getSessionCount()).isEqualTo(0);
- verify(mUwbAdvertiseManager).removeAdvertiseTarget(isA(byte[].class));
+ verify(mUwbAdvertiseManager).removeAdvertiseTarget(isA(Long.class));
}
@Test
@@ -2573,4 +2769,21 @@ public class UwbSessionManagerTest {
eq(uwbSession), eq(UwbUciConstants.STATUS_CODE_FAILED));
assertThat(mUwbSessionManager.getSessionCount()).isEqualTo(0);
}
+
+ private UwbSessionManager.ReceivedDataInfo buildReceivedDataInfo(long macAddress) {
+ return buildReceivedDataInfo(macAddress, DATA_SEQUENCE_NUM);
+ }
+
+ private UwbSessionManager.ReceivedDataInfo buildReceivedDataInfo(
+ long macAddress, long sequenceNum) {
+ UwbSessionManager.ReceivedDataInfo info = new UwbSessionManager.ReceivedDataInfo();
+ info.sessionId = TEST_SESSION_ID;
+ info.status = STATUS_CODE_OK;
+ info.sequenceNum = sequenceNum;
+ info.address = macAddress;
+ info.sourceEndPoint = SOURCE_END_POINT;
+ info.destEndPoint = DEST_END_POINT;
+ info.payload = DATA_PAYLOAD;
+ return info;
+ }
}
diff --git a/service/tests/src/com/android/server/uwb/UwbSessionNotificationManagerTest.java b/service/tests/src/com/android/server/uwb/UwbSessionNotificationManagerTest.java
index 5b3810da..32bdad81 100644
--- a/service/tests/src/com/android/server/uwb/UwbSessionNotificationManagerTest.java
+++ b/service/tests/src/com/android/server/uwb/UwbSessionNotificationManagerTest.java
@@ -315,6 +315,14 @@ public class UwbSessionNotificationManagerTest {
}
@Test
+ public void testOnRangingResult_badRangingDataForOwrAoa() throws Exception {
+ UwbRangingData testRangingData = UwbTestUtils.generateBadOwrAoaMeasurementRangingData(
+ MAC_ADDRESSING_MODE_SHORT, PEER_SHORT_MAC_ADDRESS);
+ mUwbSessionNotificationManager.onRangingResult(mUwbSession, testRangingData);
+ verify(mIUwbRangingCallbacks).onRangingResult(mSessionHandle, null);
+ }
+
+ @Test
public void testOnRangingOpened() throws Exception {
mUwbSessionNotificationManager.onRangingOpened(mUwbSession);
diff --git a/service/tests/src/com/android/server/uwb/advertisement/UwbAdvertiseManagerTest.java b/service/tests/src/com/android/server/uwb/advertisement/UwbAdvertiseManagerTest.java
index a4bd7b03..f182ae94 100644
--- a/service/tests/src/com/android/server/uwb/advertisement/UwbAdvertiseManagerTest.java
+++ b/service/tests/src/com/android/server/uwb/advertisement/UwbAdvertiseManagerTest.java
@@ -322,11 +322,11 @@ public class UwbAdvertiseManagerTest {
assertNull(mUwbAdvertiseManager.getAdvertiseTarget(TEST_MAC_ADDRESS_B_INT));
// Call removeAdvertiseTarget() for the device and verify that it has been removed.
- mUwbAdvertiseManager.removeAdvertiseTarget(TEST_MAC_ADDRESS_A);
+ mUwbAdvertiseManager.removeAdvertiseTarget(TEST_MAC_ADDRESS_A_LONG);
assertNull(mUwbAdvertiseManager.getAdvertiseTarget(TEST_MAC_ADDRESS_A_LONG));
// Call removeAdvertiseTarget() for a device that doesn't exist and verify no exceptions.
- mUwbAdvertiseManager.removeAdvertiseTarget(TEST_MAC_ADDRESS_B);
+ mUwbAdvertiseManager.removeAdvertiseTarget(TEST_MAC_ADDRESS_B_INT);
assertNull(mUwbAdvertiseManager.getAdvertiseTarget(TEST_MAC_ADDRESS_B_INT));
}
diff --git a/service/tests/src/com/android/server/uwb/correction/TestHelpers.java b/service/tests/src/com/android/server/uwb/correction/TestHelpers.java
index 7a5b3f01..fe10539c 100644
--- a/service/tests/src/com/android/server/uwb/correction/TestHelpers.java
+++ b/service/tests/src/com/android/server/uwb/correction/TestHelpers.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2023 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.
@@ -27,6 +27,7 @@ import org.junit.Assert;
public final class TestHelpers {
private TestHelpers() {}
+ // Asserts that a value is within 0.001, to account for floating point rounding errors.
public static void assertClose(double v, double c) {
Assert.assertTrue(abs(v - c) < 0.001);
}
diff --git a/service/tests/src/com/android/server/uwb/correction/UwbFilterEngineTest.java b/service/tests/src/com/android/server/uwb/correction/UwbFilterEngineTest.java
new file mode 100644
index 00000000..02ada3e7
--- /dev/null
+++ b/service/tests/src/com/android/server/uwb/correction/UwbFilterEngineTest.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2023 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 com.android.server.uwb.correction;
+
+import static com.android.server.uwb.correction.TestHelpers.assertClose;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.android.server.uwb.correction.filtering.NullFilter;
+import com.android.server.uwb.correction.filtering.PositionFilterImpl;
+import com.android.server.uwb.correction.math.Pose;
+import com.android.server.uwb.correction.math.Quaternion;
+import com.android.server.uwb.correction.math.SphericalVector;
+import com.android.server.uwb.correction.math.Vector3;
+import com.android.server.uwb.correction.pose.NullPoseSource;
+import com.android.server.uwb.correction.primers.NullPrimer;
+
+import org.junit.Test;
+
+public class UwbFilterEngineTest {
+
+ @Test
+ public void basic() {
+ UwbFilterEngine engine = new UwbFilterEngine.Builder().build();
+ engine.add(SphericalVector.fromRadians(1, 1.2f, 1.3f).toSparse());
+ SphericalVector currentVector = engine.compute();
+ assertThat(currentVector.azimuth).isEqualTo(1);
+ assertThat(currentVector.elevation).isEqualTo(1.2f);
+ assertThat(currentVector.distance).isEqualTo(1.3f);
+ engine.close();
+ }
+
+ @Test
+ public void poseChanges() {
+ NullPoseSource poseSource = new NullPoseSource();
+ UwbFilterEngine engine = new UwbFilterEngine.Builder()
+ .setFilter(
+ new PositionFilterImpl(new NullFilter(), new NullFilter(), new NullFilter()))
+ .setPoseSource(poseSource)
+ .build();
+
+ poseSource.changePose(Pose.IDENTITY);
+ engine.add(SphericalVector.fromRadians(0.7f, 1.2f, 1.3f).toSparse());
+
+ // Check initial state.
+ SphericalVector currentVector = engine.compute();
+ assertThat(currentVector.azimuth).isEqualTo(0.7f);
+ assertThat(currentVector.elevation).isEqualTo(1.2f);
+ assertThat(currentVector.distance).isEqualTo(1.3f);
+
+ // Turn left.
+ poseSource.changePose(
+ new Pose(Vector3.ORIGIN, Quaternion.yawPitchRoll(-0.5f, 0, 0))
+ );
+ currentVector = engine.compute();
+
+ // See if the azimuth is to our right now that we turned left.
+ assertClose(currentVector.azimuth, 0.7f - 0.5f);
+ assertClose(currentVector.elevation, 1.2f);
+ assertClose(currentVector.distance, 1.3f);
+
+ Pose newPose = engine.getPose();
+ assertThat(newPose.translation.lengthSquared()).isEqualTo(0);
+ assertClose(newPose.rotation.toYawPitchRoll().x, -0.5f);
+
+ engine.close();
+ }
+
+ @Test
+ public void primerTest() {
+ NullPoseSource poseSource = new NullPoseSource();
+ NullPrimer primer = new NullPrimer();
+ UwbFilterEngine engine = new UwbFilterEngine.Builder()
+ .addPrimer(primer)
+ .setFilter(
+ new PositionFilterImpl(new NullFilter(), new NullFilter(), new NullFilter()))
+ .setPoseSource(poseSource)
+ .build();
+
+ poseSource.changePose(Pose.IDENTITY);
+ engine.add(SphericalVector.fromRadians(-0.7f, 0, 1.3f).toSparse());
+
+ // Check initial state.
+ SphericalVector currentVector = engine.compute();
+ assertThat(currentVector.azimuth).isEqualTo(0.7f); // Primer would make this positive.
+ assertThat(currentVector.elevation).isEqualTo(0f);
+ assertThat(currentVector.distance).isEqualTo(1.3f);
+
+ engine.add(SphericalVector.fromRadians(0f, 0, 1.3f).toSparse());
+
+ // Look down.
+ poseSource.changePose(
+ new Pose(Vector3.ORIGIN, Quaternion.yawPitchRoll(0, 1, 0))
+ );
+
+ // Generate a new measurement that doesn't have elevation or distance.
+ engine.add(
+ SphericalVector.fromRadians(0f, 0f, 0f)
+ .toSparse(true, false, false)
+ );
+
+ // Expect the predicted elevation based on the pose change. Distance should be unaffected.
+ currentVector = engine.compute();
+ assertClose(currentVector.azimuth, 0.0f);
+ assertClose(currentVector.elevation, -1f);
+ assertClose(currentVector.distance, 1.3f);
+
+ engine.close();
+ }
+}
diff --git a/service/tests/src/com/android/server/uwb/correction/filtering/MAFilterTest.java b/service/tests/src/com/android/server/uwb/correction/filtering/MAFilterTest.java
new file mode 100644
index 00000000..9a4e059a
--- /dev/null
+++ b/service/tests/src/com/android/server/uwb/correction/filtering/MAFilterTest.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2023 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 com.android.server.uwb.correction.filtering;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import org.junit.Test;
+
+public class MAFilterTest {
+
+ @Test
+ public void averageTest() {
+ MAFilter filter = new MAFilter(3, 1);
+ filter.add(1);
+ filter.add(2);
+ filter.add(3);
+ assertThat(filter.getResult().value).isEqualTo((1 + 2 + 3) / 3f);
+ }
+
+ // Test when the sliding window is full and it needs to slide.
+ @Test
+ public void slideTest() {
+ MAFilter filter = new MAFilter(3, 1);
+ filter.add(1);
+ filter.add(2);
+ filter.add(3);
+ filter.add(4);
+ assertThat(filter.getResult().value).isEqualTo((2 + 3 + 4) / 3f);
+ }
+
+ @Test
+ public void remapTest() {
+ MAFilter filter = new MAFilter(3, 1);
+ filter.add(1);
+ filter.add(2);
+ filter.add(3);
+ filter.compensate(66);
+ assertThat(filter.getResult().value).isEqualTo((1 + 2 + 3) / 3f + 66);
+ }
+
+ // Ensures that a pure median with an even-sized window uses the center two values.
+ @Test
+ public void evenMedianTest() {
+ MAFilter filter = new MAFilter(4, 0);
+ filter.add(1);
+ filter.add(3);
+ filter.add(4);
+ filter.add(2);
+ assertThat(filter.getResult().value).isEqualTo((2f + 3f) / 2f);
+ }
+
+ // Ensures that a filter operates properly even when its window is not full.
+ @Test
+ public void shortFilterTest() {
+ MAFilter filter = new MAFilter(4, 0); // median
+ filter.add(5);
+ assertThat(filter.getResult().value).isEqualTo(5);
+ filter.add(6);
+ assertThat(filter.getResult().value).isEqualTo((5f + 6f) / 2);
+ filter.add(7);
+ assertThat(filter.getResult().value).isEqualTo(6);
+ }
+
+ @Test
+ public void mixCutTest() {
+ MAFilter filter = new MAFilter(5, 0.5f); // Average half
+ filter.add(3);
+ filter.add(13);
+ filter.add(7);
+ filter.add(11);
+ filter.add(2);
+ assertThat(filter.getResult().value).isEqualTo((3 + 7 + 11) / 3f);
+ }
+
+ @Test
+ public void getTest() {
+ MAFilter filter = new MAFilter(5, 0.5f); // Average half
+ assertThat(filter.getCut()).isEqualTo(0.5f);
+ assertThat(filter.getWindowSize()).isEqualTo(5);
+ }
+}
diff --git a/service/tests/src/com/android/server/uwb/correction/filtering/MARotationFilterTest.java b/service/tests/src/com/android/server/uwb/correction/filtering/MARotationFilterTest.java
new file mode 100644
index 00000000..7e070d97
--- /dev/null
+++ b/service/tests/src/com/android/server/uwb/correction/filtering/MARotationFilterTest.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2023 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 com.android.server.uwb.correction.filtering;
+
+import static com.android.server.uwb.correction.TestHelpers.assertClose;
+import static com.android.server.uwb.correction.math.MathHelper.F_HALF_PI;
+import static com.android.server.uwb.correction.math.MathHelper.F_PI;
+
+import static java.lang.Math.toRadians;
+
+import org.junit.Test;
+
+public class MARotationFilterTest {
+ @Test
+ public void averageTest() {
+ MARotationFilter filter = new MARotationFilter(3, 1);
+ filter.add((float) toRadians(175));
+ filter.add((float) toRadians(-175));
+ filter.add((float) toRadians(5));
+
+ // See if this average of values on either side of 180 averages out correctly.
+ assertClose(filter.getResult().value, toRadians((175 + (360 - 175) + 5) / 3f));
+ }
+
+ @Test
+ public void remapTest() {
+ MARotationFilter filter = new MARotationFilter(3, 1);
+ filter.add((float) toRadians(175));
+ filter.add((float) toRadians(-175));
+ filter.add((float) toRadians(5));
+ // Just like the averageTest, but now we're going to add 90 degrees, which
+ // should make the answer roll-over across the +/-180 boundary
+ filter.remap(b -> b + F_HALF_PI);
+
+ // See if this average of values on either side of 180 averages out correctly.
+ assertClose(
+ filter.getResult().value,
+ toRadians((175 + (360 - 175) + 5) / 3f) + F_HALF_PI - 2 * F_PI
+ );
+ }
+}
diff --git a/service/tests/src/com/android/server/uwb/correction/filtering/NullFilter.java b/service/tests/src/com/android/server/uwb/correction/filtering/NullFilter.java
new file mode 100644
index 00000000..ca057153
--- /dev/null
+++ b/service/tests/src/com/android/server/uwb/correction/filtering/NullFilter.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2023 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 com.android.server.uwb.correction.filtering;
+
+import androidx.annotation.NonNull;
+
+import java.time.Instant;
+
+public class NullFilter implements IFilter {
+
+ @NonNull
+ Instant mWhen = Instant.now();
+ float mValue;
+
+ /**
+ * Adds a value to the filter.
+ *
+ * @param value The value to add to the filter.
+ * @param instant When the value occurred, used to determine the latency introduced by the
+ * filter. Note that this has no effect on the order in which the filter operates
+ */
+ @Override
+ public void add(float value, @NonNull Instant instant) {
+ mWhen = instant;
+ this.mValue = value;
+ }
+
+ /**
+ * Alters the state of the filter such that it anticipates a change by the given amount. For
+ * example, if the filter is working with distance, and the distance of the next reading is
+ * expected to increase by 1 meter, 'shift' should be 1.
+ *
+ * @param shift How much to alter the filter state.
+ */
+ @Override
+ public void compensate(float shift) {
+ this.mValue += shift;
+ }
+
+ /**
+ * Gets a sample object with the result from the last computation. The sample's time is the
+ * average time of the samples that created the result, effectively describing the latency
+ * introduced by the filter.
+ *
+ * @return The result from the last computation.
+ */
+ @NonNull
+ @Override
+ public Sample getResult() {
+ return new Sample(mValue, mWhen);
+ }
+}
diff --git a/service/tests/src/com/android/server/uwb/correction/math/AoAVectorTest.java b/service/tests/src/com/android/server/uwb/correction/math/AoAVectorTest.java
index 5782784b..1a7854e4 100644
--- a/service/tests/src/com/android/server/uwb/correction/math/AoAVectorTest.java
+++ b/service/tests/src/com/android/server/uwb/correction/math/AoAVectorTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2023 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.
@@ -50,7 +50,7 @@ public class AoAVectorTest {
assertClose(toDegrees(vec.elevation), 10);
// This is looking right and up so far that you're basically looking
- // at what's behind you on your left.
+ // at what's behind you on your left.
vec = AoAVector.fromRadians((float) toRadians(5), (float) toRadians(110), 10);
assertClose(vec.azimuth, toRadians(-175)); // +5deg from "behind".
@@ -129,7 +129,7 @@ public class AoAVectorTest {
// looking up.
AoAVector gimbalLock = AoAVector.fromCartesian(new Vector3(0, 1, 0));
// Note that this suffers from gimbal lock - meaning that ALL azimuth values are valid
- // when looking up or down.
+ // when looking up or down.
assertClose(toDegrees(gimbalLock.elevation), 90);
assertClose(gimbalLock.distance, 1);
diff --git a/service/tests/src/com/android/server/uwb/correction/math/MathHelperTest.java b/service/tests/src/com/android/server/uwb/correction/math/MathHelperTest.java
index f66e15d3..5dcc954e 100644
--- a/service/tests/src/com/android/server/uwb/correction/math/MathHelperTest.java
+++ b/service/tests/src/com/android/server/uwb/correction/math/MathHelperTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2023 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.
diff --git a/service/tests/src/com/android/server/uwb/correction/math/QuaternionTest.java b/service/tests/src/com/android/server/uwb/correction/math/QuaternionTest.java
index 6814661b..08f97345 100644
--- a/service/tests/src/com/android/server/uwb/correction/math/QuaternionTest.java
+++ b/service/tests/src/com/android/server/uwb/correction/math/QuaternionTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2023 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.
@@ -34,7 +34,7 @@ public class QuaternionTest {
assertTrue(Math.abs(ypr.z - 1.5) < 0.001);
// See the Javadoc for the 'Quaternion' class for an explanation of how these
- // results are determined.
+ // results are determined.
quaternion = Quaternion.yawPitchRoll((float) Math.PI / 2, 0, 0);
assertClose(quaternion.rotateVector(new Vector3(1, 2, 3)), new Vector3(3, 2, -1));
diff --git a/service/tests/src/com/android/server/uwb/correction/math/SphericalVectorTest.java b/service/tests/src/com/android/server/uwb/correction/math/SphericalVectorTest.java
index 8f0a0a11..f3d112d6 100644
--- a/service/tests/src/com/android/server/uwb/correction/math/SphericalVectorTest.java
+++ b/service/tests/src/com/android/server/uwb/correction/math/SphericalVectorTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2023 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.
@@ -37,7 +37,7 @@ public class SphericalVectorTest {
assertClose(toDegrees(vec.elevation), 10);
// This is looking right and up so far that you're basically looking
- // at what's behind you on your left.
+ // at what's behind you on your left.
vec = SphericalVector.fromRadians((float) toRadians(5), (float) toRadians(110), 10);
assertClose(vec.azimuth, toRadians(-175)); // +5deg from "behind".
@@ -116,7 +116,7 @@ public class SphericalVectorTest {
// looking up.
SphericalVector gimbalLock = SphericalVector.fromCartesian(new Vector3(0, 1, 0));
// Note that this suffers from gimbal lock - meaning that ALL azimuth values are valid
- // when looking up or down.
+ // when looking up or down.
assertClose(toDegrees(gimbalLock.elevation), 90);
assertClose(gimbalLock.distance, 1);
diff --git a/service/tests/src/com/android/server/uwb/correction/math/Vector3Tests.java b/service/tests/src/com/android/server/uwb/correction/math/Vector3Tests.java
new file mode 100644
index 00000000..200b4623
--- /dev/null
+++ b/service/tests/src/com/android/server/uwb/correction/math/Vector3Tests.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2023 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 com.android.server.uwb.correction.math;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.platform.test.annotations.Presubmit;
+
+import org.junit.Test;
+
+@Presubmit
+public class Vector3Tests {
+
+ @Test
+ public void testNorm() {
+ assertThat(Vector3.ORIGIN.normalized().lengthSquared()).isEqualTo(0);
+ }
+
+ @Test
+ public void testClamp() {
+ Vector3 min = new Vector3(-1, -2, -3);
+ Vector3 max = new Vector3(5, 6, 7);
+
+ // Clamp x min
+ assertThat(
+ new Vector3(-7, 2, 1)
+ .clamp(min, max)
+ .subtract(new Vector3(-1, 2, 1))
+ .lengthSquared()
+ ).isEqualTo(0);
+
+ // Clamp y max
+ assertThat(
+ new Vector3(2, 7, 1)
+ .clamp(min, max)
+ .subtract(new Vector3(2, 6, 1))
+ .lengthSquared()
+ ).isEqualTo(0);
+
+ // Clamp z min
+ assertThat(
+ new Vector3(-1, 4, -9)
+ .clamp(min, max)
+ .subtract(new Vector3(-1, 4, -3))
+ .lengthSquared()
+ ).isEqualTo(0);
+ }
+
+ @Test
+ public void testInverted() {
+ Vector3 n = new Vector3(5, 10, 15);
+ assertThat(n.add(n.inverted()).lengthSquared()).isEqualTo(0);
+ }
+
+ @Test
+ public void testToString() {
+ Vector3 v3 = new Vector3(1, -2, -13.1f);
+ assertThat(v3.toString()).isEqualTo("[ 1.0, -2.0,-13.1]");
+ }
+}
diff --git a/service/tests/src/com/android/server/uwb/correction/pose/NullPoseSource.java b/service/tests/src/com/android/server/uwb/correction/pose/NullPoseSource.java
new file mode 100644
index 00000000..f8950d24
--- /dev/null
+++ b/service/tests/src/com/android/server/uwb/correction/pose/NullPoseSource.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2023 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 com.android.server.uwb.correction.pose;
+
+import androidx.annotation.NonNull;
+
+import com.android.server.uwb.correction.math.Pose;
+
+import java.util.EnumSet;
+
+public class NullPoseSource extends PoseSourceBase {
+
+ private EnumSet<Capabilities> mCapabilities = Capabilities.ALL;
+
+ /**
+ * Gets the capabilities of this pose source.
+ *
+ * @return An EnumSet of Capabilities.
+ */
+ @NonNull
+ @Override
+ public EnumSet<Capabilities> getCapabilities() {
+ return mCapabilities;
+ }
+
+ /**
+ * Starts the pose source. Called by the {@link PoseSourceBase} when the first listener
+ * subscribes.
+ */
+ @Override
+ protected void start() {
+
+ }
+
+ /**
+ * Stops the pose source. Called by the {@link PoseSourceBase} when the last listener
+ * unsubscribes.
+ */
+ @Override
+ protected void stop() {
+
+ }
+
+ public void changePose(Pose pose) {
+ publish(pose);
+ }
+
+ public void setCapabilities(EnumSet<Capabilities> mCapabilities) {
+ this.mCapabilities = mCapabilities;
+ }
+}
diff --git a/service/tests/src/com/android/server/uwb/correction/primers/AoAPrimerTest.java b/service/tests/src/com/android/server/uwb/correction/primers/AoAPrimerTest.java
new file mode 100644
index 00000000..49121534
--- /dev/null
+++ b/service/tests/src/com/android/server/uwb/correction/primers/AoAPrimerTest.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2023 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 com.android.server.uwb.correction.primers;
+
+import static java.lang.Math.toRadians;
+
+import com.android.server.uwb.correction.TestHelpers;
+import com.android.server.uwb.correction.math.SphericalVector;
+import com.android.server.uwb.correction.math.SphericalVector.Sparse;
+
+import com.google.common.truth.Truth;
+
+import org.junit.Test;
+
+public class AoAPrimerTest {
+ @Test
+ public void conversionTest() {
+ AoAPrimer primer = new AoAPrimer();
+ Sparse sv = SphericalVector.fromDegrees(35, 0, 10)
+ .toSparse();
+ Sparse result = primer.prime(sv, null, null);
+
+ // With zero elevation, the conversion should do nothing.
+ TestHelpers.assertClose(result.vector.azimuth, toRadians(35));
+
+ // This signal hit the azimuth antennas at an angle of 45 degrees because it came in
+ // at a downward angle - meaning the true spherical azimuth is 90deg.
+ sv = SphericalVector.fromDegrees(45, 45, 10)
+ .toSparse();
+
+ result = primer.prime(sv, null, null);
+ TestHelpers.assertClose(result.vector.azimuth, toRadians(90));
+ TestHelpers.assertClose(result.vector.elevation, toRadians(45));
+ }
+
+ @Test
+ public void missingDataTest() {
+ // Make sure data is unchanged when there is a missing azimuth or elevation.
+ AoAPrimer primer = new AoAPrimer();
+ SphericalVector sv = SphericalVector.fromDegrees(2, 3, 4);
+
+ Sparse result = primer.prime(sv.toSparse(false, true, true), null, null);
+ Truth.assertThat(result.hasAzimuth).isFalse();
+ Truth.assertThat(result.hasElevation).isTrue();
+ Truth.assertThat(result.hasDistance).isTrue();
+ Truth.assertThat(result.vector.elevation).isEqualTo(sv.elevation);
+
+ result = primer.prime(sv.toSparse(true, false, true), null, null);
+ Truth.assertThat(result.hasAzimuth).isTrue();
+ Truth.assertThat(result.hasElevation).isFalse();
+ Truth.assertThat(result.hasDistance).isTrue();
+ Truth.assertThat(result.vector.azimuth).isEqualTo(sv.azimuth);
+ }
+}
diff --git a/service/tests/src/com/android/server/uwb/correction/primers/ElevationPrimerTest.java b/service/tests/src/com/android/server/uwb/correction/primers/ElevationPrimerTest.java
new file mode 100644
index 00000000..d6eddeb5
--- /dev/null
+++ b/service/tests/src/com/android/server/uwb/correction/primers/ElevationPrimerTest.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2023 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 com.android.server.uwb.correction.primers;
+
+import static com.android.server.uwb.correction.TestHelpers.assertClose;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.android.server.uwb.correction.math.Pose;
+import com.android.server.uwb.correction.math.Quaternion;
+import com.android.server.uwb.correction.math.SphericalVector;
+import com.android.server.uwb.correction.math.SphericalVector.Sparse;
+import com.android.server.uwb.correction.math.Vector3;
+import com.android.server.uwb.correction.pose.IPoseSource.Capabilities;
+import com.android.server.uwb.correction.pose.NullPoseSource;
+
+import org.junit.Test;
+
+import java.util.EnumSet;
+
+public class ElevationPrimerTest {
+ @Test
+ public void hasElevationTest() {
+ ElevationPrimer primer = new ElevationPrimer();
+ NullPoseSource nps = new NullPoseSource();
+ nps.setCapabilities(EnumSet.of(Capabilities.UPRIGHT));
+
+ Sparse input = SphericalVector.fromDegrees(35, 0, 10).toSparse();
+ Sparse result = primer.prime(input, null, nps);
+
+ assertThat(result.hasElevation).isTrue();
+ // Verify that, since elevation was already available, it was unchanged.
+ assertThat(result.vector.elevation).isEqualTo(input.vector.elevation);
+ }
+
+ @Test
+ public void noPoseTest() {
+ ElevationPrimer primer = new ElevationPrimer();
+ NullPoseSource nps = new NullPoseSource(); // Note: no upright capability.
+
+ Sparse input = SphericalVector.fromDegrees(35, 0, 10)
+ .toSparse(true, false, true);
+
+ Sparse result = primer.prime(input, null, nps);
+ // Pose is not capable of guessing elevation.
+ assertThat(result.hasElevation).isFalse();
+
+ result = primer.prime(input, null, null);
+ // Pose source does not exist.
+ assertThat(result.hasElevation).isFalse();
+ }
+
+ @Test
+ public void noElevationTest() {
+ ElevationPrimer primer = new ElevationPrimer();
+ NullPoseSource nps = new NullPoseSource();
+ nps.setCapabilities(EnumSet.of(Capabilities.UPRIGHT));
+ float rads = (float) Math.toRadians(-5);
+ nps.changePose(new Pose(Vector3.ORIGIN, Quaternion.yawPitchRoll(0, rads, 0)));
+
+ Sparse input = SphericalVector.fromDegrees(35, 0, 10)
+ .toSparse(true, false, true);
+ SphericalVector prediction = SphericalVector.fromDegrees(5, 6, 7);
+ Sparse result = primer.prime(input, prediction, nps);
+
+ assertThat(result.hasElevation).isTrue();
+ // The phone pose is slightly facing down, so the elevation should be slightly up.
+ assertClose(result.vector.elevation, -rads);
+ }
+}
diff --git a/service/tests/src/com/android/server/uwb/correction/primers/FoVPrimerTest.java b/service/tests/src/com/android/server/uwb/correction/primers/FoVPrimerTest.java
new file mode 100644
index 00000000..4aabe1b6
--- /dev/null
+++ b/service/tests/src/com/android/server/uwb/correction/primers/FoVPrimerTest.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2023 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 com.android.server.uwb.correction.primers;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static java.lang.Math.toRadians;
+
+import com.android.server.uwb.correction.math.Quaternion;
+import com.android.server.uwb.correction.math.SphericalVector;
+import com.android.server.uwb.correction.math.SphericalVector.Sparse;
+import com.android.server.uwb.correction.math.Vector3;
+
+import org.junit.Test;
+
+public class FoVPrimerTest {
+ @Test
+ public void conversionTest() {
+ FovPrimer primer = new FovPrimer((float) toRadians(45));
+ Sparse input, result;
+ SphericalVector prediction = SphericalVector.fromDegrees(0, 0, 0);
+
+ // The FOV formula reduced to az+el<fov, which seems too simple to be real.
+ // To test this, I'll place a point one degree with the FOV, and one outside the FOV,
+ // then "roll" and test those points in increments all the way around the perimeter.
+ Quaternion roll10 = Quaternion.yawPitchRoll(0, 0, (float) toRadians(10));
+ Vector3 within = SphericalVector.fromDegrees(44, 0, 10).toCartesian();
+ Vector3 outside = SphericalVector.fromDegrees(46, 0, 10).toCartesian();
+ for (int x = 0; x < 36; x++) {
+ // Test within
+ input = SphericalVector.fromCartesian(within).toSparse();
+ result = primer.prime(input, prediction, null);
+ assertThat(result.vector.azimuth).isEqualTo(input.vector.azimuth);
+ assertThat(result.vector.elevation).isEqualTo(input.vector.elevation);
+ within = roll10.rotateVector(within);
+
+ // Test outside
+ input = SphericalVector.fromCartesian(outside).toSparse();
+ result = primer.prime(input, prediction, null);
+ assertThat(result.vector.azimuth).isEqualTo(0);
+ assertThat(result.vector.elevation).isEqualTo(0);
+ outside = roll10.rotateVector(outside);
+ }
+ }
+
+ @Test
+ public void edgeCases() {
+ FovPrimer primer = new FovPrimer((float) toRadians(45));
+ Sparse input, result;
+ SphericalVector prediction = SphericalVector.fromDegrees(0, 0, 0);
+
+ // FOV is actually permitted behind "behind" the device too, test that.
+ input = SphericalVector.fromDegrees(35 + 180, 1, 10).toSparse();
+ result = primer.prime(input, prediction, null);
+ // This is within FOV.
+ assertThat(result.vector.azimuth).isEqualTo(input.vector.azimuth);
+ assertThat(result.vector.elevation).isEqualTo(input.vector.elevation);
+
+ input = SphericalVector.fromDegrees(45 + 180, 1, 10).toSparse();
+ result = primer.prime(input, prediction, null);
+ // This is not within FOV.
+ assertThat(result.vector.azimuth).isEqualTo(0);
+ assertThat(result.vector.elevation).isEqualTo(0);
+
+ // Also test point at 0,0.
+ input = SphericalVector.fromDegrees(0, 0, 10).toSparse();
+ result = primer.prime(input, prediction, null);
+ // This is within FOV.
+ assertThat(result.vector.azimuth).isEqualTo(input.vector.azimuth);
+ assertThat(result.vector.elevation).isEqualTo(input.vector.elevation);
+
+ // Point at 90deg.
+ input = SphericalVector.fromDegrees(0, 90, 10).toSparse();
+ result = primer.prime(input, prediction, null);
+ // This is not within FOV.
+ assertThat(result.vector.azimuth).isEqualTo(0);
+ assertThat(result.vector.elevation).isEqualTo(0);
+
+ // Beyond maximum FOV
+ primer = new FovPrimer((float) toRadians(200));
+ // FOV is actually permitted behind "behind" the device too, test that.
+ input = SphericalVector.fromDegrees(35 + 180, 1, 10).toSparse();
+ result = primer.prime(input, prediction, null);
+ // This is within FOV.
+ assertThat(result.vector.azimuth).isEqualTo(input.vector.azimuth);
+ assertThat(result.vector.elevation).isEqualTo(input.vector.elevation);
+
+ // No prediction
+ primer = new FovPrimer((float) toRadians(10));
+ // Beyond the FOV, but no prediction data so it should go unchanged.
+ input = SphericalVector.fromDegrees(35, 1, 10).toSparse();
+ result = primer.prime(input, null, null);
+ // This is within FOV.
+ assertThat(result.vector.azimuth).isEqualTo(input.vector.azimuth);
+ assertThat(result.vector.elevation).isEqualTo(input.vector.elevation);
+ }
+}
diff --git a/service/tests/src/com/android/server/uwb/correction/primers/NullPrimer.java b/service/tests/src/com/android/server/uwb/correction/primers/NullPrimer.java
new file mode 100644
index 00000000..e6ad3fd4
--- /dev/null
+++ b/service/tests/src/com/android/server/uwb/correction/primers/NullPrimer.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2023 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 com.android.server.uwb.correction.primers;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.server.uwb.correction.math.SphericalVector;
+import com.android.server.uwb.correction.math.SphericalVector.Sparse;
+import com.android.server.uwb.correction.pose.IPoseSource;
+
+public class NullPrimer implements IPrimer {
+
+ /**
+ * Applies corrections to a raw position.
+ *
+ * @param input The original UWB reading.
+ * @param prediction A prediction of where the signal probably came from.
+ * @param poseSource A pose source that may indicate phone orientation.
+ * @return A replacement value for the UWB input that has been corrected for the situation.
+ */
+ @Override
+ public Sparse prime(@NonNull Sparse input, @Nullable SphericalVector prediction,
+ @Nullable IPoseSource poseSource) {
+ // This test primer will just turn any negative azimuth values to positive ones,
+ // and use the prediction for any missing values.
+ float azimuth = input.vector.azimuth;
+ float elevation = input.vector.elevation;
+ float distance = input.vector.distance;
+ if (!input.hasAzimuth) {
+ azimuth = prediction.azimuth;
+ }
+ if (!input.hasElevation) {
+ elevation = prediction.elevation;
+ }
+ if (!input.hasDistance) {
+ distance = prediction.distance;
+ }
+ return SphericalVector.fromRadians(Math.abs(azimuth), elevation, distance).toSparse();
+ }
+}
diff --git a/service/tests/src/com/android/server/uwb/params/FiraEncoderTest.java b/service/tests/src/com/android/server/uwb/params/FiraEncoderTest.java
index bcc80015..7e0bb5b5 100644
--- a/service/tests/src/com/android/server/uwb/params/FiraEncoderTest.java
+++ b/service/tests/src/com/android/server/uwb/params/FiraEncoderTest.java
@@ -25,6 +25,7 @@ import static com.google.uwb.support.fira.FiraParams.RANGING_DEVICE_TYPE_CONTROL
import static com.google.uwb.support.fira.FiraParams.RANGING_DEVICE_UT_TAG;
import static com.google.uwb.support.fira.FiraParams.RANGING_ROUND_USAGE_SS_TWR_DEFERRED_MODE;
import static com.google.uwb.support.fira.FiraParams.RANGING_ROUND_USAGE_UL_TDOA;
+import static com.google.uwb.support.fira.FiraParams.SESSION_TYPE_RANGING;
import static com.google.uwb.support.fira.FiraParams.TX_TIMESTAMP_40_BIT;
import static com.google.uwb.support.fira.FiraParams.UL_TDOA_DEVICE_ID_16_BIT;
@@ -57,6 +58,7 @@ public class FiraEncoderTest {
new FiraOpenSessionParams.Builder()
.setProtocolVersion(FiraParams.PROTOCOL_VERSION_1_1)
.setSessionId(1)
+ .setSessionType(SESSION_TYPE_RANGING)
.setRangeDataNtfConfig(RANGE_DATA_NTF_CONFIG_ENABLE_PROXIMITY_AOA_LEVEL_TRIG)
.setDeviceType(RANGING_DEVICE_TYPE_CONTROLLER)
.setDeviceRole(RANGING_DEVICE_ROLE_RESPONDER)
@@ -68,15 +70,15 @@ public class FiraEncoderTest {
.setStaticStsIV(new byte[]{0x1a, 0x55, 0x77, 0x47, 0x7e, 0x7d})
.setRangeDataNtfAoaAzimuthLower(-1.5)
.setRangeDataNtfAoaAzimuthUpper(2.5)
- .setRangeDataNtfAoaElevationLower(-2.5)
- .setRangeDataNtfAoaElevationUpper(3);
+ .setRangeDataNtfAoaElevationLower(-1.5)
+ .setRangeDataNtfAoaElevationUpper(1.2);
private static final byte[] TEST_FIRA_OPEN_SESSION_TLV_DATA =
UwbUtil.getByteArray("000101010101020100030100040109050101060206040702060408"
+ "0260090904C80000000B01000C01030D01010E01040F0200001002204E11010012010314010"
+ "A1501021601001701011B01191C01001F01002301002401002501322601002702780528061A"
+ "5577477E7D2901012A0200002C01002D01002E01012F01013101"
- + "003501012B04000000001D04079E6161");
+ + "003501012B04000000001D0807D59E4707D56022");
private static final FiraRangingReconfigureParams.Builder TEST_FIRA_RECONFIGURE_PARAMS =
new FiraRangingReconfigureParams.Builder()
@@ -86,16 +88,17 @@ public class FiraEncoderTest {
.setRangeDataProximityNear(4)
.setRangeDataAoaAzimuthLower(-1.5)
.setRangeDataAoaAzimuthUpper(2.5)
- .setRangeDataAoaElevationLower(-2.5)
- .setRangeDataAoaElevationUpper(3);
+ .setRangeDataAoaElevationLower(-1.5)
+ .setRangeDataAoaElevationUpper(1.2);
private static final byte[] TEST_FIRA_RECONFIGURE_TLV_DATA =
- UwbUtil.getByteArray("2D01060E01040F020400100206001D04079E61F1");
+ UwbUtil.getByteArray("2D01060E01040F020400100206001D0807D59E4707D56022");
private static final FiraOpenSessionParams.Builder TEST_FIRA_UT_TAG_OPEN_SESSION_PARAM =
new FiraOpenSessionParams.Builder()
.setProtocolVersion(FiraParams.PROTOCOL_VERSION_1_1)
.setSessionId(2)
+ .setSessionType(SESSION_TYPE_RANGING)
.setDeviceType(RANGING_DEVICE_TYPE_CONTROLLER)
.setDeviceRole(RANGING_DEVICE_UT_TAG)
.setDeviceAddress(UwbAddress.fromBytes(new byte[] { 0x4, 0x6}))
@@ -115,12 +118,12 @@ public class FiraEncoderTest {
+ "0260090904C80000000B01000C01030D01010E01010F0200001002204E11010412010314010"
+ "A1501021601001701011B01191C01001F01002301002401002501322601002702780528061A"
+ "5577477E7D2901012A0200002C01002D01002E01012F01013101003501012B0400000000330"
- + "8B00400000000000034081E000000000000003703010B0A380101");
+ + "4B004000034041E0000003703010B0A380101");
private final FiraEncoder mFiraEncoder = new FiraEncoder();
@Test
- public void testFiraOpenSesisonParams() throws Exception {
+ public void testFiraOpenSessionParams() throws Exception {
FiraOpenSessionParams params = TEST_FIRA_OPEN_SESSION_PARAMS.build();
TlvBuffer tlvs = mFiraEncoder.getTlvBuffer(params);
diff --git a/service/uci/jni/Android.bp b/service/uci/jni/Android.bp
index 3a4e0141..5f188cd8 100644
--- a/service/uci/jni/Android.bp
+++ b/service/uci/jni/Android.bp
@@ -34,6 +34,9 @@ rust_ffi_shared {
"libuci_hal_android",
"libuwb_core",
],
+ sanitize: {
+ hwaddress: false,
+ },
}
rust_test {
diff --git a/service/uci/jni/src/notification_manager_android.rs b/service/uci/jni/src/notification_manager_android.rs
index bd9b67c0..bed03a3b 100644
--- a/service/uci/jni/src/notification_manager_android.rs
+++ b/service/uci/jni/src/notification_manager_android.rs
@@ -24,6 +24,7 @@ use std::sync::Arc;
use jni::objects::{GlobalRef, JClass, JMethodID, JObject, JValue};
use jni::signature::TypeSignature;
+use jni::sys::jvalue;
use jni::{AttachGuard, JavaVM};
use log::{debug, error};
use uwb_core::error::{Error, Result};
@@ -254,7 +255,7 @@ pub(crate) struct NotificationManagerAndroid {
/// Global reference to the java class holding the various UCI notification callback functions.
pub callback_obj: GlobalRef,
// *_jmethod_id are cached for faster callback using call_method_unchecked
- pub jmethod_id_map: HashMap<String, JMethodID<'static>>,
+ pub jmethod_id_map: HashMap<String, JMethodID>,
// jclass are cached for faster callback
pub jclass_map: HashMap<String, GlobalRef>,
}
@@ -275,15 +276,17 @@ impl NotificationManagerAndroid {
if jclass_map.get(class_name).is_none() {
// Find class using the class loader object, needed as this call is initiated from a
// different native thread.
+
+ let env_class_name = *env.new_string(class_name).map_err(|e| {
+ error!("UCI JNI: failed to create Java String: {e:?}");
+ Error::ForeignFunctionInterface
+ })?;
let class_value = env
.call_method(
class_loader_obj.as_obj(),
"findClass",
"(Ljava/lang/String;)Ljava/lang/Class;",
- &[JValue::Object(JObject::from(env.new_string(class_name).map_err(|e| {
- error!("UCI JNI: failed to create Java String: {:?}", e);
- Error::ForeignFunctionInterface
- })?))],
+ &[JValue::Object(env_class_name)],
)
.map_err(|e| {
error!("UCI JNI: failed to find java class {}: {:?}", class_name, e);
@@ -309,7 +312,7 @@ impl NotificationManagerAndroid {
Ok(jclass_map.get(class_name).unwrap().as_obj().into())
}
- fn cached_jni_call(&mut self, name: &str, sig: &str, args: &[JValue]) -> Result<()> {
+ fn cached_jni_call(&mut self, name: &str, sig: &str, args: &[jvalue]) -> Result<()> {
debug!("UCI JNI: callback {}", name);
let type_signature = TypeSignature::from_str(sig).map_err(|e| {
error!("UCI JNI: Invalid type signature: {:?}", e);
@@ -357,9 +360,9 @@ impl NotificationManagerAndroid {
"onSessionStatusNotificationReceived",
"(JII)V",
&[
- JValue::Long(session_id as i64),
- JValue::Int(session_state as i32),
- JValue::Int(reason_code as i32),
+ jvalue::from(JValue::Long(session_id as i64)),
+ jvalue::from(JValue::Int(session_state as i32)),
+ jvalue::from(JValue::Int(reason_code as i32)),
],
)
}
@@ -400,6 +403,16 @@ impl NotificationManagerAndroid {
MULTICAST_LIST_UPDATE_STATUS_CLASS,
)?;
let method_sig = "(L".to_owned() + MULTICAST_LIST_UPDATE_STATUS_CLASS + ";)V";
+
+ // Safety: mac_address_jintarray is safely instantiated above.
+ let mac_address_jobject = unsafe { JObject::from_raw(mac_address_jintarray) };
+
+ // Safety: subsession_id_jlongarray is safely instantiated above.
+ let subsession_id_jobject = unsafe { JObject::from_raw(subsession_id_jlongarray) };
+
+ // Safety: status_jintarray is safely instantiated above.
+ let status_jobject = unsafe { JObject::from_raw(status_jintarray) };
+
let multicast_update_jobject = self
.env
.new_object(
@@ -409,16 +422,16 @@ impl NotificationManagerAndroid {
JValue::Long(session_id as i64),
JValue::Int(remaining_multicast_list_size),
JValue::Int(count),
- JValue::Object(JObject::from(mac_address_jintarray)),
- JValue::Object(JObject::from(subsession_id_jlongarray)),
- JValue::Object(JObject::from(status_jintarray)),
+ JValue::Object(mac_address_jobject),
+ JValue::Object(subsession_id_jobject),
+ JValue::Object(status_jobject),
],
)
.map_err(|_| Error::ForeignFunctionInterface)?;
self.cached_jni_call(
"onMulticastListUpdateNotificationReceived",
&method_sig,
- &[JValue::Object(multicast_update_jobject)],
+ &[jvalue::from(JValue::Object(multicast_update_jobject))],
)
}
@@ -443,8 +456,10 @@ impl NotificationManagerAndroid {
uwb_core::uci::RangingMeasurements::ExtendedAddressTwoWay(_) => {
EXTENDED_MAC_ADDRESS_LEN
}
- uwb_core::uci::RangingMeasurements::ShortDltdoa(_) => SHORT_MAC_ADDRESS_LEN,
- uwb_core::uci::RangingMeasurements::ExtendedDltdoa(_) => EXTENDED_MAC_ADDRESS_LEN,
+ uwb_core::uci::RangingMeasurements::ShortAddressDltdoa(_) => SHORT_MAC_ADDRESS_LEN,
+ uwb_core::uci::RangingMeasurements::ExtendedAddressDltdoa(_) => {
+ EXTENDED_MAC_ADDRESS_LEN
+ }
_ => {
return Err(Error::ForeignFunctionInterface);
}
@@ -459,13 +474,21 @@ impl NotificationManagerAndroid {
.env
.new_byte_array(MAX_RANGING_ROUNDS_LEN)
.map_err(|_| Error::ForeignFunctionInterface)?;
+
+ // Safety: address_jbytearray is safely instantiated above.
+ let address_jobject = unsafe { JObject::from_raw(address_jbytearray) };
+ // Safety: anchor_location is safely instantiated above.
+ let anchor_jobject = unsafe { JObject::from_raw(anchor_location) };
+ // Safety: active_ranging_rounds is safely instantiated above.
+ let active_ranging_rounds_jobject = unsafe { JObject::from_raw(active_ranging_rounds) };
+
let zero_initiated_measurement_jobject = self
.env
.new_object(
measurement_jclass,
"([BIIIIIIIIIIIJJIIJJI[B[B)V",
&[
- JValue::Object(JObject::from(address_jbytearray)),
+ JValue::Object(address_jobject),
JValue::Int(0),
JValue::Int(0),
JValue::Int(0),
@@ -484,8 +507,8 @@ impl NotificationManagerAndroid {
JValue::Long(0),
JValue::Long(0),
JValue::Int(0),
- JValue::Object(JObject::from(anchor_location)),
- JValue::Object(JObject::from(active_ranging_rounds)),
+ JValue::Object(anchor_jobject),
+ JValue::Object(active_ranging_rounds_jobject),
],
)
.map_err(|e| {
@@ -495,8 +518,8 @@ impl NotificationManagerAndroid {
let measurement_count: i32 = match &range_data.ranging_measurements {
RangingMeasurements::ShortAddressTwoWay(v) => v.len(),
RangingMeasurements::ExtendedAddressTwoWay(v) => v.len(),
- RangingMeasurements::ShortDltdoa(v) => v.len(),
- RangingMeasurements::ExtendedDltdoa(v) => v.len(),
+ RangingMeasurements::ShortAddressDltdoa(v) => v.len(),
+ RangingMeasurements::ExtendedAddressDltdoa(v) => v.len(),
_ => {
return Err(Error::BadParameters);
}
@@ -506,8 +529,8 @@ impl NotificationManagerAndroid {
let mac_indicator = match &range_data.ranging_measurements {
RangingMeasurements::ShortAddressTwoWay(_) => MacAddressIndicator::ShortAddress,
RangingMeasurements::ExtendedAddressTwoWay(_) => MacAddressIndicator::ExtendedAddress,
- RangingMeasurements::ShortDltdoa(_) => MacAddressIndicator::ShortAddress,
- RangingMeasurements::ExtendedDltdoa(_) => MacAddressIndicator::ExtendedAddress,
+ RangingMeasurements::ShortAddressDltdoa(_) => MacAddressIndicator::ShortAddress,
+ RangingMeasurements::ExtendedAddressDltdoa(_) => MacAddressIndicator::ExtendedAddress,
_ => {
return Err(Error::BadParameters);
}
@@ -523,10 +546,10 @@ impl NotificationManagerAndroid {
.map_err(|_| Error::ForeignFunctionInterface)?;
for (i, measurement) in match range_data.ranging_measurements {
- RangingMeasurements::ShortDltdoa(v) => {
+ RangingMeasurements::ShortAddressDltdoa(v) => {
v.into_iter().map(DlTdoaRangingMeasurement::from).collect::<Vec<_>>()
}
- RangingMeasurements::ExtendedDltdoa(v) => {
+ RangingMeasurements::ExtendedAddressDltdoa(v) => {
v.into_iter().map(DlTdoaRangingMeasurement::from).collect::<Vec<_>>()
}
_ => Vec::new(),
@@ -558,13 +581,22 @@ impl NotificationManagerAndroid {
.env
.byte_array_from_slice(&measurement.ranging_rounds)
.map_err(|_| Error::ForeignFunctionInterface)?;
+
+ // Safety: mac_address_jbytearray is safely instantiated above.
+ let mac_address_jobject = unsafe { JObject::from_raw(mac_address_jbytearray) };
+ // Safety: dt_anchor_location_jbytearray is safely instantiated above.
+ let dt_anchor_location_jobject =
+ unsafe { JObject::from_raw(dt_anchor_location_jbytearray) };
+ // Safety: ranging_rounds_jbytearray is safely instantiated above.
+ let ranging_rounds_jobject = unsafe { JObject::from_raw(ranging_rounds_jbytearray) };
+
let measurement_jobject = self
.env
.new_object(
measurement_jclass,
"([BIIIIIIIIIIIJJIIJJI[B[B)V",
&[
- JValue::Object(JObject::from(mac_address_jbytearray)),
+ JValue::Object(mac_address_jobject),
JValue::Int(measurement.status as i32),
JValue::Int(measurement.message_type as i32),
JValue::Int(measurement.message_control as i32),
@@ -583,8 +615,8 @@ impl NotificationManagerAndroid {
JValue::Long(measurement.initiator_reply_time as i64),
JValue::Long(measurement.responder_reply_time as i64),
JValue::Int(measurement.initiator_responder_tof as i32),
- JValue::Object(JObject::from(dt_anchor_location_jbytearray)),
- JValue::Object(JObject::from(ranging_rounds_jbytearray)),
+ JValue::Object(dt_anchor_location_jobject),
+ JValue::Object(ranging_rounds_jobject),
],
)
.map_err(|e| {
@@ -607,6 +639,12 @@ impl NotificationManagerAndroid {
)?;
let method_sig = "(JJIJIII[L".to_owned() + UWB_DL_TDOA_MEASUREMENT_CLASS + ";[B)V";
+
+ // Safety: measurements_jobjectarray is safely instantiated above.
+ let measurements_jobject = unsafe { JObject::from_raw(measurements_jobjectarray) };
+ // Safety: raw_notification_jbytearray is safely instantiated above.
+ let raw_notification_jobject = unsafe { JObject::from_raw(raw_notification_jbytearray) };
+
let range_data_jobject = self
.env
.new_object(
@@ -620,19 +658,20 @@ impl NotificationManagerAndroid {
JValue::Int(range_data.ranging_measurement_type as i32),
JValue::Int(mac_indicator as i32),
JValue::Int(measurement_count),
- JValue::Object(JObject::from(measurements_jobjectarray)),
- JValue::Object(JObject::from(raw_notification_jbytearray)),
+ JValue::Object(measurements_jobject),
+ JValue::Object(raw_notification_jobject),
],
)
.map_err(|e| {
error!("UCI JNI: Ranging Data object creation failed: {:?}", e);
Error::ForeignFunctionInterface
})?;
+
let method_sig = "(L".to_owned() + UWB_RANGING_DATA_CLASS + ";)V";
self.cached_jni_call(
"onRangeDataNotificationReceived",
&method_sig,
- &[JValue::Object(range_data_jobject)],
+ &[jvalue::from(JValue::Object(range_data_jobject))],
)
}
@@ -650,13 +689,17 @@ impl NotificationManagerAndroid {
)?;
let address_jbytearray =
self.env.new_byte_array(bytearray_len).map_err(|_| Error::ForeignFunctionInterface)?;
+
+ // Safety: address_jbytearray is safely instantiated above.
+ let address_jobject = unsafe { JObject::from_raw(address_jbytearray) };
+
let zero_initiated_measurement_jobject = self
.env
.new_object(
measurement_jclass,
"([BIIIIIIIIIIIII)V",
&[
- JValue::Object(JObject::from(address_jbytearray)),
+ JValue::Object(address_jobject),
JValue::Int(0),
JValue::Int(0),
JValue::Int(0),
@@ -676,6 +719,7 @@ impl NotificationManagerAndroid {
error!("UCI JNI: measurement object creation failed: {:?}", e);
Error::ForeignFunctionInterface
})?;
+
let measurements_jobjectarray = self
.env
.new_object_array(
@@ -700,13 +744,16 @@ impl NotificationManagerAndroid {
.set_byte_array_region(mac_address_jbytearray, 0, &mac_address_i8)
.map_err(|_| Error::ForeignFunctionInterface)?;
// casting as i32 is fine since it is wider than actual integer type.
+
+ // Safety: mac_address_jbytearray is safely instantiated above.
+ let mac_address_jobject = unsafe { JObject::from_raw(mac_address_jbytearray) };
let measurement_jobject = self
.env
.new_object(
measurement_jclass,
"([BIIIIIIIIIIIII)V",
&[
- JValue::Object(JObject::from(mac_address_jbytearray)),
+ JValue::Object(mac_address_jobject),
JValue::Int(measurement.status as i32),
JValue::Int(measurement.nlos as i32),
JValue::Int(measurement.distance as i32),
@@ -751,13 +798,16 @@ impl NotificationManagerAndroid {
)?;
let address_jbytearray =
self.env.new_byte_array(bytearray_len).map_err(|_| Error::ForeignFunctionInterface)?;
+
+ // Safety: address_jbytearray is safely instantiated above.
+ let address_jobject = unsafe { JObject::from_raw(address_jbytearray) };
let zero_initiated_measurement_jobject = self
.env
.new_object(
measurement_jclass,
"([BIIIIIIII)V",
&[
- JValue::Object(JObject::from(address_jbytearray)),
+ JValue::Object(address_jobject),
JValue::Int(0),
JValue::Int(0),
JValue::Int(0),
@@ -797,13 +847,16 @@ impl NotificationManagerAndroid {
.set_byte_array_region(mac_address_jbytearray, 0, &mac_address_i8)
.map_err(|_| Error::ForeignFunctionInterface)?;
// casting as i32 is fine since it is wider than actual integer type.
+
+ // Safety: mac_address_jbytearray is safely instantiated above.
+ let mac_address_jobject = unsafe { JObject::from_raw(mac_address_jbytearray) };
let measurement_jobject = self
.env
.new_object(
measurement_jclass,
"([BIIIIIIII)V",
&[
- JValue::Object(JObject::from(mac_address_jbytearray)),
+ JValue::Object(mac_address_jobject),
JValue::Int(measurement.status as i32),
JValue::Int(measurement.nlos as i32),
JValue::Int(measurement.frame_sequence_number as i32),
@@ -840,12 +893,12 @@ impl NotificationManagerAndroid {
let (bytearray_len, mac_indicator) = match &range_data.ranging_measurements {
RangingMeasurements::ExtendedAddressTwoWay(_)
| RangingMeasurements::ExtendedAddressOwrAoa(_)
- | RangingMeasurements::ExtendedDltdoa(_) => {
+ | RangingMeasurements::ExtendedAddressDltdoa(_) => {
(EXTENDED_MAC_ADDRESS_LEN, MacAddressIndicator::ExtendedAddress)
}
RangingMeasurements::ShortAddressTwoWay(_)
| RangingMeasurements::ShortAddressOwrAoa(_)
- | RangingMeasurements::ShortDltdoa(_) => {
+ | RangingMeasurements::ShortAddressDltdoa(_) => {
(SHORT_MAC_ADDRESS_LEN, MacAddressIndicator::ShortAddress)
}
};
@@ -854,15 +907,14 @@ impl NotificationManagerAndroid {
| RangingMeasurements::ShortAddressTwoWay(_) => UWB_TWO_WAY_MEASUREMENT_CLASS,
RangingMeasurements::ExtendedAddressOwrAoa(_)
| RangingMeasurements::ShortAddressOwrAoa(_) => UWB_OWR_AOA_MEASUREMENT_CLASS,
- RangingMeasurements::ExtendedDltdoa(_) | RangingMeasurements::ShortDltdoa(_) => {
- UWB_DL_TDOA_MEASUREMENT_CLASS
- }
+ RangingMeasurements::ExtendedAddressDltdoa(_)
+ | RangingMeasurements::ShortAddressDltdoa(_) => UWB_DL_TDOA_MEASUREMENT_CLASS,
};
let measurement_count: i32 = match &range_data.ranging_measurements {
RangingMeasurements::ShortAddressTwoWay(v) => v.len().try_into(),
RangingMeasurements::ExtendedAddressTwoWay(v) => v.len().try_into(),
- RangingMeasurements::ShortDltdoa(v) => v.len().try_into(),
- RangingMeasurements::ExtendedDltdoa(v) => v.len().try_into(),
+ RangingMeasurements::ShortAddressDltdoa(v) => v.len().try_into(),
+ RangingMeasurements::ExtendedAddressDltdoa(v) => v.len().try_into(),
RangingMeasurements::ShortAddressOwrAoa(v) => v.len().try_into(),
RangingMeasurements::ExtendedAddressOwrAoa(v) => v.len().try_into(),
}
@@ -915,6 +967,11 @@ impl NotificationManagerAndroid {
UWB_RANGING_DATA_CLASS,
)?;
let method_sig = "(JJIJIII[L".to_owned() + measurement_data_class + ";[B)V";
+
+ // Safety: measurements_jobjectarray is safely instantiated above.
+ let measurements_jobject = unsafe { JObject::from_raw(measurements_jobjectarray) };
+ // Safety: raw_notification_jobject is safely instantiated above.
+ let raw_notification_jobject = unsafe { JObject::from_raw(raw_notification_jbytearray) };
let range_data_jobject = self
.env
.new_object(
@@ -928,8 +985,8 @@ impl NotificationManagerAndroid {
JValue::Int(range_data.ranging_measurement_type as i32),
JValue::Int(mac_indicator as i32),
JValue::Int(measurement_count),
- JValue::Object(JObject::from(measurements_jobjectarray)),
- JValue::Object(JObject::from(raw_notification_jbytearray)),
+ JValue::Object(measurements_jobject),
+ JValue::Object(raw_notification_jobject),
],
)
.map_err(|e| {
@@ -940,7 +997,7 @@ impl NotificationManagerAndroid {
self.cached_jni_call(
"onRangeDataNotificationReceived",
&method_sig,
- &[JValue::Object(range_data_jobject)],
+ &[jvalue::from(JValue::Object(range_data_jobject))],
)
}
}
@@ -948,21 +1005,24 @@ impl NotificationManagerAndroid {
impl NotificationManager for NotificationManagerAndroid {
fn on_core_notification(&mut self, core_notification: CoreNotification) -> Result<()> {
debug!("UCI JNI: core notification callback.");
+
+ let env_chip_id_jobject = *self.env.new_string(&self.chip_id).unwrap();
+
match core_notification {
CoreNotification::DeviceStatus(device_state) => self.cached_jni_call(
"onDeviceStatusNotificationReceived",
"(ILjava/lang/String;)V",
&[
- JValue::Int(device_state as i32),
- JValue::Object(JObject::from(self.env.new_string(&self.chip_id).unwrap())),
+ jvalue::from(JValue::Int(device_state as i32)),
+ jvalue::from(JValue::Object(env_chip_id_jobject)),
],
),
CoreNotification::GenericError(generic_error) => self.cached_jni_call(
"onCoreGenericErrorNotificationReceived",
"(ILjava/lang/String;)V",
&[
- JValue::Int(generic_error as i32),
- JValue::Object(JObject::from(self.env.new_string(&self.chip_id).unwrap())),
+ jvalue::from(JValue::Int(generic_error as i32)),
+ jvalue::from(JValue::Object(env_chip_id_jobject)),
],
),
}
@@ -986,7 +1046,7 @@ impl NotificationManager for NotificationManagerAndroid {
// TODO(b/246678053): Refactor to do this split inside
// on_session_range_data_notification(), after computing the common parameters based on
// the RangingMeasurements type.
- SessionNotification::RangeData(range_data) => match range_data.ranging_measurements {
+ SessionNotification::SessionInfo(range_data) => match range_data.ranging_measurements {
uwb_core::uci::RangingMeasurements::ShortAddressTwoWay(_) => {
self.on_session_range_data_notification(range_data)
}
@@ -999,13 +1059,31 @@ impl NotificationManager for NotificationManagerAndroid {
uwb_core::uci::RangingMeasurements::ExtendedAddressOwrAoa(_) => {
self.on_session_range_data_notification(range_data)
}
- uwb_core::uci::RangingMeasurements::ShortDltdoa(_) => {
+ uwb_core::uci::RangingMeasurements::ShortAddressDltdoa(_) => {
self.on_session_dl_tdoa_range_data_notification(range_data)
}
- uwb_core::uci::RangingMeasurements::ExtendedDltdoa(_) => {
+ uwb_core::uci::RangingMeasurements::ExtendedAddressDltdoa(_) => {
self.on_session_dl_tdoa_range_data_notification(range_data)
}
},
+ // These session notifications should not come here, as they are handled within
+ // UciManager, for internal state management related to sending data packet(s).
+ SessionNotification::DataCredit { session_id, credit_availability } => {
+ error!(
+ "UCI JNI: Received unexpected DataCredit notification for \
+ session_id {}, credit_availability {}",
+ session_id, credit_availability
+ );
+ Ok(())
+ }
+ SessionNotification::DataTransferStatus { session_id, uci_sequence_number, status } => {
+ error!(
+ "UCI JNI: Received unexpected DataTransferStatus notification for \
+ session_id {}, uci_sequence_number {} with status {}",
+ session_id, uci_sequence_number, status
+ );
+ Ok(())
+ }
}
}
@@ -1018,14 +1096,21 @@ impl NotificationManager for NotificationManagerAndroid {
.env
.byte_array_from_slice(&vendor_notification.payload)
.map_err(|_| Error::ForeignFunctionInterface)?;
+
+ // Safety: payload_jbytearray safely instantiated above.
+ let payload_jobject = unsafe { JObject::from_raw(payload_jbytearray) };
self.cached_jni_call(
"onVendorUciNotificationReceived",
"(II[B)V",
&[
// Java only has signed integer. The range for signed int32 should be sufficient.
- JValue::Int(vendor_notification.gid.try_into().map_err(|_| Error::BadParameters)?),
- JValue::Int(vendor_notification.oid.try_into().map_err(|_| Error::BadParameters)?),
- JValue::Object(JObject::from(payload_jbytearray)),
+ jvalue::from(JValue::Int(
+ vendor_notification.gid.try_into().map_err(|_| Error::BadParameters)?,
+ )),
+ jvalue::from(JValue::Int(
+ vendor_notification.oid.try_into().map_err(|_| Error::BadParameters)?,
+ )),
+ jvalue::from(JValue::Object(payload_jobject)),
],
)
}
@@ -1048,17 +1133,22 @@ impl NotificationManager for NotificationManagerAndroid {
.env
.byte_array_from_slice(&data_rcv_notification.payload)
.map_err(|_| Error::ForeignFunctionInterface)?;
+
+ // Safety: source_address_jbytearray safely instantiated above.
+ let source_address_jobject = unsafe { JObject::from_raw(source_address_jbytearray) };
+ // Safety: payload_jbytearray safely instantiated above.
+ let payload_jobject = unsafe { JObject::from_raw(payload_jbytearray) };
self.cached_jni_call(
"onDataReceived",
"(JIJ[BII[B)V",
&[
- JValue::Long(data_rcv_notification.session_id as i64),
- JValue::Int(data_rcv_notification.status as i32),
- JValue::Long(data_rcv_notification.uci_sequence_num as i64),
- JValue::Object(JObject::from(source_address_jbytearray)),
- JValue::Int(data_rcv_notification.source_fira_component as i32),
- JValue::Int(data_rcv_notification.dest_fira_component as i32),
- JValue::Object(JObject::from(payload_jbytearray)),
+ jvalue::from(JValue::Long(data_rcv_notification.session_id as i64)),
+ jvalue::from(JValue::Int(data_rcv_notification.status as i32)),
+ jvalue::from(JValue::Long(data_rcv_notification.uci_sequence_num as i64)),
+ jvalue::from(JValue::Object(source_address_jobject)),
+ jvalue::from(JValue::Int(data_rcv_notification.source_fira_component as i32)),
+ jvalue::from(JValue::Int(data_rcv_notification.dest_fira_component as i32)),
+ jvalue::from(JValue::Object(payload_jobject)),
],
)
}
diff --git a/service/uci/jni/src/uci_jni_android_new.rs b/service/uci/jni/src/uci_jni_android_new.rs
index 3be8268b..a2552692 100644
--- a/service/uci/jni/src/uci_jni_android_new.rs
+++ b/service/uci/jni/src/uci_jni_android_new.rs
@@ -27,9 +27,9 @@ use std::iter::zip;
use jni::errors::Error as JNIError;
use jni::objects::{GlobalRef, JObject, JString, JValue};
-use jni::signature::JavaType;
+use jni::signature::ReturnType;
use jni::sys::{
- jboolean, jbyte, jbyteArray, jint, jintArray, jlong, jobject, jobjectArray, jshortArray,
+ jboolean, jbyte, jbyteArray, jint, jintArray, jlong, jobject, jobjectArray, jshortArray, jvalue,
};
use jni::JNIEnv;
use log::{debug, error};
@@ -41,8 +41,8 @@ use uwb_core::params::{
};
use uwb_uci_packets::{
AppConfigTlvType, CapTlv, Controlee, Controlee_V2_0_16_Byte_Version,
- Controlee_V2_0_32_Byte_Version, Controlees, PowerStats, ResetConfig, SessionState, SessionType,
- StatusCode, UpdateMulticastListAction,
+ Controlee_V2_0_32_Byte_Version, Controlees, FiraComponent, PowerStats, ResetConfig,
+ SessionState, SessionType, StatusCode, UpdateMulticastListAction,
};
/// Macro capturing the name of the function calling this macro.
@@ -330,6 +330,9 @@ fn create_set_config_response(response: SetAppConfigResponse, env: JNIEnv) -> Re
}
let config_status_jbytearray =
env.byte_array_from_slice(&buf).map_err(|_| Error::ForeignFunctionInterface)?;
+
+ // Safety: config_status_jbytearray is safely instantiated above.
+ let config_status_jobject = unsafe { JObject::from_raw(config_status_jbytearray) };
let config_status_jobject = env
.new_object(
uwb_config_status_class,
@@ -337,7 +340,7 @@ fn create_set_config_response(response: SetAppConfigResponse, env: JNIEnv) -> Re
&[
JValue::Int(response.status as i32),
JValue::Int(response.config_status.len() as i32),
- JValue::Object(JObject::from(config_status_jbytearray)),
+ JValue::Object(config_status_jobject),
],
)
.map_err(|_| Error::ForeignFunctionInterface)?;
@@ -405,18 +408,21 @@ fn create_get_config_response(tlvs: Vec<AppConfigTlv>, env: JNIEnv) -> Result<jb
}
let tlvs_jbytearray =
env.byte_array_from_slice(&buf).map_err(|_| Error::ForeignFunctionInterface)?;
- let tlvs_jobject = env
+
+ // Safety: tlvs_jbytearray is safely instantiated above.
+ let tlvs_jobject = unsafe { JObject::from_raw(tlvs_jbytearray) };
+ let tlvs_jobject_env = env
.new_object(
tlv_data_class,
"(II[B)V",
&[
JValue::Int(StatusCode::UciStatusOk as i32),
JValue::Int(tlvs_len as i32),
- JValue::Object(JObject::from(tlvs_jbytearray)),
+ JValue::Object(tlvs_jobject),
],
)
.map_err(|_| Error::ForeignFunctionInterface)?;
- Ok(*tlvs_jobject)
+ Ok(*tlvs_jobject_env)
}
/// Get app configurations on a single UWB device. Return null JObject if failed.
@@ -477,18 +483,21 @@ fn create_cap_response(tlvs: Vec<CapTlv>, env: JNIEnv) -> Result<jbyteArray> {
}
let tlvs_jbytearray =
env.byte_array_from_slice(&buf).map_err(|_| Error::ForeignFunctionInterface)?;
- let tlvs_jobject = env
+
+ // Safety: tlvs_jbytearray is safely instantiated above.
+ let tlvs_jobject = unsafe { JObject::from_raw(tlvs_jbytearray) };
+ let tlvs_jobject_env = env
.new_object(
tlv_data_class,
"(II[B)V",
&[
JValue::Int(StatusCode::UciStatusOk as i32),
JValue::Int(tlvs.len() as i32),
- JValue::Object(JObject::from(tlvs_jbytearray)),
+ JValue::Object(tlvs_jobject),
],
)
.map_err(|_| Error::ForeignFunctionInterface)?;
- Ok(*tlvs_jobject)
+ Ok(*tlvs_jobject_env)
}
/// Get capability info on a single UWB device. Return null JObject if failed.
@@ -582,8 +591,6 @@ fn native_controller_multicast_list_update(
{
return Err(Error::BadParameters);
}
- let sub_session_key_list =
- env.convert_byte_array(sub_session_keys).map_err(|_| Error::ForeignFunctionInterface)?;
let controlee_list = match UpdateMulticastListAction::from_u8(action as u8)
.ok_or(Error::BadParameters)?
{
@@ -596,27 +603,37 @@ fn native_controller_multicast_list_update(
}
UpdateMulticastListAction::AddControleeWithShortSubSessionKey => {
Controlees::ShortSessionKey(
- zip(zip(address_list, sub_session_id_list), sub_session_key_list.chunks(16))
- .map(|((address, id), key)| {
- Ok(Controlee_V2_0_16_Byte_Version {
- short_address: address as u16,
- subsession_id: id as u32,
- subsession_key: key.try_into().map_err(|_| Error::BadParameters)?,
- })
- })
- .collect::<Result<Vec<Controlee_V2_0_16_Byte_Version>>>()?,
- )
- }
- UpdateMulticastListAction::AddControleeWithLongSubSessionKey => Controlees::LongSessionKey(
- zip(zip(address_list, sub_session_id_list), sub_session_key_list.chunks(32))
+ zip(
+ zip(address_list, sub_session_id_list),
+ env.convert_byte_array(sub_session_keys)
+ .map_err(|_| Error::ForeignFunctionInterface)?
+ .chunks(16),
+ )
.map(|((address, id), key)| {
- Ok(Controlee_V2_0_32_Byte_Version {
+ Ok(Controlee_V2_0_16_Byte_Version {
short_address: address as u16,
subsession_id: id as u32,
subsession_key: key.try_into().map_err(|_| Error::BadParameters)?,
})
})
- .collect::<Result<Vec<Controlee_V2_0_32_Byte_Version>>>()?,
+ .collect::<Result<Vec<Controlee_V2_0_16_Byte_Version>>>()?,
+ )
+ }
+ UpdateMulticastListAction::AddControleeWithLongSubSessionKey => Controlees::LongSessionKey(
+ zip(
+ zip(address_list, sub_session_id_list),
+ env.convert_byte_array(sub_session_keys)
+ .map_err(|_| Error::ForeignFunctionInterface)?
+ .chunks(32),
+ )
+ .map(|((address, id), key)| {
+ Ok(Controlee_V2_0_32_Byte_Version {
+ short_address: address as u16,
+ subsession_id: id as u32,
+ subsession_key: key.try_into().map_err(|_| Error::BadParameters)?,
+ })
+ })
+ .collect::<Result<Vec<Controlee_V2_0_32_Byte_Version>>>()?,
),
};
uci_manager.session_update_controller_multicast_list(
@@ -677,9 +694,18 @@ fn native_set_log_mode(env: JNIEnv, obj: JObject, log_mode_jstring: JString) ->
dispatcher.set_logger_mode(logger_mode)
}
-fn create_vendor_response(msg: RawUciMessage, env: JNIEnv) -> Result<jobject> {
+// # Safety
+//
+// For this to be safe, the validity of msg should be checked before calling.
+unsafe fn create_vendor_response(msg: RawUciMessage, env: JNIEnv) -> Result<jobject> {
let vendor_response_class =
env.find_class(VENDOR_RESPONSE_CLASS).map_err(|_| Error::ForeignFunctionInterface)?;
+
+ // Unsafe from_raw call
+ let payload_jobject = JObject::from_raw(
+ env.byte_array_from_slice(&msg.payload).map_err(|_| Error::ForeignFunctionInterface)?,
+ );
+
match env.new_object(
vendor_response_class,
"(BII[B)V",
@@ -687,10 +713,7 @@ fn create_vendor_response(msg: RawUciMessage, env: JNIEnv) -> Result<jobject> {
JValue::Byte(StatusCode::UciStatusOk as i8),
JValue::Int(msg.gid as i32),
JValue::Int(msg.oid as i32),
- JValue::Object(JObject::from(
- env.byte_array_from_slice(&msg.payload)
- .map_err(|_| Error::ForeignFunctionInterface)?,
- )),
+ JValue::Object(payload_jobject),
],
) {
Ok(obj) => Ok(*obj),
@@ -716,7 +739,10 @@ fn create_invalid_vendor_response(env: JNIEnv) -> Result<jobject> {
}
}
-fn create_ranging_round_status(
+/// Safety:
+///
+/// response should be checked before calling to ensure safety.
+unsafe fn create_ranging_round_status(
response: SessionUpdateActiveRoundsDtTagResponse,
env: JNIEnv,
) -> Result<jobject> {
@@ -724,16 +750,19 @@ fn create_ranging_round_status(
.find_class(DT_RANGING_ROUNDS_STATUS_CLASS)
.map_err(|_| Error::ForeignFunctionInterface)?;
let indexes = response.ranging_round_indexes;
+
+ // Unsafe from_raw call
+ let indexes_jobject = JObject::from_raw(
+ env.byte_array_from_slice(indexes.as_ref()).map_err(|_| Error::ForeignFunctionInterface)?,
+ );
+
match env.new_object(
dt_ranging_rounds_update_status_class,
"(II[B)V",
&[
JValue::Int(response.status as i32),
JValue::Int(indexes.len() as i32),
- JValue::Object(JObject::from(
- env.byte_array_from_slice(indexes.as_ref())
- .map_err(|_| Error::ForeignFunctionInterface)?,
- )),
+ JValue::Object(indexes_jobject),
],
) {
Ok(o) => Ok(*o),
@@ -759,12 +788,17 @@ pub extern "system" fn Java_com_android_server_uwb_jni_NativeUwbManager_nativeSe
) {
// Note: unwrap() here is not desirable, but unavoidable given non-null object is returned
// even for failing cases.
- Some(msg) => create_vendor_response(msg, env)
- .map_err(|e| {
- error!("{} failed with {:?}", function_name!(), &e);
- e
- })
- .unwrap_or_else(|_| create_invalid_vendor_response(env).unwrap()),
+
+ // Safety: create_vendor_response is unsafe, however msg is safely returned from
+ // native_send_raw_vendor_cmd.
+ Some(msg) => unsafe {
+ create_vendor_response(msg, env)
+ .map_err(|e| {
+ error!("{} failed with {:?}", function_name!(), &e);
+ e
+ })
+ .unwrap_or_else(|_| create_invalid_vendor_response(env).unwrap())
+ },
None => create_invalid_vendor_response(env).unwrap(),
}
}
@@ -847,12 +881,15 @@ pub extern "system" fn Java_com_android_server_uwb_jni_NativeUwbManager_nativeSe
),
function_name!(),
) {
- Some(rr) => create_ranging_round_status(rr, env)
- .map_err(|e| {
- error!("{} failed with {:?}", function_name!(), &e);
- e
- })
- .unwrap_or(*JObject::null()),
+ // Safety: rr is safely returned from native_set_ranging_rounds_dt_tag
+ Some(rr) => unsafe {
+ create_ranging_round_status(rr, env)
+ .map_err(|e| {
+ error!("{} failed with {:?}", function_name!(), &e);
+ e
+ })
+ .unwrap_or(*JObject::null())
+ },
None => *JObject::null(),
}
}
@@ -871,6 +908,62 @@ fn native_set_ranging_rounds_dt_tag(
uci_manager.session_update_active_rounds_dt_tag(session_id, indexes)
}
+/// Send a data packet to the remote device.
+#[no_mangle]
+pub extern "system" fn Java_com_android_server_uwb_jni_NativeUwbManager_nativeSendData(
+ env: JNIEnv,
+ obj: JObject,
+ session_id: jint,
+ address: jbyteArray,
+ dest_fira_component: jbyte,
+ uci_sequence_number: jbyte,
+ app_payload_data: jbyteArray,
+ chip_id: JString,
+) -> jbyte {
+ debug!("{}: enter", function_name!());
+ byte_result_helper(
+ native_send_data(
+ env,
+ obj,
+ session_id,
+ address,
+ dest_fira_component,
+ uci_sequence_number,
+ app_payload_data,
+ chip_id,
+ ),
+ function_name!(),
+ )
+}
+
+#[allow(clippy::too_many_arguments)]
+fn native_send_data(
+ env: JNIEnv,
+ obj: JObject,
+ session_id: jint,
+ address: jbyteArray,
+ dest_fira_component: jbyte,
+ uci_sequence_number: jbyte,
+ app_payload_data: jbyteArray,
+ chip_id: JString,
+) -> Result<()> {
+ let uci_manager = Dispatcher::get_uci_manager(env, obj, chip_id)
+ .map_err(|_| Error::ForeignFunctionInterface)?;
+ let address_bytearray =
+ env.convert_byte_array(address).map_err(|_| Error::ForeignFunctionInterface)?;
+ let app_payload_data_bytearray =
+ env.convert_byte_array(app_payload_data).map_err(|_| Error::ForeignFunctionInterface)?;
+ let destination_fira_component =
+ FiraComponent::from_u8(dest_fira_component as u8).ok_or(Error::BadParameters)?;
+ uci_manager.send_data_packet(
+ session_id as u32,
+ address_bytearray,
+ destination_fira_component,
+ uci_sequence_number as u8,
+ app_payload_data_bytearray,
+ )
+}
+
/// Get the class loader object. Has to be called from a JNIEnv where the local java classes are
/// loaded. Results in a global reference to the class loader object that can be used to look for
/// classes in other native thread.
@@ -886,8 +979,8 @@ fn get_class_loader_obj(env: &JNIEnv) -> Result<GlobalRef> {
.call_method_unchecked(
ranging_data_class,
get_class_loader_method,
- JavaType::Object("java/lang/ClassLoader".into()),
- &[JValue::Void],
+ ReturnType::Object,
+ &[jvalue::from(JValue::Void)],
)
.map_err(|_| Error::ForeignFunctionInterface)?;
let class_loader_jobject = class_loader.l().map_err(|_| Error::ForeignFunctionInterface)?;
diff --git a/tests/cts/hostsidetests/multidevices/uwb/lib/uwb_ranging_params.py b/tests/cts/hostsidetests/multidevices/uwb/lib/uwb_ranging_params.py
index 0d5a333a..e7939b8c 100644
--- a/tests/cts/hostsidetests/multidevices/uwb/lib/uwb_ranging_params.py
+++ b/tests/cts/hostsidetests/multidevices/uwb/lib/uwb_ranging_params.py
@@ -72,6 +72,9 @@ class FiraParamEnums:
MULTICAST_LIST_UPDATE_ACTION_ADD = 0
MULTICAST_LIST_UPDATE_ACTION_DELETE = 1
+ # sts config
+ STS_CONFIG_STATIC = 0
+
@dataclasses.dataclass
class UwbRangingReconfigureParams():
@@ -127,6 +130,7 @@ class UwbRangingParams():
multi_node_mode: Ranging mode. Possible values 1 to 1 or 1 to many.
vendor_id: Ranging device vendor ID.
static_sts_iv: Static STS value.
+ sts_config: STS config.
Example:
An example of UWB ranging parameters passed to sl4a is below.
@@ -176,6 +180,7 @@ class UwbRangingParams():
vendor_id: List[int] = dataclasses.field(default_factory=lambda: [5, 6])
static_sts_iv: List[int] = dataclasses.field(
default_factory=lambda: [5, 6, 7, 8, 9, 10])
+ sts_config: int = FiraParamEnums.STS_CONFIG_STATIC
def to_dict(self) -> Dict[str, Any]:
"""Returns UWB ranging parameters in dictionary for sl4a.
@@ -205,6 +210,7 @@ class UwbRangingParams():
"multiNodeMode": self.multi_node_mode,
"vendorId": self.vendor_id,
"staticStsIV": self.static_sts_iv,
+ "stsConfig": self.sts_config,
}
def update(self, **kwargs: Any):
diff --git a/tests/cts/hostsidetests/multidevices/uwb/ranging_test.py b/tests/cts/hostsidetests/multidevices/uwb/ranging_test.py
index da0596e3..7c46360d 100644
--- a/tests/cts/hostsidetests/multidevices/uwb/ranging_test.py
+++ b/tests/cts/hostsidetests/multidevices/uwb/ranging_test.py
@@ -83,8 +83,7 @@ class RangingTest(uwb_base_test.UwbBaseTest):
self.responder.close_ranging()
self.initiator.close_ranging()
- def teardown_class(self):
- super().teardown_class()
+ def on_fail(self, record):
for count, ad in enumerate(self.android_devices):
test_name = "initiator" if not count else "responder"
ad.take_bug_report(
diff --git a/tests/cts/hostsidetests/multidevices/uwb/snippet/UwbManagerSnippet.java b/tests/cts/hostsidetests/multidevices/uwb/snippet/UwbManagerSnippet.java
index 0926e615..b58fa04e 100644
--- a/tests/cts/hostsidetests/multidevices/uwb/snippet/UwbManagerSnippet.java
+++ b/tests/cts/hostsidetests/multidevices/uwb/snippet/UwbManagerSnippet.java
@@ -452,15 +452,23 @@ public class UwbManagerSnippet implements Snippet {
if (j.has("preamble")) {
builder.setPreambleCodeIndex(j.getInt("preamble"));
}
- if (j.has("vendorId")) {
- JSONArray jArray = j.getJSONArray("vendorId");
- byte[] bArray = convertJSONArrayToByteArray(jArray);
- builder.setVendorId(bArray);
- }
- if (j.has("staticStsIV")) {
- JSONArray jArray = j.getJSONArray("staticStsIV");
- byte[] bArray = convertJSONArrayToByteArray(jArray);
- builder.setStaticStsIV(bArray);
+ if (j.getInt("stsConfig") == FiraParams.STS_CONFIG_STATIC) {
+ JSONArray jVendorIdArray = j.getJSONArray("vendorId");
+ builder.setVendorId(convertJSONArrayToByteArray(jVendorIdArray));
+ JSONArray jStatisStsIVArray = j.getJSONArray("staticStsIV");
+ builder.setStaticStsIV(convertJSONArrayToByteArray(jStatisStsIVArray));
+ } else if (j.getInt("stsConfig") == FiraParams.STS_CONFIG_PROVISIONED) {
+ builder.setStsConfig(j.getInt("stsConfig"));
+ JSONArray jSessionKeyArray = j.getJSONArray("sessionKey");
+ builder.setSessionKey(convertJSONArrayToByteArray(jSessionKeyArray));
+ } else if (j.getInt(
+ "stsConfig") == FiraParams.STS_CONFIG_PROVISIONED_FOR_CONTROLEE_INDIVIDUAL_KEY) {
+ builder.setStsConfig(j.getInt("stsConfig"));
+ JSONArray jSessionKeyArray = j.getJSONArray("sessionKey");
+ builder.setSessionKey(convertJSONArrayToByteArray(jSessionKeyArray));
+ JSONArray jSubSessionKeyArray = j.getJSONArray("subSessionKey");
+ builder.setSubsessionKey(convertJSONArrayToByteArray(jSubSessionKeyArray));
+ builder.setSubSessionId(j.getInt("subSessionId"));
}
if (j.has("aoaResultRequest")) {
builder.setAoaResultRequest(j.getInt("aoaResultRequest"));
diff --git a/tests/cts/hostsidetests/multidevices/uwb/uwb_manager_test.py b/tests/cts/hostsidetests/multidevices/uwb/uwb_manager_test.py
index a107d988..70b0e50b 100644
--- a/tests/cts/hostsidetests/multidevices/uwb/uwb_manager_test.py
+++ b/tests/cts/hostsidetests/multidevices/uwb/uwb_manager_test.py
@@ -45,8 +45,7 @@ class UwbManagerTest(uwb_base_test.UwbBaseTest):
super().setup_class()
self.dut = self.android_devices[0]
- def teardown_class(self):
- super().teardown_class()
+ def on_fail(self, record):
self.dut.take_bug_report(destination=self.current_test_info.output_path)
### Helper methods ###
diff --git a/tests/cts/tests/Android.bp b/tests/cts/tests/Android.bp
index 306606fb..81b7e70a 100644
--- a/tests/cts/tests/Android.bp
+++ b/tests/cts/tests/Android.bp
@@ -40,4 +40,5 @@ android_test {
],
srcs: ["src/**/*.java"],
platform_apis: true,
+ min_sdk_version: "31",
}
diff --git a/tests/cts/tests/src/android/uwb/cts/UwbManagerTest.java b/tests/cts/tests/src/android/uwb/cts/UwbManagerTest.java
index 731d8944..44df61bf 100644
--- a/tests/cts/tests/src/android/uwb/cts/UwbManagerTest.java
+++ b/tests/cts/tests/src/android/uwb/cts/UwbManagerTest.java
@@ -871,6 +871,7 @@ public class UwbManagerTest {
FiraOpenSessionParams firaOpenSessionParams = new FiraOpenSessionParams.Builder()
.setProtocolVersion(new FiraProtocolVersion(1, 1))
.setSessionId(1)
+ .setSessionType(FiraParams.SESSION_TYPE_RANGING)
.setStsConfig(FiraParams.STS_CONFIG_STATIC)
.setVendorId(new byte[]{0x5, 0x6})
.setStaticStsIV(new byte[]{0x5, 0x6, 0x9, 0xa, 0x4, 0x6})