firmware: Add neonkey Android build config
am: a9c3479b81

Change-Id: I0ba262f10013a8ed7d18ca1abccb3ffc4289fe4d
diff --git a/CleanSpec.mk b/CleanSpec.mk
new file mode 100644
index 0000000..be0d4a0
--- /dev/null
+++ b/CleanSpec.mk
@@ -0,0 +1,54 @@
+# Copyright 2017 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.
+#
+
+# If you don't need to do a full clean build but would like to touch
+# a file or delete some intermediate files, add a clean step to the end
+# of the list.  These steps will only be run once, if they haven't been
+# run before.
+#
+# E.g.:
+#     $(call add-clean-step, touch -c external/sqlite/sqlite3.h)
+#     $(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/STATIC_LIBRARIES/libz_intermediates)
+#
+# Always use "touch -c" and "rm -f" or "rm -rf" to gracefully deal with
+# files that are missing or have been moved.
+#
+# Use $(PRODUCT_OUT) to get to the "out/target/product/blah/" directory.
+# Use $(OUT_DIR) to refer to the "out" directory.
+#
+# If you need to re-do something that's already mentioned, just copy
+# the command and add it to the bottom of the list.  E.g., if a change
+# that you made last week required touching a file and a change you
+# made today requires touching the same file, just copy the old
+# touch step and add it to the end of the list.
+#
+# ************************************************
+# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST
+# ************************************************
+
+# For example:
+#$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/APPS/AndroidTests_intermediates)
+#$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/core_intermediates)
+#$(call add-clean-step, find $(OUT_DIR) -type f -name "IGTalkSession*" -print0 | xargs -0 rm -f)
+#$(call add-clean-step, rm -rf $(PRODUCT_OUT)/data/*)
+
+$(call add-clean-step, rm -f $(PRODUCT_OUT)/system/lib/hw/context_hub.default.so)
+$(call add-clean-step, rm -f $(PRODUCT_OUT)/system/lib64/hw/context_hub.default.so)
+$(call add-clean-step, rm -f $(PRODUCT_OUT)/system/lib/hw/sensors.*.so)
+$(call add-clean-step, rm -f $(PRODUCT_OUT)/system/lib64/hw/sensors.*.so)
+$(call add-clean-step, rm -f $(PRODUCT_OUT)/system/lib/hw/activity_recognition.*.so)
+$(call add-clean-step, rm -f $(PRODUCT_OUT)/system/lib64/hw/activity_recognition.*.so)
+$(call add-clean-step, rm -f $(PRODUCT_OUT)/system/lib/libhubconnection.so)
+$(call add-clean-step, rm -f $(PRODUCT_OUT)/system/lib64/libhubconnection.so)
diff --git a/contexthubhal/Android.mk b/contexthubhal/Android.mk
index 2b9f0e4..1600e1b 100644
--- a/contexthubhal/Android.mk
+++ b/contexthubhal/Android.mk
@@ -18,4 +18,6 @@
 
 LOCAL_MODULE := context_hub.default
 LOCAL_MODULE_TAGS := optional
+LOCAL_PROPRIETARY_MODULE := true
+
 include $(BUILD_SHARED_LIBRARY)
diff --git a/contexthubhal/nanohubhal.cpp b/contexthubhal/nanohubhal.cpp
index d54900a..fa3c5f8 100644
--- a/contexthubhal/nanohubhal.cpp
+++ b/contexthubhal/nanohubhal.cpp
@@ -39,7 +39,7 @@
 #include "system_comms.h"
 #include "nanohubhal.h"
 
-#define NANOHUB_LOCK_DIR        "/data/system/nanohub_lock"
+#define NANOHUB_LOCK_DIR        "/data/vendor/sensor/nanohub_lock"
 #define NANOHUB_LOCK_FILE       NANOHUB_LOCK_DIR "/lock"
 #define NANOHUB_LOCK_DIR_PERMS  (S_IRUSR | S_IWUSR | S_IXUSR)
 
diff --git a/firmware/app/chre/common/chre_app.c b/firmware/app/chre/common/chre_app.c
index cd3c7e7..c828314 100644
--- a/firmware/app/chre/common/chre_app.c
+++ b/firmware/app/chre/common/chre_app.c
@@ -257,6 +257,18 @@
         u.msg.messageSize = hdr->size;
         break;
     }
+    case EVT_APP_SENSOR_SELF_TEST:
+    case EVT_APP_SENSOR_MARSHALL:
+    case EVT_APP_SENSOR_SEND_ONE_DIR_EVT:
+    case EVT_APP_SENSOR_CFG_DATA:
+    case EVT_APP_SENSOR_CALIBRATE:
+    case EVT_APP_SENSOR_TRIGGER:
+    case EVT_APP_SENSOR_FLUSH:
+    case EVT_APP_SENSOR_SET_RATE:
+    case EVT_APP_SENSOR_FW_UPLD:
+    case EVT_APP_SENSOR_POWER:
+        // sensor events; pass through
+        break;
     default:
         // ignore any other system events; OS may send them to any app
         if (evt < EVT_NO_FIRST_USER_EVENT)
diff --git a/firmware/os/algos/calibration/accelerometer/accel_cal.c b/firmware/os/algos/calibration/accelerometer/accel_cal.c
index a789ce9..0a6d96d 100644
--- a/firmware/os/algos/calibration/accelerometer/accel_cal.c
+++ b/firmware/os/algos/calibration/accelerometer/accel_cal.c
@@ -17,7 +17,6 @@
 #include "calibration/accelerometer/accel_cal.h"
 #include <errno.h>
 #include <math.h>
-#include <seos.h>
 #include <stdio.h>
 #include <string.h>
 #include "calibration/magnetometer/mag_cal.h"
@@ -46,6 +45,10 @@
   62  // Putting all Temp counts in last bucket for temp > 62 degree C.
 #define HIST_COUNT 9
 #endif
+#ifdef IMU_TEMP_DBG_ENABLED
+#define IMU_TEMP_DELTA_TIME_NANOS \
+  5000000000 // Printing every 5 seconds IMU temp.
+#endif
 
 /////////// Start Debug //////////////////////
 
@@ -90,7 +93,7 @@
   int index = 0;
 
   // Take temp at every stillness detection.
