summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorShreshta Manu <shreshtabm@google.com>2024-05-14 21:54:07 +0000
committerShreshta Manu <shreshtabm@google.com>2024-05-14 22:06:09 +0000
commit6ab71d40b93ba02abbea47a5752e50bd7df7fc8e (patch)
treeb51d05c9a54585338e66c3816df219e79bbc6984
parentdabf6196834edc862a526eb156c2d63c5c24ce5f (diff)
downloadUwb-6ab71d40b93ba02abbea47a5752e50bd7df7fc8e.tar.gz
[generic-ranging] Add multi sensor interface to generic ranging
Arcore apk portion is commented out. Bug: 331206299 Test: Compiles Change-Id: I47c0ed89a93e2321a923e8554e39b06e8600122f
-rw-r--r--generic_ranging/Android.bp12
-rw-r--r--generic_ranging/proto/Android.bp36
-rw-r--r--generic_ranging/proto/src/debug_log.proto69
-rw-r--r--generic_ranging/proto/src/estimate.proto72
-rw-r--r--generic_ranging/proto/src/multi_sensor_finder_configuration.proto284
-rw-r--r--generic_ranging/src/com/android/ranging/generic/ranging/DefaultFusionConfig.java30
-rw-r--r--generic_ranging/src/com/android/ranging/generic/ranging/FusionData.java73
-rw-r--r--generic_ranging/src/com/android/ranging/generic/ranging/PrecisionRangingConfig.java36
-rw-r--r--generic_ranging/src/com/android/sensor/AndroidMultiSensorFinderInterface.java162
-rw-r--r--generic_ranging/src/com/android/sensor/ArCoreMultiSensorFinder.java486
-rw-r--r--generic_ranging/src/com/android/sensor/Estimate.java107
-rw-r--r--generic_ranging/src/com/android/sensor/InstallStatus.java36
-rw-r--r--generic_ranging/src/com/android/sensor/MultiSensorFinderListener.java22
-rw-r--r--generic_ranging/src/com/android/sensor/SessionWithArCoreNative.java84
-rw-r--r--generic_ranging/src/com/android/sensor/Status.java63
15 files changed, 1492 insertions, 80 deletions
diff --git a/generic_ranging/Android.bp b/generic_ranging/Android.bp
index 7d129147..454cb50b 100644
--- a/generic_ranging/Android.bp
+++ b/generic_ranging/Android.bp
@@ -23,16 +23,7 @@ java_library {
min_sdk_version: "34",
installable: false,
srcs: [
- //"src/**/*.java",
- "src/**/RangingTechnology.java",
- "src/**/RangingData.java",
- "src/**/RangingAdapter.java",
- "src/**/FusionData.java",
- "src/**/PrecisionData.java",
- "src/**/PrecisionRanging.java",
- "src/**/PrecisionRangingConfig.java",
- "src/**/UwbAdapter.java",
- "src/**/PrecisionRangingImpl.java",
+ "src/**/*.java",
],
plugins: [
"auto_value_plugin",
@@ -48,6 +39,7 @@ java_library {
"dagger2",
"framework-annotations-lib",
"guava",
+ "multi-sensor-finder-configuration-java-proto",
"uwb_androidx_backend",
],
visibility: [
diff --git a/generic_ranging/proto/Android.bp b/generic_ranging/proto/Android.bp
new file mode 100644
index 00000000..4c08354a
--- /dev/null
+++ b/generic_ranging/proto/Android.bp
@@ -0,0 +1,36 @@
+// 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 {
+ default_team: "trendy_team_fwk_uwb",
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+filegroup {
+ name: "multi-sensor-proto",
+ srcs: [
+ "src/*.proto",
+ ],
+}
+
+java_library {
+ name: "multi-sensor-finder-configuration-java-proto",
+ proto: {
+ type: "lite",
+ },
+ sdk_version: "system_current",
+ min_sdk_version: "34",
+ srcs: [
+ "src/estimate.proto",
+ "src/debug_log.proto",
+ "src/multi_sensor_finder_configuration.proto",
+ ],
+ apex_available: [
+ "com.android.uwb",
+ ],
+}
diff --git a/generic_ranging/proto/src/debug_log.proto b/generic_ranging/proto/src/debug_log.proto
new file mode 100644
index 00000000..6946eff1
--- /dev/null
+++ b/generic_ranging/proto/src/debug_log.proto
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2024 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.
+ */
+
+syntax = "proto3";
+
+package com.android.ranging.generic.proto;
+
+import "packages/modules/Uwb/generic_ranging/proto/src/estimate.proto";
+
+option java_package = "com.android.ranging.generic.proto";
+option java_multiple_files = true;
+
+message Event {
+ enum Type {
+ NONE = 0;
+ START = 1;
+ STOP = 2;
+ }
+
+ Type type = 1;
+ float timestamp_sec = 2;
+}
+
+message UwbDatum {
+ float range_m = 1;
+ int32 rssi_dbm = 2;
+ float timestamp_sec = 3;
+}
+
+message OdometryDatum {
+ float x_m = 1;
+ float y_m = 2;
+ float z_m = 3;
+ float timestamp_sec = 8;
+}
+
+message InputContainer {
+ oneof input {
+ UwbDatum uwb_datum = 1;
+ OdometryDatum odometry_datum = 3;
+ }
+}
+
+message LeanEstimate {
+ com.android.ranging.generic.proto.Estimate.Status status = 1;
+ float range_m = 2;
+ float bearing_rad = 3;
+ float estimated_beacon_position_error_std_dev_m = 4;
+ float timestamp_sec = 5;
+}
+
+message DebugLog {
+ repeated InputContainer inputs = 1;
+ repeated LeanEstimate outputs = 2;
+ repeated Event events = 3;
+} \ No newline at end of file
diff --git a/generic_ranging/proto/src/estimate.proto b/generic_ranging/proto/src/estimate.proto
new file mode 100644
index 00000000..8d86f294
--- /dev/null
+++ b/generic_ranging/proto/src/estimate.proto
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2024 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.
+ */
+
+syntax = "proto3";
+
+package com.android.ranging.generic.proto;
+
+option java_package = "com.android.ranging.generic.proto";
+option java_multiple_files = true;
+
+ // Next ID: 14
+ message Estimate {
+ enum Status {
+ UNSPECIFIED = 0;
+ // An estimate was successfully computed.
+ OK = 1;
+ // Could not produce an estimate. For example, no synchronized set of data
+ // is available.
+ ESTIMATE_NOT_AVAILABLE = 2;
+ // The filter has diverged and is attempting to recover.
+ RECOVERING = 3;
+ // Odometry failed and cannot recover.
+ ODOMETRY_ERROR = 4;
+ // The beacon is probably moving, and so cannot be tracked.
+ BEACON_MOVING_ERROR = 5;
+ // The configuration file contains an error and Finder can't be started.
+ CONFIGURATION_ERROR = 6;
+ // Permissions not granted to required sensors.
+ SENSOR_PERMISSION_DENIED = 7;
+ UNKNOWN_ERROR = 8;
+ // Tracking failed due to insufficient light. This can occur when using
+ // camera based odometry. The filter will automatically recover and produce
+ // an estimate when possible.
+ RECOVERING_FROM_FAILURE_DUE_TO_INSUFFICIENT_LIGHT = 9;
+ // Tracking failed due to excessive motion. The filter will automatically
+ // recover and produce an estimate when possible.
+ RECOVERING_FROM_FAILURE_DUE_TO_EXCESSIVE_MOTION = 10;
+ // Tracking failed due to insufficient features in the camera images. This
+ // can occur when using camera based odometry. The filter will automatically
+ // recover and produce an estimate when possible.
+ RECOVERING_FROM_FAILURE_DUE_TO_INSUFFICIENT_FEATURES = 11;
+ // Tracking failed because something else is using the camera. Tracking will
+ // recover automatically, but with a new origin.
+ RECOVERING_FROM_FAILURE_DUE_TO_CAMERA_UNAVAILABILITY = 12;
+ // Tracking failed due to a bad odometry state. The filter will
+ // automatically recover and produce an estimate when possible.
+ RECOVERING_FROM_FAILURE_DUE_TO_BAD_ODOMETRY_STATE = 13;
+ }
+ Status status = 1;
+ double range_m = 2;
+ double range_error_std_dev_m = 3;
+ // The bearing is with respect to the device Y-axis, positive ccw.
+ double bearing_rad = 4;
+ // This measure usually increases as you move closer to the beacon.
+ double bearing_error_std_dev_rad = 5;
+ // This measure does not vary with the distance to the beacon.
+ double estimated_beacon_position_error_std_dev_m = 7;
+ int64 timestamp_nanos = 6;
+ }
diff --git a/generic_ranging/proto/src/multi_sensor_finder_configuration.proto b/generic_ranging/proto/src/multi_sensor_finder_configuration.proto
new file mode 100644
index 00000000..985bcead
--- /dev/null
+++ b/generic_ranging/proto/src/multi_sensor_finder_configuration.proto
@@ -0,0 +1,284 @@
+/*
+ * Copyright (C) 2024 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.
+ */
+
+syntax = "proto3";
+
+package com.android.ranging.generic.proto;
+
+option java_package = "com.android.ranging.generic.proto";
+option java_multiple_files = true;
+
+enum ConfidenceLevel {
+CL_UNSPECIFIED = 0;
+CL_95 = 1; // 95 %
+CL_97P5 = 2; // 97.5 %
+CL_99 = 3; // 99 %
+}
+
+// Configuration for MultiSensorFinder including the configuration of sub
+// components.
+message MultiSensorFinderConfig {
+// If unset, default values will be used.
+ParticleFilterConfig particle_filter_config = 1;
+
+// If enabled, the filter will use UWB measurements.
+bool use_uwb_measurements = 2;
+
+// If enabled, the filter will use Wi-Fi RTT measurements.
+bool use_wifi_rtt_measurements = 3;
+
+// If unset, default values will be used.
+RangeMeasurementConfig uwb_range_measurement_config = 4;
+
+// If unset, default values will be used.
+RangeMeasurementConfig wifi_rtt_range_measurement_config = 5;
+
+// If unset, default values will be used.
+FuzzyUpdateSchedulerConfig fuzzy_update_scheduler_configuration = 6;
+
+double default_xy_update_process_noise_stddev_m = 7;
+
+// If unset, default values will be used.
+OdometryNoiseAdderConfig odometry_noise_adder_config = 8;
+
+// If unset, trilateration measurement updater will not be used.
+TrilaterationMeasurementUpdaterConfig
+trilateration_measurement_updater_config = 9;
+
+// The rate at which finder will poll the odometry provider, which is also
+// the maximum rate at which finder will generate estimates.
+uint32 odometry_polling_rate_hz = 10;
+
+// If unset, odometry throttler will not be used.
+OdometryThrottlerConfig odometry_throttler_config = 11;
+
+// If unset, OBEP will not be used.
+OdometryBasedEstimatePropagatorConfig
+odometry_based_estimate_propagator_config = 12;
+
+// If unset, the NIS divergence detector will not be used.
+NisDivergenceDetectorConfig nis_divergence_detector_config = 13;
+
+InitialStateSamplerConfig uwb_initial_state_sampler_config = 14;
+
+InitialStateSamplerConfig wifi_rtt_initial_state_sampler_config = 15;
+
+// Used to input and output logs for development and debugging. If unset,
+// the debug logger will not be used.
+DebugLoggerConfiguration debug_logger_configuration = 16;
+}
+
+// Configuration for the generic sensor model, which is simply a combination of
+// a Gaussian and a Uniform distribution.
+message GenericRangeSensorModelConfig {
+double gaussian_std_dev_m = 1;
+double max_sensor_range_m = 2;
+}
+
+// Configuration for the trilateration measurement updater, which uses
+// trilateration on a set of UWB range measurements to determine if the incoming
+// range measurements are consistent over some spatial region.
+message TrilaterationMeasurementUpdaterConfig {
+int32 num_measurements_for_trilateration = 2;
+double max_trilateration_rmse_m = 3;
+}
+
+// Configuration for the sensor model associated with a range measurement and
+// any additional checks/modifiers. Currently, only the GenericRangeSensorModel
+// is supported.
+message RangeMeasurementConfig {
+// Currently supported models for a range sensor. If unspecified, the generic
+// model will be used.
+enum RangeSensorModelType {
+UNSPECIFIED = 0;
+// A generic model that consists of a Gaussian + Uniform distribution.
+// The configuration message associated with this model is
+// GenericRangeSensorModelConfig.
+GENERIC = 1;
+// Switches the measurement model based on the variance heuristic.
+VARIANCE_BASED_SWITCHING = 2;
+}
+RangeSensorModelType sensor_model_type = 1;
+GenericRangeSensorModelConfig generic_range_sensor_model_configuration = 2;
+VarianceBasedSwitchingMeasurementModelConfig
+variance_based_switching_measurement_model_config = 4;
+// If unset, the distance traveled check will not be used.
+DistanceTraveledCheckConfig distance_traveled_check_config = 3;
+}
+
+// Configuration for a scheduler which determines when a motion + measurement
+// update should be done.
+//
+// For each odometry sample, it creates a frame (or window) of +-
+// max_frame_size_nanos, and waits max_wait_time_nanos for all data to show up
+// in this frame.
+//
+// max_wait_time_nanos is directly proportional to the filter latency.
+// Increasing max_frame_size_nanos will increase the number of measurements
+// used, but can decrease accuracy because of a greater tolerance on the
+// mismatch in timestamps between odometry and range sensor data.
+message FuzzyUpdateSchedulerConfig {
+uint64 max_wait_time_nanos = 1;
+uint64 max_frame_size_nanos = 2;
+int32 max_buffer_size = 3;
+}
+
+// The distance traveled check blocks measurements from being continually
+// absorbed if a user stands still. It's a simple way to prevent particle
+// depletion.
+message DistanceTraveledCheckConfig {
+double distance_traveled_threshold_m = 2;
+}
+
+message ParticleFilterConfig {
+int32 number_of_particles = 1;
+}
+
+// The odometry noise adder adds noise during a motion update based on the speed
+// of the user.
+//
+// The mapping from speed to noise stays flat at min_noise_std_dev until the
+// speed reaches min_speed_mps, after which it ramps linearly up to
+// max_speed_mps. For speed greater than max_speed_mps, it stays at
+// max_noise_std_dev.
+//
+//
+// max_speed_mps
+// /
+// ^ *
+// | ------------ max_noise_std_dev
+// sigma m | /
+// | /
+// | ------/ min_noise_std_dev
+// ----------------------------------> Speed m/s`
+// *
+// /
+// min_speed_mps
+message OdometryNoiseAdderConfig {
+int32 num_speed_filter_taps = 2;
+double min_noise_std_dev_m = 3;
+double max_noise_std_dev_m = 4;
+double min_speed_mps = 5;
+double max_speed_mps = 6;
+}
+
+message OdometryThrottlerConfig {
+int64 throttling_dt_nanos = 1;
+}
+
+message OdometryBasedEstimatePropagatorConfig {
+// The size of the odometry buffer. Set this based on the odometry polling
+// rate.
+int32 buffer_size = 1;
+}
+
+// Uses the Normalized Innovation Squared criteria to determine if the filter
+// has diverged. The Filter will reset if divergence is detected.
+message NisDivergenceDetectorConfig {
+// A larger buffer reduces sensitivity to noisy measurements.
+int32 nis_buffer_size = 1;
+
+// A higher level yields fewer false positives.
+ConfidenceLevel confidence_level = 2;
+
+// Caps the NIS score to reduce sensitivity to outliers. For example, a value
+// of 1 sigma will cap the NIS score to the 68.3 % Gaussian interval about the
+// mean.
+double nis_sigma_bound = 3;
+
+// The detector will only be active if the std dev of the filter's error in
+// the estimated beacon position is below this threshold.
+double activation_threshold_m = 4;
+
+// The default noise covariance used when computing the NIS for UWB if an
+// online computed value is not provided to the detector.
+double default_uwb_noise_covariance = 5;
+
+// The default noise covariance used when computing the NIS for Wi-Fi RTT if
+// an online computed value is not provided to the detector.
+double default_wifi_rtt_noise_covariance = 6;
+}
+
+message UniformModelConfig {
+double min_value = 1;
+double max_value = 2;
+}
+
+message GaussianModelConfig {
+double loc = 1;
+double scale = 2;
+}
+
+message ExponentiallyWeightedGaussianModelConfig {
+double lambda_scaled = 1;
+double loc = 2;
+double scale = 3;
+}
+
+message ModelConfigContainer {
+oneof model {
+UniformModelConfig uniform_model_config = 1;
+GaussianModelConfig gaussian_model_config = 2;
+ExponentiallyWeightedGaussianModelConfig
+exponentially_weighted_gaussian_model_config = 3;
+}
+}
+
+// Configuration for the variance based switching model.
+message VarianceBasedSwitchingMeasurementModelConfig {
+double switching_threshold = 1;
+
+ModelConfigContainer low_variance_model_config = 2;
+
+// If unset, measurement updates will not be done while the sensor range
+// variance is above the variance_threshold.
+ModelConfigContainer high_variance_model_config = 3;
+
+// The size of the window over which the variance used by this model is
+// computed.
+int32 variance_window_size = 4;
+}
+
+// Configuration for an initial state sampler.
+//
+// Please note that the mean of the distribution specified in the sampler
+// configs are about the measured value; if a Gaussian sampler is used with loc
+// = 0.1 and scale = 10, then the samples will be generated using:
+//
+// measured_range + Gaussian(loc, scale).
+//
+// Additionally, note that the exponential distribution will be flipped such
+// that it is skewed towardes -ve axis rather than the standard which is skewed
+// towardes the +ve axis.
+message InitialStateSamplerConfig {
+ModelConfigContainer range_sampler_config = 1;
+ModelConfigContainer bearing_sampler_config = 2;
+}
+
+message DebugLoggerConfiguration {
+// Debug logs will be automatically emitted to the sink when they exceed this
+// size.
+int32 autodump_size_threshold_bytes = 1;
+// The rate at which odometry inputs will be throttled. Throttling is disabled
+// if set to 0.
+int64 odometry_throttling_nanos = 2;
+// The rate at which UWB range measurements will be throttled. Throttling is
+// disabled if set to 0.
+int64 uwb_throttling_nanos = 3;
+// The rate at which output estimates will be throttled. Throttling is
+// disabled if set to 0.
+int64 estimate_throttling_nanos = 4;
+}
diff --git a/generic_ranging/src/com/android/ranging/generic/ranging/DefaultFusionConfig.java b/generic_ranging/src/com/android/ranging/generic/ranging/DefaultFusionConfig.java
index a6475210..7b1416b9 100644
--- a/generic_ranging/src/com/android/ranging/generic/ranging/DefaultFusionConfig.java
+++ b/generic_ranging/src/com/android/ranging/generic/ranging/DefaultFusionConfig.java
@@ -16,21 +16,21 @@
package com.android.ranging.generic.ranging;
-import location.bluemoon.finder.ConfidenceLevel;
-import location.bluemoon.finder.DistanceTraveledCheckConfig;
-import location.bluemoon.finder.ExponentiallyWeightedGaussianModelConfig;
-import location.bluemoon.finder.FuzzyUpdateSchedulerConfig;
-import location.bluemoon.finder.InitialStateSamplerConfig;
-import location.bluemoon.finder.ModelConfigContainer;
-import location.bluemoon.finder.MultiSensorFinderConfig;
-import location.bluemoon.finder.NisDivergenceDetectorConfig;
-import location.bluemoon.finder.OdometryBasedEstimatePropagatorConfig;
-import location.bluemoon.finder.OdometryNoiseAdderConfig;
-import location.bluemoon.finder.OdometryThrottlerConfig;
-import location.bluemoon.finder.ParticleFilterConfig;
-import location.bluemoon.finder.RangeMeasurementConfig;
-import location.bluemoon.finder.RangeMeasurementConfig.RangeSensorModelType;
-import location.bluemoon.finder.VarianceBasedSwitchingMeasurementModelConfig;
+import com.android.ranging.generic.proto.ConfidenceLevel;
+import com.android.ranging.generic.proto.DistanceTraveledCheckConfig;
+import com.android.ranging.generic.proto.ExponentiallyWeightedGaussianModelConfig;
+import com.android.ranging.generic.proto.FuzzyUpdateSchedulerConfig;
+import com.android.ranging.generic.proto.InitialStateSamplerConfig;
+import com.android.ranging.generic.proto.ModelConfigContainer;
+import com.android.ranging.generic.proto.MultiSensorFinderConfig;
+import com.android.ranging.generic.proto.NisDivergenceDetectorConfig;
+import com.android.ranging.generic.proto.OdometryBasedEstimatePropagatorConfig;
+import com.android.ranging.generic.proto.OdometryNoiseAdderConfig;
+import com.android.ranging.generic.proto.OdometryThrottlerConfig;
+import com.android.ranging.generic.proto.ParticleFilterConfig;
+import com.android.ranging.generic.proto.RangeMeasurementConfig;
+import com.android.ranging.generic.proto.RangeMeasurementConfig.RangeSensorModelType;
+import com.android.ranging.generic.proto.VarianceBasedSwitchingMeasurementModelConfig;
/** Default configuration for the Fusion algorithm. */
public final class DefaultFusionConfig {
diff --git a/generic_ranging/src/com/android/ranging/generic/ranging/FusionData.java b/generic_ranging/src/com/android/ranging/generic/ranging/FusionData.java
index f81a2afa..af13fb2a 100644
--- a/generic_ranging/src/com/android/ranging/generic/ranging/FusionData.java
+++ b/generic_ranging/src/com/android/ranging/generic/ranging/FusionData.java
@@ -16,9 +16,10 @@
package com.android.ranging.generic.ranging;
+import com.android.sensor.Estimate;
+import com.android.sensor.Status;
+
import com.google.auto.value.AutoValue;
-//import com.google.hardware.techeng.sensors.finder.Estimate;
-//import com.google.hardware.techeng.sensors.finder.Status;
/**
* Fusion data represents a fusion of data received from ranging technologies and data received from
@@ -63,40 +64,40 @@ public abstract class FusionData {
public abstract FusionData build();
}
-// public static FusionData fromFusionAlgorithmEstimate(Estimate estimate) {
-// return FusionData.builder()
-// .setFusionRange(estimate.getRangeM())
-// .setFusionRangeErrorStdDev(estimate.getRangeErrorStdDevM())
-// .setFusionBearing(estimate.getBearingRad())
-// .setFusionBearingErrorStdDev(estimate.getBearingErrorStdDevRad())
-// .setArCoreState(convertToArCoreStateFromStatus(estimate.getStatus()))
-// .build();
-// }
-
-// private static ArCoreState convertToArCoreStateFromStatus(Status status) {
-// switch (status) {
-// case OK:
-// return ArCoreState.OK;
-// case RECOVERING_FROM_FAILURE_DUE_TO_INSUFFICIENT_LIGHT:
-// return ArCoreState.POOR_LIGHTNING;
-// case RECOVERING_FROM_FAILURE_DUE_TO_EXCESSIVE_MOTION:
-// return ArCoreState.EXCESSIVE_MOTION;
-// case RECOVERING_FROM_FAILURE_DUE_TO_INSUFFICIENT_FEATURES:
-// return ArCoreState.INSUFFICIENT_FEATURES;
-// case RECOVERING_FROM_FAILURE_DUE_TO_CAMERA_UNAVAILABILITY:
-// return ArCoreState.CAMERA_UNAVAILABLE;
-// case ESTIMATE_NOT_AVAILABLE:
-// case RECOVERING:
-// case RECOVERING_FROM_FAILURE_DUE_TO_BAD_ODOMETRY_STATE:
-// case ODOMETRY_ERROR:
-// case BEACON_MOVING_ERROR:
-// case CONFIGURATION_ERROR:
-// case SENSOR_PERMISSION_DENIED_ERROR:
-// case UNKNOWN_ERROR:
-// return ArCoreState.BAD_STATE;
-// }
-// return ArCoreState.BAD_STATE;
-// }
+ public static FusionData fromFusionAlgorithmEstimate(Estimate estimate) {
+ return FusionData.builder()
+ .setFusionRange(estimate.getRangeM())
+ .setFusionRangeErrorStdDev(estimate.getRangeErrorStdDevM())
+ .setFusionBearing(estimate.getBearingRad())
+ .setFusionBearingErrorStdDev(estimate.getBearingErrorStdDevRad())
+ .setArCoreState(convertToArCoreStateFromStatus(estimate.getStatus()))
+ .build();
+ }
+
+ private static ArCoreState convertToArCoreStateFromStatus(Status status) {
+ switch (status) {
+ case OK:
+ return ArCoreState.OK;
+ case RECOVERING_FROM_FAILURE_DUE_TO_INSUFFICIENT_LIGHT:
+ return ArCoreState.POOR_LIGHTNING;
+ case RECOVERING_FROM_FAILURE_DUE_TO_EXCESSIVE_MOTION:
+ return ArCoreState.EXCESSIVE_MOTION;
+ case RECOVERING_FROM_FAILURE_DUE_TO_INSUFFICIENT_FEATURES:
+ return ArCoreState.INSUFFICIENT_FEATURES;
+ case RECOVERING_FROM_FAILURE_DUE_TO_CAMERA_UNAVAILABILITY:
+ return ArCoreState.CAMERA_UNAVAILABLE;
+ case ESTIMATE_NOT_AVAILABLE:
+ case RECOVERING:
+ case RECOVERING_FROM_FAILURE_DUE_TO_BAD_ODOMETRY_STATE:
+ case ODOMETRY_ERROR:
+ case BEACON_MOVING_ERROR:
+ case CONFIGURATION_ERROR:
+ case SENSOR_PERMISSION_DENIED_ERROR:
+ case UNKNOWN_ERROR:
+ return ArCoreState.BAD_STATE;
+ }
+ return ArCoreState.BAD_STATE;
+ }
/** State of ArCore */
public enum ArCoreState {
diff --git a/generic_ranging/src/com/android/ranging/generic/ranging/PrecisionRangingConfig.java b/generic_ranging/src/com/android/ranging/generic/ranging/PrecisionRangingConfig.java
index 6d9fe1f3..12dd5660 100644
--- a/generic_ranging/src/com/android/ranging/generic/ranging/PrecisionRangingConfig.java
+++ b/generic_ranging/src/com/android/ranging/generic/ranging/PrecisionRangingConfig.java
@@ -17,14 +17,14 @@
package com.android.ranging.generic.ranging;
import com.android.ranging.generic.RangingTechnology;
+import com.android.ranging.generic.proto.MultiSensorFinderConfig;
import com.google.auto.value.AutoValue;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import java.time.Duration;
-//import java.util.Optional;
-//import location.bluemoon.finder.MultiSensorFinderConfig;
+import java.util.Optional;
/** Configuration for Precision Ranging. */
@AutoValue
@@ -67,7 +67,7 @@ public abstract class PrecisionRangingConfig {
public abstract Duration getNoUpdateTimeout();
/** Returns the fusion algorithm configuration if present. */
- //public abstract Optional<MultiSensorFinderConfig> getFusionAlgorithmConfig();
+ public abstract Optional<MultiSensorFinderConfig> getFusionAlgorithmConfig();
/** Returns a builder for {@link PrecisionRangingConfig}. */
public static Builder builder() {
@@ -90,8 +90,8 @@ public abstract class PrecisionRangingConfig {
public abstract Builder setInitTimeout(Duration duration);
- //public abstract Builder setFusionAlgorithmConfig(MultiSensorFinderConfig
- // fusionAlgorithmConfig);
+ public abstract Builder setFusionAlgorithmConfig(MultiSensorFinderConfig
+ fusionAlgorithmConfig);
abstract PrecisionRangingConfig autoBuild();
@@ -101,20 +101,18 @@ public abstract class PrecisionRangingConfig {
!config.getRangingTechnologiesToRangeWith().isEmpty(),
"Ranging technologies to range with must contain at least one ranging "
+ "technology.");
-// Preconditions.checkArgument(
-// config.getUseFusingAlgorithm() == config.getFusionAlgorithmConfig()
-// .isPresent(),
-// "Fusion algorithm config must be set when and only when useFusingAlgorithm
-// is set to"
-// + " true.");
-// if (config.getUseFusingAlgorithm()
-// && config.getRangingTechnologiesToRangeWith().contains(RangingTechnology
-// .UWB)) {
-// Preconditions.checkArgument(
-// config.getFusionAlgorithmConfig().get().getUseUwbMeasurements(),
-// "Fusion algorithm should accept UWB measurements since UWB was
-// requested.");
-// }
+ Preconditions.checkArgument(
+ config.getUseFusingAlgorithm() == config.getFusionAlgorithmConfig()
+ .isPresent(),
+ "Fusion algorithm config must be set when and only when useFusingAlgorithm"
+ + "is set to");
+ if (config.getUseFusingAlgorithm()
+ && config.getRangingTechnologiesToRangeWith().contains(RangingTechnology
+ .UWB)) {
+ Preconditions.checkArgument(
+ config.getFusionAlgorithmConfig().get().getUseUwbMeasurements(),
+ "Fusion algorithm should accept UWB measurements since UWB was requested.");
+ }
return config;
}
}
diff --git a/generic_ranging/src/com/android/sensor/AndroidMultiSensorFinderInterface.java b/generic_ranging/src/com/android/sensor/AndroidMultiSensorFinderInterface.java
new file mode 100644
index 00000000..5f729109
--- /dev/null
+++ b/generic_ranging/src/com/android/sensor/AndroidMultiSensorFinderInterface.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2024 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.sensor;
+
+import android.app.Activity;
+import android.app.Application.ActivityLifecycleCallbacks;
+import android.content.Context;
+
+import java.util.concurrent.FutureTask;
+
+/**
+ * Precision Finder fuses range measurements from UWB, Wifi-RTT, and BLE HADM etc. with odometry to
+ * provide a range and bearing to the beacon.
+ *
+ * <p>Sample Usage:
+ *
+ * <p><code>
+ * // --- To create finder ---
+ * MultiSensorFinder finder = new MultiSensorFinder(config, args, ...);
+ * finder.SubscribeToEstimates("name_of_user", listener);
+ *
+ * // --- Checking availability of finder ---
+ * // IMPORTANT: Before calling start, please make that it is possible to run a particular finder
+ * // implementation on the device, and that the finder implementation has all prerequisites met:
+ * if (!finder.checkAvailability(context)) {
+ * return; // Can't use finder here.
+ * }
+ *
+ * switch (finder.requestInstall(activity)) {
+ * case USER_PROMPTED_TO_INSTALL_DEPENDENCIES:
+ * // The activity was switched and the user was requested to install dependencies.
+ * return;
+ * case USER_DECLINED_TO_INSTALL_DEPENDENCIES:
+ * // User declined to install dependencies.
+ * return;
+ * case DEVICE_INCOMPATIBLE:
+ * break;
+ * case OK:
+ * // Everything is good to go!
+ * break;
+ * }
+ *
+ * // --- Starting finder --- //
+ * finder.Start();
+ *
+ * // --- Providing data to finder --- //
+ * finder.updateWithUwbMeasurement(rangeMeters, timestampNanos);
+ * finder.updateWithWifiRTTMeasurement(rangeMeters, errorStdDevMeters, rssiDbm, timestampNanos);
+ * ...
+ *
+ * // --- Destroying finder --- //
+ * finder.stop();
+ * finder.delete();
+ * </code>
+ *
+ * <p>Please note that finder should be stopped when the activity in which it is running is switched
+ * to the background, so that all sensor streams that finder is using are stopped. This can be done
+ * in two ways:
+ *
+ * <ul>
+ * <li>Manually call finder.stop() in your activity's onPause method.
+ * <li>Call getApplication().registerActivityLifecycleCallbacks(finder) in your activity's
+ * onCreate, and finder will automatically call stop when the application goes to bg.
+ * </ul>
+ *
+ * Additionally, finder.delete() should be called when the application is destroyed. Again, this can
+ * be manually done in your applications onDestroy, or you can register finder to the activity's
+ * ActivityLifecycleCallbacks to do this for you automatically.
+ */
+public interface AndroidMultiSensorFinderInterface extends ActivityLifecycleCallbacks {
+
+ /**
+ * Checks if the device meets the requirements for running precision finder e.g. it has all the
+ * sensors etc.
+ */
+ FutureTask<Boolean> checkAvailability(Context context);
+
+ /**
+ * Checks if all dependencies have been installed, and if not, switch the activity and prompt
+ * the
+ * user to install them.
+ */
+ InstallStatus requestInstall(Activity activity);
+
+ /**
+ * Resets underlying variables, and starts odometry. Once started, MultiSensorFinder will accept
+ * measurements.
+ *
+ * <p>If MultiSensorFinder is already started, this method is a no-op.
+ *
+ * @return Status.OK if successful, and Status.ERROR_* otherwise.
+ */
+ Status start(Context context);
+
+ /**
+ * Stops producing estimates. Once stopped, MultiSensorFinder cannot accept measurements.
+ *
+ * <p>If already stopped, this method is a no-op.
+ *
+ * <p>@return Status.OK if successful, and Status.ERROR_* otherwise.
+ */
+ Status stop();
+
+ /**
+ * Adds a UWB measurement, which will be fused with other data to produce an estimate.
+ *
+ * <p>If MultiSensorFinder is stopped, this method is a no-op.
+ *
+ * <p>Note: The Android stack does not provide low level information on the received UWB
+ * signal,
+ * e.g. the number of peaks in the impulse response, or how much multipath is in the
+ * environment.
+ * It's not clear if the Fira API exposes this information, but it might be useful.
+ *
+ * @param rangeMeters The range measurement from UWB.
+ * @param timestampNanos The timestamp in nanoseconds associated with the measurement.
+ */
+ void updateWithUwbMeasurement(double rangeMeters, long timestampNanos);
+
+ /**
+ * Adds a Wifi-RTT measurement, which will be fused with other data to produce an estimate.
+ *
+ * <p>If MultiSensorFinder is stopped, this method is a no-op.
+ *
+ * @param rangeMeters The range measurement from Wifi-RTT.
+ * @param errorStdDevMeters The error bounds on the range measurement.
+ * @param rssiDbm Beacon to finder signal strength.
+ * @param timestampNanos The timestamp in nanoseconds associated with the measurement.
+ */
+ void updateWithWifiRttMeasurement(
+ double rangeMeters, double errorStdDevMeters, double rssiDbm, long timestampNanos);
+
+ /**
+ * Adds a subscriber that will be notified when a new Estimate is available. Note that Finder
+ * will
+ * not generate any estimates if it is stopped.
+ *
+ * @param listener The subscriber that will be registered.
+ */
+ void subscribeToEstimates(MultiSensorFinderListener listener);
+
+ /**
+ * Frees all native resources. This should be called when the application is destroyed, unless
+ * this class is registered to ActivityLifecycleCallbacks of the application using it, in which
+ * case it will be called automatically on the application's onDestroy.
+ */
+ void delete();
+}
diff --git a/generic_ranging/src/com/android/sensor/ArCoreMultiSensorFinder.java b/generic_ranging/src/com/android/sensor/ArCoreMultiSensorFinder.java
new file mode 100644
index 00000000..db434775
--- /dev/null
+++ b/generic_ranging/src/com/android/sensor/ArCoreMultiSensorFinder.java
@@ -0,0 +1,486 @@
+/*
+ * Copyright (C) 2024 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.sensor;
+
+import android.app.Activity;
+import android.content.Context;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.util.Log;
+
+import androidx.annotation.GuardedBy;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
+import com.android.ranging.generic.proto.MultiSensorFinderConfig;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.FutureTask;
+
+/**
+ * A MultiSensorFinder implementation that uses ARCore as the source of odometry.
+ *
+ * <p>The ArCoreMultiSensorFinder can be in two states: stopped and started.
+ *
+ * <p>Finder can transition between the started and stopped state via the start() and stop()
+ * methods. Any unrecoverable error or calling the delete method will transition finder to the
+ * stopped state. Every time finder enters the stopped state it wipes the state and the algorithm
+ * starts from scratch on a subsequent start. If finder encounters a recoverable error, it will
+ * return a RECOVERING_FROM_* status, in which case finder will not enter the stopped state and will
+ * continually provide feedback to the client on how to help it recover (e.g. the room is too dark).
+ * Once finder recovers, it will start finding the beacon from scratch.
+ *
+ * <p>All algo work and listener processing (except ARCore odometry which has its own thread)
+ * happens in a single thread, which can be user provided.
+ */
+public class ArCoreMultiSensorFinder implements AndroidMultiSensorFinderInterface {
+
+ private static final String TAG = ArCoreMultiSensorFinder.class.getSimpleName();
+ private static final int DEFAULT_ODOMETRY_POLLING_RATE_HZ = 10;
+
+ private static final long INVALID_POINTER_ADDRESS = 0;
+
+ /**
+ * ARCore's checkAvailability method can return UNKNOWN_CHECKING, meaning that it is still
+ * waiting
+ * on network to figure out if ARCore is available for a particular device. In that case, we
+ * will
+ * wait 200 milliseconds before trying again.
+ */
+ private static final long CHECK_AVAILABILITY_RETRY_DELAY_DURATION = 200;
+
+ /**
+ * How long we will wait for ARCore's checkAvailability to return a result before we give up
+ * .
+ */
+ private static final Duration CHECK_AVAILABILITY_RETRY_TIMEOUT_DURATION = Duration.ofSeconds(2);
+
+ /**
+ * The thread in which all work (except ARCore work) is done if the user did not provide a
+ * handler.
+ */
+ @Nullable
+ private HandlerThread mHandlerThread;
+
+ /**
+ * Handler to the thread in which all work (except ARCore work) is done. This can be provided by
+ * the user during construction.
+ */
+ private Handler mProcessingThreadHandler;
+
+ /**
+ * A variable for controlling ARCore's requestInstall behaviour: On first call, ask the user to
+ * install preqrequisites if required, and do not ask again on subsequent calls.
+ */
+ private boolean userRequestedInstall = false;
+
+ private final MultiSensorFinderConfig mConfig;
+
+ // Used to indicate whether this class should run its own thread for processing, or use a
+ // handler
+ // that was provided by the client.
+ private final boolean processInOwnThread;
+
+ /**
+ * Although all processing is done in a single thread, the user can still call stop/delete
+ * methods
+ * from any threads they wish. This mutex is used to guard against that.
+ */
+ private final Object mutex = new Object();
+
+ private final long odometryPollingDelayMs;
+
+ private final SessionWithArCoreNative sessionWithArCoreNative;
+
+ // Tracks whether the odometry polling runnable is running or not. This is used to guard against
+ // the case where a stop and start are called in rapid succession, and the previous odometry
+ // polling runnable did not have time to stop.
+ @GuardedBy("mutex")
+ private boolean odometryPollingRunnableIsActive = false;
+
+ /**
+ * To avoid generating garbage, native methods will populate their results in this member
+ * variable.
+ */
+ @GuardedBy("mutex")
+ private Estimate latestEstimate = new Estimate();
+
+ /** Check whether finder has been started. */
+ @GuardedBy("mutex")
+ private boolean started = false;
+
+ @GuardedBy("mutex")
+ private long nativeSessionPointer = INVALID_POINTER_ADDRESS;
+
+ @GuardedBy("mutex")
+ private final List<MultiSensorFinderListener> listeners = new ArrayList<>();
+
+ // A runnable that polls odometry and feeds it to the algorithm to generate an Estimate. This
+ // runnable is executed repeatedly at odometryPollingDelayMs.
+ private final Runnable pollAndProcessOdometryInLooperRunnable =
+ this::pollAndProcessOdometryInLooper;
+
+ // This executor is used to execute the checkAvailability method, which may take up to
+ // CHECK_AVAILABILITY_RETRY_TIMEOUT_DURATION to execute.
+ private final ExecutorService executorService;
+
+ /**
+ * Constructs ArCoreMultiSensorFinder with a user provided handler to which all algorithm and
+ * listener processing tasks will be submitted.
+ */
+ public ArCoreMultiSensorFinder(
+ MultiSensorFinderConfig config,
+ Handler processingThreadHandler,
+ SessionWithArCoreNative sessionWithArCoreNative) {
+ this.mConfig = config;
+ this.executorService = Executors.newSingleThreadExecutor();
+ this.sessionWithArCoreNative = sessionWithArCoreNative;
+
+ if (processingThreadHandler == null) {
+ processInOwnThread = true;
+ } else {
+ processInOwnThread = false;
+ this.mHandlerThread = null;
+ this.mProcessingThreadHandler = processingThreadHandler;
+ }
+
+ if (config.getOdometryPollingRateHz() > 0.0) {
+ this.odometryPollingDelayMs = (long) (1000.0 / config.getOdometryPollingRateHz());
+ } else {
+ this.odometryPollingDelayMs = (long) (1000.0 / DEFAULT_ODOMETRY_POLLING_RATE_HZ);
+ }
+ }
+
+ /**
+ * Constructs ArCoreMultiSensorFinder which will create and use its own thread and associated
+ * handler to which all algorithm and listener processing tasks will be submitted.
+ */
+ public ArCoreMultiSensorFinder(MultiSensorFinderConfig config) {
+ this(config, null, new SessionWithArCoreNative());
+ }
+
+ // For testing purposes only. A constructor for passing in a mocked SessionWithArCoreNative.
+ @VisibleForTesting
+ public ArCoreMultiSensorFinder(
+ MultiSensorFinderConfig config,
+ SessionWithArCoreNative sessionWithArCoreNative) {
+ this(config, null, sessionWithArCoreNative);
+ }
+
+ @Override
+ public FutureTask<Boolean> checkAvailability(Context context) {
+ FutureTask<Boolean> future =
+ new FutureTask<>(
+ () -> {
+ Instant startTime = Instant.now();
+ Instant deadline = startTime.plus(
+ CHECK_AVAILABILITY_RETRY_TIMEOUT_DURATION);
+
+// try {
+// while (Instant.now().isBefore(deadline)) {
+// ArCoreApk.Availability availability =
+// ArCoreApk.getInstance().checkAvailability(context);
+// if (availability.isTransient()) {
+// // Wait until we get a response.
+// TimeUnit.MILLISECONDS.sleep
+// (CHECK_AVAILABILITY_RETRY_DELAY_DURATION);
+// } else {
+// return availability.isSupported();
+// }
+// }
+// } catch (InterruptedException e) {
+// return false;
+// }
+
+ Log.w(TAG, "Deadline expired while checking for availability");
+ return false;
+ });
+
+ executorService.execute(future);
+ return future;
+ }
+
+ @Override
+ public InstallStatus requestInstall(Activity activity) {
+// try {
+// ArCoreApk.InstallStatus installStatus =
+// ArCoreApk.getInstance().requestInstall(activity, !userRequestedInstall);
+// switch (installStatus) {
+// case INSTALLED:
+// return InstallStatus.OK;
+// case INSTALL_REQUESTED:
+// userRequestedInstall = true;
+// return InstallStatus.USER_PROMPTED_TO_INSTALL_DEPENDENCIES;
+// }
+// return InstallStatus.UNKNOWN_ERROR;
+// } catch (UnavailableUserDeclinedInstallationException e) {
+// return InstallStatus.USER_DECLINED_TO_INSTALL_DEPENDENCIES;
+// } catch (UnavailableDeviceNotCompatibleException e) {
+// return InstallStatus.DEVICE_INCOMPATIBLE;
+// } catch (FatalException e) {
+// return InstallStatus.UNKNOWN_ERROR;
+// }
+ return InstallStatus.UNKNOWN_ERROR;
+ }
+
+ @Override
+ public Status start(Context context) {
+ synchronized (mutex) {
+ if (started) {
+ return Status.OK;
+ }
+
+ // Create and start the processing thread if it has not already been started.
+ if (processInOwnThread && this.mHandlerThread == null) {
+ this.mHandlerThread = new HandlerThread("MultiSensorFinder");
+ this.mHandlerThread.start();
+ this.mProcessingThreadHandler = new Handler(this.mHandlerThread.getLooper());
+ }
+
+ // Create the session if it hasn't already been created.
+ if (nativeSessionPointer == INVALID_POINTER_ADDRESS) {
+ nativeSessionPointer = sessionWithArCoreNative.createSession(mConfig.toByteArray());
+ }
+
+ if (nativeSessionPointer == INVALID_POINTER_ADDRESS) {
+ Log.w(TAG, "Could not create session.");
+ return Status.UNKNOWN_ERROR;
+ }
+
+ // Start the session.
+ Status status = sessionWithArCoreNative.start(nativeSessionPointer, context);
+ if (status != Status.OK) {
+ Log.w(TAG, "Could not start session: " + status);
+ return status;
+ }
+ started = true;
+
+ // Start odometry polling loop if it is not already running.
+ if (!odometryPollingRunnableIsActive) {
+ mProcessingThreadHandler.post(pollAndProcessOdometryInLooperRunnable);
+ odometryPollingRunnableIsActive = true;
+ }
+ }
+
+ return Status.OK;
+ }
+
+ @Override
+ public void updateWithUwbMeasurement(double rangeMeters, long timestampNanos) {
+ mProcessingThreadHandler.post(
+ () -> updateWithUwbMeasurementInLooper(rangeMeters, timestampNanos));
+ }
+
+ @Override
+ public void updateWithWifiRttMeasurement(
+ double rangeMeters, double errorStdDevMeters, double rssiDbm, long timestampNanos) {
+ mProcessingThreadHandler.post(
+ () ->
+ updateWithWifiRttMeasurementInLooper(
+ rangeMeters, errorStdDevMeters, rssiDbm, timestampNanos));
+ }
+
+ @Override
+ public void subscribeToEstimates(MultiSensorFinderListener listener) {
+ synchronized (mutex) {
+ listeners.add(listener);
+ }
+ }
+
+ @Override
+ public Status stop() {
+ synchronized (mutex) {
+ if (!started) {
+ return Status.OK;
+ }
+
+ Status status = sessionWithArCoreNative.stop(nativeSessionPointer);
+ if (status != Status.OK) {
+ Log.w(TAG, "Could not stop session: " + status);
+ }
+ started = false;
+ return status;
+ }
+ }
+
+ @Override
+ public void delete() {
+ synchronized (mutex) {
+ if (nativeSessionPointer != INVALID_POINTER_ADDRESS) {
+ Status status = stop();
+ if (status != Status.OK) {
+ Log.w(TAG, "Could not stop session: " + status);
+ }
+ sessionWithArCoreNative.deleteSession(nativeSessionPointer);
+ nativeSessionPointer = INVALID_POINTER_ADDRESS;
+ }
+
+ if (mHandlerThread != null) {
+ mHandlerThread.quit();
+ mHandlerThread = null;
+ }
+ }
+ }
+
+ @GuardedBy("mutex")
+ private void publishEstimate(Estimate estimate) {
+ for (MultiSensorFinderListener listener : listeners) {
+ listener.onUpdatedEstimate(estimate);
+ }
+ }
+
+ // A method that polls odometry at regular intervals and uses to it generate Estimates. This
+ // is intended to be called with other processing functions in one looper to avoid issues with
+ // multi-threading.
+ private void pollAndProcessOdometryInLooper() {
+ synchronized (mutex) {
+ if (!started) {
+ odometryPollingRunnableIsActive = false;
+ return;
+ }
+
+ // Attempt to update the estimate with new odometry data.
+ sessionWithArCoreNative.pollAndProcessOdometryUpdate(nativeSessionPointer);
+ sessionWithArCoreNative.getEstimate(nativeSessionPointer, latestEstimate);
+
+ // Publish estimate to all listeners.
+ if (latestEstimate.getStatus() != Status.ESTIMATE_NOT_AVAILABLE) {
+ publishEstimate(latestEstimate);
+ }
+
+ checkForErrors(latestEstimate);
+ }
+
+ mProcessingThreadHandler.postDelayed(
+ pollAndProcessOdometryInLooperRunnable, odometryPollingDelayMs);
+ }
+
+ private void updateWithUwbMeasurementInLooper(double rangeMeters, long timestampNanos) {
+ synchronized (mutex) {
+ if (!started) {
+ return;
+ }
+ sessionWithArCoreNative.updateWithUwbMeasurement(
+ nativeSessionPointer, rangeMeters, timestampNanos);
+
+ sessionWithArCoreNative.getEstimate(nativeSessionPointer, latestEstimate);
+ if (latestEstimate.getStatus() != Status.ESTIMATE_NOT_AVAILABLE) {
+ publishEstimate(latestEstimate);
+ }
+
+ checkForErrors(latestEstimate);
+ }
+ }
+
+ private void updateWithWifiRttMeasurementInLooper(
+ double rangeMeters, double errorStdDevMeters, double rssiDbm, long timestampNanos) {
+ synchronized (mutex) {
+ if (!started) {
+ return;
+ }
+ sessionWithArCoreNative.updateWithWifiRttMeasurement(
+ nativeSessionPointer, rangeMeters, errorStdDevMeters, rssiDbm, timestampNanos);
+
+ sessionWithArCoreNative.getEstimate(nativeSessionPointer, latestEstimate);
+ if (latestEstimate.getStatus() != Status.ESTIMATE_NOT_AVAILABLE) {
+ publishEstimate(latestEstimate);
+ }
+
+ checkForErrors(latestEstimate);
+ }
+ }
+
+ /**
+ * Checks if the latest estimate indicated an error, and transitions finder to stopped state if
+ * so.
+ */
+ @GuardedBy("mutex")
+ private void checkForErrors(Estimate estimate) {
+ // If we got anything but OK, ESTIMATE_NOT_AVAILABLE, or RECOVERING_*, that means there
+ // was an
+ // error, and we should quit.
+ if (estimate.getStatus() == Status.OK
+ || estimate.getStatus() == Status.ESTIMATE_NOT_AVAILABLE
+ || estimate.getStatus() == Status.RECOVERING_FROM_FAILURE_DUE_TO_INSUFFICIENT_LIGHT
+ || estimate.getStatus() == Status.RECOVERING_FROM_FAILURE_DUE_TO_EXCESSIVE_MOTION
+ || estimate.getStatus()
+ == Status.RECOVERING_FROM_FAILURE_DUE_TO_INSUFFICIENT_FEATURES
+ || estimate.getStatus()
+ == Status.RECOVERING_FROM_FAILURE_DUE_TO_CAMERA_UNAVAILABILITY
+ || estimate.getStatus()
+ == Status.RECOVERING_FROM_FAILURE_DUE_TO_BAD_ODOMETRY_STATE) {
+ return;
+ }
+ Status unused = stop();
+ }
+
+ @Override
+ public void onActivityCreated(Activity activity, Bundle bundle) {
+ }
+
+ @Override
+ public void onActivityStarted(Activity activity) {
+ }
+
+ @Override
+ public void onActivityResumed(Activity activity) {
+ }
+
+ public void onActivityResumeFragments(Activity activity) {
+ }
+
+ @Override
+ public void onActivitySaveInstanceState(Activity activity, Bundle bundle) {
+ }
+
+ @Override
+ public void onActivityStopped(Activity activity) {
+ }
+
+ @Override
+ public void onActivityPaused(Activity activity) {
+ Status unused = stop();
+ }
+
+ @Override
+ public void onActivityDestroyed(Activity activity) {
+ delete();
+ }
+
+ // For test purposes only. The estimate object is updated on the native side, which is mocked
+ // out
+ // for tests.
+ @VisibleForTesting
+ public void setEstimate(Estimate estimate) {
+ synchronized (mutex) {
+ latestEstimate = estimate;
+ }
+ }
+
+ @VisibleForTesting
+ public boolean isStarted() {
+ synchronized (mutex) {
+ return started;
+ }
+ }
+}
diff --git a/generic_ranging/src/com/android/sensor/Estimate.java b/generic_ranging/src/com/android/sensor/Estimate.java
new file mode 100644
index 00000000..f1ed841e
--- /dev/null
+++ b/generic_ranging/src/com/android/sensor/Estimate.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2024 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.sensor;
+
+/**
+ * Estimate of range and bearing returned by Finder. This is in 1:1 correspondence with
+ * location.bluemoon.Estimate proto. This class is usually populated from the native side.
+ */
+public class Estimate {
+
+ private Status status;
+
+ private double rangeM;
+
+ private double rangeErrorStdDevM;
+
+ // The bearing is with respect to the device Y-axis, positive ccw.
+ private double bearingRad;
+
+ private double bearingErrorStdDevRad;
+
+ private double estimatedBeaconPositionErrorStdDevM;
+
+ private long timestampNanos;
+
+ /** Create an "empty" estimate. */
+ public Estimate() {
+ status = Status.UNKNOWN_ERROR;
+ rangeM = 0.0;
+ rangeErrorStdDevM = 0.0;
+ bearingRad = 0.0;
+ bearingErrorStdDevRad = 0.0;
+ estimatedBeaconPositionErrorStdDevM = 0.0;
+ timestampNanos = 0;
+ }
+
+ public void setStatus(Status status) {
+ this.status = status;
+ }
+
+ public void setRangeM(double rangeM) {
+ this.rangeM = rangeM;
+ }
+
+ public void setRangeErrorStdDevM(double rangeErrorStdDevM) {
+ this.rangeErrorStdDevM = rangeErrorStdDevM;
+ }
+
+ public void setBearingRad(double bearingRad) {
+ this.bearingRad = bearingRad;
+ }
+
+ public void setBearingErrorStdDevRad(double bearingErrorStdDevRad) {
+ this.bearingErrorStdDevRad = bearingErrorStdDevRad;
+ }
+
+ public void setEstimatedBeaconPositionErrorStdDevM(double estimatedBeaconPositionErrorStdDevM) {
+ this.estimatedBeaconPositionErrorStdDevM = estimatedBeaconPositionErrorStdDevM;
+ }
+
+ public void setTimestampNanos(long timestampNanos) {
+ this.timestampNanos = timestampNanos;
+ }
+
+ public Status getStatus() {
+ return status;
+ }
+
+ public double getRangeM() {
+ return rangeM;
+ }
+
+ public double getRangeErrorStdDevM() {
+ return rangeErrorStdDevM;
+ }
+
+ /** The bearing is with respect to the device Y-axis, positive ccw. */
+ public double getBearingRad() {
+ return bearingRad;
+ }
+
+ public double getBearingErrorStdDevRad() {
+ return bearingErrorStdDevRad;
+ }
+
+ public double getEstimatedBeaconPositionErrorStdDevM() {
+ return estimatedBeaconPositionErrorStdDevM;
+ }
+
+ public long getTimestampNanos() {
+ return timestampNanos;
+ }
+} \ No newline at end of file
diff --git a/generic_ranging/src/com/android/sensor/InstallStatus.java b/generic_ranging/src/com/android/sensor/InstallStatus.java
new file mode 100644
index 00000000..a80e7801
--- /dev/null
+++ b/generic_ranging/src/com/android/sensor/InstallStatus.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2024 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.sensor;
+
+/** The status returned by MultiSensorFinder when requestInstall was called. */
+public enum InstallStatus {
+ /** Everything is installed and start can be called. */
+ OK,
+ /**
+ * The activity was switched, and the user was prompted to install dependencies. requestInstall
+ * must be called again before starting the session.
+ */
+ USER_PROMPTED_TO_INSTALL_DEPENDENCIES,
+ /**
+ * The user was asked to install dependencies, and the user rejected the request. The user will
+ * not be asked again until the app is restarted.
+ */
+ USER_DECLINED_TO_INSTALL_DEPENDENCIES,
+ /** ARCore cannot be installed on this device. */
+ DEVICE_INCOMPATIBLE,
+ UNKNOWN_ERROR
+}
diff --git a/generic_ranging/src/com/android/sensor/MultiSensorFinderListener.java b/generic_ranging/src/com/android/sensor/MultiSensorFinderListener.java
new file mode 100644
index 00000000..505eb51d
--- /dev/null
+++ b/generic_ranging/src/com/android/sensor/MultiSensorFinderListener.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2024 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.sensor;
+
+/** Listener interface to receive Estimates computed by MultiSensorFinderInterface classes. */
+public interface MultiSensorFinderListener {
+ void onUpdatedEstimate(Estimate estimate);
+}
diff --git a/generic_ranging/src/com/android/sensor/SessionWithArCoreNative.java b/generic_ranging/src/com/android/sensor/SessionWithArCoreNative.java
new file mode 100644
index 00000000..a6db4cd2
--- /dev/null
+++ b/generic_ranging/src/com/android/sensor/SessionWithArCoreNative.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2024 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.sensor;
+
+import android.content.Context;
+
+/** This class contains the Jave methods corresponding to Finder native APIs. */
+public class SessionWithArCoreNative {
+ /**
+ * The native library is loaded in the constructor so that the load can be mocked out in tests.
+ */
+ public SessionWithArCoreNative() {
+ System.loadLibrary("precisionfindingsessionwitharcorejni");
+ }
+
+ /**
+ * Creates the session. The memory allocated by this method must be freed by calling
+ * deleteSession.
+ *
+ * @return A non-zero value if the session was successfully created, zero otherwise.
+ */
+ public native long createSession(byte[] config);
+
+ /**
+ * Starts consuming sensor data streams. Once started, the session can be provided with range
+ * measurements to produce estimates.
+ *
+ * <p>IMPORTANT: Before calling start, the user must ensure that ARCore is available, and all
+ * prerequisites have been installed. This is done by using the checkAvailability and
+ * requestInstall methods.
+ * https://developers.google.com/ar/reference/java/com/google/ar/core/ArCoreApk
+ */
+ public native Status start(long sessionPointer, Context context);
+
+ /**
+ * Stops consuming all sensor data streams. Calling start after stop will start the session from
+ * scratch.
+ */
+ public native Status stop(long sessionPointer);
+
+ /**
+ * Polls and uses the latest odometry from ARCore. The result of this call can be obtained by
+ * calling getEstimate.
+ */
+ public native void pollAndProcessOdometryUpdate(long sessionPointer);
+
+ /**
+ * Forwards a UWB measurement to the underlying estimator. The result of this call can be
+ * obtained
+ * by calling getEstimate.
+ */
+ public native void updateWithUwbMeasurement(
+ long sessionPointer, double range, long timestampNanos);
+
+ /**
+ * Forwards a Wifi-RTT measurement to the underlying estimator. The result of this call can be
+ * obtained by calling getEstimate.
+ */
+ public native void updateWithWifiRttMeasurement(
+ long sessionPointer, double rangeM, double stdDevM, double rssi, long timestampNanos);
+
+ /**
+ * Returns the result of the latest call to pollAndProcessOdometryUpdate,
+ * updateWithUwbMeasurement, or updateWithWifiRttMeasurement.
+ */
+ public native void getEstimate(long sessionPointer, Estimate estimate);
+
+ /** Frees all native memory allocated by this session. */
+ public native void deleteSession(long sessionPointer);
+}
diff --git a/generic_ranging/src/com/android/sensor/Status.java b/generic_ranging/src/com/android/sensor/Status.java
new file mode 100644
index 00000000..a415ca49
--- /dev/null
+++ b/generic_ranging/src/com/android/sensor/Status.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2024 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.sensor;
+
+public enum Status {
+ /** An estimate was successfully computed. */
+ OK,
+ /** Could not produce an estimate. For example, no synchronized set of data is available. */
+ ESTIMATE_NOT_AVAILABLE,
+ /** The filter has diverged and is attempting to recover. */
+ RECOVERING,
+ /**
+ * Tracking failed due to insufficient light. This can occur when using camera based odometry.
+ * The
+ * filter will automatically recover and produce an estimate when possible.
+ */
+ RECOVERING_FROM_FAILURE_DUE_TO_INSUFFICIENT_LIGHT,
+ /**
+ * Tracking failed due to excessive motion. The filter will automatically recover and produce an
+ * estimate when possible.
+ */
+ RECOVERING_FROM_FAILURE_DUE_TO_EXCESSIVE_MOTION,
+ /**
+ * Tracking failed due to insufficient features in the camera images. This can occur when using
+ * camera based odometry. The filter will automatically recover and produce an estimate when
+ * possible.
+ */
+ RECOVERING_FROM_FAILURE_DUE_TO_INSUFFICIENT_FEATURES,
+ /**
+ * Tracking failed because something else is using the camera. Tracking will recover
+ * automatically, but with a new origin.
+ */
+ RECOVERING_FROM_FAILURE_DUE_TO_CAMERA_UNAVAILABILITY,
+ /**
+ * Tracking failed due to a bad odometry state. The filter will automatically recover and
+ * produce
+ * an estimate when possible.
+ */
+ RECOVERING_FROM_FAILURE_DUE_TO_BAD_ODOMETRY_STATE,
+ /** Odometry failed and cannot be recovered. */
+ ODOMETRY_ERROR,
+ /** The beacon is probably moving, and so cannot be tracked. */
+ BEACON_MOVING_ERROR,
+ /** The configuration file contains an error and Finder can't be started. */
+ CONFIGURATION_ERROR,
+ /** Permissions not granted to required sensors. */
+ SENSOR_PERMISSION_DENIED_ERROR,
+ UNKNOWN_ERROR,
+} \ No newline at end of file