Switch to static mode and recenter whenever screen moves

This logic intends to solve issues related to using head-tracking
while moving (e.g. being in a moving vehicle of walking).

We do so by switching to static mode and recentering whenever we
detect a significant motion of the screen.

Test: Manual verification via the SpatialAudioDemo app.
Test: atest --host libheadtracking-test
Change-Id: Iae5090c5a315d31ff89ada8d8a13694ea68ccf8e
diff --git a/media/libheadtracking/HeadTrackingProcessor.cpp b/media/libheadtracking/HeadTrackingProcessor.cpp
index dd2244a..257ee42 100644
--- a/media/libheadtracking/HeadTrackingProcessor.cpp
+++ b/media/libheadtracking/HeadTrackingProcessor.cpp
@@ -46,6 +46,11 @@
                   .translationalThreshold = options.autoRecenterTranslationalThreshold,
                   .rotationalThreshold = options.autoRecenterRotationalThreshold,
           }),
+          mScreenStillnessDetector(StillnessDetector::Options{
+                  .windowDuration = options.screenStillnessWindowDuration,
+                  .translationalThreshold = options.screenStillnessTranslationalThreshold,
+                  .rotationalThreshold = options.screenStillnessRotationalThreshold,
+          }),
           mModeSelector(ModeSelector::Options{.freshnessTimeout = options.freshnessTimeout},
                         initialMode),
           mRateLimiter(PoseRateLimiter::Options{
@@ -83,6 +88,22 @@
     }
 
     void calculate(int64_t timestamp) override {
+        // Handle the screen first, since it might trigger a recentering of the head.
+        if (mWorldToScreenTimestamp.has_value()) {
+            const Pose3f worldToLogicalScreen = mScreenPoseDriftCompensator.getOutput();
+            mScreenStillnessDetector.setInput(mWorldToScreenTimestamp.value(),
+                                              worldToLogicalScreen);
+            bool screenStable = mScreenStillnessDetector.calculate(timestamp);
+            mModeSelector.setScreenStable(mWorldToScreenTimestamp.value(), screenStable);
+            // Whenever the screen is unstable, recenter the head pose.
+            if (!screenStable) {
+                recenter(true, false);
+            }
+            mScreenHeadFusion.setWorldToScreenPose(mWorldToScreenTimestamp.value(),
+                                                   worldToLogicalScreen);
+        }
+
+        // Handle head.
         if (mWorldToHeadTimestamp.has_value()) {
             Pose3f worldToHead = mHeadPoseDriftCompensator.getOutput();
             mHeadStillnessDetector.setInput(mWorldToHeadTimestamp.value(), worldToHead);
@@ -96,12 +117,6 @@
             mModeSelector.setWorldToHeadPose(mWorldToHeadTimestamp.value(), worldToHead);
         }
 
-        if (mWorldToScreenTimestamp.has_value()) {
-            const Pose3f worldToLogicalScreen = mScreenPoseDriftCompensator.getOutput();
-            mScreenHeadFusion.setWorldToScreenPose(mWorldToScreenTimestamp.value(),
-                                                   worldToLogicalScreen);
-        }
-
         auto maybeScreenToHead = mScreenHeadFusion.calculate();
         if (maybeScreenToHead.has_value()) {
             mModeSelector.setScreenToHeadPose(maybeScreenToHead->timestamp,
@@ -131,6 +146,7 @@
         }
         if (recenterScreen) {
             mScreenPoseDriftCompensator.recenter();
+            mScreenStillnessDetector.reset();
         }
 
         // If a sensor being recentered is included in the current mode, apply rate limiting to
@@ -155,6 +171,7 @@
     PoseDriftCompensator mHeadPoseDriftCompensator;
     PoseDriftCompensator mScreenPoseDriftCompensator;
     StillnessDetector mHeadStillnessDetector;
+    StillnessDetector mScreenStillnessDetector;
     ScreenHeadFusion mScreenHeadFusion;
     ModeSelector mModeSelector;
     PoseRateLimiter mRateLimiter;