-  adf->start_time = 0;
+  adf->start_time_nanos = 0;
   if (temp <= TEMP_HIST_LOW) {
     adf->t_hist[0] += 1;
     return;
@@ -177,24 +180,28 @@
 
   acc->x_bias = acc->y_bias = acc->z_bias = 0;
   acc->x_bias_new = acc->y_bias_new = acc->z_bias_new = 0;
+
+#ifdef IMU_TEMP_DBG_ENABLED
+  acc->temp_time_nanos = 0;
+#endif
 }
 
 // Stillness time check.
 static int stillnessBatchComplete(struct AccelStillDet *asd,
-                                  uint64_t sample_time_nsec) {
+                                  uint64_t sample_time_nanos) {
   int complete = 0;
 
   // Checking if enough data is accumulated to calc Mean and Var.
-  if ((sample_time_nsec - asd->start_time > asd->min_batch_window) &&
+  if ((sample_time_nanos - asd->start_time > asd->min_batch_window) &&
       (asd->nsamples > asd->min_batch_size)) {
-    if (sample_time_nsec - asd->start_time < asd->max_batch_window) {
+    if (sample_time_nanos - asd->start_time < asd->max_batch_window) {
       complete = 1;
     } else {
       // Checking for too long batch window, if yes reset and start over.
       asdReset(asd);
       return complete;
     }
-  } else if (sample_time_nsec - asd->start_time > asd->min_batch_window &&
+  } else if (sample_time_nanos - asd->start_time > asd->min_batch_window &&
              (asd->nsamples < asd->min_batch_size)) {
     // Not enough samples collected in max_batch_window during sample window.
     asdReset(asd);
@@ -207,7 +214,7 @@
 
 // Stillness Detection.
 static int accelStillnessDetection(struct AccelStillDet *asd,
-                                   uint64_t sample_time_nsec, float x, float y,
+                                   uint64_t sample_time_nanos, float x, float y,
                                    float z) {
   float inv = 0.0f;
   int complete = 0.0f;
@@ -223,9 +230,9 @@
 
   // Setting a new start time and wait until T0 is reached.
   if (++asd->nsamples == 1) {
-    asd->start_time = sample_time_nsec;
+    asd->start_time = sample_time_nanos;
   }
-  if (stillnessBatchComplete(asd, sample_time_nsec)) {
+  if (stillnessBatchComplete(asd, sample_time_nanos)) {
     // Getting 1/#samples and checking asd->nsamples != 0.
     if (0 < asd->nsamples) {
       inv = 1.0f / asd->nsamples;
@@ -462,19 +469,33 @@
 }
 
 // Accel Cal Runner.
-void accelCalRun(struct AccelCal *acc, uint64_t sample_time_nsec, float x,
+void accelCalRun(struct AccelCal *acc, uint64_t sample_time_nanos, float x,
                  float y, float z, float temp) {
   // Scaling to 1g, better for the algorithm.
   x *= KSCALE;
   y *= KSCALE;
   z *= KSCALE;
 
+  // DBG: IMU temp messages every 5s.
+#ifdef IMU_TEMP_DBG_ENABLED
+  if ((sample_time_nanos - acc->temp_time_nanos) > IMU_TEMP_DELTA_TIME_NANOS) {
+    CAL_DEBUG_LOG("IMU Temp Data: ",
+                  ", %s%d.%02d,  %llu, %s%d.%05d, %s%d.%05d, %s%d.%05d \n",
+                  CAL_ENCODE_FLOAT(temp, 2),
+                  (unsigned long long int)sample_time_nanos,
+                  CAL_ENCODE_FLOAT(acc->x_bias_new,5),
+                  CAL_ENCODE_FLOAT(acc->y_bias_new,5),
+                  CAL_ENCODE_FLOAT(acc->z_bias_new,5));
+    acc->temp_time_nanos = sample_time_nanos;
+    }
+#endif
+
   int temp_gate = 0;
 
   // Temp GATE.
   if (temp < MAX_TEMP && temp > MIN_TEMP) {
     // Checking if accel is still.
-    if (accelStillnessDetection(&acc->asd, sample_time_nsec, x, y, z)) {
+    if (accelStillnessDetection(&acc->asd, sample_time_nanos, x, y, z)) {
 #ifdef ACCEL_CAL_DBG_ENABLED
       // Creating temp hist data.
       accelTempHisto(&acc->adf, temp);
@@ -527,7 +548,7 @@
           acc->adf.e_z[acc->adf.n_o] = acc->ac1[temp_gate].agd.e_z;
           acc->adf.var_t[acc->adf.n_o] = acc->ac1[temp_gate].agd.var_t;
           acc->adf.mean_t[acc->adf.n_o] = acc->ac1[temp_gate].agd.mean_t;
-          acc->adf.cal_time[acc->adf.n_o] = sample_time_nsec;
+          acc->adf.cal_time[acc->adf.n_o] = sample_time_nanos;
           acc->adf.rad[acc->adf.n_o] = radius;
           acc->adf.n_o += 1;
 #endif
diff --git a/firmware/os/algos/calibration/accelerometer/accel_cal.h b/firmware/os/algos/calibration/accelerometer/accel_cal.h
index 03c65e9..5c63d78 100644
--- a/firmware/os/algos/calibration/accelerometer/accel_cal.h
+++ b/firmware/os/algos/calibration/accelerometer/accel_cal.h
@@ -95,7 +95,7 @@
 struct AccelStatsMem {
   // Temp (in degree C).
   uint32_t t_hist[TEMP_HISTOGRAM];
-  uint64_t start_time;
+  uint64_t start_time_nanos;
 
   // Offset update counter.
   uint32_t noff;
@@ -136,22 +136,27 @@
 #endif
 
   // Offsets are only updated while the accelerometer is not running. Hence need
-  // to store a new offset,
-  // which gets updated during a power down event.
+  // to store a new offset, which gets updated during a power down event.
   float x_bias_new, y_bias_new, z_bias_new;
 
   // Offset values that get subtracted from live data
   float x_bias, y_bias, z_bias;
+
+#ifdef IMU_TEMP_DBG_ENABLED
+  // Temporary time variable used to to print an IMU temperature value with a
+  // lower custom sample rate.
+  uint64_t temp_time_nanos;
+#endif
 };
 
 /* This function runs the accel calibration algorithm.
- * sample_time_nsec -> is the  sensor timestamp in ns and
+ * sample_time_nanos -> is the  sensor timestamp in ns and
  *                     is used to check the stillness time.
  * x,y,z            -> is the sensor data (m/s^2) for the three axes.
  *                     Data is converted to g’s inside the function.
  * temp             -> is the temperature of the IMU (degree C).
  */
-void accelCalRun(struct AccelCal *acc, uint64_t sample_time_nsec, float x,
+void accelCalRun(struct AccelCal *acc, uint64_t sample_time_nanos, float x,
                  float y, float z, float temp);
 
 /* This function initializes the accCalRun data struct.
diff --git a/firmware/os/algos/calibration/common/diversity_checker.c b/firmware/os/algos/calibration/common/diversity_checker.c
index 312be69..198a08d 100644
--- a/firmware/os/algos/calibration/common/diversity_checker.c
+++ b/firmware/os/algos/calibration/common/diversity_checker.c
@@ -22,6 +22,9 @@
 #include <string.h>
 #include "common/math/vec.h"
 
+#define MAX_FIT_MAG 70.0f
+#define MIN_FIT_MAG 20.0f
+
 // Struct initialization.
 void diversityCheckerInit(
     struct DiversityChecker* diverse_data,
@@ -188,7 +191,7 @@
 
 void diversityCheckerLocalFieldUpdate(struct DiversityChecker* diverse_data,
                                       float local_field) {
-  if ( local_field > 0 ) {
+  if ((local_field < MAX_FIT_MAG) && (local_field > MIN_FIT_MAG)) {
     // Updating threshold based on the local field information.
     diverse_data->threshold = diverse_data->threshold_tuning_param_sq *
         (local_field * local_field);
diff --git a/firmware/os/algos/calibration/gyroscope/gyro_cal.c b/firmware/os/algos/calibration/gyroscope/gyro_cal.c
index e6bc275..34bf153 100644
--- a/firmware/os/algos/calibration/gyroscope/gyro_cal.c
+++ b/firmware/os/algos/calibration/gyroscope/gyro_cal.c
@@ -27,10 +27,10 @@
 
 // Maximum gyro bias correction (should be set based on expected max bias
 // of the given sensor).
-#define MAX_GYRO_BIAS (0.096f)  // [rad/sec]
+#define MAX_GYRO_BIAS (0.1f)  // [rad/sec]
 
 // Converts units of radians to milli-degrees.
-#define RAD_TO_MILLI_DEGREES (float)(1e3f * 180.0f / M_PI)
+#define RAD_TO_MILLI_DEGREES (float)(1e3f * 180.0f / NANO_PI)
 
 #ifdef GYRO_CAL_DBG_ENABLED
 // The time value used to throttle debug messaging.
@@ -102,7 +102,10 @@
   GYRO_MINMAX_STILLNESS_MEAN,
   ACCEL_STATS,
   GYRO_STATS,
-  MAG_STATS
+  MAG_STATS,
+  ACCEL_STATS_TUNING,
+  GYRO_STATS_TUNING,
+  MAG_STATS_TUNING
 };
 
 /*
@@ -579,10 +582,11 @@
     return;
   }
 
-  // Check for timeout condition of watchdog.
+  // Check for the watchdog timeout condition (i.e., the time elapsed since the
+  // last received sample has exceeded the allowed watchdog duration).
   watchdog_timeout =
-      ((sample_time_nanos - gyro_cal->gyro_watchdog_start_nanos) >
-       gyro_cal->gyro_watchdog_timeout_duration_nanos);
+      (sample_time_nanos > gyro_cal->gyro_watchdog_timeout_duration_nanos +
+                               gyro_cal->gyro_watchdog_start_nanos);
 
   // If a timeout occurred then reset to known good state.
   if (watchdog_timeout) {
@@ -759,9 +763,9 @@
       }
 #ifdef GYRO_CAL_DBG_ENABLED
       if (mean_not_stable) {
-        CAL_DEBUG_LOG(
-            "[GYRO_CAL:MEAN_STABILITY_GATE]",
-            "Exceeded the max variation in the stillness window mean values.");
+        CAL_DEBUG_LOG("[GYRO_CAL:MEAN_STABILITY_GATE]",
+                      "Exceeded the max variation in the gyro's stillness "
+                      "window mean values.");
       }
 #endif  // GYRO_CAL_DBG_ENABLED
       break;
@@ -1034,26 +1038,76 @@
 
     case MAG_STATS:
       if (gyro_cal->debug_gyro_cal.using_mag_sensor) {
-        CAL_DEBUG_LOG(
-            debug_tag,
-            "Cal#|Mag Mean|Var [uT|uT^2]: %lu, %s%d.%06d, "
-            "%s%d.%06d, %s%d.%06d, %s%d.%08d, %s%d.%08d, %s%d.%08d",
-            (unsigned long int)gyro_cal->debug_calibration_count,
-            CAL_ENCODE_FLOAT(gyro_cal->debug_gyro_cal.mag_mean[0], 6),
-            CAL_ENCODE_FLOAT(gyro_cal->debug_gyro_cal.mag_mean[1], 6),
-            CAL_ENCODE_FLOAT(gyro_cal->debug_gyro_cal.mag_mean[2], 6),
-            CAL_ENCODE_FLOAT(gyro_cal->debug_gyro_cal.gyro_var[0], 8),
-            CAL_ENCODE_FLOAT(gyro_cal->debug_gyro_cal.gyro_var[1], 8),
-            CAL_ENCODE_FLOAT(gyro_cal->debug_gyro_cal.gyro_var[2], 8));
+        CAL_DEBUG_LOG(debug_tag,
+                      "Cal#|Mag Mean|Var [uT|uT^2]: %lu, %s%d.%06d, "
+                      "%s%d.%06d, %s%d.%06d, %s%d.%08d, %s%d.%08d, %s%d.%08d",
+                      (unsigned long int)gyro_cal->debug_calibration_count,
+                      CAL_ENCODE_FLOAT(gyro_cal->debug_gyro_cal.mag_mean[0], 6),
+                      CAL_ENCODE_FLOAT(gyro_cal->debug_gyro_cal.mag_mean[1], 6),
+                      CAL_ENCODE_FLOAT(gyro_cal->debug_gyro_cal.mag_mean[2], 6),
+                      CAL_ENCODE_FLOAT(gyro_cal->debug_gyro_cal.mag_var[0], 8),
+                      CAL_ENCODE_FLOAT(gyro_cal->debug_gyro_cal.mag_var[1], 8),
+                      CAL_ENCODE_FLOAT(gyro_cal->debug_gyro_cal.mag_var[2], 8));
       } else {
         CAL_DEBUG_LOG(debug_tag,
                       "Cal#|Mag Mean|Var [uT|uT^2]: %lu, 0, 0, 0, -1.0, -1.0, "
                       "-1.0",
                       (unsigned long int)gyro_cal->debug_calibration_count);
       }
-
       break;
 
+#ifdef GYRO_CAL_DBG_TUNE_ENABLED
+    case ACCEL_STATS_TUNING:
+      CAL_DEBUG_LOG(
+          debug_tag,
+          "Accel Mean|Var [m/sec^2|(m/sec^2)^2]: %s%d.%06d, "
+          "%s%d.%06d, %s%d.%06d, %s%d.%08d, %s%d.%08d, %s%d.%08d",
+          CAL_ENCODE_FLOAT(gyro_cal->accel_stillness_detect.prev_mean_x, 6),
+          CAL_ENCODE_FLOAT(gyro_cal->accel_stillness_detect.prev_mean_y, 6),
+          CAL_ENCODE_FLOAT(gyro_cal->accel_stillness_detect.prev_mean_z, 6),
+          CAL_ENCODE_FLOAT(gyro_cal->accel_stillness_detect.win_var_x, 8),
+          CAL_ENCODE_FLOAT(gyro_cal->accel_stillness_detect.win_var_y, 8),
+          CAL_ENCODE_FLOAT(gyro_cal->accel_stillness_detect.win_var_z, 8));
+      break;
+
+    case GYRO_STATS_TUNING:
+      CAL_DEBUG_LOG(
+          debug_tag,
+          "Gyro Mean|Var [mdps|(rad/sec)^2]: %s%d.%06d, %s%d.%06d, %s%d.%06d, "
+          "%s%d.%08d, %s%d.%08d, %s%d.%08d",
+          CAL_ENCODE_FLOAT(gyro_cal->gyro_stillness_detect.prev_mean_x *
+                               RAD_TO_MILLI_DEGREES,
+                           6),
+          CAL_ENCODE_FLOAT(gyro_cal->gyro_stillness_detect.prev_mean_y *
+                               RAD_TO_MILLI_DEGREES,
+                           6),
+          CAL_ENCODE_FLOAT(gyro_cal->gyro_stillness_detect.prev_mean_z *
+                               RAD_TO_MILLI_DEGREES,
+                           6),
+          CAL_ENCODE_FLOAT(gyro_cal->gyro_stillness_detect.win_var_x, 8),
+          CAL_ENCODE_FLOAT(gyro_cal->gyro_stillness_detect.win_var_y, 8),
+          CAL_ENCODE_FLOAT(gyro_cal->gyro_stillness_detect.win_var_z, 8));
+      break;
+
+    case MAG_STATS_TUNING:
+      if (gyro_cal->using_mag_sensor) {
+        CAL_DEBUG_LOG(
+            debug_tag,
+            "Mag Mean|Var [uT|uT^2]: %s%d.%06d, %s%d.%06d, %s%d.%06d, "
+            "%s%d.%08d, %s%d.%08d, %s%d.%08d",
+            CAL_ENCODE_FLOAT(gyro_cal->mag_stillness_detect.prev_mean_x, 6),
+            CAL_ENCODE_FLOAT(gyro_cal->mag_stillness_detect.prev_mean_y, 6),
+            CAL_ENCODE_FLOAT(gyro_cal->mag_stillness_detect.prev_mean_z, 6),
+            CAL_ENCODE_FLOAT(gyro_cal->mag_stillness_detect.win_var_x, 8),
+            CAL_ENCODE_FLOAT(gyro_cal->mag_stillness_detect.win_var_y, 8),
+            CAL_ENCODE_FLOAT(gyro_cal->mag_stillness_detect.win_var_z, 8));
+      } else {
+        CAL_DEBUG_LOG(GYROCAL_TUNE_TAG,
+                      "Mag Mean|Var [uT|uT^2]: 0, 0, 0, -1.0, -1.0, -1.0");
+      }
+      break;
+#endif  // GYRO_CAL_DBG_TUNE_ENABLED
+
     default:
       break;
   }
@@ -1080,7 +1134,7 @@
 
     case GYRO_WAIT_STATE:
       // This helps throttle the print statements.
-      if ((timestamp_nanos - wait_timer_nanos) >= GYROCAL_WAIT_TIME_NANOS) {
+      if (timestamp_nanos >= GYROCAL_WAIT_TIME_NANOS + wait_timer_nanos) {
         gyro_cal->debug_state = next_state;
       }
       break;
@@ -1159,22 +1213,24 @@
   static uint64_t wait_timer_nanos = 0;
 
   // Output sensor variance levels to assist with tuning thresholds.
-  //   i.  Within the first 180 seconds of boot: output interval = 5
+  //   i.  Within the first 300 seconds of boot: output interval = 5
   //       seconds.
   //   ii. Thereafter: output interval is 60 seconds.
   bool condition_i =
-      ((timestamp_nanos <= 180000000000) &&
-       ((timestamp_nanos - wait_timer_nanos) > 5000000000));  // nsec
-  bool condition_ii = ((timestamp_nanos > 60000000000) &&
-                       ((timestamp_nanos - wait_timer_nanos) > 60000000000));
+      ((timestamp_nanos <= 300000000000) &&
+       (timestamp_nanos > 5000000000 + wait_timer_nanos));  // nsec
+  bool condition_ii = (timestamp_nanos > 60000000000 + wait_timer_nanos);
 
   // This is a state machine that controls the reporting out of tuning data.
   switch (debug_state) {
     case GYRO_IDLE:
       // Wait for a trigger and start the data tuning printout sequence.
       if (condition_i || condition_ii) {
-        CAL_DEBUG_LOG(GYROCAL_TUNE_TAG, "");
-        debug_state = GYRO_PRINT_OFFSET;
+        CAL_DEBUG_LOG(GYROCAL_TUNE_TAG, "Temp [C]: %s%d.%03d",
+                      CAL_ENCODE_FLOAT(gyro_cal->temperature_mean_celsius, 3));
+        wait_timer_nanos = timestamp_nanos;   // Starts the wait timer.
+        next_state = GYRO_PRINT_ACCEL_STATS;  // Sets the next state.
+        debug_state = GYRO_WAIT_STATE;        // First, go to wait state.
       } else {
         debug_state = GYRO_IDLE;
       }
@@ -1182,34 +1238,27 @@
 
     case GYRO_WAIT_STATE:
       // This helps throttle the print statements.
-      if ((timestamp_nanos - wait_timer_nanos) >= GYROCAL_WAIT_TIME_NANOS) {
+      if (timestamp_nanos >= GYROCAL_WAIT_TIME_NANOS + wait_timer_nanos) {
         debug_state = next_state;
       }
       break;
 
-    case GYRO_PRINT_OFFSET:
-      gyroCalDebugPrintData(gyro_cal, GYROCAL_TUNE_TAG, OFFSET);
-      wait_timer_nanos = timestamp_nanos;   // Starts the wait timer.
-      next_state = GYRO_PRINT_ACCEL_STATS;  // Sets the next state.
-      debug_state = GYRO_WAIT_STATE;        // First, go to wait state.
-      break;
-
     case GYRO_PRINT_ACCEL_STATS:
-      gyroCalDebugPrintData(gyro_cal, GYROCAL_TUNE_TAG, ACCEL_STATS);
+      gyroCalDebugPrintData(gyro_cal, GYROCAL_TUNE_TAG, ACCEL_STATS_TUNING);
       wait_timer_nanos = timestamp_nanos;  // Starts the wait timer.
       next_state = GYRO_PRINT_GYRO_STATS;  // Sets the next state.
       debug_state = GYRO_WAIT_STATE;       // First, go to wait state.
       break;
 
     case GYRO_PRINT_GYRO_STATS:
-      gyroCalDebugPrintData(gyro_cal, GYROCAL_TUNE_TAG, GYRO_STATS);
+      gyroCalDebugPrintData(gyro_cal, GYROCAL_TUNE_TAG, GYRO_STATS_TUNING);
       wait_timer_nanos = timestamp_nanos;  // Starts the wait timer.
       next_state = GYRO_PRINT_MAG_STATS;   // Sets the next state.
       debug_state = GYRO_WAIT_STATE;       // First, go to wait state.
       break;
 
     case GYRO_PRINT_MAG_STATS:
-      gyroCalDebugPrintData(gyro_cal, GYROCAL_TUNE_TAG, MAG_STATS);
+      gyroCalDebugPrintData(gyro_cal, GYROCAL_TUNE_TAG, MAG_STATS_TUNING);
       wait_timer_nanos = timestamp_nanos;  // Starts the wait timer.
       next_state = GYRO_IDLE;              // Sets the next state.
       debug_state = GYRO_WAIT_STATE;       // First, go to wait state.
diff --git a/firmware/os/algos/calibration/gyroscope/gyro_stillness_detect.c b/firmware/os/algos/calibration/gyroscope/gyro_stillness_detect.c
index 0b38e5b..80f2fa2 100644
--- a/firmware/os/algos/calibration/gyroscope/gyro_stillness_detect.c
+++ b/firmware/os/algos/calibration/gyroscope/gyro_stillness_detect.c
@@ -193,11 +193,11 @@
       tmp_denom = 1.f / (upper_var_thresh - lower_var_thresh);
       gyro_still_det->stillness_confidence =
           gyroStillDetLimit(
-              0.5 - (gyro_still_det->win_var_x - var_thresh) * tmp_denom) *
+              0.5f - (gyro_still_det->win_var_x - var_thresh) * tmp_denom) *
           gyroStillDetLimit(
-              0.5 - (gyro_still_det->win_var_y - var_thresh) * tmp_denom) *
+              0.5f - (gyro_still_det->win_var_y - var_thresh) * tmp_denom) *
           gyroStillDetLimit(
-              0.5 - (gyro_still_det->win_var_z - var_thresh) * tmp_denom);
+              0.5f - (gyro_still_det->win_var_z - var_thresh) * tmp_denom);
     }
   }
 
diff --git a/firmware/os/algos/calibration/over_temp/over_temp_cal.c b/firmware/os/algos/calibration/over_temp/over_temp_cal.c
index f1d02b5..ebc8cf4 100644
--- a/firmware/os/algos/calibration/over_temp/over_temp_cal.c
+++ b/firmware/os/algos/calibration/over_temp/over_temp_cal.c
@@ -26,23 +26,18 @@
 
 /////// DEFINITIONS AND MACROS ////////////////////////////////////////////////
 
-// The 'temp_sensitivity' parameters are set to this value to indicate that the
-// model is in its initial state.
-#define MODEL_INITIAL_STATE (1e6f)
-
 // Rate-limits the search for the nearest offset estimate to every 2 seconds.
 #define OVERTEMPCAL_NEAREST_NANOS (2000000000)
 
 // Rate-limits the check of old data to every 2 hours.
 #define OVERTEMPCAL_STALE_CHECK_TIME_NANOS (7200000000000)
 
-// A common sensor operating temperature at which to start producing the model
-// jump-start data.
-#define JUMPSTART_START_TEMP_CELSIUS (30.0f)
+// Value used to check whether OTC model parameters are near zero.
+#define OTC_MODELDATA_NEAR_ZERO_TOL (1e-7f)  // [rad/sec]
 
 #ifdef OVERTEMPCAL_DBG_ENABLED
 // A debug version label to help with tracking results.
-#define OVERTEMPCAL_DEBUG_VERSION_STRING "[Jan 20, 2017]"
+#define OVERTEMPCAL_DEBUG_VERSION_STRING "[Apr 05, 2017]"
 
 // The time value used to throttle debug messaging.
 #define OVERTEMPCAL_WAIT_TIME_NANOS (300000000)
@@ -51,7 +46,7 @@
 #define OVERTEMPCAL_REPORT_TAG "[OVER_TEMP_CAL:REPORT]"
 
 // Converts units of radians to milli-degrees.
-#define RAD_TO_MILLI_DEGREES (float)(1e3f * 180.0f / M_PI)
+#define RAD_TO_MILLI_DEGREES (float)(1e3f * 180.0f / NANO_PI)
 
 // Sensor axis label definition with index correspondence: 0=X, 1=Y, 2=Z.
 static const char  kDebugAxisLabel[3] = "XYZ";
@@ -59,7 +54,7 @@
 
 /////// FORWARD DECLARATIONS //////////////////////////////////////////////////
 
-// Updates the most recent model estimate data.
+// Updates the model estimate data nearest to the sensor's temperature.
 static void setNearestEstimate(struct OverTempCal *over_temp_cal,
                               const float *offset, float offset_temp_celsius,
                               uint64_t timestamp_nanos);
@@ -82,6 +77,23 @@
 static void findNearestEstimate(struct OverTempCal *over_temp_cal);
 
 /*
+ * Provides the current over-temperature compensated offset vector.
+ *
+ * INPUTS:
+ *   over_temp_cal:    Over-temp data structure.
+ *   timestamp_nanos:  The current system timestamp.
+ * OUTPUTS:
+ *   compensated_offset: Temperature compensated offset estimate array.
+ *   compensated_offset_temperature_celsius: Compensated offset temperature.
+ *
+ * NOTE: Arrays are all 3-dimensional with indices: 0=x, 1=y, 2=z.
+ */
+static void getCalOffset(struct OverTempCal *over_temp_cal,
+                         uint64_t timestamp_nanos,
+                         float *compensated_offset_temperature_celsius,
+                         float *compensated_offset);
+
+/*
  * Removes the "old" offset estimates from 'model_data' (i.e., eliminates the
  * drift-compromised data). Returns 'true' if any data was removed.
  */
@@ -121,19 +133,6 @@
                         float *temp_sensitivity, float *sensor_intercept);
 
 /*
- * Removes the over-temp compensated offset from the input sensor data.
- *
- * INPUTS:
- *   over_temp_cal:    Over-temp data structure.
- *   axis_in:          Single axis sensor data to be compensated.
- *   index:            Index for model parameter compensation (0=x, 1=y, 2=z).
- * OUTPUTS:
- *   axis_out:         Single axis sensor data that has been compensated.
- */
-static void removeSensorOffset(const struct OverTempCal *over_temp_cal,
-                               float axis_in, size_t index, float *axis_out);
-
-/*
  * Checks new offset estimates to determine if they could be an outlier that
  * should be rejected. Operates on a per-axis basis determined by 'axis_index'.
  *
@@ -148,6 +147,63 @@
 static bool outlierCheck(struct OverTempCal *over_temp_cal, const float *offset,
                          size_t axis_index, float temperature_celsius);
 
+// Sets the OTC model parameters to an "initialized" state.
+static void resetOtcLinearModel(struct OverTempCal *over_temp_cal) {
+  ASSERT_NOT_NULL(over_temp_cal);
+
+  // Sets the temperature sensitivity model parameters to
+  // OTC_INITIAL_SENSITIVITY to indicate that the model is in an "initial"
+  // state.
+  over_temp_cal->temp_sensitivity[0] = OTC_INITIAL_SENSITIVITY;
+  over_temp_cal->temp_sensitivity[1] = OTC_INITIAL_SENSITIVITY;
+  over_temp_cal->temp_sensitivity[2] = OTC_INITIAL_SENSITIVITY;
+  memset(over_temp_cal->sensor_intercept, 0, 3 * sizeof(float));
+}
+
+// Checks that the input temperature value is within the valid range. If outside
+// of range, then 'temperature_celsius' is coerced to within the limits.
+static bool checkAndEnforceTemperatureRange(float *temperature_celsius) {
+  if (*temperature_celsius > OVERTEMPCAL_TEMP_MAX_CELSIUS) {
+    *temperature_celsius = OVERTEMPCAL_TEMP_MAX_CELSIUS;
+    return false;
+  }
+  if (*temperature_celsius < OVERTEMPCAL_TEMP_MIN_CELSIUS) {
+    *temperature_celsius = OVERTEMPCAL_TEMP_MIN_CELSIUS;
+    return false;
+  }
+  return true;
+}
+
+// Returns "true" if the candidate linear model parameters are within the valid
+// range, and not all zeros.
+static bool isValidOtcLinearModel(const struct OverTempCal *over_temp_cal,
+                   float temp_sensitivity, float sensor_intercept) {
+  ASSERT_NOT_NULL(over_temp_cal);
+
+  return NANO_ABS(temp_sensitivity) < over_temp_cal->temp_sensitivity_limit &&
+         NANO_ABS(sensor_intercept) < over_temp_cal->sensor_intercept_limit &&
+         NANO_ABS(temp_sensitivity) > OTC_MODELDATA_NEAR_ZERO_TOL &&
+         NANO_ABS(sensor_intercept) > OTC_MODELDATA_NEAR_ZERO_TOL;
+}
+
+// Returns "true" if 'offset' and 'offset_temp_celsius' is valid.
+static bool isValidOtcOffset(const float *offset, float offset_temp_celsius) {
+  ASSERT_NOT_NULL(offset);
+
+  // Simple check to ensure that:
+  //   1. All of the input data is non "zero".
+  //   2. The offset temperature is within the valid range.
+  if (NANO_ABS(offset[0]) < OTC_MODELDATA_NEAR_ZERO_TOL &&
+      NANO_ABS(offset[1]) < OTC_MODELDATA_NEAR_ZERO_TOL &&
+      NANO_ABS(offset[2]) < OTC_MODELDATA_NEAR_ZERO_TOL &&
+      NANO_ABS(offset_temp_celsius) < OTC_MODELDATA_NEAR_ZERO_TOL) {
+    return false;
+  }
+
+  // Only returns the "check" result. Don't care about coercion.
+  return checkAndEnforceTemperatureRange(&offset_temp_celsius);
+}
+
 #ifdef OVERTEMPCAL_DBG_ENABLED
 // This helper function stores all of the debug tracking information necessary
 // for printing log messages.
@@ -171,11 +227,8 @@
   // as the first element in 'model_data'.
   over_temp_cal->nearest_offset = &over_temp_cal->model_data[0];
 
-  // Sets the temperature sensitivity model parameters to MODEL_INITIAL_STATE to
-  // indicate that the model is in an "initial" state.
-  over_temp_cal->temp_sensitivity[0] = MODEL_INITIAL_STATE;
-  over_temp_cal->temp_sensitivity[1] = MODEL_INITIAL_STATE;
-  over_temp_cal->temp_sensitivity[2] = MODEL_INITIAL_STATE;
+  // Initializes the OTC linear model parameters.
+  resetOtcLinearModel(over_temp_cal);
 
   // Initializes the model identification parameters.
   over_temp_cal->new_overtemp_model_available = false;
@@ -188,6 +241,9 @@
   over_temp_cal->sensor_intercept_limit = sensor_intercept_limit;
   over_temp_cal->over_temp_enable = over_temp_enable;
 
+  // Initialize the sensor's temperature with a good initial operating point.
+  over_temp_cal->temperature_celsius = JUMPSTART_START_TEMP_CELSIUS;
+
 #ifdef OVERTEMPCAL_DBG_ENABLED
   CAL_DEBUG_LOG("[OVER_TEMP_CAL:MEMORY]", "sizeof(struct OverTempCal): %lu",
                 (unsigned long int)sizeof(struct OverTempCal));
@@ -211,11 +267,16 @@
   ASSERT_NOT_NULL(temp_sensitivity);
   ASSERT_NOT_NULL(sensor_intercept);
 
+  // Initializes the OTC linear model parameters.
+  resetOtcLinearModel(over_temp_cal);
+
   // Sets the model parameters if they are within the acceptable limits.
+  // Includes a check to reject input model parameters that may have been passed
+  // in as all zeros.
   size_t i;
   for (i = 0; i < 3; i++) {
-    if (NANO_ABS(temp_sensitivity[i]) < over_temp_cal->temp_sensitivity_limit &&
-        NANO_ABS(sensor_intercept[i]) < over_temp_cal->sensor_intercept_limit) {
+    if (isValidOtcLinearModel(over_temp_cal, temp_sensitivity[i],
+                              sensor_intercept[i])) {
       over_temp_cal->temp_sensitivity[i] = temp_sensitivity[i];
       over_temp_cal->sensor_intercept[i] = sensor_intercept[i];
     }
@@ -229,18 +290,55 @@
       (jump_start_model) ? jumpStartModelData(over_temp_cal) : false;
 
   if (!model_jump_started) {
-    // Sets the initial over-temp calibration estimate and model data.
-    setNearestEstimate(over_temp_cal, offset, offset_temp_celsius,
-                      timestamp_nanos);
-
-    // Now there is one offset estimate in the model.
-    over_temp_cal->num_model_pts = 1;
+    // Checks that the new offset data is valid.
+    if (isValidOtcOffset(offset, offset_temp_celsius)) {
+      // Sets the initial over-temp calibration estimate and model data.
+      over_temp_cal->nearest_offset = &over_temp_cal->model_data[0];
+      setNearestEstimate(over_temp_cal, offset, offset_temp_celsius,
+                         timestamp_nanos);
+      over_temp_cal->num_model_pts = 1;
+    } else {
+      // No valid offset data to load.
+      over_temp_cal->num_model_pts = 0;
+#ifdef OVERTEMPCAL_DBG_ENABLED
+      CAL_DEBUG_LOG("[OVER_TEMP_CAL:RECALL]",
+                    "No valid sensor offset vector to load.");
+#endif  // OVERTEMPCAL_DBG_ENABLED
+    }
+  } else {
+    // Finds the offset nearest the sensor's current temperature.
+    findNearestEstimate(over_temp_cal);
   }
 
+  // Updates the 'compensated_offset_previous' vector to prevent from
+  // immediately triggering a new calibration update.
+  float compensated_offset_temperature_celsius = 0.0f;
+  getCalOffset(over_temp_cal, timestamp_nanos,
+               &compensated_offset_temperature_celsius,
+               over_temp_cal->compensated_offset_previous);
+
 #ifdef OVERTEMPCAL_DBG_ENABLED
   // Prints the updated model data.
-  CAL_DEBUG_LOG("[OVER_TEMP_CAL:RECALL]",
-                "Over-temperature model parameters recalled.");
+  CAL_DEBUG_LOG(
+      "[OVER_TEMP_CAL:RECALL]",
+      "Temperature|Offset|Sensitivity|Intercept [rps]: %s%d.%06d, | %s%d.%06d, "
+      "%s%d.%06d, %s%d.%06d | %s%d.%06d, %s%d.%06d, %s%d.%06d | "
+      "%s%d.%06d, "
+      "%s%d.%06d, %s%d.%06d",
+      CAL_ENCODE_FLOAT(offset_temp_celsius, 6),
+      CAL_ENCODE_FLOAT(offset[0], 6),
+      CAL_ENCODE_FLOAT(offset[1], 6),
+      CAL_ENCODE_FLOAT(offset[2], 6),
+      CAL_ENCODE_FLOAT(temp_sensitivity[0], 6),
+      CAL_ENCODE_FLOAT(temp_sensitivity[1], 6),
+      CAL_ENCODE_FLOAT(temp_sensitivity[2], 6),
+      CAL_ENCODE_FLOAT(sensor_intercept[0], 6),
+      CAL_ENCODE_FLOAT(sensor_intercept[1], 6),
+      CAL_ENCODE_FLOAT(sensor_intercept[2], 6));
+
+  // Resets the debug print machine to ensure that updateDebugData() can
+  // produce a debug report and interupt any ongoing report.
+  over_temp_cal->debug_state = OTC_IDLE;
 
   // Triggers a debug print out to view the new model parameters.
   updateDebugData(over_temp_cal);
@@ -258,54 +356,149 @@
   ASSERT_NOT_NULL(temp_sensitivity);
   ASSERT_NOT_NULL(sensor_intercept);
 
-  // Gets the over-temp calibration estimate and model data.
-  memcpy(offset, over_temp_cal->nearest_offset->offset, 3 * sizeof(float));
+  // Gets the latest over-temp calibration model data.
   memcpy(temp_sensitivity, over_temp_cal->temp_sensitivity, 3 * sizeof(float));
   memcpy(sensor_intercept, over_temp_cal->sensor_intercept, 3 * sizeof(float));
-  *offset_temp_celsius = over_temp_cal->nearest_offset->offset_temp_celsius;
-  *timestamp_nanos = over_temp_cal->nearest_offset->timestamp_nanos;
+  *timestamp_nanos = over_temp_cal->modelupdate_timestamp_nanos;
+
+  // Gets the latest temperature compensated offset estimate.
+  getCalOffset(over_temp_cal, *timestamp_nanos, offset_temp_celsius, offset);
 
 #ifdef OVERTEMPCAL_DBG_ENABLED
-  CAL_DEBUG_LOG("[OVER_TEMP_CAL:STORED]",
-                "Over-temperature model parameters stored.");
+  // Prints the updated model data.
+  CAL_DEBUG_LOG(
+      "[OVER_TEMP_CAL:STORED]",
+      "Temperature|Offset|Sensitivity|Intercept [rps]: %s%d.%06d, | %s%d.%06d, "
+      "%s%d.%06d, %s%d.%06d | %s%d.%06d, %s%d.%06d, %s%d.%06d | "
+      "%s%d.%06d, "
+      "%s%d.%06d, %s%d.%06d",
+      CAL_ENCODE_FLOAT(*offset_temp_celsius, 6),
+      CAL_ENCODE_FLOAT(offset[0], 6),
+      CAL_ENCODE_FLOAT(offset[1], 6),
+      CAL_ENCODE_FLOAT(offset[2], 6),
+      CAL_ENCODE_FLOAT(temp_sensitivity[0], 6),
+      CAL_ENCODE_FLOAT(temp_sensitivity[1], 6),
+      CAL_ENCODE_FLOAT(temp_sensitivity[2], 6),
+      CAL_ENCODE_FLOAT(sensor_intercept[0], 6),
+      CAL_ENCODE_FLOAT(sensor_intercept[1], 6),
+      CAL_ENCODE_FLOAT(sensor_intercept[2], 6));
 #endif  // OVERTEMPCAL_DBG_ENABLED
 }
 
+void overTempCalSetModelData(struct OverTempCal *over_temp_cal,
+                             size_t data_length,
+                             const struct OverTempCalDataPt *model_data) {
+  ASSERT_NOT_NULL(over_temp_cal);
+  ASSERT_NOT_NULL(model_data);
+
+  // Load only "good" data from the input 'model_data'.
+  over_temp_cal->num_model_pts = NANO_MIN(data_length, OVERTEMPCAL_MODEL_SIZE);
+  size_t i;
+  size_t valid_data_count = 0;
+  for (i = 0; i < over_temp_cal->num_model_pts; i++) {
+    if (isValidOtcOffset(model_data[i].offset,
+                         model_data[i].offset_temp_celsius)) {
+      memcpy(&over_temp_cal->model_data[i], &model_data[i],
+             sizeof(struct OverTempCalDataPt));
+      valid_data_count++;
+    }
+  }
+  over_temp_cal->num_model_pts = valid_data_count;
+
+  // Initializes the OTC linear model parameters.
+  resetOtcLinearModel(over_temp_cal);
+
+  // Finds the offset nearest the sensor's current temperature.
+  findNearestEstimate(over_temp_cal);
+
+  // Updates the 'compensated_offset_previous' vector to prevent from
+  // immediately triggering a new calibration update.
+  float compensated_offset_temperature_celsius = 0.0f;
+  getCalOffset(over_temp_cal, /*timestamp_nanos=*/0,
+               &compensated_offset_temperature_celsius,
+               over_temp_cal->compensated_offset_previous);
+
+#ifdef OVERTEMPCAL_DBG_ENABLED
+  // Prints the updated model data.
+  CAL_DEBUG_LOG("[OVER_TEMP_CAL:RECALL]",
+                "Over-temperature full model data set recalled.");
+  // Resets the debug print machine to ensure that computeModelUpdate() can
+  // produce a debug report and interupt any ongoing report.
+  over_temp_cal->debug_state = OTC_IDLE;
+#endif  // OVERTEMPCAL_DBG_ENABLED
+
+  // Ensures that minimum number of points required for a model fit has been
+  // satisfied and recomputes the OTC model parameters.
+  if (over_temp_cal->num_model_pts > over_temp_cal->min_num_model_pts) {
+    // Computes and replaces the model parameters. If successful, this will
+    // trigger a "new calibration" update.
+    computeModelUpdate(over_temp_cal,
+                       over_temp_cal->modelupdate_timestamp_nanos);
+  }
+}
+
+void overTempCalGetModelData(struct OverTempCal *over_temp_cal,
+                             size_t *data_length,
+                             struct OverTempCalDataPt *model_data) {
+  ASSERT_NOT_NULL(over_temp_cal);
+  *data_length = over_temp_cal->num_model_pts;
+  memcpy(model_data, over_temp_cal->model_data,
+         over_temp_cal->num_model_pts * sizeof(struct OverTempCalDataPt));
+}
+
+bool overTempCalGetOffset(struct OverTempCal *over_temp_cal,
+                          uint64_t timestamp_nanos,
+                          float *compensated_offset_temperature_celsius,
+                          float *compensated_offset) {
+  // Gets the temperature compensated sensor offset estimate.
+  getCalOffset(over_temp_cal, timestamp_nanos,
+               compensated_offset_temperature_celsius, compensated_offset);
+
+  // If the compensated_offset value has changed significantly then return
+  // 'true' status.
+  bool offset_has_changed = false;
+  int i;
+  for (i = 0; i < 3; i++) {
+    if (NANO_ABS(over_temp_cal->compensated_offset_previous[i] -
+                 compensated_offset[i]) >= SIGNIFICANT_OFFSET_CHANGE_RPS) {
+      offset_has_changed = true;
+
+      // Update the 'compensated_offset_previous' vector.
+      memcpy(over_temp_cal->compensated_offset_previous, compensated_offset,
+             3 * sizeof(float));
+      break;
+    }
+  }
+
+  return offset_has_changed;
+}
+
 void overTempCalRemoveOffset(struct OverTempCal *over_temp_cal,
                              uint64_t timestamp_nanos, float xi, float yi,
                              float zi, float *xo, float *yo, float *zo) {
   ASSERT_NOT_NULL(over_temp_cal);
-  ASSERT_NOT_NULL(over_temp_cal->nearest_offset);
   ASSERT_NOT_NULL(xo);
   ASSERT_NOT_NULL(yo);
   ASSERT_NOT_NULL(zo);
 
-  // Removes very old data from the collected model estimates (eliminates
-  // drift-compromised data). Only does this when there is more than one
-  // estimate in the model (i.e., don't want to remove all data, even if it is
-  // very old [something is likely better than nothing]).
-  if ((timestamp_nanos - over_temp_cal->stale_data_timer) >=
-          OVERTEMPCAL_STALE_CHECK_TIME_NANOS &&
-      over_temp_cal->num_model_pts > 1) {
-    over_temp_cal->stale_data_timer = timestamp_nanos;  // Resets timer.
-
-    if (removeStaleModelData(over_temp_cal, timestamp_nanos)) {
-      // If anything was removed, then this attempts to recompute the model.
-      if (over_temp_cal->num_model_pts >= over_temp_cal->min_num_model_pts) {
-        computeModelUpdate(over_temp_cal, timestamp_nanos);
-      }
-    }
-  }
-
   // Determines whether over-temp compensation will be applied.
-  if (!over_temp_cal->over_temp_enable) {
-    return;
-  }
+  if (over_temp_cal->over_temp_enable) {
+    // Gets the temperature compensated sensor offset estimate.
+    float compensated_offset[3] = {0.0f, 0.0f, 0.0f};
+    float compensated_offset_temperature_celsius = 0.0f;
+    getCalOffset(over_temp_cal, timestamp_nanos,
+                 &compensated_offset_temperature_celsius, compensated_offset);
 
-  // Removes the over-temperature compensated offset from the input sensor data.
-  removeSensorOffset(over_temp_cal, xi, 0, xo);
-  removeSensorOffset(over_temp_cal, yi, 1, yo);
-  removeSensorOffset(over_temp_cal, zi, 2, zo);
+    // Removes the over-temperature compensated offset from the input sensor
+    // data.
+    *xo = xi - compensated_offset[0];
+    *yo = yi - compensated_offset[1];
+    *zo = zi - compensated_offset[2];
+  } else {
+    *xo = xi;
+    *yo = yi;
+    *zo = zi;
+  }
 }
 
 bool overTempCalNewModelUpdateAvailable(struct OverTempCal *over_temp_cal) {
@@ -328,6 +521,11 @@
   ASSERT_NOT_NULL(offset);
   ASSERT(over_temp_cal->delta_temp_per_bin > 0);
 
+  // Checks that the new offset data is valid, returns if bad.
+  if (!isValidOtcOffset(offset, temperature_celsius)) {
+    return;
+  }
+
   // Prevent a divide by zero below.
   if (over_temp_cal->delta_temp_per_bin <= 0) {
     return;
@@ -354,7 +552,7 @@
                     (unsigned long long int)timestamp_nanos);
 #endif  // OVERTEMPCAL_DBG_ENABLED
 
-      return;  // Skips the process of adding this offset to the model.
+      return;  // Outlier detected: skips adding this offset to the model.
     } else {
       // Resets the count of rejected outliers.
       over_temp_cal->num_outliers = 0;
@@ -420,11 +618,6 @@
                      timestamp_nanos);
 
 #ifdef OVERTEMPCAL_DBG_ENABLED
-  // Updates the latest sensor offset estimate so this can be tracked for debug
-  // printout later.
-  memcpy(&over_temp_cal->debug_overtempcal.latest_offset,
-         over_temp_cal->nearest_offset, sizeof(struct OverTempCalDataPt));
-
   // Updates the total number of received sensor offset estimates.
   over_temp_cal->debug_num_estimates++;
 #endif  // OVERTEMPCAL_DBG_ENABLED
@@ -440,8 +633,8 @@
   //          (current_timestamp_nanos - modelupdate_timestamp_nanos) <
   //            min_update_interval_nanos
   if (over_temp_cal->num_model_pts < over_temp_cal->min_num_model_pts ||
-      (timestamp_nanos - over_temp_cal->modelupdate_timestamp_nanos) <
-          over_temp_cal->min_update_interval_nanos) {
+      timestamp_nanos < over_temp_cal->min_update_interval_nanos +
+                            over_temp_cal->modelupdate_timestamp_nanos) {
 #ifdef OVERTEMPCAL_DBG_ENABLED
     // Triggers a log printout to show the updated sensor offset estimate.
     updateDebugData(over_temp_cal);
@@ -462,7 +655,7 @@
   static uint64_t wait_timer = 0;
   // Prints the sensor temperature trajectory for debugging purposes.
   // This throttles the print statements.
-  if ((timestamp_nanos - wait_timer) >= 1000000000) {
+  if (timestamp_nanos >= 1000000000 + wait_timer) {
     wait_timer = timestamp_nanos;  // Starts the wait timer.
 
     // Prints out temperature and the current timestamp.
@@ -474,21 +667,25 @@
 #endif  // OVERTEMPCAL_DBG_LOG_TEMP
 #endif  // OVERTEMPCAL_DBG_ENABLED
 
+  // Checks that the offset temperature is within a valid range, saturates if
+  // outside.
+  checkAndEnforceTemperatureRange(&temperature_celsius);
+
   // Updates the sensor temperature.
   over_temp_cal->temperature_celsius = temperature_celsius;
 
-  // This searches for the sensor offset estimate closest to the current
+  // Searches for the sensor offset estimate closest to the current
   // temperature. A timer is used to limit the rate at which this search is
   // performed.
   if (over_temp_cal->num_model_pts > 0 &&
-      (timestamp_nanos - over_temp_cal->nearest_search_timer) >=
-          OVERTEMPCAL_NEAREST_NANOS) {
+      timestamp_nanos >=
+          OVERTEMPCAL_NEAREST_NANOS + over_temp_cal->nearest_search_timer) {
     findNearestEstimate(over_temp_cal);
     over_temp_cal->nearest_search_timer = timestamp_nanos;  // Reset timer.
   }
 }
 
-void getModelError(const struct OverTempCal *over_temp_cal,
+void overTempGetModelError(const struct OverTempCal *over_temp_cal,
                    const float *temp_sensitivity, const float *sensor_intercept,
                    float *max_error) {
   ASSERT_NOT_NULL(over_temp_cal);
@@ -517,6 +714,64 @@
 
 /////// LOCAL HELPER FUNCTION DEFINITIONS /////////////////////////////////////
 
+void getCalOffset(struct OverTempCal *over_temp_cal, uint64_t timestamp_nanos,
+                  float *compensated_offset_temperature_celsius,
+                  float *compensated_offset) {
+  ASSERT_NOT_NULL(over_temp_cal);
+  ASSERT_NOT_NULL(over_temp_cal->nearest_offset);
+  ASSERT_NOT_NULL(compensated_offset);
+  ASSERT_NOT_NULL(compensated_offset_temperature_celsius);
+
+  // Sets the sensor temperature associated with the compensated offset.
+  *compensated_offset_temperature_celsius = over_temp_cal->temperature_celsius;
+
+  // Removes very old data from the collected model estimates (eliminates
+  // drift-compromised data). Only does this when there is more than one
+  // estimate in the model (i.e., don't want to remove all data, even if it is
+  // very old [something is likely better than nothing]).
+  if ((timestamp_nanos >=
+       OVERTEMPCAL_STALE_CHECK_TIME_NANOS + over_temp_cal->stale_data_timer) &&
+      over_temp_cal->num_model_pts > 1) {
+    over_temp_cal->stale_data_timer = timestamp_nanos;  // Resets timer.
+
+    if (removeStaleModelData(over_temp_cal, timestamp_nanos)) {
+      // If anything was removed, then this attempts to recompute the model.
+      if (over_temp_cal->num_model_pts >= over_temp_cal->min_num_model_pts) {
+        computeModelUpdate(over_temp_cal, timestamp_nanos);
+      }
+    }
+  }
+
+  size_t index;
+  for (index = 0; index < 3; index++) {
+    if (over_temp_cal->temp_sensitivity[index] >= OTC_INITIAL_SENSITIVITY ||
+        NANO_ABS(over_temp_cal->temperature_celsius -
+                 over_temp_cal->nearest_offset->offset_temp_celsius) <
+            over_temp_cal->delta_temp_per_bin) {
+      // Use the nearest estimate to perform the compensation if either of the
+      // following is true:
+      //    1) This axis model is in its initial state.
+      //    2) The sensor's temperature is within a small neighborhood of the
+      //       'nearest_offset'.
+      // compensated_offset = nearest_offset
+      //
+      // If either of the above conditions applies and 'nearest_offset' is not
+      // defined, then the offset returned is zero.
+      compensated_offset[index] =
+          (over_temp_cal->num_model_pts > 0)
+              ? over_temp_cal->nearest_offset->offset[index]
+              : 0.0f;
+    } else {
+      // Offset computed from the linear model:
+      //  compensated_offset = (temp_sensitivity * temperature +
+      //  sensor_intercept)
+      compensated_offset[index] = (over_temp_cal->temp_sensitivity[index] *
+                                       over_temp_cal->temperature_celsius +
+                                   over_temp_cal->sensor_intercept[index]);
+    }
+  }
+}
+
 void setNearestEstimate(struct OverTempCal *over_temp_cal, const float *offset,
                        float offset_temp_celsius, uint64_t timestamp_nanos) {
   ASSERT_NOT_NULL(over_temp_cal);
@@ -540,11 +795,12 @@
 
   // Computes the maximum error over all of the model data.
   float max_error[3];
-  getModelError(over_temp_cal, temp_sensitivity, sensor_intercept, max_error);
+  overTempGetModelError(over_temp_cal, temp_sensitivity, sensor_intercept,
+                        max_error);
 
   //    3) A new set of model parameters are accepted if:
   //         i.  The model fit error is less than, 'max_error_limit'. See
-  //             getModelError() for error metric description.
+  //             overTempGetModelError() for error metric description.
   //         ii. The model fit parameters must be within certain absolute
   //             bounds:
   //               a. NANO_ABS(temp_sensitivity) < temp_sensitivity_limit
@@ -553,8 +809,8 @@
   bool updated_one = false;
   for (i = 0; i < 3; i++) {
     if (max_error[i] < over_temp_cal->max_error_limit &&
-        NANO_ABS(temp_sensitivity[i]) < over_temp_cal->temp_sensitivity_limit &&
-        NANO_ABS(sensor_intercept[i]) < over_temp_cal->sensor_intercept_limit) {
+        isValidOtcLinearModel(over_temp_cal, temp_sensitivity[i],
+                              sensor_intercept[i])) {
       over_temp_cal->temp_sensitivity[i] = temp_sensitivity[i];
       over_temp_cal->sensor_intercept[i] = sensor_intercept[i];
       updated_one = true;
@@ -616,8 +872,8 @@
   bool removed_one = false;
   for (i = 0; i < over_temp_cal->num_model_pts; i++) {
     if (timestamp_nanos > over_temp_cal->model_data[i].timestamp_nanos &&
-        (timestamp_nanos - over_temp_cal->model_data[i].timestamp_nanos) >
-            over_temp_cal->age_limit_nanos) {
+        timestamp_nanos > over_temp_cal->age_limit_nanos +
+                              over_temp_cal->model_data[i].timestamp_nanos) {
       removed_one |= removeModelDataByIndex(over_temp_cal, i);
     }
   }
@@ -682,17 +938,19 @@
   // In normal operation the offset estimates enter into the 'model_data' array
   // complete (i.e., x, y, z values are all provided). Therefore, the jumpstart
   // data produced here requires that the model parameters have all been fully
-  // defined (i.e., no models in an initial state) and are all within the valid
-  // range (this is assumed to have been checked prior to this function). There
-  // must also be no preexisting model data; that is, this function will not
-  // replace any actual offset estimates already buffered.
-  if (over_temp_cal->num_model_pts > 0 ||
-      over_temp_cal->temp_sensitivity[0] >= MODEL_INITIAL_STATE ||
-      over_temp_cal->temp_sensitivity[1] >= MODEL_INITIAL_STATE ||
-      over_temp_cal->temp_sensitivity[2] >= MODEL_INITIAL_STATE) {
-    return false;
+  // defined and are all within the valid range.
+  size_t i;
+  for (i = 0; i < 3; i++) {
+    if (!isValidOtcLinearModel(over_temp_cal,
+                               over_temp_cal->temp_sensitivity[i],
+                               over_temp_cal->sensor_intercept[i])) {
+      return false;
+    }
   }
 
+  // Any pre-existing model data points will be overwritten.
+  over_temp_cal->num_model_pts = 0;
+
   // This defines the minimum contiguous set of points to allow a model update
   // when the next offset estimate is received. They are placed at a common
   // temperature range that is likely to get replaced with actual data soon.
@@ -701,7 +959,6 @@
   float offset_temp_celsius =
       (start_bin_num + 0.5f) * over_temp_cal->delta_temp_per_bin;
 
-  size_t i;
   size_t j;
   for (i = 0; i < over_temp_cal->min_num_model_pts; i++) {
     float offset[3];
@@ -718,13 +975,13 @@
   }
 
 #ifdef OVERTEMPCAL_DBG_ENABLED
-  if (over_temp_cal->min_num_model_pts > 0) {
+  if (over_temp_cal->num_model_pts > 0) {
     CAL_DEBUG_LOG("[OVER_TEMP_CAL:INIT]", "Model Jump-Start:  #Points = %lu.",
-                  (unsigned long int)over_temp_cal->min_num_model_pts);
+                  (unsigned long int)over_temp_cal->num_model_pts);
   }
 #endif  // OVERTEMPCAL_DBG_ENABLED
 
-  return (over_temp_cal->min_num_model_pts > 0);
+  return (over_temp_cal->num_model_pts > 0);
 }
 
 void updateModel(const struct OverTempCal *over_temp_cal,
@@ -771,34 +1028,6 @@
   sensor_intercept[2] = (sz - st * temp_sensitivity[2]) * inv_n;
 }
 
-void removeSensorOffset(const struct OverTempCal *over_temp_cal, float axis_in,
-                        size_t index, float *axis_out) {
-  ASSERT_NOT_NULL(over_temp_cal);
-  ASSERT_NOT_NULL(over_temp_cal->nearest_offset);
-  ASSERT_NOT_NULL(axis_out);
-
-  // Removes the over-temperature compensated offset from the input sensor data.
-  if (over_temp_cal->temp_sensitivity[index] >= MODEL_INITIAL_STATE ||
-      NANO_ABS(over_temp_cal->temperature_celsius -
-               over_temp_cal->nearest_offset->offset_temp_celsius) <
-          over_temp_cal->delta_temp_per_bin) {
-    // Use the nearest estimate to perform the compensation if either of the
-    // following is true:
-    //    1) This axis model is in its initial state.
-    //    2) The current temperature is within a small neighborhood of the
-    //       'nearest_offset'.
-    // axis_out = axis_in - nearest_offset
-    *axis_out = axis_in - over_temp_cal->nearest_offset->offset[index];
-  } else {
-    // axis_out = axis_in - compensated_offset
-    // Where,
-    //  compensated_offset = (temp_sensitivity * temperature + sensor_intercept)
-    *axis_out = axis_in - (over_temp_cal->temp_sensitivity[index] *
-                               over_temp_cal->temperature_celsius +
-                           over_temp_cal->sensor_intercept[index]);
-  }
-}
-
 bool outlierCheck(struct OverTempCal *over_temp_cal, const float *offset,
                   size_t axis_index, float temperature_celsius) {
   ASSERT_NOT_NULL(over_temp_cal);
@@ -806,7 +1035,7 @@
 
   // If a model has been defined, then check to see if this offset could be a
   // potential outlier:
-  if (over_temp_cal->temp_sensitivity[axis_index] < MODEL_INITIAL_STATE) {
+  if (over_temp_cal->temp_sensitivity[axis_index] < OTC_INITIAL_SENSITIVITY) {
     const float max_error_test = NANO_ABS(
         offset[axis_index] -
         (over_temp_cal->temp_sensitivity[axis_index] * temperature_celsius +
@@ -841,6 +1070,8 @@
   memset(&over_temp_cal->debug_overtempcal, 0, sizeof(struct DebugOverTempCal));
 
   // Copies over the relevant data.
+  memcpy(over_temp_cal->debug_overtempcal.temp_sensitivity,
+         over_temp_cal->temp_sensitivity, 3 * sizeof(float));
   memcpy(over_temp_cal->debug_overtempcal.sensor_intercept,
          over_temp_cal->sensor_intercept, 3 * sizeof(float));
   memcpy(&over_temp_cal->debug_overtempcal.nearest_offset,
@@ -852,16 +1083,8 @@
   over_temp_cal->debug_overtempcal.temperature_celsius =
       over_temp_cal->temperature_celsius;
 
-  size_t j;
-  for (j = 0; j < 3; j++) {
-    over_temp_cal->debug_overtempcal.temp_sensitivity[j] =
-        (over_temp_cal->temp_sensitivity[j] >= MODEL_INITIAL_STATE)
-            ? 0.0f
-            : over_temp_cal->temp_sensitivity[j];
-  }
-
   // Computes the maximum error over all of the model data.
-  getModelError(over_temp_cal,
+  overTempGetModelError(over_temp_cal,
                 over_temp_cal->debug_overtempcal.temp_sensitivity,
                 over_temp_cal->debug_overtempcal.sensor_intercept,
                 over_temp_cal->debug_overtempcal.max_error);
@@ -892,7 +1115,7 @@
 
     case OTC_WAIT_STATE:
       // This helps throttle the print statements.
-      if ((timestamp_nanos - wait_timer) >= OVERTEMPCAL_WAIT_TIME_NANOS) {
+      if (timestamp_nanos >= OVERTEMPCAL_WAIT_TIME_NANOS + wait_timer) {
         over_temp_cal->debug_state = next_state;
       }
       break;
diff --git a/firmware/os/algos/calibration/over_temp/over_temp_cal.h b/firmware/os/algos/calibration/over_temp/over_temp_cal.h
index 4ae8952..3aa3d39 100644
--- a/firmware/os/algos/calibration/over_temp/over_temp_cal.h
+++ b/firmware/os/algos/calibration/over_temp/over_temp_cal.h
@@ -68,9 +68,24 @@
 // Defines the maximum size of the 'model_data' array.
 #define OVERTEMPCAL_MODEL_SIZE (40)
 
+// A common sensor operating temperature at which to start producing the model
+// jump-start data.
+#define JUMPSTART_START_TEMP_CELSIUS (30.0f)
+
 // The maximum number of successive outliers that may be rejected.
 #define OVERTEMPCAL_MAX_OUTLIER_COUNT (3)
 
+// The 'temp_sensitivity' parameters are set to this value to indicate that the
+// model is in its initial state.
+#define OTC_INITIAL_SENSITIVITY (1e6f)
+
+// Minimum "significant" change of offset value.
+#define SIGNIFICANT_OFFSET_CHANGE_RPS (5.23e-5f)  // 3mDPS
+
+// Valid sensor temperature operating range.
+#define OVERTEMPCAL_TEMP_MIN_CELSIUS (-40.0f)
+#define OVERTEMPCAL_TEMP_MAX_CELSIUS (85.0f)
+
 // Over-temperature sensor offset estimate structure.
 struct OverTempCalDataPt {
   // Sensor offset estimate, temperature, and timestamp.
@@ -94,9 +109,6 @@
 struct DebugOverTempCal {
   uint64_t modelupdate_timestamp_nanos;
 
-  // The most recent offset estimate received.
-  struct OverTempCalDataPt latest_offset;
-
   // The offset estimate nearest the current sensor temperature.
   struct OverTempCalDataPt nearest_offset;
 
@@ -132,6 +144,9 @@
   // The temperature at which the offset compensation is performed.
   float temperature_celsius;
 
+  // The stored value of the temperature compensated sensor offset.
+  float compensated_offset_previous[3];
+
   // Pointer to the offset estimate closest to the current sensor temperature.
   struct OverTempCalDataPt *nearest_offset;
 
@@ -150,7 +165,7 @@
   //            min_update_interval_nanos
   //    3) A new set of model parameters are accepted if:
   //         i.  The model fit error is less than, 'max_error_limit'. See
-  //             getModelError() for error metric description.
+  //             overTempGetModelError() for error metric description.
   //         ii. The model fit parameters must be within certain absolute
   //             bounds:
   //               a. ABS(temp_sensitivity) < temp_sensitivity_limit
@@ -287,6 +302,56 @@
                          float *temp_sensitivity, float *sensor_intercept);
 
 /*
+ * Sets the over-temp compensation model data set, and computes new model
+ * parameters provided that 'min_num_model_pts' is satisfied.
+ *
+ * INPUTS:
+ *   over_temp_cal:    Over-temp main data structure.
+ *   model_data:       Array of the new model data set.
+ *   data_length:      Number of model data entries in 'model_data'.
+ *
+ * NOTE: Max array length for 'model_data' is OVERTEMPCAL_MODEL_SIZE.
+ */
+void overTempCalSetModelData(struct OverTempCal *over_temp_cal,
+                             size_t data_length,
+                             const struct OverTempCalDataPt *model_data);
+
+/*
+ * Gets the over-temp compensation model data set.
+ *
+ * INPUTS:
+ *   over_temp_cal:    Over-temp main data structure.
+ * OUTPUTS:
+ *   model_data:       Array containing the model data set.
+ *   data_length:      Number of model data entries in 'model_data'.
+ *
+ * NOTE: Max array length for 'model_data' is OVERTEMPCAL_MODEL_SIZE.
+ */
+void overTempCalGetModelData(struct OverTempCal *over_temp_cal,
+                             size_t *data_length,
+                             struct OverTempCalDataPt *model_data);
+
+/*
+ * Returns 'true' if the estimated offset has changed by
+ * 'SIGNIFICANT_OFFSET_CHANGE_RPS' and provides the current over-temperature
+ * compensated offset vector. This function is useful for detecting changes in
+ * the offset vector.
+ *
+ * INPUTS:
+ *   over_temp_cal:    Over-temp data structure.
+ *   timestamp_nanos:  The current system timestamp.
+ * OUTPUTS:
+ *   compensated_offset: Temperature compensated offset estimate array.
+ *   compensated_offset_temperature_celsius: Compensated offset temperature.
+ *
+ * NOTE: Arrays are all 3-dimensional with indices: 0=x, 1=y, 2=z.
+ */
+bool overTempCalGetOffset(struct OverTempCal *over_temp_cal,
+                          uint64_t timestamp_nanos,
+                          float *compensated_offset_temperature_celsius,
+                          float *compensated_offset);
+
+/*
  * Removes the over-temp compensated offset from the input sensor data.
  *
  * INPUTS:
@@ -347,9 +412,9 @@
  * NOTE 1: Arrays are all 3-dimensional with indices: 0=x, 1=y, 2=z.
  * NOTE 2: This function is provided for testing purposes.
  */
-void getModelError(const struct OverTempCal *over_temp_cal,
-                   const float *temp_sensitivity, const float *sensor_intercept,
-                   float *max_error);
+void overTempGetModelError(const struct OverTempCal *over_temp_cal,
+                           const float *temp_sensitivity,
+                           const float *sensor_intercept, float *max_error);
 
 #ifdef OVERTEMPCAL_DBG_ENABLED
 // This debug printout function assumes the input sensor data is a gyroscope
diff --git a/firmware/os/algos/calibration/util/cal_log.h b/firmware/os/algos/calibration/util/cal_log.h
index 64f70f8..f2e711f 100644
--- a/firmware/os/algos/calibration/util/cal_log.h
+++ b/firmware/os/algos/calibration/util/cal_log.h
@@ -25,16 +25,20 @@
 #ifdef GCC_DEBUG_LOG
 # include <stdio.h>
 # define CAL_DEBUG_LOG(tag, fmt, ...) \
-  printf("%s " fmt "\n", tag, ##__VA_ARGS__);
-#else  // GCC_DEBUG_LOG
+   printf("%s " fmt "\n", tag, ##__VA_ARGS__);
+#elif _OS_BUILD_
 # include <seos.h>
 # ifndef LOG_FUNC
 #  define LOG_FUNC(level, fmt, ...) osLog(level, fmt, ##__VA_ARGS__)
 # endif  // LOG_FUNC
 # define LOGD_TAG(tag, fmt, ...) \
-  LOG_FUNC(LOG_DEBUG, "%s " fmt "\n", tag, ##__VA_ARGS__)
+   LOG_FUNC(LOG_DEBUG, "%s " fmt "\n", tag, ##__VA_ARGS__)
 # define CAL_DEBUG_LOG(tag, fmt, ...) \
-  osLog(LOG_DEBUG, "%s " fmt, tag, ##__VA_ARGS__);
+   osLog(LOG_DEBUG, "%s " fmt, tag, ##__VA_ARGS__);
+#else  // _OS_BUILD_
+# include <chre.h>
+# define CAL_DEBUG_LOG(tag, fmt, ...) \
+   chreLog(CHRE_LOG_INFO, "%s " fmt, tag, ##__VA_ARGS__)
 #endif  // GCC_DEBUG_LOG
 
 #ifdef __cplusplus
diff --git a/firmware/os/algos/common/math/mat.c b/firmware/os/algos/common/math/mat.c
index 5ab6625..e137435 100644
--- a/firmware/os/algos/common/math/mat.c
+++ b/firmware/os/algos/common/math/mat.c
@@ -18,8 +18,17 @@
 
 #include <assert.h>
 #include <float.h>
+
+#ifdef _OS_BUILD_
 #include <nanohub_math.h>
 #include <seos.h>
+#else
+#include <math.h>
+#ifndef UNROLLED
+#define UNROLLED
+#endif
+#endif  // _OS_BUILD_
+
 #include <stddef.h>
 #include <string.h>
 
@@ -595,7 +604,7 @@
   size_t i;
   for (i = 0; i < nrows; ++i) {
     const float *row = &A[i * ncols];
-    out[i] = vecDot(row, v, ncols);
+    out[i] = vecDot(row, v, (int)ncols);
   }
 }
 
@@ -617,29 +626,31 @@
   ASSERT_NOT_NULL(x);
   ASSERT_NOT_NULL(L);
   ASSERT_NOT_NULL(b);
+  ASSERT(n <= INT32_MAX);
   int32_t i, j;  // Loops below require signed integers.
+  int32_t s_n = (int32_t)n; // Signed n.
   float sum = 0.0f;
   // 1. Solve Ly = b through forward substitution. Use x[] to store y.
-  for (i = 0; i < n; ++i) {
+  for (i = 0; i < s_n; ++i) {
     sum = 0.0f;
     for (j = 0; j < i; ++j) {
-      sum += L[i * n + j] * x[j];
+      sum += L[i * s_n + j] * x[j];
     }
     // Check for non-zero diagonals (don't divide by zero).
-    if (L[i * n + i] < EPSILON) {
+    if (L[i * s_n + i] < EPSILON) {
       return false;
     }
-    x[i] = (b[i] - sum) / L[i * n + i];
+    x[i] = (b[i] - sum) / L[i * s_n + i];
   }
 
   // 2. Solve L'x = y through backwards substitution. Use x[] to store both
   // y and x.
-  for (i = n - 1; i >= 0; --i) {
+  for (i = s_n - 1; i >= 0; --i) {
     sum = 0.0f;
-    for (j = i + 1; j < n; ++j) {
-      sum += L[j * n + i] * x[j];
+    for (j = i + 1; j < s_n; ++j) {
+      sum += L[j * s_n + i] * x[j];
     }
-    x[i] = (x[i] - sum) / L[i * n + i];
+    x[i] = (x[i] - sum) / L[i * s_n + i];
   }
 
   return true;
diff --git a/firmware/os/algos/common/math/quat.c b/firmware/os/algos/common/math/quat.c
index 89727ff..f7fb3c7 100644
--- a/firmware/os/algos/common/math/quat.c
+++ b/firmware/os/algos/common/math/quat.c
@@ -15,7 +15,6 @@
  */
 
 #include "common/math/quat.h"
-#include <nanohub_math.h>
 
 static float clamp(float x) { return x < 0.0f ? 0.0f : x; }
 
diff --git a/firmware/os/algos/common/math/vec.c b/firmware/os/algos/common/math/vec.c
index 97b2b8c..62039bb 100644
--- a/firmware/os/algos/common/math/vec.c
+++ b/firmware/os/algos/common/math/vec.c
@@ -15,7 +15,6 @@
  */
 
 #include "common/math/vec.h"
-#include <nanohub_math.h>
 
 void findOrthogonalVector(float inX, float inY, float inZ, float *outX,
                           float *outY, float *outZ) {
diff --git a/firmware/os/algos/common/math/vec.h b/firmware/os/algos/common/math/vec.h
index 77ab492..098c6d1 100644
--- a/firmware/os/algos/common/math/vec.h
+++ b/firmware/os/algos/common/math/vec.h
@@ -32,7 +32,12 @@
 #ifndef LOCATION_LBS_CONTEXTHUB_NANOAPPS_COMMON_MATH_VEC_H_
 #define LOCATION_LBS_CONTEXTHUB_NANOAPPS_COMMON_MATH_VEC_H_
 
+#ifdef NANOHUB_NON_CHRE_API
 #include <nanohub_math.h>
+#else
+#include <math.h>
+#endif  // NANOHUB_NON_CHRE_API
+
 #include <stddef.h>
 #include "util/nano_assert.h"
 
@@ -48,13 +53,13 @@
   float x, y, z, w;
 };
 
-#ifndef NANO_ABS
-#define NANO_ABS(x) ((x) > 0 ? (x) : -(x))
-#endif
+#define NANO_PI (3.14159265359f)
 
-#ifndef NANO_MAX
+#define NANO_ABS(x) ((x) > 0 ? (x) : -(x))
+
 #define NANO_MAX(a, b) ((a) > (b)) ? (a) : (b)
-#endif
+
+#define NANO_MIN(a, b) ((a) < (b)) ? (a) : (b)
 
 // 3-DIMENSIONAL VECTOR MATH ///////////////////////////////////////////
 static inline void initVec3(struct Vec3 *v, float x, float y, float z) {
diff --git a/firmware/os/algos/util/nano_assert.h b/firmware/os/algos/util/nano_assert.h
index c6389c3..e4f1467 100644
--- a/firmware/os/algos/util/nano_assert.h
+++ b/firmware/os/algos/util/nano_assert.h
@@ -34,11 +34,13 @@
 
 #endif
 
+#ifndef ASSERT
 #ifdef NANO_ASSERT_ENABLED
 #define ASSERT(x) ASSERT_IMPL(x)
 #else
 #define ASSERT(x) ((void)(x))
-#endif
+#endif  // NANO_ASSERT_ENABLED
+#endif  // ASSERT
 
 // Use NULL when compiling for C and nullptr for C++.
 #ifdef __cplusplus
diff --git a/firmware/os/core/hostIntf.c b/firmware/os/core/hostIntf.c
index 4aa9a1e..084e508 100644
--- a/firmware/os/core/hostIntf.c
+++ b/firmware/os/core/hostIntf.c
@@ -29,6 +29,7 @@
 
 #include <platform.h>
 #include <cpu.h>
+#include <halIntf.h>
 #include <hostIntf.h>
 #include <hostIntf_priv.h>
 #include <nanohubCommand.h>
@@ -1082,7 +1083,7 @@
     }
 }
 
-static void hostIntfAddBlock(struct HostIntfDataBuffer *data, bool discardable)
+static void hostIntfAddBlock(struct HostIntfDataBuffer *data, bool discardable, bool interrupt)
 {
     if (!simpleQueueEnqueue(mOutputQ, data, sizeof(uint32_t) + data->length, discardable))
         return;
@@ -1091,7 +1092,7 @@
         mWakeupBlocks++;
     else if (data->interrupt == NANOHUB_INT_NONWAKEUP)
         mNonWakeupBlocks++;
-    nanohubPrefetchTx(data->interrupt, mWakeupBlocks, mNonWakeupBlocks);
+    nanohubPrefetchTx(interrupt ? data->interrupt : HOSTINTF_MAX_INTERRUPTS, mWakeupBlocks, mNonWakeupBlocks);
 }
 
 static void hostIntfNotifyReboot(uint32_t reason)
@@ -1151,6 +1152,7 @@
 
         osEventUnsubscribe(mHostIntfTid, EVT_APP_START);
         osEventSubscribe(mHostIntfTid, EVT_NO_SENSOR_CONFIG_EVENT);
+        osEventSubscribe(mHostIntfTid, EVT_APP_TO_SENSOR_HAL_DATA);
         osEventSubscribe(mHostIntfTid, EVT_APP_TO_HOST);
 #ifdef DEBUG_LOG_EVT
         osEventSubscribe(mHostIntfTid, EVT_DEBUG_LOG);
@@ -1163,7 +1165,7 @@
         data->dataType = HOSTINTF_DATA_TYPE_RESET_REASON;
         data->interrupt = NANOHUB_INT_WAKEUP;
         memcpy(data->buffer, &reason, sizeof(reason));
-        hostIntfAddBlock(data, false);
+        hostIntfAddBlock(data, false, true);
         hostIntfNotifyReboot(reason);
     }
 }
@@ -1180,7 +1182,7 @@
         data->dataType = HOSTINTF_DATA_TYPE_APP_TO_HOST;
         data->interrupt = NANOHUB_INT_WAKEUP;
         memcpy(data->buffer, evtData, data->length);
-        hostIntfAddBlock(data, false);
+        hostIntfAddBlock(data, false, true);
     }
 }
 
@@ -1198,7 +1200,7 @@
     struct HostIntfDataBuffer *data = (struct HostIntfDataBuffer *)evtData;
 
     if (data->sensType == SENS_TYPE_INVALID && data->dataType == HOSTINTF_DATA_TYPE_LOG)
-        hostIntfAddBlock(data, true);
+        hostIntfAddBlock(data, true, true);
 }
 #endif
 
@@ -1324,6 +1326,9 @@
             case CONFIG_CMD_DISABLE:
                 onConfigCmdDisableOne(sensor, cmd);
                 break;
+            case CONFIG_CMD_CFG_DATA:
+                onConfigCmdCfgDataAll(sensor, cmd);
+                break;
             }
         } else {
             switch (cmd->cmd) {
@@ -1351,6 +1356,16 @@
     }
 }
 
+static void onEvtAppToSensorHalData(const void *evtData)
+{
+    struct HostIntfDataBuffer *data = (struct HostIntfDataBuffer *)evtData;
+    if (data->sensType == SENS_TYPE_INVALID
+            && data->dataType == HOSTINTF_DATA_TYPE_APP_TO_SENSOR_HAL) {
+        struct AppToSensorHalDataBuffer *buffer = (struct AppToSensorHalDataBuffer *)data;
+        hostIntfAddBlock(data, (buffer->payload.type & EVENT_TYPE_BIT_DISCARDABLE) != 0, false);
+    }
+}
+
 static void copyEmbeddedSamples(struct ActiveSensor *sensor, const void* evtData)
 {
     uint64_t sensorTime = sensorGetTime();
@@ -1491,6 +1506,9 @@
     case EVT_NO_SENSOR_CONFIG_EVENT:
         onEvtNoSensorConfigEvent(evtData);
         break;
+    case EVT_APP_TO_SENSOR_HAL_DATA:
+        onEvtAppToSensorHalData(evtData);
+        break;
     default:
         onEvtSensorData(evtType, evtData);
         break;
diff --git a/firmware/os/core/nanohubCommand.c b/firmware/os/core/nanohubCommand.c
index e128314..1719cef 100644
--- a/firmware/os/core/nanohubCommand.c
+++ b/firmware/os/core/nanohubCommand.c
@@ -698,6 +698,9 @@
             case HOSTINTF_DATA_TYPE_RESET_REASON:
                 packet->evtType = htole32(EVT_RESET_REASON);
                 break;
+            case HOSTINTF_DATA_TYPE_APP_TO_SENSOR_HAL:
+                packet->evtType = htole32(EVT_APP_TO_SENSOR_HAL_DATA);
+                break;
 #ifdef DEBUG_LOG_EVT
             case HOSTINTF_DATA_TYPE_LOG:
                 packet->evtType = htole32(HOST_EVT_DEBUG_LOG);
diff --git a/firmware/os/drivers/bosch_bmi160/akm_ak09915_slave.c b/firmware/os/drivers/bosch_bmi160/akm_ak09915_slave.c
index 371f997..286ff21 100644
--- a/firmware/os/drivers/bosch_bmi160/akm_ak09915_slave.c
+++ b/firmware/os/drivers/bosch_bmi160/akm_ak09915_slave.c
@@ -17,8 +17,6 @@
 #include <string.h>
 #include "akm_ak09915_slave.h"
 
-#define kScale_mag 0.15f
-
 void parseMagData(struct MagTask *magTask, uint8_t *buf, float *x, float *y, float *z) {
     int32_t raw_x = (*(int16_t *)&buf[0]);
     int32_t raw_y = (*(int16_t *)&buf[2]);
diff --git a/firmware/os/drivers/bosch_bmi160/akm_ak09915_slave.h b/firmware/os/drivers/bosch_bmi160/akm_ak09915_slave.h
index d7bb12e..af9d520 100644
--- a/firmware/os/drivers/bosch_bmi160/akm_ak09915_slave.h
+++ b/firmware/os/drivers/bosch_bmi160/akm_ak09915_slave.h
@@ -25,6 +25,8 @@
 extern "C" {
 #endif
 
+#define kScale_mag 0.15f
+
 #define AKM_AK09915_DEVICE_ID     0x1048
 #define AKM_AK09915_REG_WIA1      0x00
 #define AKM_AK09915_REG_DATA      0x11
diff --git a/firmware/os/drivers/bosch_bmi160/bosch_bmi160.c b/firmware/os/drivers/bosch_bmi160/bosch_bmi160.c
index 9e10ec4..fec027c 100644
--- a/firmware/os/drivers/bosch_bmi160/bosch_bmi160.c
+++ b/firmware/os/drivers/bosch_bmi160/bosch_bmi160.c
@@ -1,3 +1,4 @@
+
 /*
  * Copyright (C) 2016 The Android Open Source Project
  *
@@ -20,6 +21,7 @@
 #include <errno.h>
 #include <gpio.h>
 #include <heap.h>
+#include <halIntf.h>
 #include <hostIntf.h>
 #include <i2c.h>
 #include <isr.h>
@@ -46,12 +48,29 @@
 #include <calibration/accelerometer/accel_cal.h>
 #endif
 
+#if defined(OVERTEMPCAL_ENABLED) && !defined(GYRO_CAL_ENABLED)
+#undef OVERTEMPCAL_ENABLED
+#endif
+
+#if defined(GYRO_CAL_DBG_ENABLED) && !defined(GYRO_CAL_ENABLED)
+#undef GYRO_CAL_DBG_ENABLED
+#endif
+
+#if defined(OVERTEMPCAL_DBG_ENABLED) && !defined(OVERTEMPCAL_ENABLED)
+#undef OVERTEMPCAL_DBG_ENABLED
+#endif
+
 #ifdef GYRO_CAL_ENABLED
 #include <calibration/gyroscope/gyro_cal.h>
+#endif  // GYRO_CAL_ENABLED
+
+#ifdef GYRO_CAL_DBG_ENABLED
+#include <calibration/util/cal_log.h>
+#endif  // GYRO_CAL_DBG_ENABLED
+
 #ifdef OVERTEMPCAL_ENABLED
 #include <calibration/over_temp/over_temp_cal.h>
 #endif  // OVERTEMPCAL_ENABLED
-#endif  // GYRO_CAL_ENABLED
 
 #include <limits.h>
 #include <stdlib.h>
@@ -85,7 +104,7 @@
 #define DBG_WM_CALC               0
 #define TIMESTAMP_DBG             0
 
-#define BMI160_APP_VERSION 12
+#define BMI160_APP_VERSION 14
 
 // fixme: to list required definitions for a slave mag
 #ifdef USE_BMM150
@@ -421,6 +440,12 @@
     enum SensorIndex idx;
 };
 
+struct OtcGyroUpdateBuffer {
+    struct AppToSensorHalDataBuffer head;
+    struct GyroOtcData data;
+    volatile uint8_t lock; // lock for static object
+} __attribute__((packed));
+
 struct BMI160Task {
     uint32_t tid;
     struct BMI160Sensor sensors[NUM_OF_SENSOR];
@@ -428,12 +453,13 @@
 #ifdef GYRO_CAL_ENABLED
     // Gyro Cal -- Declaration.
     struct GyroCal gyro_cal;
+#endif  //  GYRO_CAL_ENABLED
 
 #ifdef OVERTEMPCAL_ENABLED
     // Over-temp gyro calibration object.
     struct OverTempCal over_temp_gyro_cal;
+    struct OtcGyroUpdateBuffer otcGyroUpdateBuffer;
 #endif  //  OVERTEMPCAL_ENABLED
-#endif  // GYRO_CAL_ENABLED
 
     // time keeping.
     uint64_t last_sensortime;
@@ -684,6 +710,15 @@
 #define initiateFifoRead(a) initiateFifoRead_(_task, (a))
 static uint8_t* shallowParseFrame(uint8_t * buf, int size);
 
+#ifdef OVERTEMPCAL_ENABLED
+// otc gyro cal save restore functions
+static void handleOtcGyroConfig_(TASK, const struct AppToSensorHalDataPayload *data);
+#define handleOtcGyroConfig(a) handleOtcGyroConfig_(_task, (a))
+static bool sendOtcGyroUpdate_();
+#define sendOtcGyroUpdate() sendOtcGyroUpdate_(_task)
+static void unlockOtcGyroUpdateBuffer();
+#endif  // OVERTEMPCAL_ENABLED
+
 // Binary dump to osLog
 static void dumpBinary(void* buf, unsigned int address, size_t size);
 
@@ -704,8 +739,9 @@
     { DEC_INFO_RATE_BIAS("Gyroscope", GyrRates, SENS_TYPE_GYRO, NUM_AXIS_THREE,
             NANOHUB_INT_NONWAKEUP, 20, SENS_TYPE_GYRO_BIAS) },
 #ifdef MAG_SLAVE_PRESENT
-    { DEC_INFO_RATE_BIAS("Magnetometer", MagRates, SENS_TYPE_MAG, NUM_AXIS_THREE,
-            NANOHUB_INT_NONWAKEUP, 600, SENS_TYPE_MAG_BIAS) },
+    { DEC_INFO_RATE_RAW_BIAS("Magnetometer", MagRates, SENS_TYPE_MAG, NUM_AXIS_THREE,
+            NANOHUB_INT_NONWAKEUP, 600, SENS_TYPE_MAG_RAW, 1.0/kScale_mag,
+            SENS_TYPE_MAG_BIAS) },
 #endif
     { DEC_INFO("Step Detector", SENS_TYPE_STEP_DETECT, NUM_AXIS_EMBEDDED,
             NANOHUB_INT_NONWAKEUP, 100) },
@@ -1005,8 +1041,8 @@
     // set mag to SLEEP mode
     MAG_WRITE(BMM150_REG_CTRL_1, 0x01);
 #elif USE_AK09915
-    // set "low" Noise Suppression Filter (NSF) settings
-    MAG_WRITE(AKM_AK09915_REG_CNTL1, 0x20);
+    // Disable Noise Suppression Filter (NSF) settings
+    MAG_WRITE(AKM_AK09915_REG_CNTL1, 0x00);
 #endif
 }
 
@@ -1944,6 +1980,7 @@
 
 static void parseRawData(struct BMI160Sensor *mSensor, uint8_t *buf, float kScale, uint64_t sensorTime)
 {
+    TDECL();
     struct TripleAxisDataPoint *sample;
     uint64_t rtc_time, cur_time;
     uint32_t delta_time;
@@ -1995,9 +2032,9 @@
         gyroCalUpdateMag(&mTask.gyro_cal,
                          rtc_time,  // nsec
                          x, y, z);
-#endif
+#endif  // GYRO_CAL_ENABLED
     } else
-#endif
+#endif  // MAG_SLAVE_PRESENT
     {
         raw_x = (buf[0] | buf[1] << 8);
         raw_y = (buf[2] | buf[3] << 8);
@@ -2031,36 +2068,47 @@
                             rtc_time,  // nsec
                             x, y, z, mTask.tempCelsius);
 
-#ifdef GYRO_CAL_DBG_ENABLED
-          // Gyro Cal -- Read out Debug data.
-          gyroCalDebugPrint(&mTask.gyro_cal, rtc_time);
-#endif  // GYRO_CAL_DBG_ENABLED
-
-#ifdef OVERTEMPCAL_ENABLED
-#ifdef OVERTEMPCAL_DBG_ENABLED
-          overTempCalDebugPrint(&mTask.over_temp_gyro_cal, rtc_time);
-#endif  // OVERTEMPCAL_DBG_ENABLED
-#endif  // OVERTEMPCAL_ENABLED
-
 #ifdef OVERTEMPCAL_ENABLED
           // Over-Temp Gyro Cal -- Update measured temperature.
           overTempCalSetTemperature(&mTask.over_temp_gyro_cal, rtc_time,
                                     mTask.tempCelsius);
 
           // Over-Temp Gyro Cal -- Apply over-temp calibration correction.
-          overTempCalRemoveOffset(&mTask.over_temp_gyro_cal, rtc_time, x, y,
-                                  z, /* input values */
-                                  &x, &y, &z /* calibrated output */);
-#else
+          overTempCalRemoveOffset(&mTask.over_temp_gyro_cal, rtc_time,
+                                  x, y, z,    /* input values */
+                                  &x, &y, &z  /* calibrated output */);
+#else  // OVERTEMPCAL_ENABLED
           // Gyro Cal -- Apply calibration correction.
-          gyroCalRemoveBias(&mTask.gyro_cal, x, y, z, /* input values */
-                            &x, &y, &z /* calibrated output */);
+          gyroCalRemoveBias(&mTask.gyro_cal,
+                            x, y, z,    /* input values */
+                            &x, &y, &z  /* calibrated output */);
 #endif  // OVERTEMPCAL_ENABLED
+
+#if defined(GYRO_CAL_DBG_ENABLED) || defined(OVERTEMPCAL_DBG_ENABLED)
+          // This flag keeps GyroCal and OverTempCal from printing back-to-back.
+          // If they do, then sometimes important print log data gets dropped.
+          static size_t print_flag = 0;
+
+          if (print_flag > 0) {
+#ifdef GYRO_CAL_DBG_ENABLED
+            // Gyro Cal -- Read out Debug data.
+            gyroCalDebugPrint(&mTask.gyro_cal, rtc_time);
+#endif  // GYRO_CAL_DBG_ENABLED
+            print_flag = 0;
+          } else {
+#ifdef OVERTEMPCAL_ENABLED
+#ifdef OVERTEMPCAL_DBG_ENABLED
+            // Over-Temp Gyro Cal -- Read out Debug data.
+            overTempCalDebugPrint(&mTask.over_temp_gyro_cal, rtc_time);
+#endif  // OVERTEMPCAL_DBG_ENABLED
+#endif  // OVERTEMPCAL_ENABLED
+            print_flag = 1;
+          }
+#endif  // GYRO_CAL_DBG_ENABLED || OVERTEMPCAL_DBG_ENABLED
 #endif  // GYRO_CAL_ENABLED
         }
     }
 
-
     if (mSensor->data_evt == NULL) {
         if (!allocateDataEvt(mSensor, rtc_time)) {
             return;
@@ -2102,54 +2150,87 @@
     }
 #endif
 #ifdef GYRO_CAL_ENABLED
-    // Gyro Cal -- Notify HAL about new gyro bias calibration
-    bool new_gyro_cal_update =  gyroCalNewBiasAvailable(&mTask.gyro_cal);
-    if (mSensor->idx == GYR && new_gyro_cal_update) {
+    if (mSensor->idx == GYR) {
+      // GyroCal -- Checks for a new offset estimate update.
+      float gyro_offset[3] = {0.0f, 0.0f, 0.0f};
+      float gyro_offset_temperature_celsius = 0.0f;
+      bool new_gyrocal_offset_update = gyroCalNewBiasAvailable(&mTask.gyro_cal);
+      if (new_gyrocal_offset_update) {
+        // GyroCal -- Gets the GyroCal offset estimate.
+        gyroCalGetBias(&mTask.gyro_cal, &gyro_offset[0], &gyro_offset[1],
+                       &gyro_offset[2], &gyro_offset_temperature_celsius);
+
+#ifdef OVERTEMPCAL_ENABLED
+        // OTC-Gyro Cal -- Sends a new GyroCal estimate to the OTC-Gyro.
+        overTempCalUpdateSensorEstimate(&mTask.over_temp_gyro_cal, rtc_time,
+                                        gyro_offset,
+                                        gyro_offset_temperature_celsius);
+#endif  // OVERTEMPCAL_ENABLED
+      }
+
+#ifdef OVERTEMPCAL_ENABLED
+      // OTC-Gyro Cal -- A timer is used to limit the frequency of the offset
+      // update checks.
+      static uint64_t imu_new_otc_offset_timer = 0;  // nanoseconds
+      bool new_otc_offset_update = false;
+      if ((rtc_time - imu_new_otc_offset_timer) >= 500000000) {
+        imu_new_otc_offset_timer = rtc_time;
+
+        // OTC-Gyro Cal --  Gets the latest OTC-Gyro temperature compensated
+        // offset estimate.
+        new_otc_offset_update =
+            overTempCalGetOffset(&mTask.over_temp_gyro_cal, rtc_time,
+                                 &gyro_offset_temperature_celsius, gyro_offset);
+      }
+
+      if (new_otc_offset_update) {
+#else  // OVERTEMPCAL_ENABLED
+      if (new_gyrocal_offset_update) {
+#endif  // OVERTEMPCAL_ENABLED
         if (mSensor->data_evt->samples[0].firstSample.numSamples > 0) {
-            // flush existing samples so the bias appears after them
-            flushData(mSensor,
-                    EVENT_TYPE_BIT_DISCARDABLE | sensorGetMyEventType(mSensorInfo[GYR].sensorType));
-            if (!allocateDataEvt(mSensor, rtc_time)) {
-                return;
-            }
+          // flush existing samples so the bias appears after them.
+          flushData(mSensor,
+                    EVENT_TYPE_BIT_DISCARDABLE |
+                        sensorGetMyEventType(mSensorInfo[GYR].sensorType));
+          if (!allocateDataEvt(mSensor, rtc_time)) {
+            return;
+          }
         }
         mSensor->data_evt->samples[0].firstSample.biasCurrent = true;
         mSensor->data_evt->samples[0].firstSample.biasPresent = 1;
         mSensor->data_evt->samples[0].firstSample.biasSample =
-                mSensor->data_evt->samples[0].firstSample.numSamples;
-        sample = &mSensor->data_evt->samples[mSensor->data_evt->samples[0].firstSample.numSamples++];
-        gyroCalGetBias(&mTask.gyro_cal, &sample->x, &sample->y, &sample->z);
+            mSensor->data_evt->samples[0].firstSample.numSamples;
+        sample = &mSensor->data_evt->samples[mSensor->data_evt->samples[0]
+                                                 .firstSample.numSamples++];
+        // Updates the gyro offset in HAL.
+        sample->x = gyro_offset[0];
+        sample->y = gyro_offset[1];
+        sample->z = gyro_offset[2];
+
+#if defined(GYRO_CAL_DBG_ENABLED) || defined(OVERTEMPCAL_DBG_ENABLED)
+        CAL_DEBUG_LOG("[GYRO_OFFSET:STORED]",
+                      "Offset|Temp|Time: %s%d.%06d, %s%d.%06d, %s%d.%06d | "
+                      "%s%d.%06d | %llu",
+                      CAL_ENCODE_FLOAT(sample->x, 6),
+                      CAL_ENCODE_FLOAT(sample->y, 6),
+                      CAL_ENCODE_FLOAT(sample->z, 6),
+                      CAL_ENCODE_FLOAT(gyro_offset_temperature_celsius, 6),
+                      (unsigned long long int)rtc_time);
+#endif  // GYRO_CAL_DBG_ENABLED || OVERTEMPCAL_DBG_ENABLED
+
         flushData(mSensor, sensorGetMyEventType(mSensorInfo[GYR].biasType));
-
         if (!allocateDataEvt(mSensor, rtc_time)) {
-            return;
+          return;
         }
-    }
+      }
 #ifdef OVERTEMPCAL_ENABLED
-    // Over-Temp Gyro Cal -- Send new gyro cal result to the over-temp cal.
-    float offset[3] = {0.0f, 0.0f, 0.0f};
-    if (new_gyro_cal_update) {
-      // Gets the gyro cal offset value.
-      gyroCalGetBias(&mTask.gyro_cal, &offset[0], &offset[1], &offset[2]);
-
-      // Sends it to the over-temp cal along with the current temperature and
-      // time stamp.
-      overTempCalUpdateSensorEstimate(&mTask.over_temp_gyro_cal, rtc_time,
-                                      offset, mTask.tempCelsius);
-    }
-
-    // Over-Temp Gyro Cal -- Notify HAL about new over-temp bias model data.
-    if (mSensor->idx == GYR &&
-        overTempCalNewModelUpdateAvailable(&mTask.over_temp_gyro_cal)) {
-      float offset_temp = 0.0f;
-      float temp_sensitivity[3] = {0.0f, 0.0f, 0.0f};
-      float sensor_intercept[3] = {0.0f, 0.0f, 0.0f};
-      overTempCalGetModel(&mTask.over_temp_gyro_cal,
-                          offset, &offset_temp, &rtc_time,
-                          temp_sensitivity, sensor_intercept);
-      // TODO(davejacobs, pengxu) -- Send data to HAL.
-    }
+      if (overTempCalNewModelUpdateAvailable(&mTask.over_temp_gyro_cal)
+          || new_otc_offset_update) {
+        // Notify HAL to store new gyro OTC-Gyro data.
+        sendOtcGyroUpdate();
+      }
 #endif  // OVERTEMPCAL_ENABLED
+    }
 #endif  // GYRO_CAL_ENABLED
 
     sample = &mSensor->data_evt->samples[mSensor->data_evt->samples[0].firstSample.numSamples++];
@@ -2986,28 +3067,33 @@
 
 static bool gyrCfgData(void *data, void *cookie)
 {
-    struct CfgData {
-        int32_t hw[3];
-        float sw[3];
-    };
-    struct CfgData *values = data;
-
-    mTask.sensors[GYR].offset[0] = values->hw[0];
-    mTask.sensors[GYR].offset[1] = values->hw[1];
-    mTask.sensors[GYR].offset[2] = values->hw[2];
-    mTask.sensors[GYR].offset_enable = true;
+    TDECL();
+    const struct AppToSensorHalDataPayload *p = data;
+    if (p->type == HALINTF_TYPE_GYRO_CAL_BIAS && p->size == sizeof(struct GyroCalBias)) {
+        const struct GyroCalBias *bias = p->gyroCalBias;
+        mTask.sensors[GYR].offset[0] = bias->hardwareBias[0];
+        mTask.sensors[GYR].offset[1] = bias->hardwareBias[1];
+        mTask.sensors[GYR].offset[2] = bias->hardwareBias[2];
+        mTask.sensors[GYR].offset_enable = true;
+        INFO_PRINT("gyrCfgData hw bias: data=%02lx, %02lx, %02lx\n",
+                bias->hardwareBias[0] & 0xFF,
+                bias->hardwareBias[1] & 0xFF,
+                bias->hardwareBias[2] & 0xFF);
 
 #ifdef GYRO_CAL_ENABLED
-    gyroCalSetBias(&mTask.gyro_cal, values->sw[0], values->sw[1], values->sw[2], sensorGetTime());
+        gyroCalSetBias(&T(gyro_cal), bias->softwareBias[0], bias->softwareBias[1],
+                       bias->softwareBias[2], sensorGetTime());
 #endif  // GYRO_CAL_ENABLED
-
-    INFO_PRINT("gyrCfgData: data=%02lx, %02lx, %02lx\n",
-            values->hw[0] & 0xFF, values->hw[1] & 0xFF, values->hw[2] & 0xFF);
-
-    if (!saveCalibration()) {
-        mTask.pending_calibration_save = true;
+        if (!saveCalibration()) {
+            T(pending_calibration_save) = true;
+        }
+#if OVERTEMPCAL_ENABLED
+    } else if (p->type == HALINTF_TYPE_GYRO_OTC_DATA && p->size == sizeof(struct GyroOtcData)) {
+        handleOtcGyroConfig(data);
+#endif // OVERTEMPCAL_ENABLED
+    } else {
+        ERROR_PRINT("Unknown gyro config data type 0x%04x, size %d\n", p->type, p->size);
     }
-
     return true;
 }
 
@@ -3076,17 +3162,32 @@
 #ifdef MAG_SLAVE_PRESENT
 static bool magCfgData(void *data, void *cookie)
 {
-    float *values = data;
+    const struct AppToSensorHalDataPayload *p = data;
+    if (p->type == HALINTF_TYPE_MAG_CAL_BIAS && p->size == sizeof(struct MagCalBias)) {
+        const struct MagCalBias *d = p->magCalBias;
+        INFO_PRINT("magCfgData: calibration %ldnT, %ldnT, %ldnT\n",
+                (int32_t)(d->bias[0] * 1000),
+                (int32_t)(d->bias[1] * 1000),
+                (int32_t)(d->bias[2] * 1000));
 
-    INFO_PRINT("magCfgData: %ld, %ld, %ld\n",
-            (int32_t)(values[0] * 1000), (int32_t)(values[1] * 1000), (int32_t)(values[2] * 1000));
+        mTask.moc.x_bias = d->bias[0];
+        mTask.moc.y_bias = d->bias[1];
+        mTask.moc.z_bias = d->bias[2];
+        mTask.magBiasPosted = false;
+    } else if (p->type == HALINTF_TYPE_MAG_LOCAL_FIELD && p->size == sizeof(struct MagLocalField)) {
+        const struct MagLocalField *d = p->magLocalField;
+        INFO_PRINT("magCfgData: local field strength %dnT, dec %ddeg, inc %ddeg\n",
+                (int)(d->strength * 1000),
+                (int)(d->declination * 180 / M_PI + 0.5f),
+                (int)(d->inclination * 180 / M_PI + 0.5f));
 
-    mTask.moc.x_bias = values[0];
-    mTask.moc.y_bias = values[1];
-    mTask.moc.z_bias = values[2];
+        // Passing local field information to mag calibration routine
+        diversityCheckerLocalFieldUpdate(&mTask.moc.diversity_checker, d->strength);
 
-    mTask.magBiasPosted = false;
-
+        // TODO: pass local field information to rotation vector sensor.
+    } else {
+        ERROR_PRINT("magCfgData: unknown type 0x%04x, size %d", p->type, p->size);
+    }
     return true;
 }
 #endif
@@ -3711,14 +3812,16 @@
                 0, 0, 0,  // initial bias offset calibration
                 0,        // time stamp of initial bias calibration
                 1.5e9,    // analysis window length = 1.5 seconds
-                5e-5f,    // gyroscope variance threshold [rad/sec]^2
+                7.5e-5f,  // gyroscope variance threshold [rad/sec]^2
                 1e-5f,    // gyroscope confidence delta [rad/sec]^2
                 8e-3f,    // accelerometer variance threshold [m/sec^2]^2
                 1.6e-3f,  // accelerometer confidence delta [m/sec^2]^2
-                1.4f,     // magnetometer variance threshold [uT]^2
+                5.0f,     // magnetometer variance threshold [uT]^2
                 0.25,     // magnetometer confidence delta [uT]^2
                 0.95f,    // stillness threshold [0,1]
-                1);       // 1=gyro calibrations will be applied
+                40.0e-3f * M_PI / 180.0f,  // stillness mean variation limit [rad/sec]
+                1.5f,     // maximum temperature deviation during stillness [C]
+                true);    // gyro calibration enable
 
 #ifdef OVERTEMPCAL_ENABLED
     // Initialize over-temp calibration.
@@ -3726,27 +3829,28 @@
         &mTask.over_temp_gyro_cal,
         5,                          // Min num of points to enable model update.
         5000000000,                 // Min model update interval [nsec].
-        2.0f,                       // Temperature span of bin method [C].
+        0.75f,                      // Temperature span of bin method [C].
         50.0e-3f * M_PI / 180.0f,   // Model fit tolerance [rad/sec].
         172800000000000,            // Model data point age limit [nsec].
         50.0e-3f * M_PI / 180.0f,   // Limit for temp. sensitivity [rad/sec/C].
-        3.0f * M_PI / 180.0f,       // Limit for model intercept parameter [C].
+        3.0f * M_PI / 180.0f,       // Limit for model intercept parameter [rad/sec].
         true);                      // Over-temp compensation enable.
 #endif  // OVERTEMPCAL_ENABLED
 #endif  // GYRO_CAL_ENABLED
 
 #ifdef MAG_SLAVE_PRESENT
 #ifdef DIVERSITY_CHECK_ENABLED
-    initMagCal(&mTask.moc, 0.0f, 0.0f, 0.0f,  // bias x, y, z
-               1.0f, 0.0f, 0.0f,              // c00, c01, c02
-               0.0f, 1.0f, 0.0f,              // c10, c11, c12
-               0.0f, 0.0f, 1.0f,              // c20, c21, c22
-               500.0f,                        // threshold
-               15000.0f,                      // max_distance
-               7,                             // min_num_diverse_vectors
-               1,                             // max_num_max_distance
-               6.0f,                          // var_threshold
-               10.0f);                        // max_min_threshold
+ initMagCal(&mTask.moc, 0.0f, 0.0f, 0.0f,  // bias x, y, z
+            1.0f, 0.0f, 0.0f,              // c00, c01, c02
+            0.0f, 1.0f, 0.0f,              // c10, c11, c12
+            0.0f, 0.0f, 1.0f,              // c20, c21, c22
+            8,                             // min_num_diverse_vectors
+            1,                             // max_num_max_distance
+            6.0f,                          // var_threshold
+            10.0f,                         // max_min_threshold
+            48.f,                          // local_field
+            0.5f,                          // threshold_tuning_param
+            2.552);                        // max_distance_tuning_param
 #else
     initMagCal(&mTask.moc, 0.0f, 0.0f, 0.0f,  // bias x, y, z
                1.0f, 0.0f, 0.0f,              // c00, c01, c02
@@ -4123,4 +4227,55 @@
     }
 }
 
+#ifdef OVERTEMPCAL_ENABLED
+static void handleOtcGyroConfig_(TASK, const struct AppToSensorHalDataPayload *data) {
+    const struct GyroOtcData *d = data->gyroOtcData;
+
+    INFO_PRINT("gyrCfgData otc-data: off %d %d %d, t %d, s %d %d %d, i %d %d %d",
+            (int)(d->lastOffset[0]), (int)(d->lastOffset[1]), (int)(d->lastOffset[2]),
+            (int)(d->lastTemperature),
+            (int)(d->sensitivity[0]), (int)(d->sensitivity[1]), (int)(d->sensitivity[2]),
+            (int)(d->intercept[0]), (int)(d->intercept[1]), (int)(d->intercept[2]));
+
+    overTempCalSetModel(&T(over_temp_gyro_cal), d->lastOffset, d->lastTemperature,
+                        sensorGetTime(), d->sensitivity, d->intercept, true /*jumpstart*/);
+}
+
+static bool sendOtcGyroUpdate_(TASK) {
+    int step = 0;
+    if (atomicCmpXchgByte(&T(otcGyroUpdateBuffer).lock, false, true)) {
+        ++step;
+        //fill HostIntfDataBuffer header
+        struct HostIntfDataBuffer *p = (struct HostIntfDataBuffer *)(&T(otcGyroUpdateBuffer));
+        p->sensType = SENS_TYPE_INVALID;
+        p->length = sizeof(struct AppToSensorHalDataPayload) + sizeof(struct GyroOtcData);
+        p->dataType = HOSTINTF_DATA_TYPE_APP_TO_SENSOR_HAL;
+        p->interrupt = NANOHUB_INT_NONWAKEUP;
+
+        //fill AppToSensorHalDataPayload header
+        struct AppToSensorHalDataBuffer *q = (struct AppToSensorHalDataBuffer *)p;
+        q->payload.size = sizeof(struct GyroOtcData);
+        q->payload.type = HALINTF_TYPE_GYRO_OTC_DATA; // bit-or EVENT_TYPE_BIT_DISCARDABLE
+                                                      // to make it discardable
+
+        // fill payload data
+        struct GyroOtcData *data = q->payload.gyroOtcData;
+        uint64_t timestamp;
+        overTempCalGetModel(&T(over_temp_gyro_cal), data->lastOffset, &data->lastTemperature,
+                            &timestamp, data->sensitivity, data->intercept);
+        if (osEnqueueEvtOrFree(EVT_APP_TO_SENSOR_HAL_DATA, // bit-or EVENT_TYPE_BIT_DISCARDABLE
+                                                          // to make event discardable
+                               p, unlockOtcGyroUpdateBuffer)) {
+            ++step;
+        }
+    }
+    DEBUG_PRINT("otc gyro update, finished at step %d", step);
+    return step == 2;
+}
+
+static void unlockOtcGyroUpdateBuffer(void *event) {
+    atomicXchgByte(&(((struct OtcGyroUpdateBuffer*)(event))->lock), false);
+}
+#endif // OVERTEMPCAL_ENABLED
+
 INTERNAL_APP_INIT(BMI160_APP_ID, BMI160_APP_VERSION, startTask, endTask, handleEvent);
diff --git a/firmware/os/drivers/bosch_bmi160/bosch_bmm150_slave.c b/firmware/os/drivers/bosch_bmi160/bosch_bmm150_slave.c
index 4a223fb..3f21dc7 100644
--- a/firmware/os/drivers/bosch_bmi160/bosch_bmm150_slave.c
+++ b/firmware/os/drivers/bosch_bmi160/bosch_bmm150_slave.c
@@ -17,8 +17,6 @@
 #include <string.h>
 #include "bosch_bmm150_slave.h"
 
-#define kScale_mag 0.0625f         // 1.0f / 16.0f;
-
 void bmm150SaveDigData(struct MagTask *magTask, uint8_t *data, size_t offset)
 {
     // magnetometer temperature calibration data is read in 3 bursts of 8 byte
diff --git a/firmware/os/drivers/bosch_bmi160/bosch_bmm150_slave.h b/firmware/os/drivers/bosch_bmi160/bosch_bmm150_slave.h
index 59e53fb..abc82a9 100644
--- a/firmware/os/drivers/bosch_bmi160/bosch_bmm150_slave.h
+++ b/firmware/os/drivers/bosch_bmi160/bosch_bmm150_slave.h
@@ -25,6 +25,8 @@
 extern "C" {
 #endif
 
+#define kScale_mag 0.0625f         // 1.0f / 16.0f;
+
 #define BMM150_REG_DATA           0x42
 #define BMM150_REG_CTRL_1         0x4b
 #define BMM150_REG_CTRL_2         0x4c
diff --git a/firmware/os/drivers/synaptics_s3708/synaptics_s3708.c b/firmware/os/drivers/synaptics_s3708/synaptics_s3708.c
index be5d3b1..df1afce 100644
--- a/firmware/os/drivers/synaptics_s3708/synaptics_s3708.c
+++ b/firmware/os/drivers/synaptics_s3708/synaptics_s3708.c
@@ -129,6 +129,8 @@
     uint64_t totalEnabledTime;
     uint64_t totalProxEnabledTime;
     uint64_t totalProxFarTime;
+    uint32_t totalProxBecomesFar;
+    uint32_t totalProxBecomesNear;
 };
 
 enum ProxState {
@@ -371,10 +373,14 @@
     proxFarSeconds = U64_DIV_BY_U64_CONSTANT(mTask.stats.totalProxFarTime, 1000000000);
     INFO_PRINT("STATS: enabled %02" PRIu32 ":%02" PRIu32 ":%02" PRIu32
                ", prox enabled %02" PRIu32 ":%02" PRIu32 ":%02" PRIu32
-               ", prox far %02" PRIu32 ":%02" PRIu32 ":%02" PRIu32,
+               ", prox far %02" PRIu32 ":%02" PRIu32 ":%02" PRIu32
+               ", prox *->f %" PRIu32
+               ", prox *->n %" PRIu32,
         enabledSeconds / 3600, (enabledSeconds % 3600) / 60, enabledSeconds % 60,
         proxEnabledSeconds / 3600, (proxEnabledSeconds % 3600) / 60, proxEnabledSeconds % 60,
-        proxFarSeconds / 3600, (proxFarSeconds % 3600) / 60, proxFarSeconds % 60);
+        proxFarSeconds / 3600, (proxFarSeconds % 3600) / 60, proxFarSeconds % 60,
+        mTask.stats.totalProxBecomesFar,
+        mTask.stats.totalProxBecomesNear);
 
     // If the task is disabled, that means the AP is on and has switched the I2C
     // mux. Therefore, no I2C transactions will succeed so skip them.
@@ -539,11 +545,15 @@
                 mTask.proxState = (embeddedSample.fdata < PROXIMITY_THRESH_NEAR) ? PROX_STATE_NEAR : PROX_STATE_FAR;
 
                 if ((lastProxState != PROX_STATE_FAR) && (mTask.proxState == PROX_STATE_FAR)) {
+                    ++mTask.stats.totalProxBecomesFar;
                     mTask.stats.lastProxFarTimestamp = sensorGetTime();
                     setGesturePower(true, false);
-                } else if ((lastProxState == PROX_STATE_FAR) && (mTask.proxState == PROX_STATE_NEAR)) {
-                    mTask.stats.totalProxFarTime += sensorGetTime() - mTask.stats.lastProxFarTimestamp;
-                    setGesturePower(false, false);
+                } else if ((lastProxState != PROX_STATE_NEAR) && (mTask.proxState == PROX_STATE_NEAR)) {
+                    ++mTask.stats.totalProxBecomesNear;
+                    if (lastProxState == PROX_STATE_FAR) {
+                        mTask.stats.totalProxFarTime += sensorGetTime() - mTask.stats.lastProxFarTimestamp;
+                        setGesturePower(false, false);
+                    }
                 }
             }
             break;
@@ -567,6 +577,9 @@
     syscfgSetExtiPort(mTask.pin);
     mTask.isr.func = touchIsr;
 
+    mTask.stats.totalProxBecomesFar = 0;
+    mTask.stats.totalProxBecomesNear = 0;
+
     osEventSubscribe(taskId, EVT_APP_START);
     return true;
 }
diff --git a/firmware/os/inc/eventnums.h b/firmware/os/inc/eventnums.h
index e22fa04..246587a 100644
--- a/firmware/os/inc/eventnums.h
+++ b/firmware/os/inc/eventnums.h
@@ -29,6 +29,7 @@
 #define EVT_APP_TO_HOST                  0x00000401    //app data to host. Type is struct HostHubRawPacket
 #define EVT_MARSHALLED_SENSOR_DATA       0x00000402    //marshalled event data. Type is MarshalledUserEventData
 #define EVT_RESET_REASON                 0x00000403    //reset reason to host.
+#define EVT_APP_TO_SENSOR_HAL_DATA       0x00000404    // sensor driver out of band data update to sensor hal
 #define EVT_DEBUG_LOG                    0x00007F01    // send message payload to Linux kernel log
 #define EVT_MASK                         0x0000FFFF
 
diff --git a/firmware/os/inc/halIntf.h b/firmware/os/inc/halIntf.h
new file mode 100644
index 0000000..1fc727a
--- /dev/null
+++ b/firmware/os/inc/halIntf.h
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#ifndef __HALINTF_H
+#define __HALINTF_H
+#include <stdint.h>
+#include "toolchain.h"
+#include "sensType.h"
+
+/*
+ * This files contains data structure for HAL and driver / algo to exchange information.
+ */
+
+#define APP_TO_SENSOR_HAL_SIZE_MAX      HOSTINTF_SENSOR_DATA_MAX
+#define APP_TO_SENSOR_HAL_PAYLOAD_MAX \
+        (APP_TO_SENSOR_HAL_DATA_MAX - sizeof(struct AppToSensorHalDataBuffer))
+#define APP_TO_SENSOR_HAL_TYPE(sensor, subtype) (((sensor) && 0xFF) | (((subtype) & 0x7F) << 8))
+#define APP_TO_SENSOR_HAL_TYPE_MASK     0x7FFF
+
+enum {
+    // gyro sensor calibration:  GyroCalBias
+    HALINTF_TYPE_GYRO_CAL_BIAS = APP_TO_SENSOR_HAL_TYPE(SENS_TYPE_GYRO, 1),
+
+    // gyro sensor over temperature calibration: GyroOtcData
+    HALINTF_TYPE_GYRO_OTC_DATA = APP_TO_SENSOR_HAL_TYPE(SENS_TYPE_GYRO, 2),
+
+    // mag sensor calibration: MagCalBias
+    HALINTF_TYPE_MAG_CAL_BIAS = APP_TO_SENSOR_HAL_TYPE(SENS_TYPE_MAG, 1),
+
+    // mag local field information: MagLocalField
+    HALINTF_TYPE_MAG_LOCAL_FIELD = APP_TO_SENSOR_HAL_TYPE(SENS_TYPE_MAG, 2),
+
+    // mag sensor spherical calibration: MagSphericalData
+    HALINTF_TYPE_MAG_SPERICAL_DATA = APP_TO_SENSOR_HAL_TYPE(SENS_TYPE_MAG, 3),
+};
+
+SET_PACKED_STRUCT_MODE_ON
+struct GyroCalBias {
+    int32_t hardwareBias[3];   // unit depending on hardware implementation
+    float softwareBias[3];          // rad/s
+} ATTRIBUTE_PACKED;
+SET_PACKED_STRUCT_MODE_OFF
+
+SET_PACKED_STRUCT_MODE_ON
+struct GyroOtcData {
+    float lastOffset[3];    // rad/s
+    float lastTemperature;  // Celsius
+    float sensitivity[3];   // rad/s/K
+    float intercept[3];     // rad/s
+} ATTRIBUTE_PACKED;
+SET_PACKED_STRUCT_MODE_OFF
+
+SET_PACKED_STRUCT_MODE_ON
+struct MagCalBias {
+    float bias[3];     // in uT
+} ATTRIBUTE_PACKED;
+SET_PACKED_STRUCT_MODE_OFF
+
+SET_PACKED_STRUCT_MODE_ON
+struct MagLocalField {
+    float strength;    // in uT
+    float declination; // in rad
+    float inclination; // in rad
+} ATTRIBUTE_PACKED;
+SET_PACKED_STRUCT_MODE_OFF
+
+SET_PACKED_STRUCT_MODE_ON
+struct MagSphericalData {
+    float bias[3];
+    float scale[3];
+    float skew[3];
+} ATTRIBUTE_PACKED;
+SET_PACKED_STRUCT_MODE_OFF
+
+// data structure for upload bulk data to sensor hal
+SET_PACKED_STRUCT_MODE_ON
+struct AppToSensorHalDataPayload {
+    uint8_t size;       // number of bytes in payload data
+    uint8_t reserved;
+    uint16_t type;      // use EVENT_TYPE_BIT_DISCARDABLE to mark discardable update
+    union
+    {
+        uint32_t u[0];
+        int32_t i[0];
+        float f[0];
+        struct GyroCalBias gyroCalBias[0];
+        struct GyroOtcData gyroOtcData[0];
+        struct MagCalBias  magCalBias[0];
+        struct MagLocalField magLocalField[0];
+        struct MagSphericalData magSphericalData[0];
+    };
+} ATTRIBUTE_PACKED;
+SET_PACKED_STRUCT_MODE_OFF
+
+// buffer data structure with header, header compatible with HostIntfDataBuffer
+SET_PACKED_STRUCT_MODE_ON
+struct AppToSensorHalDataBuffer {
+    uint32_t eventType; // placeholder for HostIntfDataBuffer event type field
+    struct AppToSensorHalDataPayload payload;
+};
+SET_PACKED_STRUCT_MODE_OFF
+
+#endif //__HALINTF_H
+
diff --git a/firmware/os/inc/hostIntf.h b/firmware/os/inc/hostIntf.h
index 66ed7e4..0d83841 100644
--- a/firmware/os/inc/hostIntf.h
+++ b/firmware/os/inc/hostIntf.h
@@ -34,6 +34,7 @@
     HOSTINTF_DATA_TYPE_LOG,
     HOSTINTF_DATA_TYPE_APP_TO_HOST,
     HOSTINTF_DATA_TYPE_RESET_REASON,
+    HOSTINTF_DATA_TYPE_APP_TO_SENSOR_HAL,         // for config data upload
 };
 
 SET_PACKED_STRUCT_MODE_ON
diff --git a/firmware/os/inc/seos.h b/firmware/os/inc/seos.h
index f10108a..7b17889 100644
--- a/firmware/os/inc/seos.h
+++ b/firmware/os/inc/seos.h
@@ -79,7 +79,7 @@
 #define ENCR_KEY_GOOGLE_PREPOPULATED     1 // our key ID is 1
 
 #define APP_HDR_MAGIC              NANOAPP_FW_MAGIC
-#define APP_HDR_VER_CUR            0
+#define APP_HDR_VER_CUR            1
 
 #define FL_APP_HDR_INTERNAL        0x0001 // to be able to fork behavior at run time for internal apps
 #define FL_APP_HDR_APPLICATION     0x0002 // image has AppHdr; otherwise is has AppInfo header
diff --git a/inc/chre/sensor.h b/inc/chre/sensor.h
index 1b350c5..9e74336 100644
--- a/inc/chre/sensor.h
+++ b/inc/chre/sensor.h
@@ -444,7 +444,7 @@
  *
  *   struct chreSensorTypeData {
  *       struct chreSensorDataHeader header;
- *       struct {
+ *       struct chreSensorTypeSampleData {
  *           uint32_t timestampDelta;
  *           union {
  *               <type> value;
@@ -512,7 +512,8 @@
 /**
  * Data for a sensor which reports on three axes.
  *
- * This is used by CHRE_EVENT_SENSOR_DATA, CHRE_EVENT_SENSOR_GYROSCOPE_DATA,
+ * This is used by CHRE_EVENT_SENSOR_ACCELEROMETER_DATA,
+ * CHRE_EVENT_SENSOR_GYROSCOPE_DATA,
  * CHRE_EVENT_SENSOR_GYROSCOPE_BIAS_INFO,
  * CHRE_EVENT_SENSOR_GEOMAGNETIC_FIELD_DATA, and
  * CHRE_EVENT_SENSOR_GEOMAGNETIC_FIELD_BIAS_INFO.
@@ -522,7 +523,7 @@
      * @see chreSensorDataHeader
      */
     struct chreSensorDataHeader header;
-    struct {
+    struct chreSensorThreeAxisSampleData {
         /**
          * @see chreSensorDataHeader
          */
@@ -557,7 +558,7 @@
  */
 struct chreSensorOccurrenceData {
     struct chreSensorDataHeader header;
-    struct {
+    struct chreSensorOccurenceSampleData {
         uint32_t timestampDelta;
         // This space intentionally left blank.
         // Only the timestamp is meaningful here, there
@@ -570,7 +571,7 @@
  */
 struct chreSensorFloatData {
     struct chreSensorDataHeader header;
-    struct {
+    struct chreSensorFloatSampleData {
         uint32_t timestampDelta;
         union {
             float value;
@@ -585,7 +586,7 @@
  */
 struct chreSensorByteData {
     struct chreSensorDataHeader header;
-    struct {
+    struct chreSensorByteSampleData {
         uint32_t timestampDelta;
         union {
             uint8_t value;
@@ -799,8 +800,8 @@
  *
  * @see chreSensorConfigure
  */
-inline bool chreSensorConfigureModeOnly(uint32_t sensorHandle,
-                                        enum chreSensorConfigureMode mode) {
+static inline bool chreSensorConfigureModeOnly(
+        uint32_t sensorHandle, enum chreSensorConfigureMode mode) {
     return chreSensorConfigure(sensorHandle,
                                mode,
                                CHRE_SENSOR_INTERVAL_DEFAULT,
diff --git a/sensorhal/Android.mk b/sensorhal/Android.mk
index 9c00452..1f35410 100644
--- a/sensorhal/Android.mk
+++ b/sensorhal/Android.mk
@@ -60,6 +60,7 @@
 LOCAL_MODULE_RELATIVE_PATH := hw
 LOCAL_MODULE_TAGS := optional
 LOCAL_MODULE_OWNER := google
+LOCAL_PROPRIETARY_MODULE := true
 
 LOCAL_CFLAGS += $(COMMON_CFLAGS)
 
@@ -78,6 +79,15 @@
 	libstagefright_foundation \
 	libutils
 
+ifeq ($(NANOHUB_SENSORHAL_DIRECT_REPORT_ENABLED), true)
+LOCAL_CFLAGS += -DDIRECT_REPORT_ENABLED
+endif
+
+ifeq ($(NANOHUB_SENSORHAL_DYNAMIC_SENSOR_EXT_ENABLED), true)
+LOCAL_CFLAGS += -DDYNAMIC_SENSOR_EXT_ENABLED
+LOCAL_SHARED_LIBRARIES += libdynamic_sensor_ext
+endif
+
 include $(BUILD_SHARED_LIBRARY)
 
 ################################################################################
@@ -88,6 +98,7 @@
 LOCAL_MODULE_RELATIVE_PATH := hw
 LOCAL_MODULE_TAGS := optional
 LOCAL_MODULE_OWNER := google
+LOCAL_PROPRIETARY_MODULE := true
 
 LOCAL_CFLAGS += $(COMMON_CFLAGS)
 
@@ -114,8 +125,12 @@
 LOCAL_MODULE := libhubconnection
 LOCAL_MODULE_TAGS := optional
 LOCAL_MODULE_OWNER := google
+LOCAL_PROPRIETARY_MODULE := true
 
 LOCAL_CFLAGS += $(COMMON_CFLAGS)
+ifeq ($(PRODUCT_FULL_TREBLE),true)
+LOCAL_CFLAGS += -DUSE_SENSORSERVICE_TO_GET_FIFO
+endif
 
 ifeq ($(NANOHUB_SENSORHAL_LID_STATE_ENABLED), true)
 LOCAL_CFLAGS += -DLID_STATE_REPORTING_ENABLED
@@ -129,21 +144,30 @@
 LOCAL_CFLAGS += -DDOUBLE_TOUCH_ENABLED
 endif
 
+ifeq ($(NANOHUB_SENSORHAL_DIRECT_REPORT_ENABLED), true)
+LOCAL_CFLAGS += -DDIRECT_REPORT_ENABLED
+endif
+
 LOCAL_C_INCLUDES += \
     device/google/contexthub/firmware/os/inc
 
 LOCAL_SRC_FILES := \
-    hubconnection.cpp
+    hubconnection.cpp \
+    directchannel.cpp
 
 LOCAL_STATIC_LIBRARIES := \
     libhubutilcommon
 
 LOCAL_SHARED_LIBRARIES := \
+    android.frameworks.schedulerservice@1.0 \
     libcutils \
+    libhardware \
+    libhardware_legacy \
+    libhidlbase \
+    libhidltransport \
     liblog \
     libstagefright_foundation \
-    libhardware_legacy \
-    libutils
+    libutils \
 
 include $(BUILD_SHARED_LIBRARY)
 
diff --git a/sensorhal/directchannel.cpp b/sensorhal/directchannel.cpp
new file mode 100644
index 0000000..d0e719d
--- /dev/null
+++ b/sensorhal/directchannel.cpp
@@ -0,0 +1,289 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+#define LOG_TAG "directchannel"
+#include "directchannel.h"
+
+#include <cutils/ashmem.h>
+#include <hardware/sensors.h>
+#include <utils/Log.h>
+
+#include <sys/mman.h>
+
+namespace android {
+
+bool DirectChannelBase::isValid() {
+    return mBuffer != nullptr;
+}
+
+int DirectChannelBase::getError() {
+    return mError;
+}
+
+void DirectChannelBase::write(const sensors_event_t * ev) {
+    if (isValid()) {
+        mBuffer->write(ev, 1);
+    }
+}
+
+AshmemDirectChannel::AshmemDirectChannel(const struct sensors_direct_mem_t *mem) : mAshmemFd(0) {
+    mAshmemFd = mem->handle->data[0];
+
+    if (!::ashmem_valid(mAshmemFd)) {
+        mError = BAD_VALUE;
+        return;
+    }
+
+    if ((size_t)::ashmem_get_size_region(mAshmemFd) != mem->size) {
+        mError = BAD_VALUE;
+        return;
+    }
+
+    mSize = mem->size;
+
+    mBase = ::mmap(NULL, mem->size, PROT_WRITE, MAP_SHARED, mAshmemFd, 0);
+    if (mBase == nullptr) {
+        mError = NO_MEMORY;
+        return;
+    }
+
+    mBuffer = std::unique_ptr<LockfreeBuffer>(new LockfreeBuffer(mBase, mSize));
+    if (!mBuffer) {
+        mError = NO_MEMORY;
+    }
+}
+
+AshmemDirectChannel::~AshmemDirectChannel() {
+    if (mBase) {
+        mBuffer = nullptr;
+        ::munmap(mBase, mSize);
+        mBase = nullptr;
+    }
+    ::close(mAshmemFd);
+}
+
+ANDROID_SINGLETON_STATIC_INSTANCE(GrallocHalWrapper);
+
+GrallocHalWrapper::GrallocHalWrapper()
+        : mError(NO_INIT), mVersion(-1),
+          mGrallocModule(nullptr), mAllocDevice(nullptr), mGralloc1Device(nullptr),
+          mPfnRetain(nullptr), mPfnRelease(nullptr), mPfnLock(nullptr), mPfnUnlock(nullptr) {
+    const hw_module_t *module;
+    status_t err = ::hw_get_module(GRALLOC_HARDWARE_MODULE_ID, &module);
+    ALOGE_IF(err, "couldn't load %s module (%s)", GRALLOC_HARDWARE_MODULE_ID, strerror(-err));
+
+    if (module == nullptr) {
+        mError = (err < 0) ? err : NO_INIT;
+    }
+
+    switch ((module->module_api_version >> 8) & 0xFF) {
+        case 0:
+            err = ::gralloc_open(module, &mAllocDevice);
+            if (err != NO_ERROR) {
+                ALOGE("cannot open alloc device (%s)", strerror(-err));
+                break;
+            }
+
+            if (mAllocDevice == nullptr) {
+                ALOGE("gralloc_open returns no error, but result is nullptr");
+                err = INVALID_OPERATION;
+                break;
+            }
+
+            // successfully initialized gralloc
+            mGrallocModule = (gralloc_module_t *)module;
+            mVersion = 0;
+            break;
+        case 1:
+            err = ::gralloc1_open(module, &mGralloc1Device);
+            if (err != NO_ERROR) {
+                ALOGE("cannot open gralloc1 device (%s)", strerror(-err));
+                break;
+            }
+
+            if (mGralloc1Device == nullptr || mGralloc1Device->getFunction == nullptr) {
+                ALOGE("gralloc1_open returns no error, but result is nullptr");
+                err = INVALID_OPERATION;
+                break;
+            }
+
+            mPfnRetain = (GRALLOC1_PFN_RETAIN)(mGralloc1Device->getFunction(mGralloc1Device,
+                                                      GRALLOC1_FUNCTION_RETAIN));
+            mPfnRelease = (GRALLOC1_PFN_RELEASE)(mGralloc1Device->getFunction(mGralloc1Device,
+                                                       GRALLOC1_FUNCTION_RELEASE));
+            mPfnLock = (GRALLOC1_PFN_LOCK)(mGralloc1Device->getFunction(mGralloc1Device,
+                                                    GRALLOC1_FUNCTION_LOCK));
+            mPfnUnlock = (GRALLOC1_PFN_UNLOCK)(mGralloc1Device->getFunction(mGralloc1Device,
+                                                      GRALLOC1_FUNCTION_UNLOCK));
+            if (mPfnRetain == nullptr || mPfnRelease == nullptr
+                    || mPfnLock == nullptr || mPfnUnlock == nullptr) {
+                ALOGE("Function pointer for retain, release, lock and unlock are %p, %p, %p, %p",
+                      mPfnRetain, mPfnRelease, mPfnLock, mPfnUnlock);
+                err = BAD_VALUE;
+                break;
+            }
+
+            // successfully initialized gralloc1
+            mGrallocModule = (gralloc_module_t *)module;
+            mVersion = 1;
+            break;
+        default:
+            ALOGE("Unknown version, not supported");
+            break;
+    }
+    mError = err;
+}
+
+GrallocHalWrapper::~GrallocHalWrapper() {
+    if (mAllocDevice != nullptr) {
+        ::gralloc_close(mAllocDevice);
+    }
+}
+
+int GrallocHalWrapper::registerBuffer(const native_handle_t *handle) {
+    switch (mVersion) {
+        case 0:
+            return mGrallocModule->registerBuffer(mGrallocModule, handle);
+        case 1:
+            return mapGralloc1Error(mPfnRetain(mGralloc1Device, handle));
+        default:
+            return NO_INIT;
+    }
+}
+
+int GrallocHalWrapper::unregisterBuffer(const native_handle_t *handle) {
+    switch (mVersion) {
+        case 0:
+            return mGrallocModule->unregisterBuffer(mGrallocModule, handle);
+        case 1:
+            return mapGralloc1Error(mPfnRelease(mGralloc1Device, handle));
+        default:
+            return NO_INIT;
+    }
+}
+
+int GrallocHalWrapper::lock(const native_handle_t *handle,
+                           int usage, int l, int t, int w, int h, void **vaddr) {
+    switch (mVersion) {
+        case 0:
+            return mGrallocModule->lock(mGrallocModule, handle, usage, l, t, w, h, vaddr);
+        case 1: {
+            const gralloc1_rect_t rect = {
+                .left = l,
+                .top = t,
+                .width = w,
+                .height = h
+            };
+            return mapGralloc1Error(mPfnLock(mGralloc1Device, handle,
+                                             GRALLOC1_PRODUCER_USAGE_CPU_WRITE_OFTEN,
+                                             GRALLOC1_CONSUMER_USAGE_NONE,
+                                             &rect, vaddr, -1));
+        }
+        default:
+            return NO_INIT;
+    }
+}
+
+int GrallocHalWrapper::unlock(const native_handle_t *handle) {
+    switch (mVersion) {
+        case 0:
+            return mGrallocModule->unlock(mGrallocModule, handle);
+        case 1: {
+            int32_t dummy;
+            return mapGralloc1Error(mPfnUnlock(mGralloc1Device, handle, &dummy));
+        }
+        default:
+            return NO_INIT;
+    }
+}
+
+int GrallocHalWrapper::mapGralloc1Error(int grallocError) {
+    switch (grallocError) {
+        case GRALLOC1_ERROR_NONE:
+            return NO_ERROR;
+        case GRALLOC1_ERROR_BAD_DESCRIPTOR:
+        case GRALLOC1_ERROR_BAD_HANDLE:
+        case GRALLOC1_ERROR_BAD_VALUE:
+            return BAD_VALUE;
+        case GRALLOC1_ERROR_NOT_SHARED:
+        case GRALLOC1_ERROR_NO_RESOURCES:
+            return NO_MEMORY;
+        case GRALLOC1_ERROR_UNDEFINED:
+        case GRALLOC1_ERROR_UNSUPPORTED:
+            return INVALID_OPERATION;
+        default:
+            return UNKNOWN_ERROR;
+    }
+}
+
+GrallocDirectChannel::GrallocDirectChannel(const struct sensors_direct_mem_t *mem)
+        : mNativeHandle(nullptr) {
+    if (mem->handle == nullptr) {
+        ALOGE("mem->handle == nullptr");
+        mError = BAD_VALUE;
+        return;
+    }
+
+    mNativeHandle = ::native_handle_clone(mem->handle);
+    if (mNativeHandle == nullptr) {
+        ALOGE("clone mem->handle failed...");
+        mError = NO_MEMORY;
+        return;
+    }
+
+    mError = GrallocHalWrapper::getInstance().registerBuffer(mNativeHandle);
+    if (mError != NO_ERROR) {
+        ALOGE("registerBuffer failed");
+        return;
+    }
+
+    mError = GrallocHalWrapper::getInstance().lock(mNativeHandle,
+            GRALLOC_USAGE_SW_WRITE_OFTEN, 0, 0, mem->size, 1, &mBase);
+    if (mError != NO_ERROR) {
+        ALOGE("lock buffer failed");
+        return;
+    }
+
+    if (mBase == nullptr) {
+        ALOGE("lock buffer => nullptr");
+        mError = NO_MEMORY;
+        return;
+    }
+
+    mSize = mem->size;
+    mBuffer = std::make_unique<LockfreeBuffer>(mBase, mSize);
+    if (!mBuffer) {
+        mError = NO_MEMORY;
+        return;
+    }
+
+    mError = NO_ERROR;
+}
+
+GrallocDirectChannel::~GrallocDirectChannel() {
+    if (mNativeHandle != nullptr) {
+        if (mBase) {
+            mBuffer = nullptr;
+            GrallocHalWrapper::getInstance().unlock(mNativeHandle);
+            mBase = nullptr;
+        }
+        GrallocHalWrapper::getInstance().unregisterBuffer(mNativeHandle);
+        ::native_handle_close(mNativeHandle);
+        ::native_handle_delete(mNativeHandle);
+        mNativeHandle = nullptr;
+    }
+}
+
+} // namespace android
diff --git a/sensorhal/directchannel.h b/sensorhal/directchannel.h
new file mode 100644
index 0000000..192e35d
--- /dev/null
+++ b/sensorhal/directchannel.h
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#ifndef DIRECTCHANNEL_H_
+#define DIRECTCHANNEL_H_
+
+#include "ring.h"
+#include <cutils/native_handle.h>
+#include <hardware/gralloc.h>
+#include <hardware/gralloc1.h>
+#include <hardware/sensors.h>
+#include <utils/Singleton.h>
+#include <memory>
+
+namespace android {
+
+class DirectChannelBase {
+public:
+    DirectChannelBase() : mError(NO_INIT), mSize(0), mBase(nullptr) { }
+    virtual ~DirectChannelBase() {}
+
+    bool isValid();
+    int getError();
+    void write(const sensors_event_t * ev);
+
+protected:
+    int mError;
+    std::unique_ptr<LockfreeBuffer> mBuffer;
+
+    size_t mSize;
+    void* mBase;
+};
+
+class AshmemDirectChannel : public DirectChannelBase {
+public:
+    AshmemDirectChannel(const struct sensors_direct_mem_t *mem);
+    virtual ~AshmemDirectChannel();
+private:
+    int mAshmemFd;
+};
+
+class GrallocHalWrapper : public Singleton<GrallocHalWrapper> {
+public:
+    int registerBuffer(const native_handle_t *handle);
+    int unregisterBuffer(const native_handle_t *handle);
+    int lock(const native_handle_t *handle, int usage, int l, int t, int w, int h, void **vaddr);
+    int unlock(const native_handle_t *handle);
+private:
+    friend class Singleton<GrallocHalWrapper>;
+    GrallocHalWrapper();
+    ~GrallocHalWrapper();
+    static int mapGralloc1Error(int grallocError);
+
+    int mError;
+    int mVersion;
+    gralloc_module_t *mGrallocModule;
+    // gralloc
+    alloc_device_t *mAllocDevice;
+
+    // gralloc1
+    gralloc1_device_t *mGralloc1Device;
+    GRALLOC1_PFN_RETAIN mPfnRetain;
+    GRALLOC1_PFN_RELEASE mPfnRelease;
+    GRALLOC1_PFN_LOCK mPfnLock;
+    GRALLOC1_PFN_UNLOCK mPfnUnlock;
+};
+
+class GrallocDirectChannel : public DirectChannelBase {
+public:
+    GrallocDirectChannel(const struct sensors_direct_mem_t *mem);
+    virtual ~GrallocDirectChannel();
+private:
+    native_handle_t *mNativeHandle;
+};
+
+} // namespace android
+
+#endif  // DIRECTCHANNEL_H_
diff --git a/sensorhal/hubconnection.cpp b/sensorhal/hubconnection.cpp
index 5c5db92..b57a334 100644
--- a/sensorhal/hubconnection.cpp
+++ b/sensorhal/hubconnection.cpp
@@ -14,13 +14,14 @@
  * limitations under the License.
  */
 
-#include "hubconnection.h"
-#include "eventnums.h"
-#include "sensType.h"
-
 #define LOG_TAG "nanohub"
-#include <utils/Log.h>
-#include <utils/SystemClock.h>
+#define LOG_NDEBUG 1
+
+#include "hubconnection.h"
+
+// TODO: remove the includes that introduce LIKELY and UNLIKELY (firmware/os/inc/toolchain.h)
+#undef LIKELY
+#undef UNLIKELY
 
 #include "file.h"
 #include "JSONObject.h"
@@ -35,9 +36,18 @@
 #include <linux/input.h>
 #include <linux/uinput.h>
 
+#include <android/frameworks/schedulerservice/1.0/ISchedulingPolicyService.h>
+#include <cutils/ashmem.h>
 #include <cutils/properties.h>
 #include <hardware_legacy/power.h>
 #include <media/stagefright/foundation/ADebug.h>
+#include <utils/Log.h>
+#include <utils/SystemClock.h>
+
+#include <algorithm>
+#include <cmath>
+#include <sstream>
+#include <vector>
 
 #define APP_ID_GET_VENDOR(appid)       ((appid) >> 24)
 #define APP_ID_MAKE(vendor, app)       ((((uint64_t)(vendor)) << 24) | ((app) & 0x00FFFFFF))
@@ -47,7 +57,7 @@
 #define SENS_TYPE_TO_EVENT(_sensorType) (EVT_NO_FIRST_SENSOR_EVENT + (_sensorType))
 
 #define NANOHUB_FILE_PATH       "/dev/nanohub"
-#define NANOHUB_LOCK_DIR        "/data/system/nanohub_lock"
+#define NANOHUB_LOCK_DIR        "/data/vendor/sensor/nanohub_lock"
 #define NANOHUB_LOCK_FILE       NANOHUB_LOCK_DIR "/lock"
 #define MAG_BIAS_FILE_PATH      "/sys/class/power_supply/battery/compass_compensation"
 #define DOUBLE_TOUCH_FILE_PATH  "/sys/android_touch/synaptics_rmi4_dsx/wake_event"
@@ -60,12 +70,8 @@
 #define MIN_MAG_SQ              (10.0f * 10.0f)
 #define MAX_MAG_SQ              (80.0f * 80.0f)
 
-#define ACCEL_RAW_KSCALE        (8.0f * 9.81f / 32768.0f)
-
 #define OS_LOG_EVENT            0x474F4C41  // ascii: ALOG
 
-#define HUBCONNECTION_SCHED_FIFO_PRIORITY 10
-
 #ifdef LID_STATE_REPORTING_ENABLED
 const char LID_STATE_PROPERTY[] = "sensors.contexthub.lid_state";
 const char LID_STATE_UNKNOWN[]  = "unknown";
@@ -76,6 +82,12 @@
 static const uint32_t delta_time_encoded = 1;
 static const uint32_t delta_time_shift_table[2] = {9, 0};
 
+#ifdef USE_SENSORSERVICE_TO_GET_FIFO
+// TODO(b/35219747): retain sched_fifo before eval is done to avoid
+// performance regression.
+const char SCHED_FIFO_PRIOIRTY[] = "sensor.hubconnection.sched_fifo";
+#endif
+
 namespace android {
 
 // static
@@ -102,6 +114,8 @@
     : Thread(false /* canCallJava */),
       mRing(10 *1024),
       mActivityEventHandler(NULL),
+      mScaleAccel(1.0f),
+      mScaleMag(1.0f),
       mStepCounterOffset(0ull),
       mLastStepCount(0ull)
 {
@@ -110,6 +124,7 @@
     mMagAccuracyRestore = SENSOR_STATUS_UNRELIABLE;
     mGyroBias[0] = mGyroBias[1] = mGyroBias[2] = 0.0f;
     mAccelBias[0] = mAccelBias[1] = mAccelBias[2] = 0.0f;
+    memset(&mGyroOtcData, 0, sizeof(mGyroOtcData));
 
     memset(&mSensorState, 0x00, sizeof(mSensorState));
     mFd = open(NANOHUB_FILE_PATH, O_RDWR);
@@ -153,6 +168,9 @@
 #endif  // DOUBLE_TOUCH_ENABLED
 
     mSensorState[COMMS_SENSOR_ACCEL].sensorType = SENS_TYPE_ACCEL;
+    mSensorState[COMMS_SENSOR_ACCEL].alt = COMMS_SENSOR_ACCEL_UNCALIBRATED;
+    mSensorState[COMMS_SENSOR_ACCEL_UNCALIBRATED].sensorType = SENS_TYPE_ACCEL;
+    mSensorState[COMMS_SENSOR_ACCEL_UNCALIBRATED].alt = COMMS_SENSOR_ACCEL;
     mSensorState[COMMS_SENSOR_GYRO].sensorType = SENS_TYPE_GYRO;
     mSensorState[COMMS_SENSOR_GYRO].alt = COMMS_SENSOR_GYRO_UNCALIBRATED;
     mSensorState[COMMS_SENSOR_GYRO_UNCALIBRATED].sensorType = SENS_TYPE_GYRO;
@@ -227,7 +245,7 @@
 
     // set initial lid state
     if (property_set(LID_STATE_PROPERTY, LID_STATE_UNKNOWN) < 0) {
-        ALOGE("could not set lid_state property");
+        ALOGW("could not set lid_state property");
     }
 
     // enable hall sensor for folio
@@ -235,6 +253,13 @@
         queueActivate(COMMS_SENSOR_HALL, true /* enable */);
     }
 #endif  // LID_STATE_REPORTING_ENABLED
+
+#ifdef DIRECT_REPORT_ENABLED
+    mDirectChannelHandle = 1;
+    mSensorToChannel.emplace(COMMS_SENSOR_ACCEL, std::unordered_map<int32_t, int32_t>());
+    mSensorToChannel.emplace(COMMS_SENSOR_GYRO, std::unordered_map<int32_t, int32_t>());
+    mSensorToChannel.emplace(COMMS_SENSOR_MAG, std::unordered_map<int32_t, int32_t>());
+#endif // DIRECT_REPORT_ENABLED
 }
 
 HubConnection::~HubConnection()
@@ -245,16 +270,49 @@
 void HubConnection::onFirstRef()
 {
     run("HubConnection", PRIORITY_URGENT_DISPLAY);
-    enableSchedFifoMode();
+#ifdef USE_SENSORSERVICE_TO_GET_FIFO
+    if (property_get_bool(SCHED_FIFO_PRIOIRTY, true)) {
+        ALOGV("Try activate sched-fifo priority for HubConnection thread");
+        mEnableSchedFifoThread = std::thread(enableSchedFifoMode, this);
+    }
+#else
+    enableSchedFifoMode(this);
+#endif
 }
 
 // Set main thread to SCHED_FIFO to lower sensor event latency when system is under load
-void HubConnection::enableSchedFifoMode() {
+void HubConnection::enableSchedFifoMode(sp<HubConnection> hub) {
+#ifdef USE_SENSORSERVICE_TO_GET_FIFO
+    using ::android::frameworks::schedulerservice::V1_0::ISchedulingPolicyService;
+    using ::android::hardware::Return;
+
+    // SchedulingPolicyService will not start until system server start.
+    // Thus, cannot block on this.
+    sp<ISchedulingPolicyService> scheduler = ISchedulingPolicyService::getService();
+
+    if (scheduler == nullptr) {
+        ALOGW("Couldn't get scheduler scheduler to set SCHED_FIFO.");
+    } else {
+        Return<int32_t> max = scheduler->getMaxAllowedPriority();
+        if (!max.isOk()) {
+            ALOGW("Failed to retrieve maximum allowed priority for HubConnection.");
+            return;
+        }
+        Return<bool> ret = scheduler->requestPriority(::getpid(), hub->getTid(), max);
+        if (!ret.isOk() || !ret) {
+            ALOGW("Failed to set SCHED_FIFO for HubConnection.");
+        } else {
+            ALOGV("Enabled sched fifo thread mode (prio %d)", static_cast<int32_t>(max));
+        }
+    }
+#else
+#define HUBCONNECTION_SCHED_FIFO_PRIORITY 10
     struct sched_param param = {0};
     param.sched_priority = HUBCONNECTION_SCHED_FIFO_PRIORITY;
-    if (sched_setscheduler(getTid(), SCHED_FIFO | SCHED_RESET_ON_FORK, &param) != 0) {
-        ALOGE("Couldn't set SCHED_FIFO for HubConnection thread");
+    if (sched_setscheduler(hub->getTid(), SCHED_FIFO | SCHED_RESET_ON_FORK, &param) != 0) {
+        ALOGW("Couldn't set SCHED_FIFO for HubConnection thread");
     }
+#endif
 }
 
 status_t HubConnection::initCheck() const
@@ -331,6 +389,32 @@
     return true;
 }
 
+static std::vector<int32_t> getInt32Setting(const sp<JSONObject> &settings, const char *key) {
+    std::vector<int32_t> ret;
+
+    sp<JSONArray> array;
+    if (settings->getArray(key, &array)) {
+        ret.resize(array->size());
+        for (size_t i = 0; i < array->size(); ++i) {
+            array->getInt32(i, &ret[i]);
+        }
+    }
+    return ret;
+}
+
+static std::vector<float> getFloatSetting(const sp<JSONObject> &settings, const char *key) {
+    std::vector<float> ret;
+
+    sp<JSONArray> array;
+    if (settings->getArray(key, &array)) {
+        ret.resize(array->size());
+        for (size_t i = 0; i < array->size(); ++i) {
+            array->getFloat(i, &ret[i]);
+        }
+    }
+    return ret;
+}
+
 static void loadSensorSettings(sp<JSONObject>* settings,
                                sp<JSONObject>* saved_settings) {
     File settings_file(CONTEXTHUB_SETTINGS_PATH, "r");
@@ -338,7 +422,7 @@
 
     status_t err;
     if ((err = settings_file.initCheck()) != OK) {
-        ALOGE("settings file open failed: %d (%s)",
+        ALOGW("settings file open failed: %d (%s)",
               err,
               strerror(-err));
 
@@ -348,7 +432,7 @@
     }
 
     if ((err = saved_settings_file.initCheck()) != OK) {
-        ALOGE("saved settings file open failed: %d (%s)",
+        ALOGW("saved settings file open failed: %d (%s)",
               err,
               strerror(-err));
         *saved_settings = new JSONObject;
@@ -363,7 +447,7 @@
 
     status_t err;
     if ((err = saved_settings_file.initCheck()) != OK) {
-        ALOGE("saved settings file open failed %d (%s)",
+        ALOGW("saved settings file open failed %d (%s)",
               err,
               strerror(-err));
         return;
@@ -378,27 +462,37 @@
 #endif  // USB_MAG_BIAS_REPORTING_ENABLED
     magArray->addFloat(mMagBias[1]);
     magArray->addFloat(mMagBias[2]);
-    settingsObject->setArray("mag", magArray);
+    settingsObject->setArray(MAG_BIAS_TAG, magArray);
 
     // Add gyro settings
     sp<JSONArray> gyroArray = new JSONArray;
     gyroArray->addFloat(mGyroBias[0]);
     gyroArray->addFloat(mGyroBias[1]);
     gyroArray->addFloat(mGyroBias[2]);
-    settingsObject->setArray("gyro_sw", gyroArray);
+    settingsObject->setArray(GYRO_SW_BIAS_TAG, gyroArray);
 
     // Add accel settings
     sp<JSONArray> accelArray = new JSONArray;
     accelArray->addFloat(mAccelBias[0]);
     accelArray->addFloat(mAccelBias[1]);
     accelArray->addFloat(mAccelBias[2]);
-    settingsObject->setArray("accel_sw", accelArray);
+    settingsObject->setArray(ACCEL_SW_BIAS_TAG, accelArray);
+
+    // Add overtemp calibration values for gyro
+    sp<JSONArray> gyroOtcDataArray = new JSONArray;
+    const float *f;
+    size_t i;
+    for (f = reinterpret_cast<const float *>(&mGyroOtcData), i = 0;
+            i < sizeof(mGyroOtcData)/sizeof(float); ++i, ++f) {
+        gyroOtcDataArray->addFloat(*f);
+    }
+    settingsObject->setArray(GYRO_OTC_DATA_TAG, gyroOtcDataArray);
 
     // Write the JSON string to disk.
     AString serializedSettings = settingsObject->toString();
     size_t size = serializedSettings.size();
     if ((err = saved_settings_file.write(serializedSettings.c_str(), size)) != (ssize_t)size) {
-        ALOGE("saved settings file write failed %d (%s)",
+        ALOGW("saved settings file write failed %d (%s)",
               err,
               strerror(-err));
     }
@@ -530,13 +624,13 @@
     if (cnt > 0) {
         // If event is a wake event, protect it with a wakelock
         protectIfWakeEvent(sensor);
-        mRing.write(nev, cnt);
+        write(nev, cnt);
     }
 }
 
-void HubConnection::magAccuracyUpdate(float x, float y, float z)
+uint8_t HubConnection::magAccuracyUpdate(sensors_vec_t *sv)
 {
-    float magSq = x * x + y * y + z * z;
+    float magSq = sv->x * sv->x + sv->y * sv->y + sv->z * sv->z;
 
     if (magSq < MIN_MAG_SQ || magSq > MAX_MAG_SQ) {
         // save last good accuracy (either MEDIUM or HIGH)
@@ -547,22 +641,65 @@
         // restore
         mMagAccuracy = mMagAccuracyRestore;
     }
+
+    return mMagAccuracy;
 }
 
 void HubConnection::processSample(uint64_t timestamp, uint32_t type, uint32_t sensor, struct RawThreeAxisSample *sample, __attribute__((unused)) bool highAccuracy)
 {
     sensors_vec_t *sv;
+    uncalibrated_event_t *ue;
     sensors_event_t nev[2];
     int cnt = 0;
 
     switch (sensor) {
     case COMMS_SENSOR_ACCEL:
-        sv = &initEv(&nev[cnt++], timestamp, type, sensor)->acceleration;
-        sv->x = sample->ix * ACCEL_RAW_KSCALE;
-        sv->y = sample->iy * ACCEL_RAW_KSCALE;
-        sv->z = sample->iz * ACCEL_RAW_KSCALE;
+        sv = &initEv(&nev[cnt], timestamp, type, sensor)->acceleration;
+        sv->x = sample->ix * mScaleAccel;
+        sv->y = sample->iy * mScaleAccel;
+        sv->z = sample->iz * mScaleAccel;
         sv->status = SENSOR_STATUS_ACCURACY_HIGH;
+        sendDirectReportEvent(&nev[cnt], 1);
+
+        if (mSensorState[sensor].enable) {
+            ++cnt;
+        }
+
+        if (mSensorState[COMMS_SENSOR_ACCEL_UNCALIBRATED].enable) {
+            ue = &initEv(&nev[cnt++], timestamp,
+                SENSOR_TYPE_ACCELEROMETER_UNCALIBRATED,
+                COMMS_SENSOR_ACCEL_UNCALIBRATED)->uncalibrated_accelerometer;
+            ue->x_uncalib = sample->ix * mScaleAccel + mAccelBias[0];
+            ue->y_uncalib = sample->iy * mScaleAccel + mAccelBias[1];
+            ue->z_uncalib = sample->iz * mScaleAccel + mAccelBias[2];
+            ue->x_bias = mAccelBias[0];
+            ue->y_bias = mAccelBias[1];
+            ue->z_bias = mAccelBias[2];
+        }
         break;
+    case COMMS_SENSOR_MAG:
+        sv = &initEv(&nev[cnt], timestamp, type, sensor)->magnetic;
+        sv->x = sample->ix * mScaleMag;
+        sv->y = sample->iy * mScaleMag;
+        sv->z = sample->iz * mScaleMag;
+        sv->status = magAccuracyUpdate(sv);
+        sendDirectReportEvent(&nev[cnt], 1);
+
+        if (mSensorState[sensor].enable) {
+            ++cnt;
+        }
+
+        if (mSensorState[COMMS_SENSOR_MAG_UNCALIBRATED].enable) {
+            ue = &initEv(&nev[cnt++], timestamp,
+                SENSOR_TYPE_MAGNETIC_FIELD_UNCALIBRATED,
+                COMMS_SENSOR_MAG_UNCALIBRATED)->uncalibrated_magnetic;
+            ue->x_uncalib = sample->ix * mScaleMag + mMagBias[0];
+            ue->y_uncalib = sample->iy * mScaleMag + mMagBias[1];
+            ue->z_uncalib = sample->iz * mScaleMag + mMagBias[2];
+            ue->x_bias = mMagBias[0];
+            ue->y_bias = mMagBias[1];
+            ue->z_bias = mMagBias[2];
+        }
     default:
         break;
     }
@@ -570,7 +707,7 @@
     if (cnt > 0) {
         // If event is a wake event, protect it with a wakelock
         protectIfWakeEvent(sensor);
-        mRing.write(nev, cnt);
+        write(nev, cnt);
     }
 }
 
@@ -586,19 +723,39 @@
 
     switch (sensor) {
     case COMMS_SENSOR_ACCEL:
-        sv = &initEv(&nev[cnt++], timestamp, type, sensor)->acceleration;
+        sv = &initEv(&nev[cnt], timestamp, type, sensor)->acceleration;
         sv->x = sample->x;
         sv->y = sample->y;
         sv->z = sample->z;
         sv->status = SENSOR_STATUS_ACCURACY_HIGH;
+        sendDirectReportEvent(&nev[cnt], 1);
+
+        if (mSensorState[sensor].enable) {
+            ++cnt;
+        }
+
+        if (mSensorState[COMMS_SENSOR_ACCEL_UNCALIBRATED].enable) {
+            ue = &initEv(&nev[cnt++], timestamp,
+                SENSOR_TYPE_ACCELEROMETER_UNCALIBRATED,
+                COMMS_SENSOR_ACCEL_UNCALIBRATED)->uncalibrated_accelerometer;
+            ue->x_uncalib = sample->x + mAccelBias[0];
+            ue->y_uncalib = sample->y + mAccelBias[1];
+            ue->z_uncalib = sample->z + mAccelBias[2];
+            ue->x_bias = mAccelBias[0];
+            ue->y_bias = mAccelBias[1];
+            ue->z_bias = mAccelBias[2];
+        }
         break;
     case COMMS_SENSOR_GYRO:
+        sv = &initEv(&nev[cnt], timestamp, type, sensor)->gyro;
+        sv->x = sample->x;
+        sv->y = sample->y;
+        sv->z = sample->z;
+        sv->status = SENSOR_STATUS_ACCURACY_HIGH;
+        sendDirectReportEvent(&nev[cnt], 1);
+
         if (mSensorState[sensor].enable) {
-            sv = &initEv(&nev[cnt++], timestamp, type, sensor)->gyro;
-            sv->x = sample->x;
-            sv->y = sample->y;
-            sv->z = sample->z;
-            sv->status = SENSOR_STATUS_ACCURACY_HIGH;
+            ++cnt;
         }
 
         if (mSensorState[COMMS_SENSOR_GYRO_UNCALIBRATED].enable) {
@@ -626,14 +783,15 @@
         saveSensorSettings();
         break;
     case COMMS_SENSOR_MAG:
-        magAccuracyUpdate(sample->x, sample->y, sample->z);
+        sv = &initEv(&nev[cnt], timestamp, type, sensor)->magnetic;
+        sv->x = sample->x;
+        sv->y = sample->y;
+        sv->z = sample->z;
+        sv->status = magAccuracyUpdate(sv);
+        sendDirectReportEvent(&nev[cnt], 1);
 
         if (mSensorState[sensor].enable) {
-            sv = &initEv(&nev[cnt++], timestamp, type, sensor)->magnetic;
-            sv->x = sample->x;
-            sv->y = sample->y;
-            sv->z = sample->z;
-            sv->status = mMagAccuracy;
+            ++cnt;
         }
 
         if (mSensorState[COMMS_SENSOR_MAG_UNCALIBRATED].enable) {
@@ -704,7 +862,7 @@
     if (cnt > 0) {
         // If event is a wake event, protect it with a wakelock
         protectIfWakeEvent(sensor);
-        mRing.write(nev, cnt);
+        write(nev, cnt);
     }
 }
 
@@ -714,7 +872,7 @@
     if (mInotifyPollIndex >= 0) {
         char buf[sizeof(struct inotify_event) + NAME_MAX + 1];
         int ret = ::read(mPollFds[mInotifyPollIndex].fd, buf, sizeof(buf));
-        ALOGD("Discarded %d bytes of inotify data", ret);
+        ALOGV("Discarded %d bytes of inotify data", ret);
     }
 }
 
@@ -746,21 +904,21 @@
 
             initConfigCmd(&cmd, i);
 
-            ALOGI("restoring: sensor=%d, handle=%d, enable=%d, period=%" PRId64 ", latency=%" PRId64,
+            ALOGV("restoring: sensor=%d, handle=%d, enable=%d, period=%" PRId64 ", latency=%" PRId64,
                   cmd.sensorType, i, mSensorState[i].enable, frequency_q10_to_period_ns(mSensorState[i].rate),
                   mSensorState[i].latency);
 
-            int ret = TEMP_FAILURE_RETRY(write(mFd, &cmd, sizeof(cmd)));
+            int ret = TEMP_FAILURE_RETRY(::write(mFd, &cmd, sizeof(cmd)));
             if (ret != sizeof(cmd)) {
-                ALOGE("failed to send config command to restore sensor %d\n", cmd.sensorType);
+                ALOGW("failed to send config command to restore sensor %d\n", cmd.sensorType);
             }
 
             cmd.cmd = CONFIG_CMD_FLUSH;
 
             for (int j = 0; j < mSensorState[i].flushCnt; j++) {
-                int ret = TEMP_FAILURE_RETRY(write(mFd, &cmd, sizeof(cmd)));
+                int ret = TEMP_FAILURE_RETRY(::write(mFd, &cmd, sizeof(cmd)));
                 if (ret != sizeof(cmd)) {
-                    ALOGE("failed to send flush command to sensor %d\n", cmd.sensorType);
+                    ALOGW("failed to send flush command to sensor %d\n", cmd.sensorType);
                 }
             }
         }
@@ -788,16 +946,44 @@
         ALOGW("osLog: %s", &buf[5]);
         break;
     case 'I':
-        ALOGI("osLog: %s", &buf[5]);
+        // The other side of this is too chatty, reducing the priority to VERBOSE
+        ALOGV("osLog: %s", &buf[5]);
         break;
     case 'D':
-        ALOGD("osLog: %s", &buf[5]);
+        // The other side of this is too chatty, reducing the priority to VERBOSE
+        ALOGV("osLog: %s", &buf[5]);
         break;
     default:
         break;
     }
 }
 
+void HubConnection::processAppData(uint8_t *buf, ssize_t len) {
+    if (len < static_cast<ssize_t>(sizeof(AppToSensorHalDataBuffer)))
+        return;
+
+    AppToSensorHalDataPayload *data =
+            &(reinterpret_cast<AppToSensorHalDataBuffer *>(buf)->payload);
+    if (data->size + sizeof(AppToSensorHalDataBuffer) != len) {
+        ALOGW("Received corrupted data update packet, len %zd, size %u", len, data->size);
+        return;
+    }
+
+    switch (data->type & APP_TO_SENSOR_HAL_TYPE_MASK) {
+    case HALINTF_TYPE_GYRO_OTC_DATA:
+        if (data->size != sizeof(GyroOtcData)) {
+            ALOGW("Corrupted HALINTF_TYPE_GYRO_OTC_DATA with size %u", data->size);
+            return;
+        }
+        mGyroOtcData = data->gyroOtcData[0];
+        saveSensorSettings();
+        break;
+    default:
+        ALOGW("Unknown app to hal data type 0x%04x", data->type);
+        break;
+    }
+}
+
 ssize_t HubConnection::processBuf(uint8_t *buf, size_t len)
 {
     struct nAxisEvent *data = (struct nAxisEvent *)buf;
@@ -816,6 +1002,9 @@
         case OS_LOG_EVENT:
             postOsLog(buf, len);
             return 0;
+        case EVT_APP_TO_SENSOR_HAL_DATA:
+            processAppData(buf, len);
+            return 0;
         case SENS_TYPE_TO_EVENT(SENS_TYPE_ACCEL):
             type = SENSOR_TYPE_ACCELEROMETER;
             sensor = COMMS_SENSOR_ACCEL;
@@ -839,6 +1028,11 @@
             bias = COMMS_SENSOR_MAG_BIAS;
             three = true;
             break;
+        case SENS_TYPE_TO_EVENT(SENS_TYPE_MAG_RAW):
+            type = SENSOR_TYPE_MAGNETIC_FIELD;
+            sensor = COMMS_SENSOR_MAG;
+            rawThree = true;
+            break;
         case SENS_TYPE_TO_EVENT(SENS_TYPE_ALS):
             type = SENSOR_TYPE_LIGHT;
             sensor = COMMS_SENSOR_LIGHT;
@@ -1028,11 +1222,11 @@
             restoreSensorState();
             return 0;
         default:
-            ALOGE("unknown evtType: 0x%08x\n", data->evtType);
+            ALOGW("unknown evtType: 0x%08x len: %zu\n", data->evtType, len);
             return -1;
         }
     } else {
-        ALOGE("too little data: len=%zu\n", len);
+        ALOGW("too little data: len=%zu\n", len);
         return -1;
     }
 
@@ -1048,7 +1242,7 @@
 
             if (one) {
                 if (ret + sizeof(data->oneSamples[i]) > len) {
-                    ALOGE("sensor %d (one): ret=%zd, numSamples=%d, i=%d\n", currSensor, ret, numSamples, i);
+                    ALOGW("sensor %d (one): ret=%zd, numSamples=%d, i=%d\n", currSensor, ret, numSamples, i);
                     return -1;
                 }
                 if (i > 0)
@@ -1057,7 +1251,7 @@
                 ret += sizeof(data->oneSamples[i]);
             } else if (rawThree) {
                 if (ret + sizeof(data->rawThreeSamples[i]) > len) {
-                    ALOGE("sensor %d (rawThree): ret=%zd, numSamples=%d, i=%d\n", currSensor, ret, numSamples, i);
+                    ALOGW("sensor %d (rawThree): ret=%zd, numSamples=%d, i=%d\n", currSensor, ret, numSamples, i);
                     return -1;
                 }
                 if (i > 0)
@@ -1066,7 +1260,7 @@
                 ret += sizeof(data->rawThreeSamples[i]);
             } else if (three) {
                 if (ret + sizeof(data->threeSamples[i]) > len) {
-                    ALOGE("sensor %d (three): ret=%zd, numSamples=%d, i=%d\n", currSensor, ret, numSamples, i);
+                    ALOGW("sensor %d (three): ret=%zd, numSamples=%d, i=%d\n", currSensor, ret, numSamples, i);
                     return -1;
                 }
                 if (i > 0)
@@ -1074,7 +1268,7 @@
                 processSample(timestamp, type, currSensor, &data->threeSamples[i], data->firstSample.highAccuracy);
                 ret += sizeof(data->threeSamples[i]);
             } else {
-                ALOGE("sensor %d (unknown): cannot processSample\n", currSensor);
+                ALOGW("sensor %d (unknown): cannot processSample\n", currSensor);
                 return -1;
             }
         }
@@ -1100,12 +1294,12 @@
                     ev.meta_data.sensor = sensor;
                 }
 
-                mRing.write(&ev, 1);
-                ALOGI("flushing %d", ev.meta_data.sensor);
+                write(&ev, 1);
+                ALOGV("flushing %d", ev.meta_data.sensor);
             }
         }
     } else {
-        ALOGE("too little data for sensor %d: len=%zu\n", sensor, len);
+        ALOGW("too little data for sensor %d: len=%zu\n", sensor, len);
         return -1;
     }
 
@@ -1119,16 +1313,16 @@
     struct {
         int32_t hw[3];
         float sw[3];
-    } gyro, accel;
+    } accel;
+
     int32_t proximity, proximity_array[4];
-    float barometer, humidity, mag[3], light;
-    bool gyro_hw_cal_exists, gyro_sw_cal_exists;
+    float barometer, humidity, light;
     bool accel_hw_cal_exists, accel_sw_cal_exists;
 
     loadSensorSettings(&settings, &saved_settings);
 
-    accel_hw_cal_exists = getCalibrationInt32(settings, "accel", accel.hw, 3);
-    accel_sw_cal_exists = getCalibrationFloat(saved_settings, "accel_sw", accel.sw);
+    accel_hw_cal_exists = getCalibrationInt32(settings, ACCEL_BIAS_TAG, accel.hw, 3);
+    accel_sw_cal_exists = getCalibrationFloat(saved_settings, ACCEL_SW_BIAS_TAG, accel.sw);
     if (accel_hw_cal_exists || accel_sw_cal_exists) {
         // Store SW bias so we can remove bias for uncal data
         mAccelBias[0] = accel.sw[0];
@@ -1138,15 +1332,70 @@
         queueDataInternal(COMMS_SENSOR_ACCEL, &accel, sizeof(accel));
     }
 
-    gyro_hw_cal_exists = getCalibrationInt32(settings, "gyro", gyro.hw, 3);
-    gyro_sw_cal_exists = getCalibrationFloat(saved_settings, "gyro_sw", gyro.sw);
-    if (gyro_hw_cal_exists || gyro_sw_cal_exists) {
-        // Store SW bias so we can remove bias for uncal data
-        mGyroBias[0] = gyro.sw[0];
-        mGyroBias[1] = gyro.sw[1];
-        mGyroBias[2] = gyro.sw[2];
+    ALOGV("Use new configuration format");
+    std::vector<int32_t> hardwareGyroBias = getInt32Setting(settings, GYRO_BIAS_TAG);
+    std::vector<float> softwareGyroBias = getFloatSetting(saved_settings, GYRO_SW_BIAS_TAG);
+    if (hardwareGyroBias.size() == 3 || softwareGyroBias.size() == 3) {
+        struct {
+            AppToSensorHalDataPayload header;
+            GyroCalBias data;
+        } packet = {
+            .header = {
+                .size = sizeof(GyroCalBias),
+                .type = HALINTF_TYPE_GYRO_CAL_BIAS }
+        };
+        if (hardwareGyroBias.size() == 3) {
+            std::copy(hardwareGyroBias.begin(), hardwareGyroBias.end(),
+                      packet.data.hardwareBias);
+        }
+        if (softwareGyroBias.size() == 3) {
+            // Store SW bias so we can remove bias for uncal data
+            std::copy(softwareGyroBias.begin(), softwareGyroBias.end(),
+                      mGyroBias);
 
-        queueDataInternal(COMMS_SENSOR_GYRO, &gyro, sizeof(gyro));
+            std::copy(softwareGyroBias.begin(), softwareGyroBias.end(),
+                      packet.data.softwareBias);
+        }
+        // send packet to hub
+        queueDataInternal(COMMS_SENSOR_GYRO, &packet, sizeof(packet));
+    }
+
+    // over temp cal
+    std::vector<float> gyroOtcData = getFloatSetting(saved_settings, GYRO_OTC_DATA_TAG);
+    if (gyroOtcData.size() == sizeof(GyroOtcData) / sizeof(float)) {
+        std::copy(gyroOtcData.begin(), gyroOtcData.end(),
+                  reinterpret_cast<float*>(&mGyroOtcData));
+        struct {
+            AppToSensorHalDataPayload header;
+            GyroOtcData data;
+        } packet = {
+            .header = {
+                .size = sizeof(GyroOtcData),
+                .type = HALINTF_TYPE_GYRO_OTC_DATA },
+            .data = mGyroOtcData
+        };
+
+        // send it to hub
+        queueDataInternal(COMMS_SENSOR_GYRO, &packet, sizeof(packet));
+    } else {
+        ALOGW("Illegal otc_gyro data size = %zu", gyroOtcData.size());
+    }
+
+    std::vector<float> magBiasData = getFloatSetting(saved_settings, MAG_BIAS_TAG);
+    if (magBiasData.size() == 3) {
+        // Store SW bias so we can remove bias for uncal data
+        std::copy(magBiasData.begin(), magBiasData.end(), mMagBias);
+
+        struct {
+            AppToSensorHalDataPayload header;
+            MagCalBias mag;
+        } packet = {
+            .header = {
+                .size = sizeof(MagCalBias),
+                .type = HALINTF_TYPE_MAG_CAL_BIAS }
+        };
+        std::copy(magBiasData.begin(), magBiasData.end(), packet.mag.bias);
+        queueDataInternal(COMMS_SENSOR_MAG, &packet, sizeof(packet));
     }
 
     if (settings->getFloat("barometer", &barometer))
@@ -1163,22 +1412,13 @@
 
     if (settings->getFloat("light", &light))
         queueDataInternal(COMMS_SENSOR_LIGHT, &light, sizeof(light));
-
-    if (getCalibrationFloat(saved_settings, "mag", mag)) {
-        // Store SW bias so we can remove bias for uncal data
-        mMagBias[0] = mag[0];
-        mMagBias[1] = mag[1];
-        mMagBias[2] = mag[2];
-
-        queueDataInternal(COMMS_SENSOR_MAG, mag, sizeof(mag));
-    }
 }
 
 bool HubConnection::threadLoop() {
-    ALOGI("threadLoop: starting");
+    ALOGV("threadLoop: starting");
 
     if (mFd < 0) {
-        ALOGE("threadLoop: exiting prematurely: nanohub is unavailable");
+        ALOGW("threadLoop: exiting prematurely: nanohub is unavailable");
         return false;
     }
     waitOnNanohubLock();
@@ -1217,7 +1457,7 @@
             ::read(mPollFds[mDoubleTouchPollIndex].fd, buf, 16);
             sensors_event_t gestureEvent;
             initEv(&gestureEvent, elapsedRealtimeNano(), SENSOR_TYPE_PICK_UP_GESTURE, COMMS_SENSOR_GESTURE)->data[0] = 8;
-            mRing.write(&gestureEvent, 1);
+            write(&gestureEvent, 1);
         }
 #endif // DOUBLE_TOUCH_ENABLED
 
@@ -1235,7 +1475,7 @@
                         break;
                 }
             } else {
-                ALOGE("read -1: errno=%d\n", errno);
+                ALOGW("read -1: errno=%d\n", errno);
             }
         }
     }
@@ -1281,6 +1521,9 @@
         cmd->rate = mSensorState[handle].rate;
         cmd->latency = mSensorState[handle].latency;
     }
+
+    // will be a nop if direct report mode is not enabled
+    mergeDirectReportRequest(cmd, handle);
 }
 
 void HubConnection::queueActivate(int handle, bool enable)
@@ -1295,15 +1538,15 @@
 
         initConfigCmd(&cmd, handle);
 
-        ret = TEMP_FAILURE_RETRY(write(mFd, &cmd, sizeof(cmd)));
+        ret = TEMP_FAILURE_RETRY(::write(mFd, &cmd, sizeof(cmd)));
         if (ret == sizeof(cmd))
-            ALOGI("queueActivate: sensor=%d, handle=%d, enable=%d",
+            ALOGV("queueActivate: sensor=%d, handle=%d, enable=%d",
                     cmd.sensorType, handle, enable);
         else
-            ALOGE("queueActivate: failed to send command: sensor=%d, handle=%d, enable=%d",
+            ALOGW("queueActivate: failed to send command: sensor=%d, handle=%d, enable=%d",
                     cmd.sensorType, handle, enable);
     } else {
-        ALOGI("queueActivate: unhandled handle=%d, enable=%d", handle, enable);
+        ALOGV("queueActivate: unhandled handle=%d, enable=%d", handle, enable);
     }
 }
 
@@ -1323,15 +1566,15 @@
 
         initConfigCmd(&cmd, handle);
 
-        ret = TEMP_FAILURE_RETRY(write(mFd, &cmd, sizeof(cmd)));
+        ret = TEMP_FAILURE_RETRY(::write(mFd, &cmd, sizeof(cmd)));
         if (ret == sizeof(cmd))
-            ALOGI("queueSetDelay: sensor=%d, handle=%d, period=%" PRId64,
+            ALOGV("queueSetDelay: sensor=%d, handle=%d, period=%" PRId64,
                     cmd.sensorType, handle, sampling_period_ns);
         else
-            ALOGE("queueSetDelay: failed to send command: sensor=%d, handle=%d, period=%" PRId64,
+            ALOGW("queueSetDelay: failed to send command: sensor=%d, handle=%d, period=%" PRId64,
                     cmd.sensorType, handle, sampling_period_ns);
     } else {
-        ALOGI("queueSetDelay: unhandled handle=%d, period=%" PRId64, handle, sampling_period_ns);
+        ALOGV("queueSetDelay: unhandled handle=%d, period=%" PRId64, handle, sampling_period_ns);
     }
 }
 
@@ -1355,15 +1598,15 @@
 
         initConfigCmd(&cmd, handle);
 
-        ret = TEMP_FAILURE_RETRY(write(mFd, &cmd, sizeof(cmd)));
+        ret = TEMP_FAILURE_RETRY(::write(mFd, &cmd, sizeof(cmd)));
         if (ret == sizeof(cmd))
-            ALOGI("queueBatch: sensor=%d, handle=%d, period=%" PRId64 ", latency=%" PRId64,
+            ALOGV("queueBatch: sensor=%d, handle=%d, period=%" PRId64 ", latency=%" PRId64,
                     cmd.sensorType, handle, sampling_period_ns, max_report_latency_ns);
         else
-            ALOGE("queueBatch: failed to send command: sensor=%d, handle=%d, period=%" PRId64 ", latency=%" PRId64,
+            ALOGW("queueBatch: failed to send command: sensor=%d, handle=%d, period=%" PRId64 ", latency=%" PRId64,
                     cmd.sensorType, handle, sampling_period_ns, max_report_latency_ns);
     } else {
-        ALOGI("queueBatch: unhandled handle=%d, period=%" PRId64 ", latency=%" PRId64,
+        ALOGV("queueBatch: unhandled handle=%d, period=%" PRId64 ", latency=%" PRId64,
                 handle, sampling_period_ns, max_report_latency_ns);
     }
 }
@@ -1381,16 +1624,16 @@
         initConfigCmd(&cmd, handle);
         cmd.cmd = CONFIG_CMD_FLUSH;
 
-        ret = TEMP_FAILURE_RETRY(write(mFd, &cmd, sizeof(cmd)));
+        ret = TEMP_FAILURE_RETRY(::write(mFd, &cmd, sizeof(cmd)));
         if (ret == sizeof(cmd)) {
-            ALOGI("queueFlush: sensor=%d, handle=%d",
+            ALOGV("queueFlush: sensor=%d, handle=%d",
                     cmd.sensorType, handle);
         } else {
-            ALOGE("queueFlush: failed to send command: sensor=%d, handle=%d"
+            ALOGW("queueFlush: failed to send command: sensor=%d, handle=%d"
                   " with error %s", cmd.sensorType, handle, strerror(errno));
         }
     } else {
-        ALOGI("queueFlush: unhandled handle=%d", handle);
+        ALOGV("queueFlush: unhandled handle=%d", handle);
     }
 }
 
@@ -1404,15 +1647,15 @@
         memcpy(cmd->data, data, length);
         cmd->cmd = CONFIG_CMD_CFG_DATA;
 
-        ret = TEMP_FAILURE_RETRY(write(mFd, cmd, sizeof(*cmd) + length));
+        ret = TEMP_FAILURE_RETRY(::write(mFd, cmd, sizeof(*cmd) + length));
         if (ret == sizeof(*cmd) + length)
-            ALOGI("queueData: sensor=%d, length=%zu",
+            ALOGV("queueData: sensor=%d, length=%zu",
                     cmd->sensorType, length);
         else
-            ALOGE("queueData: failed to send command: sensor=%d, length=%zu",
+            ALOGW("queueData: failed to send command: sensor=%d, length=%zu",
                     cmd->sensorType, length);
     } else {
-        ALOGI("queueData: unhandled handle=%d", handle);
+        ALOGV("queueData: unhandled handle=%d", handle);
     }
     free(cmd);
 }
@@ -1423,19 +1666,47 @@
     queueDataInternal(handle, data, length);
 }
 
+void HubConnection::setOperationParameter(const additional_info_event_t &info) {
+    switch (info.type) {
+        case AINFO_LOCAL_GEOMAGNETIC_FIELD: {
+            ALOGV("local geomag field update: strength %fuT, dec %fdeg, inc %fdeg",
+                  static_cast<double>(info.data_float[0]),
+                  info.data_float[1] * 180 / M_PI,
+                  info.data_float[2] * 180 / M_PI);
+
+            struct {
+                AppToSensorHalDataPayload header;
+                MagLocalField magLocalField;
+            } packet = {
+                .header = {
+                    .size = sizeof(MagLocalField),
+                    .type = HALINTF_TYPE_MAG_LOCAL_FIELD },
+                .magLocalField = {
+                    .strength = info.data_float[0],
+                    .declination = info.data_float[1],
+                    .inclination = info.data_float[2]}
+            };
+            queueDataInternal(COMMS_SENSOR_MAG, &packet, sizeof(packet));
+            break;
+        }
+        default:
+            break;
+    }
+}
+
 void HubConnection::initNanohubLock() {
     // Create the lock directory (if it doesn't already exist)
     if (mkdir(NANOHUB_LOCK_DIR, NANOHUB_LOCK_DIR_PERMS) < 0 && errno != EEXIST) {
-        ALOGE("Couldn't create Nanohub lock directory: %s", strerror(errno));
+        ALOGW("Couldn't create Nanohub lock directory: %s", strerror(errno));
         return;
     }
 
     mInotifyPollIndex = -1;
     int inotifyFd = inotify_init1(IN_NONBLOCK);
     if (inotifyFd < 0) {
-        ALOGE("Couldn't initialize inotify: %s", strerror(errno));
+        ALOGW("Couldn't initialize inotify: %s", strerror(errno));
     } else if (inotify_add_watch(inotifyFd, NANOHUB_LOCK_DIR, IN_CREATE | IN_DELETE) < 0) {
-        ALOGE("Couldn't add inotify watch: %s", strerror(errno));
+        ALOGW("Couldn't add inotify watch: %s", strerror(errno));
         close(inotifyFd);
     } else {
         mPollFds[mNumPollFds].fd = inotifyFd;
@@ -1446,6 +1717,10 @@
     }
 }
 
+ssize_t HubConnection::write(const sensors_event_t *ev, size_t n) {
+    return mRing.write(ev, n);
+}
+
 #ifdef USB_MAG_BIAS_REPORTING_ENABLED
 void HubConnection::queueUsbMagBias()
 {
@@ -1458,11 +1733,11 @@
         cmd->msg.dataLen = sizeof(float);
         memcpy((float *)(cmd+1), &mUsbMagBias, sizeof(float));
 
-        ret = TEMP_FAILURE_RETRY(write(mFd, cmd, sizeof(*cmd) + sizeof(float)));
+        ret = TEMP_FAILURE_RETRY(::write(mFd, cmd, sizeof(*cmd) + sizeof(float)));
         if (ret == sizeof(*cmd) + sizeof(float))
-            ALOGI("queueUsbMagBias: bias=%f\n", mUsbMagBias);
+            ALOGV("queueUsbMagBias: bias=%f\n", mUsbMagBias);
         else
-            ALOGE("queueUsbMagBias: failed to send command: bias=%f\n", mUsbMagBias);
+            ALOGW("queueUsbMagBias: failed to send command: bias=%f\n", mUsbMagBias);
         free(cmd);
     }
 }
@@ -1476,7 +1751,7 @@
     // Open uinput dev node
     mUinputFd = TEMP_FAILURE_RETRY(open("/dev/uinput", O_WRONLY | O_NONBLOCK));
     if (mUinputFd < 0) {
-        ALOGE("could not open uinput node: %s", strerror(errno));
+        ALOGW("could not open uinput node: %s", strerror(errno));
         return UNKNOWN_ERROR;
     }
 
@@ -1485,7 +1760,7 @@
     ret |= TEMP_FAILURE_RETRY(ioctl(mUinputFd, UI_SET_EVBIT, EV_SYN));
     ret |= TEMP_FAILURE_RETRY(ioctl(mUinputFd, UI_SET_SWBIT, SW_LID));
     if (ret < 0) {
-        ALOGE("could not send ioctl to uinput node: %s", strerror(errno));
+        ALOGW("could not send ioctl to uinput node: %s", strerror(errno));
         return UNKNOWN_ERROR;
     }
 
@@ -1498,15 +1773,15 @@
     uidev.id.product = 0;
     uidev.id.version = 0;
 
-    ret = TEMP_FAILURE_RETRY(write(mUinputFd, &uidev, sizeof(uidev)));
+    ret = TEMP_FAILURE_RETRY(::write(mUinputFd, &uidev, sizeof(uidev)));
     if (ret < 0) {
-        ALOGE("write to uinput node failed: %s", strerror(errno));
+        ALOGW("write to uinput node failed: %s", strerror(errno));
         return UNKNOWN_ERROR;
     }
 
     ret = TEMP_FAILURE_RETRY(ioctl(mUinputFd, UI_DEV_CREATE));
     if (ret < 0) {
-        ALOGE("could not send ioctl to uinput node: %s", strerror(errno));
+        ALOGW("could not send ioctl to uinput node: %s", strerror(errno));
         return UNKNOWN_ERROR;
     }
 
@@ -1522,9 +1797,9 @@
     ev.type = EV_SW;
     ev.code = SW_LID;
     ev.value =  data;
-    ret = TEMP_FAILURE_RETRY(write(mUinputFd, &ev, sizeof(ev)));
+    ret = TEMP_FAILURE_RETRY(::write(mUinputFd, &ev, sizeof(ev)));
     if (ret < 0) {
-        ALOGE("write to uinput node failed: %s", strerror(errno));
+        ALOGW("write to uinput node failed: %s", strerror(errno));
         return;
     }
 
@@ -1532,18 +1807,232 @@
     ev.type = EV_SYN;
     ev.code = SYN_REPORT;
     ev.value =  0;
-    ret = TEMP_FAILURE_RETRY(write(mUinputFd, &ev, sizeof(ev)));
+    ret = TEMP_FAILURE_RETRY(::write(mUinputFd, &ev, sizeof(ev)));
     if (ret < 0) {
-        ALOGE("write to uinput node failed: %s", strerror(errno));
+        ALOGW("write to uinput node failed: %s", strerror(errno));
         return;
     }
 
     // Set lid state property
     if (property_set(LID_STATE_PROPERTY,
                      (data ? LID_STATE_CLOSED : LID_STATE_OPEN)) < 0) {
-        ALOGE("could not set lid_state property");
+        ALOGW("could not set lid_state property");
     }
 }
 #endif  // LID_STATE_REPORTING_ENABLED
 
+#ifdef DIRECT_REPORT_ENABLED
+void HubConnection::sendDirectReportEvent(const sensors_event_t *nev, size_t n) {
+    // short circuit to avoid lock operation
+    if (n == 0) {
+        return;
+    }
+
+    // no intention to block sensor delivery thread. when lock is needed ignore
+    // the event (this only happens when the channel is reconfiured, so it's ok
+    if (mDirectChannelLock.tryLock() == NO_ERROR) {
+        while (n--) {
+            auto i = mSensorToChannel.find(nev->sensor);
+            if (i != mSensorToChannel.end()) {
+                for (auto &j : i->second) {
+                    mDirectChannel[j.first]->write(nev);
+                }
+            }
+            ++nev;
+        }
+        mDirectChannelLock.unlock();
+    }
+}
+
+void HubConnection::mergeDirectReportRequest(struct ConfigCmd *cmd, int handle) {
+    auto j = mSensorToChannel.find(handle);
+    if (j != mSensorToChannel.end()) {
+        bool enable = false;
+        rate_q10_t rate;
+
+        if (!j->second.empty()) {
+            int maxRateLevel = SENSOR_DIRECT_RATE_STOP;
+            for (auto &i : j->second) {
+                maxRateLevel = (i.second) > maxRateLevel ? i.second : maxRateLevel;
+            }
+            switch(maxRateLevel) {
+                case SENSOR_DIRECT_RATE_NORMAL:
+                    enable = true;
+                    rate = period_ns_to_frequency_q10(20000000ull); // NORMAL = 50Hz
+                    break;
+                case SENSOR_DIRECT_RATE_FAST:
+                    enable = true;
+                    rate = period_ns_to_frequency_q10(5000000ull);  // FAST = 200Hz
+                    break;
+                default:
+                    break;
+            }
+        }
+
+        if (enable) {
+            cmd->rate = (rate > cmd->rate || cmd->cmd == CONFIG_CMD_DISABLE) ? rate : cmd->rate;
+            cmd->latency = 0;
+            cmd->cmd = CONFIG_CMD_ENABLE;
+        }
+    }
+}
+
+int HubConnection::addDirectChannel(const struct sensors_direct_mem_t *mem) {
+    std::unique_ptr<DirectChannelBase> ch;
+    int ret = NO_MEMORY;
+
+    switch(mem->type) {
+        case SENSOR_DIRECT_MEM_TYPE_ASHMEM:
+            ch = std::make_unique<AshmemDirectChannel>(mem);
+            break;
+        case SENSOR_DIRECT_MEM_TYPE_GRALLOC:
+            ch = std::make_unique<GrallocDirectChannel>(mem);
+            break;
+        default:
+            ret = INVALID_OPERATION;
+    }
+
+    if (ch) {
+        if (ch->isValid()) {
+            Mutex::Autolock autoLock(mDirectChannelLock);
+            ret = mDirectChannelHandle++;
+            mDirectChannel.insert(std::make_pair(ret, std::move(ch)));
+        } else {
+            ret = ch->getError();
+            ALOGW("Direct channel object(type:%d) has error %d upon init", mem->type, ret);
+        }
+    }
+
+    return ret;
+}
+
+int HubConnection::removeDirectChannel(int channel_handle) {
+    // make sure no active sensor in this channel
+    std::vector<int32_t> activeSensorList;
+    stopAllDirectReportOnChannel(channel_handle, &activeSensorList);
+
+    // sensor service is responsible for stop all sensors before remove direct
+    // channel. Thus, this is an error.
+    if (!activeSensorList.empty()) {
+        std::stringstream ss;
+        std::copy(activeSensorList.begin(), activeSensorList.end(),
+                std::ostream_iterator<int32_t>(ss, ","));
+        ALOGW("Removing channel %d when sensors (%s) are not stopped.",
+                channel_handle, ss.str().c_str());
+    }
+
+    // remove the channel record
+    Mutex::Autolock autoLock(mDirectChannelLock);
+    mDirectChannel.erase(channel_handle);
+    return NO_ERROR;
+}
+
+int HubConnection::stopAllDirectReportOnChannel(
+        int channel_handle, std::vector<int32_t> *activeSensorList) {
+    Mutex::Autolock autoLock(mDirectChannelLock);
+    if (mDirectChannel.find(channel_handle) == mDirectChannel.end()) {
+        return BAD_VALUE;
+    }
+
+    std::vector<int32_t> sensorToStop;
+    for (auto &it : mSensorToChannel) {
+        auto j = it.second.find(channel_handle);
+        if (j != it.second.end()) {
+            it.second.erase(j);
+            if (it.second.empty()) {
+                sensorToStop.push_back(it.first);
+            }
+        }
+    }
+
+    if (activeSensorList != nullptr) {
+        *activeSensorList = sensorToStop;
+    }
+
+    // re-evaluate and send config for all sensor that need to be stopped
+    bool ret = true;
+    for (auto sensor_handle : sensorToStop) {
+        Mutex::Autolock autoLock2(mLock);
+        struct ConfigCmd cmd;
+        initConfigCmd(&cmd, sensor_handle);
+
+        int result = TEMP_FAILURE_RETRY(::write(mFd, &cmd, sizeof(cmd)));
+        ret = ret && (result == sizeof(cmd));
+    }
+    return ret ? NO_ERROR : BAD_VALUE;
+}
+
+int HubConnection::configDirectReport(int sensor_handle, int channel_handle, int rate_level) {
+    if (sensor_handle == -1 && rate_level == SENSOR_DIRECT_RATE_STOP) {
+        return stopAllDirectReportOnChannel(channel_handle, nullptr);
+    }
+
+    if (!isValidHandle(sensor_handle)) {
+        return BAD_VALUE;
+    }
+
+    // clamp to fast
+    if (rate_level > SENSOR_DIRECT_RATE_FAST) {
+        rate_level = SENSOR_DIRECT_RATE_FAST;
+    }
+
+    // manage direct channel data structure
+    Mutex::Autolock autoLock(mDirectChannelLock);
+    auto i = mDirectChannel.find(channel_handle);
+    if (i == mDirectChannel.end()) {
+        return BAD_VALUE;
+    }
+
+    auto j = mSensorToChannel.find(sensor_handle);
+    if (j == mSensorToChannel.end()) {
+        return BAD_VALUE;
+    }
+
+    j->second.erase(channel_handle);
+    if (rate_level != SENSOR_DIRECT_RATE_STOP) {
+        j->second.insert(std::make_pair(channel_handle, rate_level));
+    }
+
+    Mutex::Autolock autoLock2(mLock);
+    struct ConfigCmd cmd;
+    initConfigCmd(&cmd, sensor_handle);
+
+    int ret = TEMP_FAILURE_RETRY(::write(mFd, &cmd, sizeof(cmd)));
+
+    if (rate_level == SENSOR_DIRECT_RATE_STOP) {
+        ret = NO_ERROR;
+    } else {
+        ret = (ret == sizeof(cmd)) ? sensor_handle : BAD_VALUE;
+    }
+    return ret;
+}
+
+bool HubConnection::isDirectReportSupported() const {
+    return true;
+}
+#else // DIRECT_REPORT_ENABLED
+// nop functions if feature is turned off
+int HubConnection::addDirectChannel(const struct sensors_direct_mem_t *) {
+    return INVALID_OPERATION;
+}
+
+int HubConnection::removeDirectChannel(int) {
+    return INVALID_OPERATION;
+}
+
+int HubConnection::configDirectReport(int, int, int) {
+    return INVALID_OPERATION;
+}
+
+void HubConnection::sendDirectReportEvent(const sensors_event_t *, size_t) {
+}
+
+void HubConnection::mergeDirectReportRequest(struct ConfigCmd *, int) {
+}
+
+bool HubConnection::isDirectReportSupported() const {
+    return false;
+}
+#endif // DIRECT_REPORT_ENABLED
+
 } // namespace android
diff --git a/sensorhal/hubconnection.h b/sensorhal/hubconnection.h
index a891612..ce35877 100644
--- a/sensorhal/hubconnection.h
+++ b/sensorhal/hubconnection.h
@@ -28,12 +28,26 @@
 #include <utils/Thread.h>
 
 #include "activityeventhandler.h"
+#include "directchannel.h"
 #include "eventnums.h"
+#include "halIntf.h"
 #include "hubdefs.h"
 #include "ring.h"
 
+#ifdef USE_SENSORSERVICE_TO_GET_FIFO
+#include <thread>
+#endif
+#include <unordered_map>
+
 #define WAKELOCK_NAME "sensorHal"
 
+#define ACCEL_BIAS_TAG     "accel"
+#define ACCEL_SW_BIAS_TAG  "accel_sw"
+#define GYRO_BIAS_TAG      "gyro"
+#define GYRO_OTC_DATA_TAG  "gyro_otc"
+#define GYRO_SW_BIAS_TAG   "gyro_sw"
+#define MAG_BIAS_TAG       "mag"
+
 namespace android {
 
 struct HubConnection : public Thread {
@@ -59,17 +73,26 @@
     void queueFlush(int handle);
     void queueData(int handle, void *data, size_t length);
 
+    void setOperationParameter(const additional_info_event_t &info);
+
     bool isWakeEvent(int32_t sensor);
     void releaseWakeLockIfAppropriate();
     ssize_t getWakeEventCount();
     ssize_t decrementWakeEventCount();
 
+    //TODO: factor out event ring buffer functionality into a separate class
     ssize_t read(sensors_event_t *ev, size_t size);
+    ssize_t write(const sensors_event_t *ev, size_t n);
 
     void setActivityCallback(ActivityEventHandler *eventHandler);
 
     void saveSensorSettings() const;
 
+    void setRawScale(float scaleAccel, float scaleMag) {
+        mScaleAccel = scaleAccel;
+        mScaleMag = scaleMag;
+    }
+
 protected:
     HubConnection();
     virtual ~HubConnection();
@@ -95,6 +118,10 @@
             return (nsecs_t)0;
     }
 
+    static inline uint64_t frequency_to_frequency_q10(float frequency) {
+        return period_ns_to_frequency_q10(static_cast<nsecs_t>(1e9f/frequency));
+    }
+
     enum
     {
         CONFIG_CMD_DISABLE      = 0,
@@ -200,6 +227,9 @@
     uint8_t mMagAccuracyRestore;
 
     float mGyroBias[3], mAccelBias[3];
+    GyroOtcData mGyroOtcData;
+
+    float mScaleAccel, mScaleMag;
 
     SensorState mSensorState[NUM_COMMS_SENSORS_PLUS_1];
 
@@ -212,11 +242,12 @@
     int mNumPollFds;
 
     sensors_event_t *initEv(sensors_event_t *ev, uint64_t timestamp, uint32_t type, uint32_t sensor);
-    void magAccuracyUpdate(float x, float y, float z);
+    uint8_t magAccuracyUpdate(sensors_vec_t *sv);
     void processSample(uint64_t timestamp, uint32_t type, uint32_t sensor, struct OneAxisSample *sample, bool highAccuracy);
     void processSample(uint64_t timestamp, uint32_t type, uint32_t sensor, struct RawThreeAxisSample *sample, bool highAccuracy);
     void processSample(uint64_t timestamp, uint32_t type, uint32_t sensor, struct ThreeAxisSample *sample, bool highAccuracy);
     void postOsLog(uint8_t *buf, ssize_t len);
+    void processAppData(uint8_t *buf, ssize_t len);
     ssize_t processBuf(uint8_t *buf, size_t len);
 
     inline bool isValidHandle(int handle) {
@@ -237,8 +268,11 @@
     void restoreSensorState();
     void sendCalibrationOffsets();
 
+#ifdef USE_SENSORSERVICE_TO_GET_FIFO
     // Enable SCHED_FIFO priority for main thread
-    void enableSchedFifoMode();
+    std::thread mEnableSchedFifoThread;
+#endif
+    static void enableSchedFifoMode(sp<HubConnection> hub);
 
 #ifdef LID_STATE_REPORTING_ENABLED
     int mUinputFd;
@@ -258,6 +292,26 @@
     int mDoubleTouchPollIndex;
 #endif  // DOUBLE_TOUCH_ENABLED
 
+    // Direct report functions
+public:
+    int addDirectChannel(const struct sensors_direct_mem_t *mem);
+    int removeDirectChannel(int channel_handle);
+    int configDirectReport(int sensor_handle, int channel_handle, int rate_level);
+    bool isDirectReportSupported() const;
+private:
+    void sendDirectReportEvent(const sensors_event_t *nev, size_t n);
+    void mergeDirectReportRequest(struct ConfigCmd *cmd, int handle);
+#ifdef DIRECT_REPORT_ENABLED
+    int stopAllDirectReportOnChannel(
+            int channel_handle, std::vector<int32_t> *unstoppedSensors);
+    Mutex mDirectChannelLock;
+    //sensor_handle=>(channel_handle, rate_level)
+    std::unordered_map<int32_t, std::unordered_map<int32_t, int32_t> > mSensorToChannel;
+    //channel_handle=>ptr of Channel obj
+    std::unordered_map<int32_t, std::unique_ptr<DirectChannelBase>> mDirectChannel;
+    int32_t mDirectChannelHandle;
+#endif
+
     DISALLOW_EVIL_CONSTRUCTORS(HubConnection);
 };
 
diff --git a/sensorhal/hubdefs.h b/sensorhal/hubdefs.h
index ba1f6e9..99c8e26 100644
--- a/sensorhal/hubdefs.h
+++ b/sensorhal/hubdefs.h
@@ -24,7 +24,7 @@
 namespace android {
 
 #define CONTEXTHUB_SETTINGS_PATH        "/persist/sensorcal.json"
-#define CONTEXTHUB_SAVED_SETTINGS_PATH  "/data/misc/sensorcal_saved.json"
+#define CONTEXTHUB_SAVED_SETTINGS_PATH  "/data/vendor/sensor/sensorcal_saved.json"
 #define MAG_BIAS_FILE_PATH              "/sys/class/power_supply/battery/compass_compensation"
 
 static const uint32_t kMinClockRateHz = 960000;
diff --git a/sensorhal/sensorlist.h b/sensorhal/sensorlist.h
index b26110b..4d09133 100644
--- a/sensorhal/sensorlist.h
+++ b/sensorhal/sensorlist.h
@@ -20,6 +20,8 @@
 
 #include <hardware/sensors.h>
 
+extern const float kScaleAccel;
+extern const float kScaleMag;
 // A list of sensors provided by a device.
 extern const sensor_t kSensorList[];
 extern const size_t kSensorCount;
diff --git a/sensorhal/sensors.cpp b/sensorhal/sensors.cpp
index 77ab3ba..65b881c 100644
--- a/sensorhal/sensors.cpp
+++ b/sensorhal/sensors.cpp
@@ -15,28 +15,37 @@
  */
 
 #define LOG_TAG "sensors"
-// #defined LOG_NDEBUG  1
+#define LOG_NDEBUG  1
 #include <utils/Log.h>
 
 #include "hubconnection.h"
 #include "sensorlist.h"
 #include "sensors.h"
 
+#include <cutils/ashmem.h>
 #include <errno.h>
 #include <math.h>
 #include <media/stagefright/foundation/ADebug.h>
 #include <string.h>
+#include <sys/mman.h>
+#include <stdlib.h>
+
+#ifdef DYNAMIC_SENSOR_EXT_ENABLED
+#include <DynamicSensorManager.h>
+#include <SensorEventCallback.h>
+#endif
 
 using namespace android;
 
 ////////////////////////////////////////////////////////////////////////////////
 
 SensorContext::SensorContext(const struct hw_module_t *module)
-    : mHubConnection(HubConnection::getInstance()) {
+        : mSensorList(kSensorList, kSensorList + kSensorCount),
+          mHubConnection(HubConnection::getInstance()) {
     memset(&device, 0, sizeof(device));
 
     device.common.tag = HARDWARE_DEVICE_TAG;
-    device.common.version = SENSORS_DEVICE_API_VERSION_1_3;
+    device.common.version = SENSORS_DEVICE_API_VERSION_1_4;
     device.common.module = const_cast<hw_module_t *>(module);
     device.common.close = CloseWrapper;
     device.activate = ActivateWrapper;
@@ -44,13 +53,20 @@
     device.poll = PollWrapper;
     device.batch = BatchWrapper;
     device.flush = FlushWrapper;
+    device.inject_sensor_data = InjectSensorDataWrapper;
+    mHubConnection->setRawScale(kScaleAccel, kScaleMag);
+    if (mHubConnection->isDirectReportSupported()) {
+        device.register_direct_channel = RegisterDirectChannelWrapper;
+        device.config_direct_report = ConfigDirectReportWrapper;
+    }
 
-    mHubAlive = (mHubConnection->initCheck() == OK
-        && mHubConnection->getAliveCheck() == OK);
+    mOperationHandler.emplace_back(new HubConnectionOperation(mHubConnection));
+
+    initializeHalExtension();
 }
 
 int SensorContext::close() {
-    ALOGI("close");
+    ALOGV("close");
 
     delete this;
 
@@ -58,38 +74,25 @@
 }
 
 int SensorContext::activate(int handle, int enabled) {
-    ALOGI("activate");
+    ALOGV("activate");
 
-    mHubConnection->queueActivate(handle, enabled);
-
-    return 0;
+    for (auto &h : mOperationHandler) {
+        if (h->owns(handle)) {
+            return h->activate(handle, enabled);
+        }
+    }
+    return INVALID_OPERATION;
 }
 
 int SensorContext::setDelay(int handle, int64_t delayNs) {
-    ALOGI("setDelay");
+    ALOGV("setDelay");
 
-    // clamp sample rate based on minDelay and maxDelay defined in kSensorList
-    int64_t delayNsClamped = delayNs;
-    for (size_t i = 0; i < kSensorCount; i++) {
-        sensor_t sensor = kSensorList[i];
-        if (sensor.handle != handle) {
-            continue;
+    for (auto &h: mOperationHandler) {
+        if (h->owns(handle)) {
+            return h->setDelay(handle, delayNs);
         }
-
-        if ((sensor.flags & REPORTING_MODE_MASK) == SENSOR_FLAG_CONTINUOUS_MODE) {
-            if ((delayNs/1000) < sensor.minDelay) {
-                delayNsClamped = sensor.minDelay * 1000;
-            } else if ((delayNs/1000) > sensor.maxDelay) {
-                delayNsClamped = sensor.maxDelay * 1000;
-            }
-        }
-
-        break;
     }
-
-    mHubConnection->queueSetDelay(handle, delayNsClamped);
-
-    return 0;
+    return INVALID_OPERATION;
 }
 
 int SensorContext::poll(sensors_event_t *data, int count) {
@@ -126,37 +129,43 @@
         int handle,
         int64_t sampling_period_ns,
         int64_t max_report_latency_ns) {
-    ALOGI("batch");
+    ALOGV("batch");
 
-    // clamp sample rate based on minDelay and maxDelay defined in kSensorList
-    int64_t sampling_period_ns_clamped = sampling_period_ns;
-    for (size_t i = 0; i < kSensorCount; i++) {
-        sensor_t sensor = kSensorList[i];
-        if (sensor.handle != handle) {
-            continue;
+    for (auto &h : mOperationHandler) {
+        if (h->owns(handle)) {
+            return h->batch(handle, sampling_period_ns, max_report_latency_ns);
         }
-
-        if ((sensor.flags & REPORTING_MODE_MASK) == SENSOR_FLAG_CONTINUOUS_MODE) {
-            if ((sampling_period_ns/1000) < sensor.minDelay) {
-                sampling_period_ns_clamped = sensor.minDelay * 1000;
-            } else if ((sampling_period_ns/1000) > sensor.maxDelay) {
-                sampling_period_ns_clamped = sensor.maxDelay * 1000;
-            }
-        }
-
-        break;
     }
-
-    mHubConnection->queueBatch(handle, sampling_period_ns_clamped,
-                               max_report_latency_ns);
-    return 0;
+    return INVALID_OPERATION;
 }
 
 int SensorContext::flush(int handle) {
-    ALOGI("flush");
+    ALOGV("flush");
 
-    mHubConnection->queueFlush(handle);
-    return 0;
+    for (auto &h : mOperationHandler) {
+        if (h->owns(handle)) {
+            return h->flush(handle);
+        }
+    }
+    return INVALID_OPERATION;
+}
+
+int SensorContext::register_direct_channel(
+        const struct sensors_direct_mem_t *mem, int32_t channel_handle) {
+    if (mem) {
+        //add
+        return mHubConnection->addDirectChannel(mem);
+    } else {
+        //remove
+        mHubConnection->removeDirectChannel(channel_handle);
+        return NO_ERROR;
+    }
+}
+
+int SensorContext::config_direct_report(
+        int32_t sensor_handle, int32_t channel_handle, const struct sensors_direct_cfg_t * config) {
+    int rate_level = config->rate_level;
+    return mHubConnection->configDirectReport(sensor_handle, channel_handle, rate_level);
 }
 
 // static
@@ -199,22 +208,202 @@
     return reinterpret_cast<SensorContext *>(dev)->flush(handle);
 }
 
+// static
+int SensorContext::RegisterDirectChannelWrapper(struct sensors_poll_device_1 *dev,
+        const struct sensors_direct_mem_t* mem, int channel_handle) {
+    return reinterpret_cast<SensorContext *>(dev)->register_direct_channel(
+            mem, channel_handle);
+}
+
+// static
+int SensorContext::ConfigDirectReportWrapper(struct sensors_poll_device_1 *dev,
+        int sensor_handle, int channel_handle, const sensors_direct_cfg_t * config) {
+    return reinterpret_cast<SensorContext *>(dev)->config_direct_report(
+            sensor_handle, channel_handle, config);
+}
+
+int SensorContext::inject_sensor_data(const sensors_event_t *event) {
+    ALOGV("inject_sensor_data");
+
+    // only support set operation parameter, which will have handle == 0
+    if (event == nullptr || event->type != SENSOR_TYPE_ADDITIONAL_INFO) {
+        return -EINVAL;
+    }
+
+    if (event->sensor != SENSORS_HANDLE_BASE - 1) {
+        return -ENOSYS;
+    }
+
+    if (event->additional_info.type == AINFO_BEGIN
+            || event->additional_info.type == AINFO_END) {
+        return 0;
+    }
+
+    mHubConnection->setOperationParameter(event->additional_info);
+    return 0;
+}
+
+// static
+int SensorContext::InjectSensorDataWrapper(struct sensors_poll_device_1 *dev,
+        const struct sensors_event_t *event) {
+    return reinterpret_cast<SensorContext *>(dev)->inject_sensor_data(event);
+}
+
 bool SensorContext::getHubAlive() {
-    return mHubAlive;
+    return (mHubConnection->initCheck() == OK && mHubConnection->getAliveCheck() == OK);
+}
+
+size_t SensorContext::getSensorList(sensor_t const **list) {
+    ALOGE("sensor p = %p, n = %zu", mSensorList.data(), mSensorList.size());
+    *list = mSensorList.data();
+    return mSensorList.size();
+}
+
+// HubConnectionOperation functions
+SensorContext::HubConnectionOperation::HubConnectionOperation(sp<HubConnection> hubConnection)
+        : mHubConnection(hubConnection) {
+    for (size_t i = 0; i < kSensorCount; i++) {
+        mHandles.emplace(kSensorList[i].handle);
+    }
+}
+
+bool SensorContext::HubConnectionOperation::owns(int handle) {
+    return mHandles.find(handle) != mHandles.end();
+}
+
+int SensorContext::HubConnectionOperation::activate(int handle, int enabled) {
+    mHubConnection->queueActivate(handle, enabled);
+    return 0;
+}
+
+int SensorContext::HubConnectionOperation::setDelay(int handle, int64_t delayNs) {
+    // clamp sample rate based on minDelay and maxDelay defined in kSensorList
+    int64_t delayNsClamped = delayNs;
+    for (size_t i = 0; i < kSensorCount; i++) {
+        sensor_t sensor = kSensorList[i];
+        if (sensor.handle != handle) {
+            continue;
+        }
+
+        if ((sensor.flags & REPORTING_MODE_MASK) == SENSOR_FLAG_CONTINUOUS_MODE) {
+            if ((delayNs/1000) < sensor.minDelay) {
+                delayNsClamped = sensor.minDelay * 1000;
+            } else if ((delayNs/1000) > sensor.maxDelay) {
+                delayNsClamped = sensor.maxDelay * 1000;
+            }
+        }
+
+        break;
+    }
+
+    mHubConnection->queueSetDelay(handle, delayNsClamped);
+    return 0;
+}
+
+int SensorContext::HubConnectionOperation::batch(
+        int handle, int64_t sampling_period_ns,
+        int64_t max_report_latency_ns) {
+    // clamp sample rate based on minDelay and maxDelay defined in kSensorList
+    int64_t sampling_period_ns_clamped = sampling_period_ns;
+    for (size_t i = 0; i < kSensorCount; i++) {
+        sensor_t sensor = kSensorList[i];
+        if (sensor.handle != handle) {
+            continue;
+        }
+
+        if ((sensor.flags & REPORTING_MODE_MASK) == SENSOR_FLAG_CONTINUOUS_MODE) {
+            if ((sampling_period_ns/1000) < sensor.minDelay) {
+                sampling_period_ns_clamped = sensor.minDelay * 1000;
+            } else if ((sampling_period_ns/1000) > sensor.maxDelay) {
+                sampling_period_ns_clamped = sensor.maxDelay * 1000;
+            }
+        }
+
+        break;
+    }
+
+    mHubConnection->queueBatch(handle, sampling_period_ns_clamped,
+                               max_report_latency_ns);
+    return 0;
+}
+
+int SensorContext::HubConnectionOperation::flush(int handle) {
+    mHubConnection->queueFlush(handle);
+    return 0;
+}
+
+#ifdef DYNAMIC_SENSOR_EXT_ENABLED
+namespace {
+// adaptor class
+class Callback : public SensorEventCallback {
+public:
+    Callback(sp<HubConnection> hubConnection) : mHubConnection(hubConnection) {}
+    virtual int submitEvent(sp<BaseSensorObject> source, const sensors_event_t &e) override;
+private:
+    sp<HubConnection> mHubConnection;
+};
+
+int Callback::submitEvent(sp<BaseSensorObject> source, const sensors_event_t &e) {
+    (void) source; // irrelavent in this context
+    return (mHubConnection->write(&e, 1) == 1) ? 0 : -ENOSPC;
+}
+} // anonymous namespace
+
+SensorContext::DynamicSensorManagerOperation::DynamicSensorManagerOperation(DynamicSensorManager* manager)
+        : mDynamicSensorManager(manager) {
+}
+
+bool SensorContext::DynamicSensorManagerOperation::owns(int handle) {
+    return mDynamicSensorManager->owns(handle);
+}
+
+int SensorContext::DynamicSensorManagerOperation::activate(int handle, int enabled) {
+    return mDynamicSensorManager->activate(handle, enabled);
+}
+
+int SensorContext::DynamicSensorManagerOperation::setDelay(int handle, int64_t delayNs) {
+    return mDynamicSensorManager->setDelay(handle, delayNs);
+}
+
+int SensorContext::DynamicSensorManagerOperation::batch(int handle, int64_t sampling_period_ns,
+        int64_t max_report_latency_ns) {
+    return mDynamicSensorManager->batch(handle, sampling_period_ns, max_report_latency_ns);
+}
+
+int SensorContext::DynamicSensorManagerOperation::flush(int handle) {
+    return mDynamicSensorManager->flush(handle);
+}
+#endif
+
+void SensorContext::initializeHalExtension() {
+#ifdef DYNAMIC_SENSOR_EXT_ENABLED
+    // initialize callback and dynamic sensor manager
+    mEventCallback.reset(new Callback(mHubConnection));
+    DynamicSensorManager* manager = DynamicSensorManager::createInstance(
+        kDynamicHandleBase, kMaxDynamicHandleCount, mEventCallback.get());
+
+    // add meta sensor to list
+    mSensorList.push_back(manager->getDynamicMetaSensor());
+
+    // register operation
+    mOperationHandler.emplace_back(new DynamicSensorManagerOperation(manager));
+#endif
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 
 static bool gHubAlive;
+static sensor_t const *sensor_list;
+static int n_sensor;
 
 static int open_sensors(
         const struct hw_module_t *module,
         const char *,
         struct hw_device_t **dev) {
-    ALOGI("open_sensors");
+    ALOGV("open_sensors");
 
     SensorContext *ctx = new SensorContext(module);
-
+    n_sensor = ctx->getSensorList(&sensor_list);
     gHubAlive = ctx->getHubAlive();
     *dev = &ctx->device.common;
 
@@ -228,11 +417,10 @@
 static int get_sensors_list(
         struct sensors_module_t *,
         struct sensor_t const **list) {
-    ALOGI("get_sensors_list");
-
-    if (gHubAlive) {
-        *list = kSensorList;
-        return kSensorCount;
+    ALOGV("get_sensors_list");
+    if (gHubAlive && sensor_list != nullptr) {
+        *list = sensor_list;
+        return n_sensor;
     } else {
         *list = {};
         return 0;
@@ -240,8 +428,13 @@
 }
 
 static int set_operation_mode(unsigned int mode) {
-    ALOGI("set_operation_mode");
-    return (mode) ? -EINVAL : 0;
+    ALOGV("set_operation_mode");
+
+    // This is no-op because there is no sensor in the hal that system can
+    // inject events. Only operation parameter injection is implemented, which
+    // works in both data injection and normal mode.
+    (void) mode;
+    return 0;
 }
 
 struct sensors_module_t HAL_MODULE_INFO_SYM = {
diff --git a/sensorhal/sensors.h b/sensorhal/sensors.h
index 69ba2d6..a30a730 100644
--- a/sensorhal/sensors.h
+++ b/sensorhal/sensors.h
@@ -21,8 +21,30 @@
 #include <hardware/hardware.h>
 #include <hardware/sensors.h>
 #include <media/stagefright/foundation/ABase.h>
+#include <utils/RefBase.h>
 
-#include "hubconnection.h"
+#include <memory>
+#include <unordered_set>
+#include <vector>
+
+using android::sp;
+
+namespace android {
+    struct HubConnection;
+} // namespace android
+using android::HubConnection;
+
+namespace android {
+    namespace SensorHalExt {
+        class BaseSensorObject;
+        class DynamicSensorManager;
+        class SensorEventCallback;
+    } // namespace BaseSensorObject
+} // namespace android
+
+using android::SensorHalExt::BaseSensorObject;
+using android::SensorHalExt::DynamicSensorManager;
+using android::SensorHalExt::SensorEventCallback;
 
 struct SensorContext {
     struct sensors_poll_device_1 device;
@@ -31,9 +53,9 @@
 
     bool getHubAlive();
 
+    size_t getSensorList(sensor_t const **list);
+
 private:
-    android::sp<android::HubConnection> mHubConnection;
-    bool mHubAlive;
 
     int close();
     int activate(int handle, int enabled);
@@ -45,6 +67,17 @@
 
     int flush(int handle);
 
+    int register_direct_channel(
+            const struct sensors_direct_mem_t* mem, int channel_handle);
+
+    int config_direct_report(
+            int sensor_handle, int channel_handle, const struct sensors_direct_cfg_t * config);
+
+    int inject_sensor_data(const struct sensors_event_t *event);
+
+    void initializeHalExtension();
+
+    // static wrappers
     static int CloseWrapper(struct hw_device_t *dev);
 
     static int ActivateWrapper(
@@ -65,6 +98,68 @@
 
     static int FlushWrapper(struct sensors_poll_device_1 *dev, int handle);
 
+    static int RegisterDirectChannelWrapper(struct sensors_poll_device_1 *dev,
+            const struct sensors_direct_mem_t* mem, int channel_handle);
+    static int ConfigDirectReportWrapper(struct sensors_poll_device_1 *dev,
+            int sensor_handle, int channel_handle, const struct sensors_direct_cfg_t * config);
+    static int InjectSensorDataWrapper(struct sensors_poll_device_1 *dev, const sensors_event_t *event);
+
+    class SensorOperation {
+    public:
+        virtual bool owns(int handle) = 0;
+        virtual int activate(int handle, int enabled) = 0;
+        virtual int setDelay(int handle, int64_t delayNs) = 0;
+        virtual int batch(
+                int handle, int64_t sampling_period_ns,
+                int64_t max_report_latency_ns) = 0;
+        virtual int flush(int handle) = 0;
+        virtual ~SensorOperation() {}
+    };
+
+    class HubConnectionOperation : public SensorOperation {
+    public:
+        HubConnectionOperation(sp<HubConnection> hubConnection);
+        virtual bool owns(int handle) override;
+        virtual int activate(int handle, int enabled) override;
+        virtual int setDelay(int handle, int64_t delayNs) override;
+        virtual int batch(
+                int handle, int64_t sampling_period_ns,
+                int64_t max_report_latency_ns) override;
+        virtual int flush(int handle) override;
+        virtual ~HubConnectionOperation() {}
+    private:
+        sp<HubConnection> mHubConnection;
+        std::unordered_set<int> mHandles;
+    };
+
+    std::vector<sensor_t> mSensorList;
+
+    sp<HubConnection> mHubConnection;
+    std::vector<std::unique_ptr<SensorOperation> > mOperationHandler;
+
+#ifdef DYNAMIC_SENSOR_EXT_ENABLED
+private:
+    class DynamicSensorManagerOperation : public SensorOperation {
+    public:
+        DynamicSensorManagerOperation(DynamicSensorManager* manager);
+        virtual bool owns(int handle) override;
+        virtual int activate(int handle, int enabled) override;
+        virtual int setDelay(int handle, int64_t delayNs) override;
+        virtual int batch(
+                int handle, int64_t sampling_period_ns,
+                int64_t max_report_latency_ns) override;
+        virtual int flush(int handle) override;
+        virtual ~DynamicSensorManagerOperation() {}
+    private:
+        std::unique_ptr<DynamicSensorManager> mDynamicSensorManager;
+    };
+
+    static constexpr int32_t kDynamicHandleBase = 0x10000;
+    static constexpr int32_t kMaxDynamicHandleCount = 0xF0000; // ~1M handles, enough before reboot
+
+    std::unique_ptr<SensorEventCallback> mEventCallback;
+#endif //DYNAMIC_SENSOR_EXT_ENABLED
+
     DISALLOW_EVIL_CONSTRUCTORS(SensorContext);
 };
 
diff --git a/util/common/ring.cpp b/util/common/ring.cpp
index 1d23b82..26d44d6 100644
--- a/util/common/ring.cpp
+++ b/util/common/ring.cpp
@@ -104,5 +104,30 @@
     return size;
 }
 
+LockfreeBuffer::LockfreeBuffer(void* buf, size_t size)
+        : mData((sensors_event_t *)buf), mSize(size/sizeof(sensors_event_t)),
+        mWritePos(0), mCounter(1) {
+    memset(mData, 0, size);
+}
+
+LockfreeBuffer::~LockfreeBuffer() {
+    memset(mData, 0, mSize*sizeof(sensors_event_t));
+}
+
+void LockfreeBuffer::write(const sensors_event_t *ev, size_t size) {
+    if (!mSize) {
+        return;
+    }
+
+    while(size--) {
+        mData[mWritePos] = *(ev++);
+        mData[mWritePos].reserved0 = mCounter++;
+
+        if (++mWritePos >= mSize) {
+            mWritePos = 0;
+        }
+    }
+}
+
 }  // namespace android
 
diff --git a/util/common/ring.h b/util/common/ring.h
index c77c3de..31bf848 100644
--- a/util/common/ring.h
+++ b/util/common/ring.h
@@ -43,6 +43,21 @@
     DISALLOW_EVIL_CONSTRUCTORS(RingBuffer);
 };
 
+struct LockfreeBuffer {
+    LockfreeBuffer(void* buf, size_t size);
+    ~LockfreeBuffer();
+
+    // support single writer
+    void write(const sensors_event_t *ev, size_t size);
+private:
+    sensors_event_t *mData;
+    size_t mSize;
+    size_t mWritePos;
+    int32_t mCounter;
+
+    DISALLOW_EVIL_CONSTRUCTORS(LockfreeBuffer);
+};
+
 }  // namespace android
 
 #endif  // RING_BUFFER_H_
diff --git a/util/nanoapp_cmd/Android.mk b/util/nanoapp_cmd/Android.mk
index 80af754..35320eb 100644
--- a/util/nanoapp_cmd/Android.mk
+++ b/util/nanoapp_cmd/Android.mk
@@ -19,13 +19,17 @@
 
 LOCAL_SRC_FILES:= nanoapp_cmd.c
 
-LOCAL_C_INCLUDES := $(LOCAL_PATH)/../../firmware/os/inc
+LOCAL_C_INCLUDES += \
+	$(LOCAL_PATH)/../../firmware/os/inc \
+	$(LOCAL_PATH)/../../lib/include
+
 LOCAL_CFLAGS := -Wall -Werror -Wextra
 
 LOCAL_MODULE:= nanoapp_cmd
 
 LOCAL_MODULE_TAGS:= optional
 LOCAL_MODULE_OWNER := google
+LOCAL_PROPRIETARY_MODULE := true
 
 LOCAL_LDLIBS := -llog
 
diff --git a/util/nanoapp_cmd/nanoapp_cmd.c b/util/nanoapp_cmd/nanoapp_cmd.c
index e0334ef..328ba0c 100644
--- a/util/nanoapp_cmd/nanoapp_cmd.c
+++ b/util/nanoapp_cmd/nanoapp_cmd.c
@@ -32,14 +32,20 @@
 
 #include <android/log.h>
 
+#include <nanohub/nanohub.h>
 #include <eventnums.h>
 #include <sensType.h>
 
 #define SENSOR_RATE_ONCHANGE    0xFFFFFF01UL
 #define SENSOR_RATE_ONESHOT     0xFFFFFF02UL
 #define SENSOR_HZ(_hz)          ((uint32_t)((_hz) * 1024.0f))
+#define MAX_APP_NAME_LEN        32
 #define MAX_INSTALL_CNT         8
+#define MAX_UNINSTALL_CNT       8
 #define MAX_DOWNLOAD_RETRIES    4
+#define UNINSTALL_CMD           "uninstall"
+
+#define NANOHUB_EXT_APP_DELETE  2
 
 #define LOGE(fmt, ...) do { \
         __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, fmt, ##__VA_ARGS__); \
@@ -66,7 +72,7 @@
     uint8_t data[];
 } __attribute__((packed));
 
-struct AppInfo
+struct App
 {
     uint32_t num;
     uint64_t id;
@@ -170,9 +176,10 @@
 bool stop = false;
 char *buf;
 int nread, buf_size = 2048;
-struct AppInfo apps[32];
+struct App apps[32];
 uint8_t appCount;
-char appsToInstall[MAX_INSTALL_CNT][32];
+char appsToInstall[MAX_INSTALL_CNT][MAX_APP_NAME_LEN+1];
+uint64_t appsToUninstall[MAX_UNINSTALL_CNT];
 
 void sig_handle(__attribute__((unused)) int sig)
 {
@@ -204,7 +211,7 @@
         return;
 
     while ((numRead = getline(&line, &len, fp)) != -1) {
-        struct AppInfo *currApp = &apps[appCount++];
+        struct App *currApp = &apps[appCount++];
         sscanf(line, "app: %d id: %" PRIx64 " ver: %" PRIx32 " size: %" PRIx32 "\n", &currApp->num, &currApp->id, &currApp->version, &currApp->size);
     }
 
@@ -214,7 +221,7 @@
         free(line);
 }
 
-struct AppInfo *findApp(uint64_t appId)
+struct App *findApp(uint64_t appId)
 {
     uint8_t i;
 
@@ -227,13 +234,12 @@
     return NULL;
 }
 
-int parseConfigAppInfo()
+int parseConfigAppInfo(int *installCnt, int *uninstallCnt)
 {
     FILE *fp;
     char *line = NULL;
     size_t len;
     ssize_t numRead;
-    int installCnt;
 
     fp = openFile("/vendor/firmware/napp_list.cfg", "r");
     if (!fp)
@@ -241,17 +247,22 @@
 
     parseInstalledAppInfo();
 
-    installCnt = 0;
-    while (((numRead = getline(&line, &len, fp)) != -1) && (installCnt < MAX_INSTALL_CNT)) {
+    *installCnt = *uninstallCnt = 0;
+    while (((numRead = getline(&line, &len, fp)) != -1) && (*installCnt < MAX_INSTALL_CNT) && (*uninstallCnt < MAX_UNINSTALL_CNT)) {
         uint64_t appId;
         uint32_t appVersion;
-        struct AppInfo* installedApp;
+        struct App *installedApp;
 
-        sscanf(line, "%32s %" PRIx64 " %" PRIx32 "\n", appsToInstall[installCnt], &appId, &appVersion);
+        sscanf(line, "%" STRINGIFY(MAX_APP_NAME_LEN) "s %" PRIx64 " %" PRIx32 "\n", appsToInstall[*installCnt], &appId, &appVersion);
 
         installedApp = findApp(appId);
-        if (!installedApp || (installedApp->version < appVersion)) {
-            installCnt++;
+        if (strncmp(appsToInstall[*installCnt], UNINSTALL_CMD, MAX_APP_NAME_LEN) == 0) {
+            if (installedApp) {
+                appsToUninstall[*uninstallCnt] = appId;
+                (*uninstallCnt)++;
+            }
+        } else if (!installedApp || (installedApp->version < appVersion)) {
+            (*installCnt)++;
         }
     }
 
@@ -260,7 +271,7 @@
     if (line)
         free(line);
 
-    return installCnt;
+    return *installCnt + *uninstallCnt;
 }
 
 bool fileWriteData(const char *fname, const void *data, size_t size)
@@ -294,6 +305,27 @@
         printf("done\n");
 }
 
+void removeApps(int updateCnt)
+{
+    uint8_t buffer[sizeof(struct HostMsgHdr) + 1 + sizeof(uint64_t)];
+    struct HostMsgHdr *mHostMsgHdr = (struct HostMsgHdr *)(&buffer[0]);
+    uint8_t *cmd = (uint8_t *)(&buffer[sizeof(struct HostMsgHdr)]);
+    uint64_t *appId = (uint64_t *)(&buffer[sizeof(struct HostMsgHdr) + 1]);
+    int i;
+
+    for (i = 0; i < updateCnt; i++) {
+        mHostMsgHdr->eventId = EVT_APP_FROM_HOST;
+        mHostMsgHdr->appId = APP_ID_MAKE(NANOHUB_VENDOR_GOOGLE, 0);
+        mHostMsgHdr->len = 1 + sizeof(uint64_t);
+        *cmd = NANOHUB_EXT_APP_DELETE;
+        memcpy(appId, &appsToUninstall[i], sizeof(uint64_t));
+        printf("Deleting \"%016" PRIx64 "\"...", appsToUninstall[i]);
+        fflush(stdout);
+        if (fileWriteData("/dev/nanohub", buffer, sizeof(buffer)))
+            printf("done\n");
+    }
+}
+
 void downloadApps(int updateCnt)
 {
     int i;
@@ -434,27 +466,31 @@
             return 1;
         }
     } else if (strcmp(argv[1], "download") == 0) {
+        int installCnt, uninstallCnt;
+
         if (argc != 2) {
             printf("Wrong arg number\n");
             return 1;
         }
         downloadNanohub();
         for (i = 0; i < MAX_DOWNLOAD_RETRIES; i++) {
-            int updateCnt = parseConfigAppInfo();
+            int updateCnt = parseConfigAppInfo(&installCnt, &uninstallCnt);
             if (updateCnt > 0) {
                 if (i == MAX_DOWNLOAD_RETRIES - 1) {
                     LOGE("Download failed after %d retries; erasing all apps "
                          "before final attempt", i);
                     eraseSharedArea();
+                    uninstallCnt = 0;
                 }
-                downloadApps(updateCnt);
+                removeApps(uninstallCnt);
+                downloadApps(installCnt);
                 resetHub();
             } else if (!updateCnt){
                 return 0;
             }
         }
 
-        if (parseConfigAppInfo() != 0) {
+        if (parseConfigAppInfo(&installCnt, &uninstallCnt) != 0) {
             LOGE("Failed to download all apps!");
         }
         return 1;
diff --git a/util/nanotool/androidcontexthub.cpp b/util/nanotool/androidcontexthub.cpp
index 8ffecc0..44af1b4 100644
--- a/util/nanotool/androidcontexthub.cpp
+++ b/util/nanotool/androidcontexthub.cpp
@@ -37,8 +37,8 @@
 
 constexpr char kSensorDeviceFile[] = "/dev/nanohub";
 constexpr char kCommsDeviceFile[] = "/dev/nanohub_comms";
-constexpr char kLockDirectory[] = "/data/system/nanohub_lock";
-constexpr char kLockFile[] = "/data/system/nanohub_lock/lock";
+constexpr char kLockDirectory[] = "/data/vendor/sensor/nanohub_lock";
+constexpr char kLockFile[] = "/data/vendor/sensor/nanohub_lock/lock";
 
 constexpr mode_t kLockDirPermissions = (S_IRUSR | S_IWUSR | S_IXUSR);
 
diff --git a/util/sensortest/Android.mk b/util/sensortest/Android.mk
index cbf1b3e..0f385e0 100644
--- a/util/sensortest/Android.mk
+++ b/util/sensortest/Android.mk
@@ -33,5 +33,7 @@
 
 LOCAL_MODULE:= sensortest
 
+LOCAL_SDK_VERSION := current
+
 include $(BUILD_EXECUTABLE)
 
diff --git a/util/sensortest/sensortest.cpp b/util/sensortest/sensortest.cpp
index c032ca9..36d727a 100644
--- a/util/sensortest/sensortest.cpp
+++ b/util/sensortest/sensortest.cpp
@@ -26,7 +26,7 @@
     int listIndex;
     int type;
     int32_t rate;
-    int reportLatency;
+    int64_t reportLatency;
     bool receivedEvent;
 };
 
@@ -119,7 +119,7 @@
 
             if (existingSensorConfigIndex >= 0) {
                 printf("Replacing previous config for sensor type %d\n", atoi(argv[currArgumentIndex+1]));
-                mSensorConfigList[existingSensorConfigIndex] = {
+                mSensorConfigList[existingSensorConfigIndex] = (SensorConfig) {
                     .listIndex = sensorIndex,
                     .type = atoi(argv[currArgumentIndex+1]),
                     .rate = atoi(argv[currArgumentIndex+2]),
@@ -127,7 +127,7 @@
                     .receivedEvent = false
                 };
             } else {
-                mSensorConfigList[(mNumSensorConfigs)++] = {
+                mSensorConfigList[(mNumSensorConfigs)++] = (SensorConfig) {
                     .listIndex = sensorIndex,
                     .type = atoi(argv[currArgumentIndex+1]),
                     .rate = atoi(argv[currArgumentIndex+2]),
@@ -152,7 +152,7 @@
 
             if (existingSensorConfigIndex >= 0) {
                 printf("Replacing previous config for sensor type %d\n", atoi(argv[currArgumentIndex+1]));
-                mSensorConfigList[existingSensorConfigIndex] = {
+                mSensorConfigList[existingSensorConfigIndex] = (SensorConfig) {
                     .listIndex = sensorIndex,
                     .type = atoi(argv[currArgumentIndex+1]),
                     .rate = atoi(argv[currArgumentIndex+2]),
@@ -160,7 +160,7 @@
                     .receivedEvent = false
                 };
             } else {
-                mSensorConfigList[(mNumSensorConfigs)++] = {
+                mSensorConfigList[(mNumSensorConfigs)++] = (SensorConfig) {
                     .listIndex = sensorIndex,
                     .type = atoi(argv[currArgumentIndex+1]),
                     .rate = atoi(argv[currArgumentIndex+2]),
@@ -215,8 +215,9 @@
     for (int i = 0; i < mNumSensorConfigs; i++) {
         if (ASensorEventQueue_registerSensor(sensorEventQueue, mSensorList[mSensorConfigList[i].listIndex],
                                              mSensorConfigList[i].rate, mSensorConfigList[i].reportLatency) < 0) {
-            printf("Unable to register sensor %d with rate %d and report latency %d\n", mSensorConfigList[i].listIndex,
-                   mSensorConfigList[i].rate, mSensorConfigList[i].reportLatency);
+            printf("Unable to register sensor %d with rate %d and report latency %" PRId64 "\n",
+                   mSensorConfigList[i].listIndex, mSensorConfigList[i].rate,
+                   mSensorConfigList[i].reportLatency);
         }
 
     }