Merge pi-qpr1-release PQ1A.181105.017.A1 to pi-platform-release
am: cc1118a4e8

Change-Id: I5fc7ebc3c80bf2ec21144eaed386cce7d0cb48b5
diff --git a/Android.bp b/Android.bp
index 4ffb2da..c99a3ab 100644
--- a/Android.bp
+++ b/Android.bp
@@ -59,6 +59,26 @@
     gtest: false,
 }
 
+cc_test {
+    name: "audio_stress_test",
+    vendor: true,
+    local_include_dirs: [
+        "chre_api/include/chre_api",
+        "util/include",
+    ],
+    srcs: [
+        "host/common/audio_stress_test/audio_stress_test.cc",
+    ],
+    cflags: ["-Wall", "-Werror"],
+    shared_libs: [
+        "libcutils",
+        "liblog",
+        "libutils",
+    ],
+    static_libs: ["chre_client"],
+    gtest: false,
+}
+
 cc_library_shared {
     name: "[email protected]",
     vendor: true,
diff --git a/apps/audio_stress_test/Makefile b/apps/audio_stress_test/Makefile
new file mode 100644
index 0000000..7421478
--- /dev/null
+++ b/apps/audio_stress_test/Makefile
@@ -0,0 +1,37 @@
+#
+# Audio Stress Test Nanoapp Makefile
+#
+
+# Environment Checks ###########################################################
+
+ifeq ($(CHRE_PREFIX),)
+ifneq ($(ANDROID_BUILD_TOP),)
+CHRE_PREFIX = $(ANDROID_BUILD_TOP)/system/chre
+else
+$(error "You must run 'lunch' to setup ANDROID_BUILD_TOP, or explicitly define \
+         the CHRE_PREFIX environment variable to point to the CHRE root \
+         directory.")
+endif
+endif
+
+# Nanoapp Configuration ########################################################
+
+NANOAPP_NAME_STRING = \"Audio\ Stress\ Test\"
+NANOAPP_NAME = audio_stress_test
+NANOAPP_ID = 0x012345678900000e
+NANOAPP_VERSION = 0x00000001
+
+# Common Compiler Flags ########################################################
+
+# Defines.
+COMMON_CFLAGS += -DCHRE_NANOAPP_DISABLE_BACKCOMPAT
+COMMON_CFLAGS += -DNANOAPP_MINIMUM_LOG_LEVEL=CHRE_LOG_LEVEL_DEBUG
+
+# Common Source Files ##########################################################
+
+COMMON_SRCS += audio_stress_test.cc
+COMMON_SRCS += $(CHRE_PREFIX)/util/nanoapp/audio.cc
+
+# Makefile Includes ############################################################
+
+include $(CHRE_PREFIX)/build/nanoapp/app.mk
diff --git a/apps/audio_stress_test/audio_stress_test.cc b/apps/audio_stress_test/audio_stress_test.cc
new file mode 100644
index 0000000..86c5dfb
--- /dev/null
+++ b/apps/audio_stress_test/audio_stress_test.cc
@@ -0,0 +1,242 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+
+#include <chre.h>
+#include <cinttypes>
+
+#include "chre/util/macros.h"
+#include "chre/util/nanoapp/audio.h"
+#include "chre/util/nanoapp/log.h"
+#include "chre/util/time.h"
+
+#define LOG_TAG "[AudioStress]"
+
+/**
+ * @file
+ *
+ * This nanoapp is designed to subscribe to audio for varying durations of
+ * time and verify that audio data is delivered when it is expected to be.
+ */
+
+using chre::Milliseconds;
+using chre::Nanoseconds;
+using chre::Seconds;
+
+namespace {
+
+//! The required buffer size for the stress test.
+constexpr Nanoseconds kBufferDuration = Nanoseconds(Seconds(2));
+
+//! The required sample format for the stress test.
+constexpr uint8_t kBufferFormat = CHRE_AUDIO_DATA_FORMAT_16_BIT_SIGNED_PCM;
+
+//! The required sample rate for the stress test.
+constexpr uint32_t kBufferSampleRate = 16000;
+
+//! The maximum amount of time that audio will not be delivered for.
+constexpr Seconds kMaxAudioGap = Seconds(300);
+
+//! The list of durations to subscribe to audio for. Even durations are for when
+//! audio is enabled and odd is for when audio is disabled.
+constexpr Milliseconds kStressPlan[] = {
+  // Enabled, Disabled
+  Milliseconds(20000), Milliseconds(20000),
+  Milliseconds(30000), Milliseconds(200),
+  Milliseconds(10000), Milliseconds(1000),
+  Milliseconds(10000), Milliseconds(1999),
+  Milliseconds(8000), Milliseconds(60000),
+  Milliseconds(1000), Milliseconds(1000),
+  Milliseconds(1000), Milliseconds(1000),
+  Milliseconds(1000), Milliseconds(1000),
+  Milliseconds(1000), Milliseconds(1000),
+  Milliseconds(1000), Milliseconds(1000),
+  Milliseconds(1000), Milliseconds(1000),
+  Milliseconds(1000), Milliseconds(1000),
+  Milliseconds(1000), Milliseconds(1000),
+};
+
+//! The discovered audio handle found at startup.
+uint32_t gAudioHandle;
+
+//! The current position in the stress plan.
+size_t gTestPosition = 0;
+
+//! The timer handle to advance through the stress test.
+uint32_t gTimerHandle;
+
+//! Whether or not audio is currently suspended. If audio is delivered when this
+//! is set to true, this is considered a test failure.
+bool gAudioIsSuspended = true;
+
+//! The timestamp of the last audio data event.
+Nanoseconds gLastAudioTimestamp;
+
+/**
+ * @return true when the current test phase is expecting audio data events to be
+ *         delivered.
+ */
+bool audioIsExpected() {
+  // Even test intervals are expected to return audio events. The current test
+  // interval is gTestPosition - 1 so there is no need to invert the bit.
+  return (gTestPosition % 2);
+}
+
+/**
+ * Discovers an audio source to use for the stress test. The gAudioHandle will
+ * be set if the audio source was found.
+ *
+ * @return true if a matching source was discovered successfully.
+ */
+bool discoverAudioHandle() {
+  bool success = false;
+  struct chreAudioSource source;
+  for (uint32_t i = 0; !success && chreAudioGetSource(i, &source); i++) {
+    LOGI("Found audio source '%s' with %" PRIu32 "Hz %s data",
+         source.name, source.sampleRate,
+         chre::getChreAudioFormatString(source.format));
+    LOGI("  buffer duration: [%" PRIu64 "ns, %" PRIu64 "ns]",
+        source.minBufferDuration, source.maxBufferDuration);
+
+    if (source.sampleRate == kBufferSampleRate
+        && source.minBufferDuration <= kBufferDuration.toRawNanoseconds()
+        && source.maxBufferDuration >= kBufferDuration.toRawNanoseconds()
+        && source.format == kBufferFormat) {
+      gAudioHandle = i;
+      success = true;
+    }
+  }
+
+  if (!success) {
+    LOGW("Failed to find suitable audio source");
+  }
+
+  return success;
+}
+
+void checkTestPassing() {
+  auto lastAudioDuration = Nanoseconds(chreGetTime()) - gLastAudioTimestamp;
+  if (lastAudioDuration > kMaxAudioGap) {
+    LOGE("Test fail - audio not received for %" PRIu64 "ns",
+         lastAudioDuration.toRawNanoseconds());
+    chreAbort(-1);
+  }
+}
+
+bool requestAudioForCurrentTestState(const Nanoseconds& testStateDuration) {
+  bool success = false;
+  LOGD("Test stage %zu", gTestPosition);
+  if (audioIsExpected()) {
+    if (!chreAudioConfigureSource(gAudioHandle, true, kBufferDuration.toRawNanoseconds(),
+                                  kBufferDuration.toRawNanoseconds())) {
+      LOGE("Failed to enable audio");
+    } else {
+      LOGI("Enabled audio for %" PRIu64, testStateDuration.toRawNanoseconds());
+      success = true;
+    }
+  } else {
+    if (!chreAudioConfigureSource(0, false, 0, 0)) {
+      LOGE("Failed to disable audio");
+    } else {
+      LOGI("Disabled audio for %" PRIu64, testStateDuration.toRawNanoseconds());
+      success = true;
+    }
+  }
+
+  return success;
+}
+
+bool advanceTestPosition() {
+  checkTestPassing();
+  gTimerHandle = chreTimerSet(kStressPlan[gTestPosition].toRawNanoseconds(),
+                              nullptr, true /* oneShot */);
+  bool success = (gTimerHandle != CHRE_TIMER_INVALID);
+  if (!success) {
+    LOGE("Failed to set timer");
+  } else {
+    // Grab the duration prior to incrementing the test position.
+    Nanoseconds timerDuration = kStressPlan[gTestPosition++];
+    if (gTestPosition >= ARRAY_SIZE(kStressPlan)) {
+      gTestPosition = 0;
+    }
+
+    success = requestAudioForCurrentTestState(timerDuration);
+  }
+
+  return success;
+}
+
+void handleTimerEvent() {
+  if (!advanceTestPosition()) {
+    LOGE("Test fail");
+  }
+}
+
+void handleAudioDataEvent(const chreAudioDataEvent *audioDataEvent) {
+  LOGI("Handling audio data event");
+  gLastAudioTimestamp = Nanoseconds(audioDataEvent->timestamp);
+
+  if (gAudioIsSuspended) {
+    LOGE("Test fail - received audio when suspended");
+  } else if (!audioIsExpected()) {
+    LOGE("Test fail - received audio unexpectedly");
+  } else {
+    LOGI("Test passing - received audio when expected");
+  }
+}
+
+void handleAudioSamplingChangeEvent(
+    const chreAudioSourceStatusEvent *audioSourceStatusEvent) {
+  LOGI("Handling audio sampling change event - suspended: %d",
+       audioSourceStatusEvent->status.suspended);
+  gAudioIsSuspended = audioSourceStatusEvent->status.suspended;
+}
+
+}  // namespace
+
+
+bool nanoappStart() {
+  LOGI("start");
+  gLastAudioTimestamp = Nanoseconds(chreGetTime());
+  return (discoverAudioHandle() && advanceTestPosition());
+}
+
+void nanoappHandleEvent(uint32_t senderInstanceId,
+                        uint16_t eventType,
+                        const void *eventData) {
+  switch (eventType) {
+    case CHRE_EVENT_TIMER:
+      handleTimerEvent();
+      break;
+
+    case CHRE_EVENT_AUDIO_DATA:
+      handleAudioDataEvent(
+          static_cast<const chreAudioDataEvent *>(eventData));
+      break;
+
+    case CHRE_EVENT_AUDIO_SAMPLING_CHANGE:
+      handleAudioSamplingChangeEvent(
+          static_cast<const chreAudioSourceStatusEvent *>(eventData));
+      break;
+
+    default:
+      LOGW("Unexpected event %" PRIu16, eventType);
+      break;
+  }
+}
+
+void nanoappEnd() {
+  LOGI("stop");
+}
diff --git a/apps/chqts/src/busy_startup/busy_startup.cc b/apps/chqts/src/busy_startup/busy_startup.cc
index 91e4b69..8759a93 100644
--- a/apps/chqts/src/busy_startup/busy_startup.cc
+++ b/apps/chqts/src/busy_startup/busy_startup.cc
@@ -47,13 +47,13 @@
 #include <chre.h>
 
 #include <shared/send_message.h>
+#include <shared/time_util.h>
 
 using nanoapp_testing::MessageType;
 using nanoapp_testing::sendMessageToHost;
 using nanoapp_testing::sendFatalFailureToHost;
 using nanoapp_testing::sendSuccessToHost;
 
-
 static bool gInMethod = false;
 static uint32_t gInstanceId;
 static uint32_t gTimerId;
@@ -198,9 +198,12 @@
     chreLog(CHRE_LOG_ERROR, "Failed sensorFindDefault in start");
     return false;
   }
+
+  // Configure accel request at 50 Hz (reasonable rate, e.g. for AR)
+  // TODO: Add a way to find the range of possible sample rates
   if (!chreSensorConfigure(gSensorHandle,
                            CHRE_SENSOR_CONFIGURE_MODE_CONTINUOUS,
-                           CHRE_SENSOR_INTERVAL_DEFAULT,
+                           20 * nanoapp_testing::kOneMillisecondInNanoseconds,
                            CHRE_SENSOR_LATENCY_ASAP)) {
     chreLog(CHRE_LOG_ERROR, "Failed sensorConfigure in start");
     return false;
diff --git a/apps/chqts/src/general_test/basic_audio_test.cc b/apps/chqts/src/general_test/basic_audio_test.cc
index 01aabb1..7452c49 100644
--- a/apps/chqts/src/general_test/basic_audio_test.cc
+++ b/apps/chqts/src/general_test/basic_audio_test.cc
@@ -17,16 +17,15 @@
 #include <general_test/basic_audio_test.h>
 
 #include <shared/send_message.h>
+#include <shared/time_util.h>
 
+using nanoapp_testing::kOneSecondInNanoseconds;
 using nanoapp_testing::sendFatalFailureToHost;
 using nanoapp_testing::sendSuccessToHost;
 
 namespace general_test {
 namespace {
 
-//! Unit conversion nanoseconds per second.
-constexpr uint64_t kNanosecondsPerSecond = 1000000000;
-
 //! This is a reasonably high limit on the number of audio sources that a system
 //! would expose. Use this to verify that there are no gaps in the source
 //! handles.
@@ -50,12 +49,12 @@
 //! sample rate possible, a minimum number of samples will be delivered in
 //! a batch.
 constexpr uint64_t kMinBufferDuration =
-    (kNanosecondsPerSecond / kMaxAudioSampleRate) * 10;
+    (kOneSecondInNanoseconds / kMaxAudioSampleRate) * 10;
 
 //! Provide a ceiling for the maximum buffer duration. This is to catch buggy
 //! descriptors of audio sources who expose very long buffers of data which are
 //! not practical for always-on, low-power use-cases.
-constexpr uint64_t kMaxBufferDuration = kNanosecondsPerSecond * 120;
+constexpr uint64_t kMaxBufferDuration = kOneSecondInNanoseconds * 120;
 
 /**
  * @return true if the character is ASCII printable.
@@ -126,7 +125,7 @@
 bool validateMinimumAudioSource(const struct chreAudioSource& source) {
   // CHQTS requires a 16kHz, PCM-format, 2 second buffer.
   constexpr uint32_t kRequiredSampleRate = 16000;
-  constexpr uint64_t kRequiredBufferDuration = 2 * kNanosecondsPerSecond;
+  constexpr uint64_t kRequiredBufferDuration = 2 * kOneSecondInNanoseconds;
 
   // Ensure that the minimum buffer size is less than or equal to the required
   // size.
diff --git a/apps/chqts/src/general_test/basic_sensor_test_base.cc b/apps/chqts/src/general_test/basic_sensor_test_base.cc
index 3024eda..c686a06 100644
--- a/apps/chqts/src/general_test/basic_sensor_test_base.cc
+++ b/apps/chqts/src/general_test/basic_sensor_test_base.cc
@@ -20,10 +20,13 @@
 #include <cstddef>
 
 #include <shared/send_message.h>
+#include <shared/time_util.h>
 
 #include <chre.h>
 
 using nanoapp_testing::MessageType;
+using nanoapp_testing::kOneMillisecondInNanoseconds;
+using nanoapp_testing::kOneSecondInNanoseconds;
 using nanoapp_testing::sendFatalFailureToHost;
 using nanoapp_testing::sendInternalFailureToHost;
 using nanoapp_testing::sendStringToHost;
@@ -52,9 +55,7 @@
 
 namespace {
 constexpr uint16_t kStartEvent = CHRE_EVENT_FIRST_USER_VALUE;
-constexpr uint16_t kPassiveCompleteEvent = CHRE_EVENT_FIRST_USER_VALUE + 1;
-constexpr uint64_t kNanosecondsPerSecond = 1000000000;
-constexpr uint64_t kEventLoopSlack = 100000000;  // 100 msec
+constexpr uint64_t kEventLoopSlack = 100 * kOneMillisecondInNanoseconds;
 
 uint64_t getEventDuration(const chreSensorThreeAxisData *event) {
   uint64_t duration = 0;
@@ -70,7 +71,6 @@
   : Test(CHRE_API_VERSION_1_0),
     mInMethod(true),
     mExternalSamplingStatusChange(false),
-    mDoneWithPassiveConfigure(false),
     mState(State::kPreStart),
     mInstanceId(chreGetInstanceId())
     /* All other members initialized later */ {
@@ -121,19 +121,19 @@
   } else {
     if (!chreSensorConfigure(mSensorHandle, mode,
                              CHRE_SENSOR_INTERVAL_DEFAULT,
-                             kNanosecondsPerSecond)) {
+                             kOneSecondInNanoseconds)) {
       sendFatalFailureToHost("chreSensorConfigure() failed passive with "
                              "default interval and non-default latency");
     }
     if (!isOneShotSensor() && !chreSensorConfigure(
-        mSensorHandle, mode, kNanosecondsPerSecond,
+        mSensorHandle, mode, kOneSecondInNanoseconds,
         CHRE_SENSOR_LATENCY_DEFAULT)) {
       sendFatalFailureToHost("chreSensorConfigure() failed passive with "
                              "non-default interval and default latency");
     }
     if (!isOneShotSensor() && !chreSensorConfigure(
-        mSensorHandle, mode, kNanosecondsPerSecond,
-        kNanosecondsPerSecond)) {
+        mSensorHandle, mode, kOneSecondInNanoseconds,
+        kOneSecondInNanoseconds)) {
       sendFatalFailureToHost("chreSensorConfigure() failed passive with "
                              "non-default interval and latency");
     }
@@ -176,15 +176,9 @@
     sendFatalFailureToHost("chreGetSensorSamplingStatus() failed");
   }
 
-  // Nanoapp may start getting events with a passive request. Set the base
-  // timestamp to compare against before configuring the sensor.
+  // Set the base timestamp to compare against before configuring the sensor.
   mPreTimestamp = chreGetTime();
 
-  checkPassiveConfigure();
-  if (!chreSendEvent(kPassiveCompleteEvent, nullptr, nullptr, mInstanceId)) {
-    sendFatalFailureToHost("Failed chreSendEvent to complete passive test");
-  }
-
   // Default interval/latency must be accepted by all sensors.
   mNewStatus = {
     CHRE_SENSOR_INTERVAL_DEFAULT, /* interval */
@@ -211,7 +205,7 @@
     //     from what it currently is for the sensor, and confirm it
     //     changes back when we're DONE.  But that's beyond the current
     //     scope of this 'basic' test.
-    kNanosecondsPerSecond, /* interval */
+    kOneSecondInNanoseconds, /* interval */
     // We want the test to run as quickly as possible.
     // TODO: Similar to the interval, we could try to test changes in
     //     this value, but it's beyond our 'basic' scope for now.
@@ -241,6 +235,8 @@
 }
 
 void BasicSensorTestBase::finishTest() {
+  checkPassiveConfigure();
+
   if (!chreSensorConfigureModeOnly(mSensorHandle,
                                    CHRE_SENSOR_CONFIGURE_MODE_DONE)) {
     sendFatalFailureToHost("Unable to configure sensor mode to DONE");
@@ -255,11 +251,14 @@
     if (status.enabled != mOriginalStatus.enabled) {
       sendFatalFailureToHost("SensorInfo.enabled not back to original");
     }
-    if (status.interval != mOriginalStatus.interval) {
-      sendFatalFailureToHost("SensorInfo.interval not back to original");
-    }
-    if (status.latency != mOriginalStatus.latency) {
-      sendFatalFailureToHost("SensorInfo.latency not back to original");
+    // Interval and latency values are only relevent if the sensor is enabled.
+    if (status.enabled) {
+      if (status.interval != mOriginalStatus.interval) {
+        sendFatalFailureToHost("SensorInfo.interval not back to original");
+      }
+      if (status.latency != mOriginalStatus.latency) {
+        sendFatalFailureToHost("SensorInfo.latency not back to original");
+      }
     }
   }
   mState = State::kFinished;
@@ -362,7 +361,7 @@
   }
   // Passive sensor requests do not guarantee sensors will always be enabled.
   // Bypass 'enabled' check for passive configurations.
-  if (mDoneWithPassiveConfigure && !eventData->status.enabled) {
+  if (!eventData->status.enabled) {
     sendFatalFailureToHost("SamplingChangeEvent disabled the sensor.");
   }
 
@@ -412,10 +411,7 @@
   if (senderInstanceId == mInstanceId) {
     if ((eventType == kStartEvent) && (mState == State::kPreStart)) {
       startTest();
-    } else if (eventType == kPassiveCompleteEvent) {
-      mDoneWithPassiveConfigure = true;
     }
-
   } else if ((mState == State::kPreStart) ||
              (mState == State::kPreConfigure)) {
     unexpectedEvent(eventType);
diff --git a/apps/chqts/src/general_test/basic_sensor_test_base.h b/apps/chqts/src/general_test/basic_sensor_test_base.h
index ccbf43a..e268693 100644
--- a/apps/chqts/src/general_test/basic_sensor_test_base.h
+++ b/apps/chqts/src/general_test/basic_sensor_test_base.h
@@ -100,7 +100,6 @@
   // If some external user changes the sampling status of our sensor,
   // we shouldn't perform some of the checking, because it will be flaky.
   bool mExternalSamplingStatusChange;
-  bool mDoneWithPassiveConfigure;
   State mState;
   uint32_t mInstanceId;
   uint32_t mSensorHandle;
diff --git a/apps/chqts/src/general_test/heap_exhaustion_stability_test.cc b/apps/chqts/src/general_test/heap_exhaustion_stability_test.cc
index 9828a3a..0aae364 100644
--- a/apps/chqts/src/general_test/heap_exhaustion_stability_test.cc
+++ b/apps/chqts/src/general_test/heap_exhaustion_stability_test.cc
@@ -20,9 +20,12 @@
 #include <cstddef>
 
 #include <shared/send_message.h>
+#include <shared/time_util.h>
 
 #include <chre.h>
 
+using nanoapp_testing::kOneMillisecondInNanoseconds;
+using nanoapp_testing::kOneSecondInNanoseconds;
 using nanoapp_testing::sendFailureToHost;
 using nanoapp_testing::sendFatalFailureToHost;
 using nanoapp_testing::sendSuccessToHost;
@@ -49,10 +52,8 @@
 // Thus we make this "static const" instead of "constexpr", as we expect
 // them to have backing memory.
 
-// 5 seconds
-static const uint64_t kExhaustionDuration = UINT64_C(5000000000);
-// 10 milliseconds
-static const uint64_t kShortDuration = UINT64_C(10000000);
+static const uint64_t kExhaustionDuration = 5 * kOneSecondInNanoseconds;
+static const uint64_t kShortDuration = 10 * kOneMillisecondInNanoseconds;
 
 constexpr uint16_t kEventType = CHRE_EVENT_FIRST_USER_VALUE;
 
diff --git a/apps/chqts/src/general_test/timer_cancel_test.cc b/apps/chqts/src/general_test/timer_cancel_test.cc
index 689d868..6a2fa6c 100644
--- a/apps/chqts/src/general_test/timer_cancel_test.cc
+++ b/apps/chqts/src/general_test/timer_cancel_test.cc
@@ -20,9 +20,11 @@
 #include <cstddef>
 
 #include <shared/send_message.h>
+#include <shared/time_util.h>
 
 #include <chre.h>
 
+using nanoapp_testing::kOneMillisecondInNanoseconds;
 using nanoapp_testing::sendFatalFailureToHost;
 using nanoapp_testing::sendInternalFailureToHost;
 using nanoapp_testing::sendSuccessToHost;
@@ -36,8 +38,7 @@
  * When all of our stages have succeeded, then we send success to the host.
  */
 
-// 10 milliseconds
-static uint64_t kDuration = UINT64_C(10000000);
+static uint64_t kDuration = 10 * kOneMillisecondInNanoseconds;
 
 namespace general_test {
 
diff --git a/apps/chqts/src/general_test/timer_set_test.cc b/apps/chqts/src/general_test/timer_set_test.cc
index fb7165b..2a04e9a 100644
--- a/apps/chqts/src/general_test/timer_set_test.cc
+++ b/apps/chqts/src/general_test/timer_set_test.cc
@@ -21,9 +21,12 @@
 #include <new>
 
 #include <shared/send_message.h>
+#include <shared/time_util.h>
 
 #include <chre.h>
 
+using nanoapp_testing::kOneMillisecondInNanoseconds;
+using nanoapp_testing::kOneSecondInNanoseconds;
 using nanoapp_testing::sendFatalFailureToHost;
 using nanoapp_testing::sendInternalFailureToHost;
 using nanoapp_testing::sendSuccessToHost;
@@ -44,11 +47,8 @@
  * us more time to notice them (incorrectly) firing multiple times.
  */
 
-// 10 milliseconds
-static uint64_t kShortDuration = UINT64_C(10000000);
-// 1 second
-static uint64_t kOneSecond = UINT64_C(1000000000);
-static uint64_t kLongDuration = kOneSecond;
+static uint64_t kShortDuration = 10 * kOneMillisecondInNanoseconds;
+static uint64_t kLongDuration = kOneSecondInNanoseconds;
 
 namespace general_test {
 
@@ -79,7 +79,7 @@
     sendFatalFailureToHost("Timer triggered too soon ", &mStage);
   }
   // TODO(b/32179037): Make this check much stricter.
-  if (timestamp > (expectedTime + kOneSecond)) {
+  if (timestamp > (expectedTime + kOneSecondInNanoseconds)) {
     sendFatalFailureToHost("Timer triggered over a second late ", &mStage);
   }
 
diff --git a/apps/chqts/src/shared/time_util.h b/apps/chqts/src/shared/time_util.h
new file mode 100644
index 0000000..38c9e1e
--- /dev/null
+++ b/apps/chqts/src/shared/time_util.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2018 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 _GTS_NANOAPPS_SHARED_TIME_UTIL_H_
+#define _GTS_NANOAPPS_SHARED_TIME_UTIL_H_
+
+#include <cstdint>
+
+namespace nanoapp_testing {
+
+//! The number of milliseconds in one min.
+constexpr uint64_t kOneMinuteInMilliseconds(60000);
+
+//! The number of milliseconds in one second.
+constexpr uint64_t kOneSecondInMilliseconds(1000);
+
+//! The number of nanoseconds in one second.
+constexpr uint64_t kOneSecondInNanoseconds(1000000000);
+
+//! The number of nanoseconds in one millisecond.
+constexpr uint64_t kOneMillisecondInNanoseconds(1000000);
+
+//! The number of nanoseconds in one microsecond.
+constexpr uint64_t kOneMicrosecondInNanoseconds(1000);
+
+//! The number of microseconds in one millisecond.
+constexpr uint64_t kOneMillisecondInMicroseconds(1000);
+
+}  // namespace nanoapp_testing
+
+#endif  // _GTS_NANOAPPS_SHARED_TIME_UTIL_H_
diff --git a/apps/timer_world/timer_world.cc b/apps/timer_world/timer_world.cc
index 578b799..5c421c7 100644
--- a/apps/timer_world/timer_world.cc
+++ b/apps/timer_world/timer_world.cc
@@ -17,6 +17,7 @@
 #include <chre.h>
 #include <cinttypes>
 
+#include "chre/util/nanoapp/audio.h"
 #include "chre/util/nanoapp/log.h"
 
 #define LOG_TAG "[TimerWorld]"
diff --git a/core/audio_request_manager.cc b/core/audio_request_manager.cc
index a4f6128..14d8839 100644
--- a/core/audio_request_manager.cc
+++ b/core/audio_request_manager.cc
@@ -19,6 +19,7 @@
 #include "chre/core/event_loop_manager.h"
 #include "chre/platform/fatal_error.h"
 #include "chre/platform/system_time.h"
+#include "chre/util/system/debug_dump.h"
 
 /*
  * TODO(P1-62e045): Evict pending audio events from the event queue as needed.
@@ -59,52 +60,10 @@
                                           uint64_t bufferDuration,
                                           uint64_t deliveryInterval) {
   uint32_t numSamples;
-  bool success = validateConfigureSourceArguments(
-      handle, enable, bufferDuration, deliveryInterval, &numSamples);
-  if (success) {
-    size_t requestIndex;
-    auto *audioRequest = findAudioRequest(handle, nanoapp->getInstanceId(),
-                                          &requestIndex);
-    Nanoseconds nextEventTimestamp = SystemTime::getMonotonicTime()
-        + Nanoseconds(deliveryInterval);
-    size_t lastNumRequests = mAudioRequestLists[handle].requests.size();
-    if (audioRequest == nullptr) {
-      // The nanoapp is making a new request for audio data.
-      if (enable) {
-        mAudioRequestLists[handle].requests.emplace_back(
-            nanoapp->getInstanceId(), numSamples,
-            Nanoseconds(deliveryInterval), nextEventTimestamp);
-        postAudioSamplingChangeEvent(nanoapp->getInstanceId(), handle,
-                                     mAudioRequestLists[handle].available);
-        scheduleNextAudioDataEvent(handle);
-      } else {
-        LOGW("Nanoapp disabling nonexistent audio request");
-      }
-    } else {
-      // The nanoapp is modifying an existing request for audio.
-      if (enable) {
-        audioRequest->numSamples = numSamples;
-        audioRequest->deliveryInterval = Nanoseconds(deliveryInterval);
-        audioRequest->nextEventTimestamp = nextEventTimestamp;
-      } else {
-        mAudioRequestLists[handle].requests.erase(requestIndex);
-      }
-
-      // Note that if the next request did not change, this call is not strictly
-      // necessary. The expectation is that the platform will gracefully handle
-      // rescheduling the same request.
-      scheduleNextAudioDataEvent(handle);
-    }
-
-    size_t numRequests = mAudioRequestLists[handle].requests.size();
-    if (lastNumRequests == 0 && numRequests > 0) {
-      mPlatformAudio.setHandleEnabled(handle, true);
-    } else if (lastNumRequests > 0 && numRequests == 0) {
-      mPlatformAudio.setHandleEnabled(handle, false);
-    }
-  }
-
-  return success;
+  return validateConfigureSourceArguments(handle, enable, bufferDuration,
+                                          deliveryInterval, &numSamples)
+      && doConfigureSource(nanoapp->getInstanceId(), handle, enable, numSamples,
+                           Nanoseconds(deliveryInterval));
 }
 
 void AudioRequestManager::handleAudioDataEvent(
@@ -148,6 +107,35 @@
   }
 }
 
+bool AudioRequestManager::logStateToBuffer(char *buffer, size_t *bufferPos,
+                                           size_t bufferSize) const {
+  bool success = debugDumpPrint(buffer, bufferPos, bufferSize, "\nAudio:\n");
+  for (size_t i = 0; i < mAudioRequestLists.size(); i++) {
+    uint32_t handle = static_cast<uint32_t>(i);
+    struct chreAudioSource source;
+    mPlatformAudio.getAudioSource(handle, &source);
+    success &= debugDumpPrint(buffer, bufferPos, bufferSize,
+        " handle=%" PRIu32 ", name=\"%s\", sampleRate=%" PRIu32
+        ", buffer(ms)=[%" PRIu64 ",%" PRIu64 "], format=%" PRIu8 "\n",
+        handle, source.name, source.sampleRate,
+        Milliseconds(Nanoseconds(source.minBufferDuration)).getMilliseconds(),
+        Milliseconds(Nanoseconds(source.maxBufferDuration)).getMilliseconds(),
+        source.format);
+
+    for (const auto& request : mAudioRequestLists[i].requests) {
+      for (const auto& instanceId : request.instanceIds) {
+        success &= debugDumpPrint(buffer, bufferPos, bufferSize,
+            "  nanoappId=%" PRIu32 ", numSamples=%" PRIu32
+            ", interval(ms)=%" PRIu64 "\n", instanceId, request.numSamples,
+            Milliseconds(Nanoseconds(request.deliveryInterval))
+                .getMilliseconds());
+      }
+    }
+  }
+
+  return success;
+}
+
 bool AudioRequestManager::validateConfigureSourceArguments(
     uint32_t handle, bool enable, uint64_t bufferDuration,
     uint64_t deliveryInterval, uint32_t *numSamples) {
@@ -178,13 +166,127 @@
   return success;
 }
 
-AudioRequestManager::AudioRequest *AudioRequestManager::findAudioRequest(
-    uint32_t handle, uint32_t instanceId, size_t *index) {
+bool AudioRequestManager::doConfigureSource(
+    uint32_t instanceId, uint32_t handle, bool enable, uint32_t numSamples,
+    Nanoseconds deliveryInterval) {
+  size_t requestIndex;
+  size_t requestInstanceIdIndex;
+  auto *audioRequest = findAudioRequestByInstanceId(
+      handle, instanceId, &requestIndex, &requestInstanceIdIndex);
+
+  AudioRequestList& requestList = mAudioRequestLists[handle];
+  size_t lastNumRequests = requestList.requests.size();
+
+  bool success = false;
+  if (audioRequest == nullptr) {
+    if (enable) {
+      success = createAudioRequest(handle, instanceId, numSamples,
+                                   deliveryInterval);
+    } else {
+      LOGW("Nanoapp disabling nonexistent audio request");
+    }
+  } else {
+    if (audioRequest->instanceIds.size() > 1) {
+      // If there are other clients listening in this configuration, remove
+      // just the instance ID.
+      audioRequest->instanceIds.erase(requestInstanceIdIndex);
+    } else {
+      // If this is the last client listening in this configuration, remove
+      // the entire request.
+      requestList.requests.erase(requestIndex);
+    }
+
+    // If the client is disabling, there is nothing to do, otherwise a request
+    // must be created successfully.
+    success = (!enable || createAudioRequest(handle, instanceId, numSamples,
+                                             deliveryInterval));
+  }
+
+  if (success) {
+    scheduleNextAudioDataEvent(handle);
+    updatePlatformHandleEnabled(handle, lastNumRequests);
+  }
+
+  return success;
+}
+
+void AudioRequestManager::updatePlatformHandleEnabled(
+    uint32_t handle, size_t lastNumRequests) {
+  size_t numRequests = mAudioRequestLists[handle].requests.size();
+  if (lastNumRequests == 0 && numRequests > 0) {
+    mPlatformAudio.setHandleEnabled(handle, true /* enabled */);
+  } else if (lastNumRequests > 0 && numRequests == 0) {
+    mPlatformAudio.setHandleEnabled(handle, false /* enabled */);
+  }
+}
+
+bool AudioRequestManager::createAudioRequest(
+    uint32_t handle, uint32_t instanceId, uint32_t numSamples,
+    Nanoseconds deliveryInterval) {
+  AudioRequestList& requestList = mAudioRequestLists[handle];
+
+  size_t matchingRequestIndex;
+  auto *matchingAudioRequest = findAudioRequestByConfiguration(
+      handle, numSamples, deliveryInterval, &matchingRequestIndex);
+
+  bool success = false;
+  if (matchingAudioRequest != nullptr) {
+    if (!matchingAudioRequest->instanceIds.push_back(instanceId)) {
+      LOG_OOM();
+    } else {
+      success = true;
+    }
+  } else {
+    Nanoseconds timeNow = SystemTime::getMonotonicTime();
+    Nanoseconds nextEventTimestamp = timeNow + deliveryInterval;
+    if (!requestList.requests.emplace_back(numSamples, deliveryInterval,
+                                           nextEventTimestamp)) {
+      LOG_OOM();
+    } else if (!requestList.requests.back().instanceIds.push_back(instanceId)) {
+      requestList.requests.pop_back();
+      LOG_OOM();
+    } else {
+      success = true;
+    }
+  }
+
+  if (success) {
+    postAudioSamplingChangeEvent(instanceId, handle, requestList.available);
+  }
+
+  return success;
+}
+
+AudioRequestManager::AudioRequest *AudioRequestManager::
+    findAudioRequestByInstanceId(
+        uint32_t handle, uint32_t instanceId, size_t *index,
+        size_t *instanceIdIndex) {
   AudioRequest *foundAudioRequest = nullptr;
   auto& requests = mAudioRequestLists[handle].requests;
   for (size_t i = 0; i < requests.size(); i++) {
     auto& audioRequest = requests[i];
-    if (audioRequest.instanceId == instanceId) {
+    size_t foundInstanceIdIndex = audioRequest.instanceIds.find(instanceId);
+    if (foundInstanceIdIndex != audioRequest.instanceIds.size()) {
+      foundAudioRequest = &audioRequest;
+      *index = i;
+      *instanceIdIndex = foundInstanceIdIndex;
+      break;
+    }
+  }
+
+  return foundAudioRequest;
+}
+
+AudioRequestManager::AudioRequest *AudioRequestManager::
+    findAudioRequestByConfiguration(
+        uint32_t handle, uint32_t numSamples, Nanoseconds deliveryInterval,
+        size_t *index) {
+  AudioRequest *foundAudioRequest = nullptr;
+  auto& requests = mAudioRequestLists[handle].requests;
+  for (size_t i = 0; i < requests.size(); i++) {
+    auto& audioRequest = requests[i];
+    if (audioRequest.numSamples == numSamples
+        && audioRequest.deliveryInterval == deliveryInterval) {
       foundAudioRequest = &audioRequest;
       *index = i;
       break;
@@ -216,14 +318,13 @@
   if (handle < mAudioRequestLists.size()) {
     auto& reqList = mAudioRequestLists[handle];
     AudioRequest *nextAudioRequest = reqList.nextAudioRequest;
-
-    if (reqList.nextAudioRequest != nullptr) {
-      postAudioDataEventFatal(event, nextAudioRequest->instanceId);
+    if (nextAudioRequest != nullptr) {
+      postAudioDataEventFatal(event, nextAudioRequest->instanceIds);
       nextAudioRequest->nextEventTimestamp = SystemTime::getMonotonicTime()
           + nextAudioRequest->deliveryInterval;
-      reqList.nextAudioRequest = nullptr;
     } else {
       LOGW("Received audio data event with no pending audio request");
+      mPlatformAudio.releaseAudioDataEvent(event);
     }
 
     scheduleNextAudioDataEvent(handle);
@@ -235,8 +336,11 @@
 void AudioRequestManager::handleAudioAvailabilitySync(uint32_t handle,
                                                       bool available) {
   if (handle < mAudioRequestLists.size()) {
-    mAudioRequestLists[handle].available = available;
-    postAudioSamplingChangeEvents(handle, available);
+    if (mAudioRequestLists[handle].available != available) {
+      mAudioRequestLists[handle].available = available;
+      postAudioSamplingChangeEvents(handle);
+    }
+
     scheduleNextAudioDataEvent(handle);
   } else {
     LOGE("Audio availability handle out of range: %" PRIu32, handle);
@@ -247,6 +351,8 @@
   auto& reqList = mAudioRequestLists[handle];
   AudioRequest *nextRequest = findNextAudioRequest(handle);
 
+  // Clear the next request and it will be reset below if needed.
+  reqList.nextAudioRequest = nullptr;
   if (reqList.available && nextRequest != nullptr) {
     Nanoseconds curTime = SystemTime::getMonotonicTime();
     Nanoseconds eventDelay = Nanoseconds(0);
@@ -261,10 +367,12 @@
   }
 }
 
-void AudioRequestManager::postAudioSamplingChangeEvents(uint32_t handle,
-                                                        bool available) {
-  for (const auto& request : mAudioRequestLists[handle].requests) {
-    postAudioSamplingChangeEvent(request.instanceId, handle, available);
+void AudioRequestManager::postAudioSamplingChangeEvents(uint32_t handle) {
+  const auto& requestList = mAudioRequestLists[handle];
+  for (const auto& request : requestList.requests) {
+    for (const auto& instanceId : request.instanceIds) {
+      postAudioSamplingChangeEvent(instanceId, handle, requestList.available);
+    }
   }
 }
 
@@ -282,16 +390,43 @@
 }
 
 void AudioRequestManager::postAudioDataEventFatal(
-    struct chreAudioDataEvent *event, uint32_t instanceId) {
-  EventLoopManagerSingleton::get()->getEventLoop()
-      .postEvent(CHRE_EVENT_AUDIO_DATA, event,
-                 freeAudioDataEventCallback,
-                 kSystemInstanceId, instanceId);
+    struct chreAudioDataEvent *event,
+    const DynamicVector<uint32_t>& instanceIds) {
+  if (instanceIds.empty()) {
+    LOGW("Received audio data event for no clients");
+    mPlatformAudio.releaseAudioDataEvent(event);
+  } else {
+    for (const auto& instanceId : instanceIds) {
+      EventLoopManagerSingleton::get()->getEventLoop()
+          .postEvent(CHRE_EVENT_AUDIO_DATA, event,
+                     freeAudioDataEventCallback,
+                     kSystemInstanceId, instanceId);
+    }
+
+    mAudioDataEventRefCounts.emplace_back(
+        event, static_cast<uint32_t>(instanceIds.size()));
+  }
 }
 
 void AudioRequestManager::handleFreeAudioDataEvent(
     struct chreAudioDataEvent *audioDataEvent) {
-  mPlatformAudio.releaseAudioDataEvent(audioDataEvent);
+  size_t audioDataEventRefCountIndex =
+      mAudioDataEventRefCounts.find(AudioDataEventRefCount(audioDataEvent));
+  if (audioDataEventRefCountIndex == mAudioDataEventRefCounts.size()) {
+    LOGE("Freeing invalid audio data event");
+  } else {
+    auto& audioDataEventRefCount =
+        mAudioDataEventRefCounts[audioDataEventRefCountIndex];
+    if (audioDataEventRefCount.refCount == 0) {
+      LOGE("Attempting to free an event with zero published events");
+    } else {
+      audioDataEventRefCount.refCount--;
+      if (audioDataEventRefCount.refCount == 0) {
+        mAudioDataEventRefCounts.erase(audioDataEventRefCountIndex);
+        mPlatformAudio.releaseAudioDataEvent(audioDataEvent);
+      }
+    }
+  }
 }
 
 void AudioRequestManager::freeAudioDataEventCallback(uint16_t eventType,
diff --git a/core/event_loop.cc b/core/event_loop.cc
index e6f80fa..027b7a2 100644
--- a/core/event_loop.cc
+++ b/core/event_loop.cc
@@ -152,7 +152,7 @@
     LOGE("App with ID 0x%016" PRIx64 " already exists as instance ID 0x%"
          PRIx32, nanoapp->getAppId(), existingInstanceId);
   } else if (!mNanoapps.prepareForPush()) {
-    LOGE("Failed to allocate space for new nanoapp");
+    LOG_OOM();
   } else {
     nanoapp->setInstanceId(eventLoopManager->getNextInstanceId());
     LOGD("Instance ID %" PRIu32 " assigned to app ID 0x%016" PRIx64,
@@ -433,7 +433,7 @@
                                       const Nanoapp& nanoapp) {
   auto *info = memoryAlloc<chreNanoappInfo>();
   if (info == nullptr) {
-    LOGE("Couldn't alloc app status change event");
+    LOG_OOM();
   } else {
     info->appId      = nanoapp.getAppId();
     info->version    = nanoapp.getAppVersion();
diff --git a/core/event_loop_manager.cc b/core/event_loop_manager.cc
index 182ddc6..0b0d254 100644
--- a/core/event_loop_manager.cc
+++ b/core/event_loop_manager.cc
@@ -41,22 +41,27 @@
     size_t debugStrPos = 0;
     if (!mMemoryManager.logStateToBuffer(debugStr, &debugStrPos,
                                          kDebugStringSize)) {
-      LOGE("Memory manager debug dump failed.");
+      LOG_OOM();
     } else if (!mEventLoop.logStateToBuffer(debugStr, &debugStrPos,
                                             kDebugStringSize)) {
-      LOGE("Event loop debug dump failed.");
+      LOG_OOM();
     } else if (!mSensorRequestManager.logStateToBuffer(debugStr, &debugStrPos,
                                                        kDebugStringSize)) {
-      LOGE("Sensor request manager debug dump failed.");
+      LOG_OOM();
     } else if (!mGnssManager.logStateToBuffer(debugStr, &debugStrPos,
                                               kDebugStringSize)) {
-      LOGE("GNSS manager debug dump failed.");
+      LOG_OOM();
     } else if (!mWifiRequestManager.logStateToBuffer(debugStr, &debugStrPos,
                                                      kDebugStringSize)) {
-      LOGE("Wifi request manager debug dump failed.");
+      LOG_OOM();
     } else if (!mWwanRequestManager.logStateToBuffer(debugStr, &debugStrPos,
                                                      kDebugStringSize)) {
-      LOGE("WWAN request manager debug dump failed.");
+      LOG_OOM();
+#ifdef CHRE_AUDIO_SUPPORT_ENABLED
+    } else if (!mAudioRequestManager.logStateToBuffer(debugStr, &debugStrPos,
+                                                      kDebugStringSize)) {
+      LOG_OOM();
+#endif  // CHRE_AUDIO_SUPPORT_ENABLED
     }
     LOGD("Debug dump used %zu bytes of log buffer", debugStrPos);
   }
diff --git a/core/gnss_manager.cc b/core/gnss_manager.cc
index eec4e8b..0939289 100644
--- a/core/gnss_manager.cc
+++ b/core/gnss_manager.cc
@@ -91,7 +91,7 @@
 
   auto *cbState = memoryAlloc<CallbackState>();
   if (cbState == nullptr) {
-    LOGE("Failed to allocate callback state for GNSS session state change");
+    LOG_OOM();
   } else {
     cbState->enabled = enabled;
     cbState->errorCode = errorCode;
@@ -280,7 +280,7 @@
         request.minInterval = minInterval;
         success = mRequests.push_back(request);
         if (!success) {
-          LOGE("Failed to add nanoapp to the list of GNSS session nanoapps");
+          LOG_OOM();
         } else {
           nanoapp->registerForBroadcastEvent(mReportEventType);
         }
@@ -308,7 +308,7 @@
   if (!success || updateRequests(enable, minInterval, instanceId)) {
     chreAsyncResult *event = memoryAlloc<chreAsyncResult>();
     if (event == nullptr) {
-      LOGE("Failed to allocate GNSS session async result event");
+      LOG_OOM();
     } else {
       event->requestType = enable ? mStartRequestType : mStopRequestType;
       event->success = success;
diff --git a/core/host_comms_manager.cc b/core/host_comms_manager.cc
index 399db9f..1019628 100644
--- a/core/host_comms_manager.cc
+++ b/core/host_comms_manager.cc
@@ -46,7 +46,7 @@
     MessageToHost *msgToHost = mMessagePool.allocate();
 
     if (msgToHost == nullptr) {
-      LOGE("Couldn't allocate message to host");
+      LOG_OOM();
     } else {
       msgToHost->appId = nanoapp->getAppId();
       msgToHost->message.wrap(static_cast<uint8_t *>(messageData), messageSize);
@@ -75,7 +75,7 @@
 
   MessageFromHost *msgFromHost = mMessagePool.allocate();
   if (msgFromHost == nullptr) {
-    LOGE("Couldn't allocate message from host");
+    LOG_OOM();
   } else if (!msgFromHost->message.copy_array(
       static_cast<const uint8_t *>(messageData), messageSize)) {
     LOGE("Couldn't allocate %" PRIu32 " bytes for message data from host "
diff --git a/core/include/chre/core/audio_request_manager.h b/core/include/chre/core/audio_request_manager.h
index 4e7a06e..11caf20 100644
--- a/core/include/chre/core/audio_request_manager.h
+++ b/core/include/chre/core/audio_request_manager.h
@@ -97,6 +97,19 @@
   void handleAudioAvailability(uint32_t handle, bool available);
 
   /**
+   * Prints state in a string buffer. Must only be called from the context of
+   * the main CHRE thread.
+   *
+   * @param buffer Pointer to the start of the buffer.
+   * @param bufferPos Pointer to buffer position to start the print (in-out).
+   * @param size Size of the buffer in bytes.
+   *
+   * @return true if entire log printed, false if overflow or error.
+   */
+  bool logStateToBuffer(char *buffer, size_t *bufferPos,
+                        size_t bufferSize) const;
+
+  /**
    * A convenience function to convert sample count and sample rate into a time
    * duration. It is illegal to call this function with a rate of zero.
    *
@@ -134,20 +147,27 @@
         / kOneSecondInNanoseconds);
   }
 
+  /**
+   * @return the instance of platform audio to allow platform-specific
+   * funtionality to call it. Example: handling host awake events.
+   */
+  PlatformAudio& getPlatformAudio() {
+    return mPlatformAudio;
+  }
+
  private:
   /**
    * One instance of an audio request from a nanoapp.
    */
   struct AudioRequest {
-    AudioRequest(uint32_t instanceId, uint32_t numSamples,
-                 Nanoseconds deliveryInterval, Nanoseconds nextEventTimestamp)
-        : instanceId(instanceId),
-          numSamples(numSamples),
+    AudioRequest(uint32_t numSamples, Nanoseconds deliveryInterval,
+                 Nanoseconds nextEventTimestamp)
+        : numSamples(numSamples),
           deliveryInterval(deliveryInterval),
           nextEventTimestamp(nextEventTimestamp) {}
 
-    //! The nanoapp instance ID that owns this request.
-    uint32_t instanceId;
+    //! The nanoapp instance IDs that own this request.
+    DynamicVector<uint32_t> instanceIds;
 
     //! The number of samples requested for this request.
     uint32_t numSamples;
@@ -175,6 +195,49 @@
     DynamicVector<AudioRequest> requests;
   };
 
+  /**
+   * Keep track of the number of times an audio data event is published to a
+   * nanoapp.
+   *
+   * TODO: Add support for multicast events to the core event loop to avoid this
+   * kind of logic from appearing in the AudioRequestManager.
+   */
+  struct AudioDataEventRefCount {
+    /**
+     * Constructs an AudioDataEventRefCount object with an uninitialized
+     * refCount to allow searching in a list using the equality comparison
+     * below.
+     *
+     * @param event The event that this object tracks.
+     */
+    explicit AudioDataEventRefCount(struct chreAudioDataEvent *event)
+        : event(event) {}
+
+    AudioDataEventRefCount(struct chreAudioDataEvent *event, uint32_t refCount)
+        : event(event), refCount(refCount) {}
+
+    /**
+     * @param audioDataEventRefCount The other object to perform an equality
+     *        comparison against.
+     * @return true if the supplied AudioDataEventRefCount is tracking the same
+     *         published event as current object.
+     */
+    bool operator==(const AudioDataEventRefCount& audioDataEventRefCount) {
+      return (event == audioDataEventRefCount.event);
+    }
+
+    //! The event that is ref counted here.
+    struct chreAudioDataEvent *event;
+
+    //! The number of outstanding published events.
+    uint32_t refCount;
+  };
+
+  //! Maps published audio data events to a refcount that is used to determine
+  //! when to let the platform audio implementation know that this audio data
+  //! event no longer needed.
+  DynamicVector<AudioDataEventRefCount> mAudioDataEventRefCounts;
+
   //! Maps audio handles to requests from multiple nanoapps for an audio source.
   //! The array index implies the audio handle which is being managed.
   DynamicVector<AudioRequestList> mAudioRequestLists;
@@ -198,6 +261,43 @@
       uint64_t bufferDuration, uint64_t deliveryInterval, uint32_t *numSamples);
 
   /**
+   * Performs the configuration of an audio source with validated arguments. See
+   * configureSource for more details.
+   *
+   * @param instanceId The instanceId of the nanoapp making the request.
+   * @param handle The audio source that is being configured.
+   * @param enable true if enabling the source, false if disabling.
+   * @param numSamples The number of samples being requested.
+   * @param deliveryInterval When to deliver the samples.
+   * @return true if successful, false otherwise.
+   */
+  bool doConfigureSource(uint32_t instanceId, uint32_t handle, bool enable,
+                         uint32_t numSamples, Nanoseconds deliveryInterval);
+
+  /**
+   * Notify the platform if a given handle has been enabled or disabled.
+   *
+   * @param handle The handle that may have changed enabled state.
+   * @param lastNumRequests The last number of requests for a handle before it
+   *        was reconfigured.
+   */
+  void updatePlatformHandleEnabled(uint32_t handle, size_t lastNumRequests);
+
+  /**
+   * Creates an audio request given a configuration and instance ID for a given
+   * handle.
+   *
+   * @param handle The handle to create a request for.
+   * @param instanceId The instance ID that will own this request.
+   * @param numSamples The number of samples requested.
+   * @param deliveryInterval When to deliver the samples.
+   * @return true if successful, false otherwise.
+   */
+  bool createAudioRequest(
+      uint32_t handle, uint32_t instanceId, uint32_t numSamples,
+      Nanoseconds deliveryInterval);
+
+  /**
    * Finds an audio request for a given audio handle and nanoapp instance ID. If
    * no existing request is available, nullptr is returned.
    *
@@ -206,11 +306,30 @@
    * @param instanceId The nanoapp instance ID that owns the existing request
    *     for this handle.
    * @param index Populated with the index of the request if it was found.
+   * @param instanceIdIndex Populated with the index of the instance ID within
+   *        the returned audio request if it was found.
    * @return The AudioRequest for this handle and instanceId, nullptr if not
    *     found.
    */
-  AudioRequest *findAudioRequest(uint32_t handle, uint32_t instanceId,
-                                 size_t *index);
+  AudioRequest *findAudioRequestByInstanceId(
+      uint32_t handle, uint32_t instanceId, size_t *index,
+      size_t *instanceIdIndex);
+
+  /**
+   * Finds an audio request for a given handle and configuration. If no existing
+   * request is available, nullptr is returned.
+   *
+   * @param handle The audio handle to query for. This must be guaranteed by the
+   *        caller to be less than the size of the mAudioRequestLists member.
+   * @param numSamples The number of samples to match for.
+   * @param deliveryInterval The delivery interval to match for.
+   * @param index Populated with the index of the request if it was found.
+   * @return The AudioRequest for this handle and configuration, nullptr if not
+   *     found.
+   */
+  AudioRequest *findAudioRequestByConfiguration(
+      uint32_t handle, uint32_t numSamples, Nanoseconds deliveryInterval,
+      size_t *index);
 
   /**
    * Finds the next expiring request for audio data for a given handle.
@@ -246,13 +365,11 @@
 
   /**
    * Posts CHRE_EVENT_AUDIO_SAMPLING_CHANGE events to all nanoapps subscribed to
-   * the supplied handle.
+   * the supplied handle with the current availability of the source.
    *
    * @param handle The handle for the audio source that is changing.
-   * @param available true if audio is available for the supplied handle, false
-   *        otherwise.
    */
-  void postAudioSamplingChangeEvents(uint32_t handle, bool available);
+  void postAudioSamplingChangeEvents(uint32_t handle);
 
   /**
    * Posts a CHRE_EVENT_AUDIO_SAMPLING_CHANGE event to the specified nanoapp.
@@ -272,10 +389,10 @@
    * requirements of the API without posting an event.
    *
    * @param audioDataEvent The audio data event to send to a nanoapp.
-   * @param instanceId The nanoapp instance ID to direct the event to.
+   * @param instanceIds The list of nanoapp instance IDs to direct the event to.
    */
   void postAudioDataEventFatal(struct chreAudioDataEvent *event,
-                               uint32_t instanceId);
+                               const DynamicVector<uint32_t>& instanceIds);
 
   /**
    * Invoked by the freeAudioDataEventCallback to decrement the reference count
diff --git a/core/include/chre/core/event_loop_manager.h b/core/include/chre/core/event_loop_manager.h
index c1991ff..50d9dec 100644
--- a/core/include/chre/core/event_loop_manager.h
+++ b/core/include/chre/core/event_loop_manager.h
@@ -59,6 +59,7 @@
   WifiHandleFailedRanging,
   WifiHandleRangingEvent,
   AudioAvailabilityChange,
+  AudioHandleHostAwake,
 };
 
 //! The function signature of a system callback mirrors the CHRE event free
diff --git a/core/include/chre/core/sensor_type.h b/core/include/chre/core/sensor_type.h
index e542794..047f5ad 100644
--- a/core/include/chre/core/sensor_type.h
+++ b/core/include/chre/core/sensor_type.h
@@ -58,6 +58,7 @@
   VendorType0,
   VendorType1,
   VendorType2,
+  VendorType3,
 
   // Note to future developers: don't forget to update the implementation of
   // 1) getSensorTypeName,
@@ -85,6 +86,7 @@
   Vendor0,
   Vendor1,
   Vendor2,
+  Vendor3,
   Unknown,
 };
 
diff --git a/core/sensor_type.cc b/core/sensor_type.cc
index 4bac1eb..d1a486f 100644
--- a/core/sensor_type.cc
+++ b/core/sensor_type.cc
@@ -62,6 +62,8 @@
       return "Vendor Type 1";
     case SensorType::VendorType2:
       return "Vendor Type 2";
+    case SensorType::VendorType3:
+      return "Vendor Type 3";
     default:
       CHRE_ASSERT(false);
       return "";
@@ -116,6 +118,8 @@
       return SensorType::VendorType1;
     case (CHRE_SENSOR_TYPE_VENDOR_START + 2):
       return SensorType::VendorType2;
+    case (CHRE_SENSOR_TYPE_VENDOR_START + 3):
+      return SensorType::VendorType3;
     default:
       return SensorType::Unknown;
   }
@@ -157,6 +161,8 @@
       return (CHRE_SENSOR_TYPE_VENDOR_START + 1);
     case SensorType::VendorType2:
       return (CHRE_SENSOR_TYPE_VENDOR_START + 2);
+    case SensorType::VendorType3:
+      return (CHRE_SENSOR_TYPE_VENDOR_START + 3);
     default:
       // Update implementation to prevent undefined or SensorType::Unknown from
       // being used.
@@ -201,12 +207,16 @@
       return SensorSampleType::Occurrence;
     case SensorType::Proximity:
       return SensorSampleType::Byte;
+#ifdef CHREX_SENSOR_SUPPORT
     case SensorType::VendorType0:
       return SensorSampleType::Vendor0;
     case SensorType::VendorType1:
       return SensorSampleType::Vendor1;
     case SensorType::VendorType2:
       return SensorSampleType::Vendor2;
+    case SensorType::VendorType3:
+      return SensorSampleType::Vendor3;
+#endif  // CHREX_SENSOR_SUPPORT
     case SensorType::Unknown:
       return SensorSampleType::Unknown;
     default:
diff --git a/core/wifi_request_manager.cc b/core/wifi_request_manager.cc
index 0df3a2a..651ea80 100644
--- a/core/wifi_request_manager.cc
+++ b/core/wifi_request_manager.cc
@@ -109,7 +109,7 @@
       success = req.targetList.copy_array(params->targetList,
                                           params->targetListLen);
       if (!success) {
-        LOGE("Couldn't make copy of target list");
+        LOG_OOM();
         mPendingRangingRequests.pop_back();
       }
     }
@@ -168,7 +168,7 @@
 
   auto *cbState = memoryAlloc<CallbackState>();
   if (cbState == nullptr) {
-    LOGE("Failed to allocate callback state for scan monitor state change");
+    LOG_OOM();
   } else {
     cbState->enabled = enabled;
     cbState->errorCode = errorCode;
@@ -194,7 +194,7 @@
 
   auto *cbState = memoryAlloc<CallbackState>();
   if (cbState == nullptr) {
-    LOGE("Failed to allocate callback state for wifi scan response");
+    LOG_OOM();
   } else {
     cbState->pending = pending;
     cbState->errorCode = errorCode;
@@ -350,8 +350,7 @@
         // nanoapps.
         success = mScanMonitorNanoapps.push_back(instanceId);
         if (!success) {
-          LOGE("Failed to add nanoapp to the list of scan monitoring "
-               "nanoapps");
+          LOG_OOM();
         } else {
           nanoapp->registerForBroadcastEvent(CHRE_EVENT_WIFI_SCAN_RESULT);
         }
@@ -380,7 +379,7 @@
   if (!success || updateNanoappScanMonitoringList(enable, nanoappInstanceId)) {
     chreAsyncResult *event = memoryAlloc<chreAsyncResult>();
     if (event == nullptr) {
-      LOGE("Failed to allocate wifi scan monitor async result event");
+      LOG_OOM();
     } else {
       event->requestType = CHRE_WIFI_REQUEST_TYPE_CONFIGURE_SCAN_MONITOR;
       event->success = success;
@@ -416,7 +415,7 @@
   bool eventPosted = false;
   chreAsyncResult *event = memoryAlloc<chreAsyncResult>();
   if (event == nullptr) {
-    LOGE("Failed to allocate wifi scan request async result event");
+    LOG_OOM();
   } else {
     event->requestType = CHRE_WIFI_REQUEST_TYPE_REQUEST_SCAN;
     event->success = success;
@@ -553,7 +552,7 @@
   } else {
     auto *event = memoryAlloc<struct chreAsyncResult>();
     if (event == nullptr) {
-      LOGE("Couldn't allocate ranging async result");
+      LOG_OOM();
     } else {
       const PendingRangingRequest& req = mPendingRangingRequests.front();
 
diff --git a/host/common/audio_stress_test/audio_stress_test.cc b/host/common/audio_stress_test/audio_stress_test.cc
new file mode 100644
index 0000000..fbef0c8
--- /dev/null
+++ b/host/common/audio_stress_test/audio_stress_test.cc
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+
+#include "chre/util/nanoapp/app_id.h"
+#include "chre_host/host_protocol_host.h"
+#include "chre_host/log.h"
+#include "chre_host/socket_client.h"
+
+#include <inttypes.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+
+#include <fstream>
+#include <thread>
+
+#include <cutils/sockets.h>
+#include <utils/StrongPointer.h>
+
+/**
+ * @file
+ * A test utility that loads the audio stress test nanoapp and quits.
+ */
+
+using android::sp;
+using android::chre::getStringFromByteVector;
+using android::chre::FragmentedLoadTransaction;
+using android::chre::HostProtocolHost;
+using android::chre::IChreMessageHandlers;
+using android::chre::SocketClient;
+using flatbuffers::FlatBufferBuilder;
+
+// Aliased for consistency with the way these symbols are referenced in
+// CHRE-side code
+namespace fbs = ::chre::fbs;
+
+namespace {
+
+class SocketCallbacks : public SocketClient::ICallbacks,
+                        public IChreMessageHandlers {
+ public:
+  void onMessageReceived(const void *data, size_t length) override {
+    if (!HostProtocolHost::decodeMessageFromChre(data, length, *this)) {
+      LOGE("Failed to decode message");
+    }
+  }
+
+  void onConnected() override {
+    LOGI("Socket (re)connected");
+  }
+
+  void onConnectionAborted() override {
+    LOGI("Socket (re)connection aborted");
+  }
+
+  void onDisconnected() override {
+    LOGI("Socket disconnected");
+  }
+
+  void handleLoadNanoappResponse(const fbs::LoadNanoappResponseT& response)
+      override {
+    LOGI("Got load nanoapp response, transaction ID 0x%" PRIx32 " result %d",
+         response.transaction_id, response.success);
+  }
+};
+
+void sendLoadNanoappRequest(SocketClient& client, const char *filename,
+                            uint64_t appId, uint32_t appVersion) {
+  std::ifstream file(filename, std::ios::binary | std::ios::ate);
+  if (!file) {
+    LOGE("Couldn't open file '%s': %s", filename, strerror(errno));
+    return;
+  }
+  ssize_t size = file.tellg();
+  file.seekg(0, std::ios::beg);
+
+  std::vector<uint8_t> buffer(size);
+  if (!file.read(reinterpret_cast<char *>(buffer.data()), size)) {
+    LOGE("Couldn't read from file: %s", strerror(errno));
+    return;
+  }
+
+  // Perform loading with 1 fragment for simplicity
+  FlatBufferBuilder builder(size + 128);
+  FragmentedLoadTransaction transaction = FragmentedLoadTransaction(
+      1 /* transactionId */, appId, appVersion,
+      0x01000000 /* targetApiVersion */, buffer,
+      buffer.size() /* fragmentSize */);
+  HostProtocolHost::encodeFragmentedLoadNanoappRequest(
+      builder, transaction.getNextRequest());
+
+  LOGI("Sending load nanoapp request (%" PRIu32 " bytes total w/%zu bytes of "
+       "payload)", builder.GetSize(), buffer.size());
+  if (!client.sendMessage(builder.GetBufferPointer(), builder.GetSize())) {
+    LOGE("Failed to send message");
+  }
+}
+
+}  // anonymous namespace
+
+int main() {
+  SocketClient client;
+  sp<SocketCallbacks> callbacks = new SocketCallbacks();
+
+  if (!client.connect("chre", callbacks)) {
+    LOGE("Couldn't connect to socket");
+  } else {
+    sendLoadNanoappRequest(client, "/data/audio_stress_test.so",
+                           chre::kAudioStressTestAppId,
+                           1 /* appVersion */);
+  }
+
+  return 0;
+}
diff --git a/host/msm/daemon/chre_daemon.cc b/host/msm/daemon/chre_daemon.cc
index 4194659..f2f2921 100644
--- a/host/msm/daemon/chre_daemon.cc
+++ b/host/msm/daemon/chre_daemon.cc
@@ -392,17 +392,19 @@
 }
 
 /**
- * Unloads the LPMA use case via the SoundTrigger HAL HIDL service.
+ * Unloads the LPMA use case via the SoundTrigger HAL HIDL service. This
+ * function does not indicate success/failure as it is expected that even in the
+ * event of a failure to unload, the use case will be unloaded. As long as the
+ * sound trigger HAL received the request we can be assured that the use case
+ * will be unloaded (even if it means reseting the codec or otherwise).
  *
  * @param lpmaHandle A handle that was previously produced by the setLpmaEnabled
  *        function. This is the handle that is unloaded from the ST HAL to
  *        disable LPMA.
- * @return true if LPMA was disabled successfully, false otherwise.
  */
-static bool unloadLpma(SoundModelHandle lpmaHandle) {
+static void unloadLpma(SoundModelHandle lpmaHandle) {
   LOGD("Unloading LPMA");
 
-  bool unloaded = false;
   sp<ISoundTriggerHw> stHal = ISoundTriggerHw::getService();
   if (stHal == nullptr) {
     LOGE("Failed to get ST HAL service for LPMA unload");
@@ -412,7 +414,6 @@
     if (hidlResult.isOk()) {
       if (hidlResult == 0) {
         LOGI("Unloaded LPMA");
-        unloaded = true;
       } else {
         LOGE("Failed to unload LPMA with %" PRId32, int32_t(hidlResult));
       }
@@ -421,8 +422,6 @@
            hidlResult.description().c_str());
     }
   }
-
-  return unloaded;
 }
 
 static void *chreLpmaEnableThread(void *arg) {
@@ -445,8 +444,14 @@
       releaseWakeLock();  // Allow the system to suspend while waiting.
       pthread_cond_wait(&state->cond, &state->mutex);
       acquireWakeLock();  // Ensure the system stays up while retrying.
-    } else if ((state->targetLpmaEnabled && loadLpma(&lpmaHandle))
-               || (!state->targetLpmaEnabled && unloadLpma(lpmaHandle))) {
+    } else if (state->targetLpmaEnabled && loadLpma(&lpmaHandle)) {
+      state->currentLpmaEnabled = state->targetLpmaEnabled;
+    } else if (!state->targetLpmaEnabled) {
+      // Regardless of whether the use case fails to unload, set the
+      // currentLpmaEnabled to the targetLpmaEnabled. This will allow the next
+      // enable request to proceed. After a failure to unload occurs, the
+      // supplied handle is invalid and should not be unloaded again.
+      unloadLpma(lpmaHandle);
       state->currentLpmaEnabled = state->targetLpmaEnabled;
     } else {
       // Unlock while delaying to avoid blocking the client thread. No shared
diff --git a/platform/android/platform_audio.cc b/platform/android/platform_audio.cc
index bb97146..8844c1c 100644
--- a/platform/android/platform_audio.cc
+++ b/platform/android/platform_audio.cc
@@ -159,7 +159,7 @@
 }
 
 bool PlatformAudio::getAudioSource(uint32_t handle,
-                                   chreAudioSource *audioSource) {
+                                   chreAudioSource *audioSource) const {
   bool success = false;
   if (handle == 0) {
     audioSource->name = "Default Android Audio Input";
diff --git a/platform/include/chre/platform/platform_audio.h b/platform/include/chre/platform/platform_audio.h
index 8e755ff..0757ffd 100644
--- a/platform/include/chre/platform/platform_audio.h
+++ b/platform/include/chre/platform/platform_audio.h
@@ -126,7 +126,7 @@
    * @param audioSource the chreAudioSource to populate with details of the
    *     audio source. This pointer must never be null.
    */
-  bool getAudioSource(uint32_t handle, chreAudioSource *audioSource);
+  bool getAudioSource(uint32_t handle, chreAudioSource *audioSource) const;
 };
 
 }  // namespace chre
diff --git a/platform/linux/platform_audio.cc b/platform/linux/platform_audio.cc
index c146edc..51382a7 100644
--- a/platform/linux/platform_audio.cc
+++ b/platform/linux/platform_audio.cc
@@ -108,7 +108,7 @@
 }
 
 bool PlatformAudio::getAudioSource(uint32_t handle,
-                                   chreAudioSource *audioSource) {
+                                   chreAudioSource *audioSource) const {
   bool success = (handle < gAudioSources.size());
   if (success) {
     const auto& source = gAudioSources[handle];
diff --git a/platform/platform.mk b/platform/platform.mk
index 6be1352..6a3ed77 100644
--- a/platform/platform.mk
+++ b/platform/platform.mk
@@ -70,8 +70,13 @@
 SLPI_SEE_CFLAGS += -I$(SLPI_PREFIX)/ssc/inc/pb
 
 SLPI_SEE_CFLAGS += -DCHRE_SLPI_SEE
+
+# Needed to define __SIZEOF_ATTR_THREAD in sns_osa_thread.h, included in
+# sns_memmgr.h.
 SLPI_SEE_CFLAGS += -DSSC_TARGET_HEXAGON
-SLPI_SEE_CFLAGS += -DSNS_ISLAND_INCLUDE_QCM
+
+# Defined in slpi_proc/ssc/build/ssc.scons
+SLPI_SEE_CFLAGS += -DPB_FIELD_16BIT
 
 # SLPI-specific Source Files ###################################################
 
@@ -122,30 +127,24 @@
 
 # SLPI/SEE-specific Source Files ###############################################
 
-# TODO(P2-c001d8): (b/68860346) replace pb-related source files with exported
-# symbols.
-SLPI_SEE_SRCS += $(SLPI_PREFIX)/ssc/utils/nanopb/src/pb_common.c
-SLPI_SEE_SRCS += $(SLPI_PREFIX)/ssc/utils/nanopb/src/pb_decode.c
-SLPI_SEE_SRCS += $(SLPI_PREFIX)/ssc/utils/nanopb/src/pb_encode.c
-
 SLPI_SEE_SRCS += platform/slpi/see/island_vote_client.cc
 SLPI_SEE_SRCS += platform/slpi/see/platform_sensor.cc
 SLPI_SEE_SRCS += platform/slpi/see/power_control_manager.cc
+SLPI_SEE_SRCS += platform/slpi/see/see_cal_helper.cc
 SLPI_SEE_SRCS += platform/slpi/see/see_helper.cc
 
 SLPI_SEE_SRCS += $(SLPI_PREFIX)/ssc/framework/cm/pb/sns_client.pb.c
-SLPI_SEE_QSK_SRCS += $(SLPI_PREFIX)/ssc/framework/qcm/pb/sns_client_qsocket.pb.c
 SLPI_SEE_SRCS += $(SLPI_PREFIX)/ssc/framework/suid_sensor/pb/sns_suid.pb.c
 SLPI_SEE_SRCS += $(SLPI_PREFIX)/ssc/sensors/pb/sns_cal.pb.c
 SLPI_SEE_SRCS += $(SLPI_PREFIX)/ssc/sensors/pb/sns_physical_sensor_test.pb.c
 SLPI_SEE_SRCS += $(SLPI_PREFIX)/ssc/sensors/pb/sns_proximity.pb.c
 SLPI_SEE_SRCS += $(SLPI_PREFIX)/ssc/sensors/pb/sns_remote_proc_state.pb.c
+SLPI_SEE_SRCS += $(SLPI_PREFIX)/ssc/sensors/pb/sns_resampler.pb.c
 SLPI_SEE_SRCS += $(SLPI_PREFIX)/ssc/sensors/pb/sns_std.pb.c
 SLPI_SEE_SRCS += $(SLPI_PREFIX)/ssc/sensors/pb/sns_std_sensor.pb.c
 SLPI_SEE_SRCS += $(SLPI_PREFIX)/ssc/sensors/pb/sns_std_type.pb.c
 
-SLPI_SEE_SRCS += $(SLPI_PREFIX)/chre/chre/src/system/chre/platform/slpi/sns_osa.c
-SLPI_SEE_QSK_SRCS += $(SLPI_PREFIX)/chre/chre/src/system/chre/platform/slpi/sns_qsocket_client.c
+SLPI_SEE_QSK_SRCS += $(SLPI_PREFIX)/chre/chre/src/system/chre/platform/slpi/sns_qmi_client_alt.c
 SLPI_SEE_QMI_SRCS += $(SLPI_PREFIX)/chre/chre/src/system/chre/platform/slpi/sns_qmi_client.c
 
 # Simulator-specific Compiler Flags ############################################
diff --git a/platform/shared/chre_api_sensor.cc b/platform/shared/chre_api_sensor.cc
index 13b8e71..b186dce 100644
--- a/platform/shared/chre_api_sensor.cc
+++ b/platform/shared/chre_api_sensor.cc
@@ -30,7 +30,35 @@
 using chre::getSensorModeFromEnum;
 using chre::getSensorTypeFromUnsignedInt;
 
+#if defined(CHRE_SLPI_SEE) && defined(CHRE_SLPI_UIMG_ENABLED)
+namespace {
+constexpr uint8_t kBigImageAccelSensorType =
+    (CHRE_SENSOR_TYPE_VENDOR_START + 3);
+} //  anonymous namespace
+#endif  // defined(CHRE_SLPI_SEE) && defined(CHRE_SLPI_UIMG_ENABLED)
+
 DLL_EXPORT bool chreSensorFindDefault(uint8_t sensorType, uint32_t *handle) {
+#if defined(CHRE_SLPI_SEE) && defined(CHRE_SLPI_UIMG_ENABLED)
+  // HACK: as SEE does not support software batching in uimg via QCM/uQSockets,
+  // reroute requests for accelerometer from a big image nanoapp to a separate
+  // sensor type internally. Accel is the only always-on sensor used today by
+  // big image nanoapps, and this change allows these requests to transparently
+  // go to a separate sensor implementation that supports uimg batching via
+  // CM/QMI.
+  // TODO(P2-5673a9): work with QC to determine a better long-term solution
+  chre::Nanoapp *nanoapp = EventLoopManager::validateChreApiCall(__func__);
+  if (!nanoapp->isUimgApp()) {
+    if (sensorType == CHRE_SENSOR_TYPE_ACCELEROMETER) {
+      sensorType = kBigImageAccelSensorType;
+    } else if (sensorType == kBigImageAccelSensorType) {
+      // Since we have an accompanying hack in PlatformNanoapp::handleEvent(),
+      // hide the vendor sensor type from big image nanoapps as we're unable to
+      // deliver events for it
+      return false;
+    }
+  }
+#endif  // defined(CHRE_SLPI_SEE) && defined(CHRE_SLPI_UIMG_ENABLED)
+
   SensorType validatedSensorType = getSensorTypeFromUnsignedInt(sensorType);
   return (validatedSensorType != SensorType::Unknown
       && EventLoopManagerSingleton::get()->getSensorRequestManager()
@@ -47,6 +75,15 @@
   if (info != nullptr) {
     success = EventLoopManagerSingleton::get()->getSensorRequestManager().
         getSensorInfo(sensorHandle, *nanoapp, info);
+
+    // The distinction between big/uimg accel should be abstracted away from
+    // big image nanoapps, so overwrite any platform implementation here.
+#if defined(CHRE_SLPI_SEE) && defined(CHRE_SLPI_UIMG_ENABLED)
+    if (!nanoapp->isUimgApp() && info->sensorType == kBigImageAccelSensorType) {
+      info->sensorType = CHRE_SENSOR_TYPE_ACCELEROMETER;
+    }
+#endif  // defined(CHRE_SLPI_SEE) && defined(CHRE_SLPI_UIMG_ENABLED)
+
   }
   return success;
 }
diff --git a/platform/slpi/include/chre/platform/slpi/see/island_vote_client.h b/platform/slpi/include/chre/platform/slpi/see/island_vote_client.h
index 283cef8..f112e99 100644
--- a/platform/slpi/include/chre/platform/slpi/see/island_vote_client.h
+++ b/platform/slpi/include/chre/platform/slpi/see/island_vote_client.h
@@ -67,6 +67,10 @@
   void decrementBigImageRefCount();
 
  private:
+  //! The maximum allowed duration to be voted into big image by
+  //! incrementBigImageRefCount before a FATAL_ERROR is triggered.
+  static constexpr Seconds kSeeMaxBigImageDuration = Seconds(300);
+
   //! Last big image request made through voteBigImage().
   bool mLastBigImageRequest = false;
 
@@ -93,6 +97,16 @@
    * @return true if the vote returned success.
    */
   bool voteSnsPowerMode(bool bigImage);
+
+  /**
+   * Check how long the system has been voted into big image due to
+   * incrementBigImageRefCount. If longer than kSeeMaxBigImageDuration, trigger
+   * a crash.
+   *
+   * @return the duration in milliseconds since the system has been voted into
+   *         big image due to incrementBigImageRefCount.
+   */
+  uint64_t checkBigImageDuration() const;
 #endif  // CHRE_SLPI_UIMG_ENABLED
 };
 
diff --git a/platform/slpi/include/chre/platform/slpi/see/see_cal_helper.h b/platform/slpi/include/chre/platform/slpi/see/see_cal_helper.h
new file mode 100644
index 0000000..9015649
--- /dev/null
+++ b/platform/slpi/include/chre/platform/slpi/see/see_cal_helper.h
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2018 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 CHRE_PLATFORM_SLPI_SEE_SEE_CAL_HELPER_H_
+#define CHRE_PLATFORM_SLPI_SEE_SEE_CAL_HELPER_H_
+
+extern "C" {
+
+#include "sns_client.h"
+
+}  // extern "C"
+
+#include "sns_suid.pb.h"
+
+#include "chre/core/sensor_type.h"
+#include "chre/platform/mutex.h"
+#include "chre/util/non_copyable.h"
+#include "chre/util/optional.h"
+
+namespace chre {
+
+class SeeHelper;
+
+/**
+ * Helps manage and apply sensor calibration data provided through SEE.
+ */
+class SeeCalHelper : public NonCopyable {
+ public:
+  /**
+   * Applies cached calibration (if any) to raw 3-axis sensor readings.
+   * Thread-safe.
+   *
+   * @param sensorType Type of sensor that generated the sample
+   * @param input 3-axis raw sample {x,y,z}
+   * @param output Location to store sample with calibration applied (can be
+   *               same as input)
+   */
+  void applyCalibration(SensorType sensorType, const float input[3],
+                        float output[3]) const;
+
+  /**
+   * Get the cached SUID of a calibration sensor that corresponds to the
+   * specified sensorType.
+   *
+   * @param sensorType The sensor type of the calibration sensor.
+   *
+   * @return A constant reference to the calibration sensor's SUID if present.
+   *         Otherwise, a reference to sns_suid_sensor_init_zero is returned.
+   */
+  const sns_std_suid& getCalSuidFromSensorType(SensorType sensorType) const;
+
+  /**
+   * Uses the supplied SeeHelper instance to register for updates to all
+   * supported SEE calibration sensors. The SeeHelper instance should then pass
+   * decoded calibration data to updateCalibration() and use applyCalibration()
+   * as needed.
+   *
+   * @param seeHelper SeeHelper instance to use when looking up calibration
+   *                  sensor SUIDs and registering for their output
+   *
+   * @return true if all SEE calibration sensors were successfully registered
+   */
+  bool registerForCalibrationUpdates(SeeHelper& seeHelper);
+
+  /**
+   * Updates the cached calibration data used in subsequent calls to
+   * applyCalibration. Thread-safe.
+   *
+   * @param suid Sensor UID associated with the incoming calibration data
+   * @param hasBias true if bias was decoded from the proto
+   * @param bias 3-axis bias; only valid if hasBias is true
+   * @param hasScale true if scale was decoded from the proto
+   * @param scale 3-axis scale factor; only valid if hasScale is true
+   * @param hasMatrix true if matrix was decoded from the proto
+   * @param matrix 3x3 compensation matrix; only valid if hasMatrix is true
+   * @param accuracy Android accuracy rating of the calibration quality (see
+   *                 sns_std_sensor_sample_status)
+   */
+  void updateCalibration(const sns_std_suid& suid, bool hasBias, float bias[3],
+                         bool hasScale, float scale[3], bool hasMatrix,
+                         float matrix[9], uint8_t accuracy);
+
+ private:
+  //! A struct to store a sensor's calibration data
+  struct SeeCalData {
+    float bias[3];
+    float scale[3];
+    float matrix[9];
+    bool hasBias;
+    bool hasScale;
+    bool hasMatrix;
+    uint8_t accuracy;
+  };
+
+  //! A struct to store a cal sensor's UID and its cal data.
+  struct SeeCalInfo {
+    Optional<sns_std_suid> suid;
+    SeeCalData cal;
+  };
+
+  //! The list of SEE cal sensors supported.
+  enum class SeeCalSensor : size_t {
+    AccelCal,
+    GyroCal,
+    MagCal,
+    NumCalSensors,
+  };
+
+  //! A convenience constant.
+  static constexpr size_t kNumSeeCalSensors = static_cast<size_t>(
+      SeeCalSensor::NumCalSensors);
+
+  //! Protects access to calibration data, which may be used in multiple threads
+  mutable Mutex mMutex;
+
+  //! Cal info of all the cal sensors.
+  SeeCalInfo mCalInfo[kNumSeeCalSensors] = {};
+
+  //! Map SensorType to associated index in mCalInfo
+  static size_t getCalIndexFromSensorType(SensorType sensorType);
+
+  //! Map index in mCalInfo to SEE sensor data type string
+  static const char *getDataTypeForCalSensorIndex(size_t calSensorIndex);
+
+  //! Map SUID to associated index in mCalInfo
+  size_t getCalIndexFromSuid(const sns_std_suid& suid) const;
+};
+
+}  // namespace chre
+
+#endif  // CHRE_PLATFORM_SLPI_SEE_SEE_CAL_HELPER_H_
diff --git a/platform/slpi/include/chre/platform/slpi/see/see_client.h b/platform/slpi/include/chre/platform/slpi/see/see_client.h
index 0665832..64064f1 100644
--- a/platform/slpi/include/chre/platform/slpi/see/see_client.h
+++ b/platform/slpi/include/chre/platform/slpi/see/see_client.h
@@ -40,6 +40,14 @@
   return SeeHelperSingleton::get();
 }
 
+#ifdef CHRE_SLPI_UIMG_ENABLED
+typedef Singleton<BigImageSeeHelper> BigImageSeeHelperSingleton;
+
+inline SeeHelper *getBigImageSeeHelper() {
+  return BigImageSeeHelperSingleton::get();
+}
+#endif  // CHRE_SLPI_UIMG_ENABLED
+
 }  // namespace chre
 
 #endif  // CHRE_PLATFORM_SLPI_SEE_SEE_CLIENT_H_
diff --git a/platform/slpi/include/chre/platform/slpi/see/see_helper.h b/platform/slpi/include/chre/platform/slpi/see/see_helper.h
index 32481c3..a8bc173 100644
--- a/platform/slpi/include/chre/platform/slpi/see/see_helper.h
+++ b/platform/slpi/include/chre/platform/slpi/see/see_helper.h
@@ -21,14 +21,17 @@
 
 #include "sns_client.h"
 
-} // extern "C"
+}  // extern "C"
+
+#include "sns_suid.pb.h"
 
 #include "chre/core/sensor_type.h"
 #include "chre/platform/condition_variable.h"
 #include "chre/platform/mutex.h"
-#include "chre/platform/slpi/see/see_helper_internal.h"
+#include "chre/platform/slpi/see/see_cal_helper.h"
 #include "chre/util/dynamic_vector.h"
 #include "chre/util/non_copyable.h"
+#include "chre/util/optional.h"
 #include "chre/util/time.h"
 #include "chre/util/unique_ptr.h"
 
@@ -76,6 +79,9 @@
 //! Default timeout for sendReq indication
 constexpr Nanoseconds kDefaultSeeIndTimeout = Seconds(2);
 
+//! Allowed number of consecutive missing responses.
+constexpr uint32_t kSeeNumMissingResp = 5;
+
 //! Length of the char array to store sensor string attributes.
 constexpr size_t kSeeAttrStrValLen = 64;
 
@@ -114,14 +120,43 @@
     sns_std_suid suid;
     SensorType sensorType;
     sns_client *client;
+    //! The SUID of the underlying physical sensor, different from suid if
+    //! resampler is used.
+    sns_std_suid physicalSuid;
   };
 
   /**
+   * Constructor for a SeeHelper that manages its own SeeCalHelper
+   */
+  SeeHelper();
+
+  /**
+   * Constructor for a SeeHelper that uses the supplied SeeCalHelper object
+   * rather than creating its own. Caller must ensure that the lifetime of the
+   * SeeCalHelper object exceeds the lifetime of this SeeHelper instance.
+   *
+   * TODO: this would be a good case for a shared ptr implementation
+   *
+   * @param calHelper Non-null pointer to a calibration helper object to use
+   */
+  SeeHelper(SeeCalHelper *calHelper);
+
+  /**
    * Deinits clients before destructing this object.
    */
   ~SeeHelper();
 
   /**
+   * Makes a request to SEE to enable an on-change sensor, with no additional
+   * payload. Can be used for registering a calibration sensor, for example.
+   *
+   * @param suid Sensor UID, usually determined via findSuidSync()
+   *
+   * @return true on success
+   */
+  bool configureOnChangeSensor(const sns_std_suid& suid, bool enable);
+
+  /**
    * A synchronous call to discover SUID(s) that supports the specified data
    * type. This API will clear the provided dynamic vector before populating it.
    *
@@ -165,6 +200,13 @@
   bool getAttributesSync(const sns_std_suid& suid, SeeAttributes *attr);
 
   /**
+   * @return the SeeCalHelper instance used by this SeeHelper
+   */
+  SeeCalHelper *getCalHelper() {
+    return mCalHelper;
+  }
+
+  /**
    * Initializes and waits for the sensor client service to become available,
    * and obtains remote_proc and cal sensors' info for future operations. This
    * function must be called first to initialize the object and be called only
@@ -174,11 +216,15 @@
    *             handle all async requests with callback data type defined in
    *             the interface.
    * @param timeout The wait timeout in microseconds.
+   * @param skipDefaultSensorInit If true, don't register remote proc status and
+   *                              calibration sensors (e.g. if another SeeHelper
+   *                              instance will manage these)
    *
    * @return true if all initialization steps succeeded.
    */
   bool init(SeeHelperCallbackInterface *cbIf,
-            Microseconds timeout = kDefaultSeeWaitTimeout);
+            Microseconds timeout = kDefaultSeeWaitTimeout,
+            bool skipDefaultSensorInit = false);
 
   /**
    * Makes a sensor request to SEE.
@@ -201,13 +247,14 @@
    *
    * @param sensorType The SensorType to register.
    * @param suid The SUID of the sensor.
+   * @param resample Whether to resample this sensorType.
    * @param prevRegistered A non-null pointer to a boolean that indicates
    *        whether the SUID/SensorType pair has been previously registered.
    *
    * @return true if the SUID/SensorType pair was successfully registered.
    */
   bool registerSensor(SensorType sensorType, const sns_std_suid& suid,
-                      bool *prevRegistered);
+                      bool resample, bool *prevRegistered);
 
   /**
    * Checks whether the given SensorType has been successfully registered
@@ -226,6 +273,9 @@
     decltype(sns_client_send)   *sns_client_send;
   };
 
+  //! Contains the API this SeeHelper instance uses to interact with SEE
+  const SnsClientApi *mSnsClientApi = &kDefaultApi;
+
   /**
    * Get the cached SUID of a calibration sensor that corresponds to the
    * specified sensorType.
@@ -235,7 +285,9 @@
    * @return A constant reference to the calibration sensor's SUID if present.
    *         Otherwise, a reference to sns_suid_sensor_init_zero is returned.
    */
-  const sns_std_suid& getCalSuidFromSensorType(SensorType sensorType) const;
+  const sns_std_suid& getCalSuidFromSensorType(SensorType sensorType) const {
+    return mCalHelper->getCalSuidFromSensorType(sensorType);
+  }
 
   /**
    * A convenience method to send a request and wait for the indication if it's
@@ -259,10 +311,6 @@
                    timeoutResp, timeoutInd);
   }
 
-  void setSnsClientApi(const SnsClientApi *api) {
-    mSnsClientApi = api;
-  }
-
  private:
   static const SnsClientApi kDefaultApi;
 
@@ -308,21 +356,20 @@
   //! A transaction ID that increments for each request.
   uint32_t mCurrentTxnId = 0;
 
+  //! The number of consecutive missing responses.
+  uint32_t mNumMissingResp = 0;
+
   //! The SUID for the remote_proc sensor.
   Optional<sns_std_suid> mRemoteProcSuid;
 
-  //! Cal info of all the cal sensors.
-  SeeCalInfo mCalInfo[kNumSeeCalSensors];
+  //! The SUID for the resampler sensor.
+  Optional<sns_std_suid> mResamplerSuid;
 
-  //! Contains the API this SeeHelper instance uses to interact with SEE
-  const SnsClientApi *mSnsClientApi = &kDefaultApi;
+  //! Handles sensor calibration data
+  SeeCalHelper *mCalHelper;
 
-  /**
-   * Initializes SEE calibration sensors and makes data request.
-   *
-   * @return true if cal sensor have been succcessfully initialized.
-   */
-  bool initCalSensors();
+  //! true if we own the memory to mCalHelper and should free it when done
+  bool mOwnsCalHelper;
 
   /**
    * Initializes the SEE remote processor sensor and makes a data request.
@@ -332,6 +379,13 @@
   bool initRemoteProcSensor();
 
   /**
+   * Initializes the SEE resampler sensor.
+   *
+   * @return true if the resampler sensor was successfully initialized.
+   */
+  bool initResamplerSensor();
+
+  /**
    * Sends a request to SEE and waits for the response.
    *
    * @param client The pointer to sns_client to make the request with.
@@ -431,17 +485,31 @@
                       Microseconds timeout = kDefaultSeeWaitTimeout);
 
   /**
-   * Obtains the pointer to cal data by SUID.
-   */
-  SeeCalData *getCalDataFromSuid(const sns_std_suid& suid);
-
-  /**
    * @return SensorInfo instance found in mSensorInfos with the given
    *         SensorType, or nullptr if not found
    */
   const SensorInfo *getSensorInfo(SensorType sensorType) const;
 };
 
+#ifdef CHRE_SLPI_UIMG_ENABLED
+/**
+ * A version of SeeHelper that explicitly uses the QMI API on the bottom edge
+ * and therefore only works in big image (but goes through CM instead of QCM
+ * within SEE).
+ *
+ * @see SeeHelper
+ */
+class BigImageSeeHelper : public SeeHelper {
+ public:
+  BigImageSeeHelper(SeeCalHelper *calHelper) : SeeHelper(calHelper) {
+    mSnsClientApi = &kQmiApi;
+  }
+
+ private:
+  static const SnsClientApi kQmiApi;
+};
+#endif  // CHRE_SLPI_UIMG_ENABLED
+
 }  // namespace chre
 
 #endif  // CHRE_PLATFORM_SLPI_SEE_SEE_HELPER_H_
diff --git a/platform/slpi/include/chre/platform/slpi/see/see_helper_internal.h b/platform/slpi/include/chre/platform/slpi/see/see_helper_internal.h
deleted file mode 100644
index c1537e8..0000000
--- a/platform/slpi/include/chre/platform/slpi/see/see_helper_internal.h
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright (C) 2018 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 CHRE_PLATFORM_SLPI_SEE_SEE_HELPER_INTERNAL_H_
-#define CHRE_PLATFORM_SLPI_SEE_SEE_HELPER_INTERNAL_H_
-
-#include <stddef.h>
-#include <stdint.h>
-
-#include "sns_suid.pb.h"
-
-#include "chre/util/optional.h"
-
-namespace chre {
-
-//! A struct to store a sensor's calibration data
-struct SeeCalData {
-  float bias[3];
-  float scale[3];
-  float matrix[9];
-  bool hasBias;
-  bool hasScale;
-  bool hasMatrix;
-  uint8_t accuracy;
-};
-
-//! A struct to store a cal sensor's UID and its cal data.
-struct SeeCalInfo {
-  Optional<sns_std_suid> suid;
-  SeeCalData cal;
-};
-
-//! The list of SEE cal sensors supported.
-enum class SeeCalSensor {
-  AccelCal,
-  GyroCal,
-  MagCal,
-  NumCalSensors,
-};
-
-//! A convenience constant.
-constexpr size_t kNumSeeCalSensors = static_cast<size_t>(
-    SeeCalSensor::NumCalSensors);
-
-}  // namespace chre
-
-#endif  // CHRE_PLATFORM_SLPI_SEE_SEE_HELPER_INTERNAL_H_
diff --git a/platform/slpi/include/chre/target_platform/platform_audio_base.h b/platform/slpi/include/chre/target_platform/platform_audio_base.h
index 3b84c3c..d4424d5 100644
--- a/platform/slpi/include/chre/target_platform/platform_audio_base.h
+++ b/platform/slpi/include/chre/target_platform/platform_audio_base.h
@@ -24,10 +24,24 @@
  * functionality from.
  */
 class PlatformAudioBase {
+ public:
+  /**
+   * Invoked whenever the host goes awake. This is used to implement the
+   * deferred audio disable operation. This is called on the CHRE thread.
+   */
+  void onHostAwake();
+
  protected:
   //! The number of open audio clients. This is incremented/decremented by the
   //! setHandleEnabled platform API.
   uint32_t mNumAudioClients = 0;
+
+  //! The current state of the audio feature enabled on the host.
+  bool mCurrentAudioEnabled = false;
+
+  //! The target state of the audio feature enabled on the host. This is used to
+  //! support deferred disabling when the next AP wake occurs.
+  bool mTargetAudioEnabled = false;
 };
 
 }  // namespace chre
diff --git a/platform/slpi/platform_audio.cc b/platform/slpi/platform_audio.cc
index 96c74dc..834dd3a 100644
--- a/platform/slpi/platform_audio.cc
+++ b/platform/slpi/platform_audio.cc
@@ -58,6 +58,7 @@
   auto *dataEvent = memoryAlloc<struct chreAudioDataEvent>();
   if (dataEvent == nullptr) {
     LOGE("Failed to allocate data event");
+    wcd_spi_client_release_audio_data_event(event->handle);
   } else {
     dataEvent->handle = event->handle;
     dataEvent->timestamp = event->timestamp_ns;
@@ -106,11 +107,20 @@
   }
 
   if (lastNumAudioClients == 0 && mNumAudioClients > 0) {
-    LOGD("Enabling WCD SLPI");
-    sendAudioRequest();
+    mTargetAudioEnabled = true;
+    if (!mCurrentAudioEnabled) {
+      LOGD("Enabling WCD SLPI");
+      mCurrentAudioEnabled = true;
+      sendAudioRequest();
+    }
   } else if (lastNumAudioClients > 0 && mNumAudioClients == 0) {
-    LOGD("Disabling WCD SLPI");
-    sendAudioRelease();
+    mTargetAudioEnabled = false;
+    if (EventLoopManagerSingleton::get()->getEventLoop()
+            .getPowerControlManager().hostIsAwake()) {
+      onHostAwake();
+    } else {
+      LOGD("Deferring disable WCD SLPI");
+    }
   }
 }
 
@@ -138,7 +148,7 @@
 }
 
 bool PlatformAudio::getAudioSource(uint32_t handle,
-                                   chreAudioSource *source) {
+                                   chreAudioSource *source) const {
   slpiForceBigImage();
   wcd_spi_audio_source_s wcd_spi_audio_source;
   bool result = wcd_spi_client_get_source(handle, &wcd_spi_audio_source);
@@ -151,4 +161,12 @@
   return result;
 }
 
+void PlatformAudioBase::onHostAwake() {
+  if (mCurrentAudioEnabled && !mTargetAudioEnabled) {
+    LOGD("Disabling WCD SPI");
+    mCurrentAudioEnabled = mTargetAudioEnabled;
+    sendAudioRelease();
+  }
+}
+
 }  // namespace chre
diff --git a/platform/slpi/platform_nanoapp.cc b/platform/slpi/platform_nanoapp.cc
index cb3cb78..20b8196 100644
--- a/platform/slpi/platform_nanoapp.cc
+++ b/platform/slpi/platform_nanoapp.cc
@@ -55,6 +55,21 @@
                                   const void *eventData) {
   if (!isUimgApp()) {
     slpiForceBigImage();
+
+#if defined(CHRE_SLPI_SEE) && defined(CHRE_SLPI_UIMG_ENABLED)
+    // HACK: as SEE does not support software batching in uimg via
+    // QCM/uQSockets, we rewrite requests for accel from big image nanoapps to
+    // vendor type 3 in chreSensorFindDefault(), which is implemented as accel
+    // routed through CM/QMI and supports batching. Rewrite sensor data arriving
+    // on this event type to the vanilla accel event type so that this appears
+    // transparent to the nanoapp.
+    // TODO(P2-5673a9): work with QC to determine a better long-term solution
+    constexpr uint16_t kAccelBigImageEventType =
+        (CHRE_EVENT_SENSOR_DATA_EVENT_BASE + CHRE_SENSOR_TYPE_VENDOR_START + 3);
+    if (eventType == kAccelBigImageEventType) {
+      eventType = CHRE_EVENT_SENSOR_ACCELEROMETER_DATA;
+    }
+#endif  // defined(CHRE_SLPI_SEE) && defined(CHRE_SLPI_UIMG_ENABLED)
   }
 
   mAppInfo->entryPoints.handleEvent(senderInstanceId, eventType, eventData);
diff --git a/platform/slpi/see/include/chre/target_platform/power_control_manager_base.h b/platform/slpi/see/include/chre/target_platform/power_control_manager_base.h
index 9081262..22cdba4 100644
--- a/platform/slpi/see/include/chre/target_platform/power_control_manager_base.h
+++ b/platform/slpi/see/include/chre/target_platform/power_control_manager_base.h
@@ -17,10 +17,17 @@
 #ifndef CHRE_PLATFORM_SLPI_SEE_POWER_CONTROL_MANAGER_BASE_H_
 #define CHRE_PLATFORM_SLPI_SEE_POWER_CONTROL_MANAGER_BASE_H_
 
+extern "C" {
+#include "sns_client_thread_util.h"
+} // extern "C"
+
 namespace chre {
 
 class PowerControlManagerBase {
  public:
+   PowerControlManagerBase();
+  ~PowerControlManagerBase();
+
   /**
    * Makes a power mode request. An actual vote to the SLPI power manager may
    * not be cast depending on current power mode and mBigImageRefCount.
@@ -43,6 +50,13 @@
  protected:
   //! Set to true if the host is awake, false if suspended.
   bool mHostIsAwake = true;
+
+  //! Set to true if the thread is currently idle (no pending events),
+  //! false otherwise.
+  bool mIsThreadIdle = true;
+
+  //! A pointer to the client to compute thread utilization
+  sns_thread_util_client *mThreadUtilClient = nullptr;
 };
 
 } // namespace chre
diff --git a/platform/slpi/see/island_vote_client.cc b/platform/slpi/see/island_vote_client.cc
index 02fe701..a80f8d0 100644
--- a/platform/slpi/see/island_vote_client.cc
+++ b/platform/slpi/see/island_vote_client.cc
@@ -26,6 +26,10 @@
 
 namespace chre {
 
+#ifdef CHRE_SLPI_UIMG_ENABLED
+constexpr Seconds IslandVoteClient::kSeeMaxBigImageDuration;
+#endif  // CHRE_SLPI_UIMG_ENABLED
+
 IslandVoteClient::IslandVoteClient(const char *clientName) {
 #ifdef CHRE_SLPI_UIMG_ENABLED
   mClientHandle = sns_island_aggregator_register_client(clientName);
@@ -75,6 +79,8 @@
       voteSnsPowerMode(true /* bigImage */);
       mLastBigImageVote = true;
     }
+  } else {
+    checkBigImageDuration();
   }
 }
 
@@ -83,10 +89,8 @@
   CHRE_ASSERT_LOG(mBigImageRefCount > 0,
                   "Tried to decrement big image ref count when it's 0");
 
+  uint64_t duration = checkBigImageDuration();
   if (--mBigImageRefCount == 0) {
-    uint64_t duration =
-        Milliseconds(SystemTime::getMonotonicTime()).getMilliseconds()
-        - mRefCountStart.getMilliseconds();
     LOGW("Big image ref count ends: %" PRIu64 " ms", duration);
 
     // There's no big image activity now, restore the intended uimg power state.
@@ -110,6 +114,22 @@
   }
   return success;
 }
+
+uint64_t IslandVoteClient::checkBigImageDuration() const {
+  uint64_t duration = 0;
+  if (mBigImageRefCount > 0) {
+    duration = Milliseconds(SystemTime::getMonotonicTime()).getMilliseconds()
+               - mRefCountStart.getMilliseconds();
+  }
+
+  // Bimg memory fallback only intends to handle a surge of uimg memory
+  // requests. If there's a prolonged period of memory fallback, this might
+  // indicate a memory leak or inadequate uimg heap size.
+  if (duration > kSeeMaxBigImageDuration.getMilliseconds()) {
+    FATAL_ERROR("Forced into big image for %" PRIu64 " msec", duration);
+  }
+  return duration;
+}
 #endif  // CHRE_SLPI_UIMG_ENABLED
 
 //! Explicitly instantiate the IslandVoteClient singleton to reduce code size.
diff --git a/platform/slpi/see/platform_sensor.cc b/platform/slpi/see/platform_sensor.cc
index fa56270..5cf6a51 100644
--- a/platform/slpi/see/platform_sensor.cc
+++ b/platform/slpi/see/platform_sensor.cc
@@ -28,6 +28,7 @@
 #include "chre/platform/fatal_error.h"
 #include "chre/platform/log.h"
 #include "chre/platform/shared/platform_sensor_util.h"
+#include "chre/platform/slpi/power_control_util.h"
 #include "chre/platform/slpi/see/see_client.h"
 #include "chre/platform/slpi/see/see_helper.h"
 #include "chre/platform/system_time.h"
@@ -46,6 +47,17 @@
 namespace chre {
 namespace {
 
+#ifdef CHRE_SLPI_UIMG_ENABLED
+#ifndef CHREX_SENSOR_SUPPORT
+// The current implementation uses vendor sensor type 3 to remap into accel,
+// with requests made through QMI instead of QSockets, as SEE does not support
+// micro-image batching in QCM.
+#error "CHRE extensions are required for micro-image SEE support"
+#endif  // CHREX_SENSOR_SUPPORT
+
+constexpr SensorType kAccelBigImageSensorType = SensorType::VendorType3;
+#endif  // CHRE_SLPI_UIMG_ENABLED
+
 //! A class that implements SeeHelperCallbackInterface.
 class SeeHelperCallback : public SeeHelperCallbackInterface {
   void onSamplingStatusUpdate(
@@ -178,9 +190,9 @@
       newStatus.latency = update.status.latency;
     }
 
-    if (newStatus.enabled != prevStatus.enabled
-        || newStatus.interval != prevStatus.interval
-        || newStatus.latency != prevStatus.latency) {
+    if (newStatus.enabled != prevStatus.enabled ||
+        (newStatus.enabled && (newStatus.interval != prevStatus.interval
+                               || newStatus.latency != prevStatus.latency))) {
       sensor->setSamplingStatus(newStatus);
 
       // Only post to Nanoapps with an open request.
@@ -279,14 +291,16 @@
 /**
  * Constructs and initializes a sensor, and adds it to the sensor list.
  *
+ * @param seeHelper SeeHelper instance to register sensor with
  * @param suid The SUID of the sensor as provided by SEE.
  * @param sensorType The sensor type of the sensor.
  * @param calibrated Whether the sensor is runtime-calibrated or not.
  * @param attr A reference to SeeAttrbutes.
  * @param sensor The sensor list.
  */
-void addSensor(SensorType sensorType, const sns_std_suid& suid,
-               const SeeAttributes& attr, DynamicVector<Sensor> *sensors) {
+void addSensor(SeeHelper& seeHelper, SensorType sensorType,
+               const sns_std_suid& suid, const SeeAttributes& attr,
+               DynamicVector<Sensor> *sensors) {
   // Concatenate vendor and name with a space in between.
   char sensorName[kSensorNameMaxLen];
   strlcpy(sensorName, attr.vendor, sizeof(sensorName));
@@ -311,9 +325,15 @@
     FATAL_ERROR("Failed to allocate new sensor: out of memory");
   }
 
+  // Resample big image accel to reduce system load during sw flush.
+#ifdef CHRE_SLPI_UIMG_ENABLED
+  bool resample = (sensorType == kAccelBigImageSensorType);
+#else
+  bool resample = false;
+#endif
   bool prevRegistered;
-  bool registered = getSeeHelper()->registerSensor(
-      sensorType, suid, &prevRegistered);
+  bool registered = seeHelper.registerSensor(
+      sensorType, suid, resample, &prevRegistered);
   if (!registered && prevRegistered) {
     LOGW("SUID has been previously registered");
   } else if (!registered) {
@@ -344,10 +364,10 @@
  * Obtains the list of SUIDs and their attributes that support the specified
  * data type.
  */
-bool getSuidAndAttrs(const char *dataType, DynamicVector<SuidAttr> *suidAttrs,
-                     uint8_t minNumSuids) {
+bool getSuidAndAttrs(SeeHelper& seeHelper, const char *dataType,
+                     DynamicVector<SuidAttr> *suidAttrs, uint8_t minNumSuids) {
   DynamicVector<sns_std_suid> suids;
-  bool success = getSeeHelper()->findSuidSync(dataType, &suids, minNumSuids);
+  bool success = seeHelper.findSuidSync(dataType, &suids, minNumSuids);
   if (!success) {
     LOGE("Failed to find sensor '%s'", dataType);
   } else {
@@ -355,7 +375,7 @@
 
     for (const auto& suid : suids) {
       SeeAttributes attr;
-      if (!getSeeHelper()->getAttributesSync(suid, &attr)) {
+      if (!seeHelper.getAttributesSync(suid, &attr)) {
         success = false;
         LOGE("Failed to get attributes of SUID 0x%" PRIx64 " %" PRIx64,
              suid.suid_high, suid.suid_low);
@@ -378,8 +398,8 @@
   return success;
 }
 
-// Check whether two sensors with the specified attrtibutes belong to the same
-// sensor hardware module.
+//! Check whether two sensors with the specified attrtibutes belong to the same
+//! sensor hardware module.
 bool sensorHwMatch(const SeeAttributes& attr0, const SeeAttributes& attr1) {
   // When HW ID is absent, it's default to 0 and won't be a factor.
   return ((strncmp(attr0.vendor, attr1.vendor, kSeeAttrStrValLen) == 0)
@@ -387,6 +407,92 @@
           && (attr0.hwId == attr1.hwId));
 }
 
+/**
+ * Looks up SUID(s) associated with a given sensor data type string and sensor
+ * type enum, registers them with SeeHelper, and adds a Sensor instance to the
+ * supplied vector for use in CHRE. When given an uncalibrated sensor type, will
+ * also look for and add the calibrated sensor type.
+ *
+ * @param seeHelper SeeHelper instance to use for lookup/registration
+ * @param temperatureSensors List of previously discovered temperature sensor
+ *        info to use for adding temp sensors associated with this sensor type
+ * @param dataType SEE data type string
+ * @param sensorType CHRE sensor type enum associated with dataType
+ * @param skipAdditionalTypes if true, don't attempt to add
+ *        calibrated/temperature sensor types associated with this sensorType
+ * @param sensors Vector to append found sensor(s) to
+ */
+void findAndAddSensorsForType(
+    SeeHelper& seeHelper, const DynamicVector<SuidAttr>& temperatureSensors,
+    const char *dataType, SensorType sensorType, bool skipAdditionalTypes,
+    DynamicVector<Sensor> *sensors) {
+  DynamicVector<SuidAttr> primarySensors;
+  if (!getSuidAndAttrs(seeHelper, dataType, &primarySensors,
+                       1 /* minNumSuids */)) {
+    FATAL_ERROR("Failed to get primary sensor UID and attributes");
+  }
+
+  for (const auto& primarySensor : primarySensors) {
+    sns_std_suid suid = primarySensor.suid;
+    SeeAttributes attr = primarySensor.attr;
+
+    // Some sensors support both continuous and on-change streams.
+    // If there are more than one SUIDs that support the data type,
+    // choose the first one that has the expected stream type.
+    if (isStreamTypeCorrect(sensorType, attr.streamType)) {
+      addSensor(seeHelper, sensorType, suid, attr, sensors);
+
+      if (!skipAdditionalTypes) {
+        // Check if this sensor has a runtime-calibrated version.
+        SensorType calibratedType = getSensorTypeFromDataType(
+            dataType, true /* calibrated */);
+        if (calibratedType != sensorType) {
+          addSensor(seeHelper, calibratedType, suid, attr, sensors);
+        }
+
+        // Check if this sensor has a secondary temperature sensor.
+        SensorType temperatureType = getTempSensorType(sensorType);
+        if (temperatureType != SensorType::Unknown) {
+          bool tempFound = false;
+          for (const auto& tempSensor : temperatureSensors) {
+            sns_std_suid tempSuid = tempSensor.suid;
+            SeeAttributes tempAttr = tempSensor.attr;
+
+            if (sensorHwMatch(attr, tempAttr)) {
+              LOGD("Found matching temperature sensor type");
+              tempFound = true;
+              addSensor(seeHelper, temperatureType, tempSuid, tempAttr,
+                        sensors);
+              break;
+            }
+          }
+          if (!tempFound) {
+            LOGW("Temperature sensor type %" PRIu8 " not found!",
+                 static_cast<uint8_t>(temperatureType));
+          }
+        }
+      }
+      break;
+    }
+  }
+}
+
+#ifdef CHRE_SLPI_UIMG_ENABLED
+/**
+ * Registers alternate sensor(s) to be used separately by big image nanoapps.
+ */
+void getBigImageSensors(DynamicVector<Sensor> *sensors) {
+  // Currently, just adding calibrated accel, as it's the one we know that big
+  // image nanoapps will need at a different batching rate compared to uimg
+  SeeHelper& seeHelper = *getBigImageSeeHelper();
+  const char *kAccelDataType = "accel";
+  DynamicVector<SuidAttr> nullTemperatureSensorList;
+  findAndAddSensorsForType(
+      seeHelper, nullTemperatureSensorList, kAccelDataType,
+      kAccelBigImageSensorType, true /* skipAdditionalTypes */, sensors);
+}
+#endif  // CHRE_SLPI_UIMG_ENABLED
+
 }  // anonymous namespace
 
 PlatformSensor::~PlatformSensor() {
@@ -404,17 +510,30 @@
   if (!getSeeHelper()->init(&seeHelperCallback)) {
     FATAL_ERROR("Failed to initialize SEE helper");
   }
+
+#ifdef CHRE_SLPI_UIMG_ENABLED
+  BigImageSeeHelperSingleton::init(getSeeHelper()->getCalHelper());
+  if (!getBigImageSeeHelper()->init(&seeHelperCallback, kDefaultSeeWaitTimeout,
+                                    true /* skipDefaultSensorInit */)) {
+    FATAL_ERROR("Failed to init bimg SEE helper");
+  }
+#endif  // CHRE_SLPI_UIMG_ENABLED
 }
 
 void PlatformSensor::deinit() {
+#ifdef CHRE_SLPI_UIMG_ENABLED
+  BigImageSeeHelperSingleton::deinit();
+#endif
+
   SeeHelperSingleton::deinit();
 }
 
 bool PlatformSensor::getSensors(DynamicVector<Sensor> *sensors) {
   CHRE_ASSERT(sensors);
 
+  SeeHelper& seeHelper = *getSeeHelper();
   DynamicVector<SuidAttr> tempSensors;
-  if (!getSuidAndAttrs("sensor_temperature", &tempSensors,
+  if (!getSuidAndAttrs(seeHelper, "sensor_temperature", &tempSensors,
                        CHRE_SEE_NUM_TEMP_SENSORS)) {
       FATAL_ERROR("Failed to get temperature sensor UID and attributes");
   }
@@ -435,52 +554,14 @@
       continue;
     }
 
-    DynamicVector<SuidAttr> primarySensors;
-    if (!getSuidAndAttrs(dataType, &primarySensors, 1 /* minNumSuids */)) {
-      FATAL_ERROR("Failed to get primary sensor UID and attributes");
-    } else {
-      for (const auto& primarySensor : primarySensors) {
-        sns_std_suid suid = primarySensor.suid;
-        SeeAttributes attr = primarySensor.attr;
-
-        // Some sensors support both continuous and on-change streams.
-        // If there are more than one SUIDs that support the data type,
-        // choose the first one that has the expected stream type.
-        if (isStreamTypeCorrect(sensorType, attr.streamType)) {
-          addSensor(sensorType, suid, attr, sensors);
-
-          // Check if this sensor has a runtime-calibrated version.
-          SensorType calibratedType = getSensorTypeFromDataType(
-              dataType, true /* calibrated */);
-          if (calibratedType != sensorType) {
-            addSensor(calibratedType, suid, attr, sensors);
-          }
-
-          // Check if this sensor has a secondary temperature sensor.
-          SensorType temperatureType = getTempSensorType(sensorType);
-          if (temperatureType != SensorType::Unknown) {
-            bool tempFound = false;
-            for (const auto& tempSensor : tempSensors) {
-              sns_std_suid tempSuid = tempSensor.suid;
-              SeeAttributes tempAttr = tempSensor.attr;
-
-              if (sensorHwMatch(attr, tempAttr)) {
-                LOGD("Found matching temperature sensor type");
-                tempFound = true;
-                addSensor(temperatureType, tempSuid, tempAttr, sensors);
-                break;
-              }
-            }
-            if (!tempFound) {
-              LOGW("Temperature sensor type %" PRIu8 " not found!",
-                   static_cast<uint8_t>(temperatureType));
-            }
-          }
-          break;
-        }
-      }
-    }
+    findAndAddSensorsForType(seeHelper, tempSensors, dataType, sensorType,
+                             false /* skipAdditionalTypes */, sensors);
   }
+
+#ifdef CHRE_SLPI_UIMG_ENABLED
+  getBigImageSensors(sensors);
+#endif
+
   return true;
 }
 
@@ -503,11 +584,36 @@
          static_cast<uint8_t>(getSensorType()));
   }
 
-  bool success = SeeHelperSingleton::get()->makeRequest(req);
+  SeeHelper *seeHelper = getSeeHelper();
+#ifdef CHRE_SLPI_UIMG_ENABLED
+  if (getSensorType() == kAccelBigImageSensorType) {
+    seeHelper = getBigImageSeeHelper();
+    slpiForceBigImage();
+  }
+#endif
 
-  // TODO: remove setSamplingStatus when .latency is available in status update
-  // from SEE.
+  bool wasInUImage = slpiInUImage();
+  bool success = seeHelper->makeRequest(req);
+
+  // If we dropped into micro-image during that blocking call to SEE, go back to
+  // big image. This won't happen if the calling nanoapp is a big image one, but
+  // other code paths currently assume that we will only transition from big
+  // image to micro-image from CHRE's perspective while it's waiting for an
+  // event to arrive in its empty queue.
+  // TODO: transition back to big image only when needed, at the point of
+  // invoking a nanoapp's free event/message callback
+  if (!wasInUImage && slpiInUImage()) {
+    LOGD("Restoring big image operating mode");
+    slpiForceBigImage();
+  }
+
   if (success) {
+    if (request.getMode() == SensorMode::Off) {
+      mLastEventValid = false;
+    }
+
+    // TODO: remove setSamplingStatus when .latency is available in status
+    // update from SEE.
     struct chreSensorSamplingStatus status;
     if (getSamplingStatus(&status)) {
 
diff --git a/platform/slpi/see/power_control_manager.cc b/platform/slpi/see/power_control_manager.cc
index b0fd3b8..ad49094 100644
--- a/platform/slpi/see/power_control_manager.cc
+++ b/platform/slpi/see/power_control_manager.cc
@@ -23,6 +23,14 @@
 
 namespace chre {
 
+PowerControlManagerBase::PowerControlManagerBase() {
+  sns_client_create_thread_utilization_client(&mThreadUtilClient);
+}
+
+PowerControlManagerBase::~PowerControlManagerBase() {
+  sns_client_remove_thread_utilization_client(mThreadUtilClient);
+}
+
 bool PowerControlManagerBase::voteBigImage(bool bigImage) {
   return IslandVoteClientSingleton::get()->voteBigImage(bigImage);
 }
@@ -34,10 +42,39 @@
     EventLoopManagerSingleton::get()->getEventLoop().postEvent(
         mHostIsAwake ? CHRE_EVENT_HOST_AWAKE : CHRE_EVENT_HOST_ASLEEP,
         nullptr /* eventData */, nullptr /* freeCallback */);
+
+#ifdef CHRE_AUDIO_SUPPORT_ENABLED
+    if (awake) {
+      auto callback = [](uint16_t /* eventType */, void * /* eventData*/) {
+        EventLoopManagerSingleton::get()->getAudioRequestManager()
+            .getPlatformAudio().onHostAwake();
+      };
+
+      EventLoopManagerSingleton::get()->deferCallback(
+          SystemCallbackType::AudioHandleHostAwake, nullptr, callback);
+    }
+#endif  // CHRE_AUDIO_SUPPORT_ENABLED
   }
 }
 
 void PowerControlManager::postEventLoopProcess(size_t numPendingEvents) {
+  // Although this execution point does not actually represent the start
+  // of the CHRE thread's activity, we only care about cases where the
+  // CHRE's event queue is highly backlogged for voting higher clock rates.
+  if (mIsThreadIdle && numPendingEvents != 0) {
+    sns_client_thread_utilization_start(mThreadUtilClient);
+    mIsThreadIdle = false;
+  } else if (!mIsThreadIdle) {
+    // Update the time profile as frequently as possible so that clock updates
+    // are not deferred until all events are processed.
+    sns_client_thread_utilization_stop(mThreadUtilClient);
+    if (numPendingEvents != 0) {
+      sns_client_thread_utilization_start(mThreadUtilClient);
+    } else {
+      mIsThreadIdle = true;
+    }
+  }
+
   if (numPendingEvents == 0 && !slpiInUImage()) {
     voteBigImage(false /* bigImage */);
   }
diff --git a/platform/slpi/see/see_cal_helper.cc b/platform/slpi/see/see_cal_helper.cc
new file mode 100644
index 0000000..d910012
--- /dev/null
+++ b/platform/slpi/see/see_cal_helper.cc
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+
+#include "chre/platform/slpi/see/see_cal_helper.h"
+
+#include "chre/platform/assert.h"
+#include "chre/platform/log.h"
+#include "chre/platform/slpi/see/see_helper.h"
+#include "chre/util/lock_guard.h"
+#include "chre/util/macros.h"
+
+namespace chre {
+
+void SeeCalHelper::applyCalibration(SensorType sensorType, const float input[3],
+                                    float output[3]) const {
+  bool applied = false;
+  size_t index = getCalIndexFromSensorType(sensorType);
+  if (index < ARRAY_SIZE(mCalInfo)) {
+    LockGuard<Mutex> lock(mMutex);
+
+    // TODO: Support compensation matrix and scaling factor calibration
+    if (mCalInfo[index].cal.hasBias) {
+      for (size_t i = 0; i < 3; i++) {
+        output[i] = input[i] - mCalInfo[index].cal.bias[i];
+      }
+      applied = true;
+    }
+  }
+
+  if (!applied) {
+    for (size_t i = 0; i < 3; i++) {
+      output[i] = input[i];
+    }
+  }
+}
+
+const sns_std_suid& SeeCalHelper::getCalSuidFromSensorType(
+    SensorType sensorType) const {
+  static sns_std_suid suid = sns_suid_sensor_init_zero;
+
+  // Mutex not needed, SUID is not modified after init
+  size_t calIndex = getCalIndexFromSensorType(sensorType);
+  if (calIndex < ARRAY_SIZE(mCalInfo) && mCalInfo[calIndex].suid.has_value()) {
+    suid = mCalInfo[calIndex].suid.value();
+  }
+  return suid;
+}
+
+bool SeeCalHelper::registerForCalibrationUpdates(SeeHelper& seeHelper) {
+  bool success = true;
+
+  // Find the cal sensor's SUID, assign it to mCalInfo, and make cal sensor data
+  // request.
+  DynamicVector<sns_std_suid> suids;
+  for (size_t i = 0; i < ARRAY_SIZE(mCalInfo); i++) {
+    const char *calType = getDataTypeForCalSensorIndex(i);
+    if (!seeHelper.findSuidSync(calType, &suids)) {
+      success = false;
+      LOGE("Failed to find sensor '%s'", calType);
+    } else {
+      mCalInfo[i].suid = suids[0];
+      if (!seeHelper.configureOnChangeSensor(suids[0], true /* enable */)) {
+        success = false;
+        LOGE("Failed to request '%s' data", calType);
+      }
+    }
+  }
+
+  return success;
+}
+
+void SeeCalHelper::updateCalibration(
+    const sns_std_suid& suid, bool hasBias, float bias[3], bool hasScale,
+    float scale[3], bool hasMatrix, float matrix[9], uint8_t accuracy) {
+  size_t index = getCalIndexFromSuid(suid);
+  if (index < ARRAY_SIZE(mCalInfo)) {
+    LockGuard<Mutex> lock(mMutex);
+    SeeCalData& calData = mCalInfo[index].cal;
+
+    calData.hasBias = hasBias;
+    if (hasBias) {
+      memcpy(calData.bias, bias, sizeof(calData.bias));
+    }
+
+    calData.hasScale = hasScale;
+    if (hasScale) {
+      memcpy(calData.scale, scale, sizeof(calData.scale));
+    }
+
+    calData.hasMatrix = hasMatrix;
+    if (hasMatrix) {
+      memcpy(calData.matrix, matrix, sizeof(calData.matrix));
+    }
+
+    calData.accuracy = accuracy;
+  }
+}
+
+size_t SeeCalHelper::getCalIndexFromSensorType(SensorType sensorType) {
+  SeeCalSensor index;
+  switch (sensorType) {
+    case SensorType::Accelerometer:
+      index = SeeCalSensor::AccelCal;
+      break;
+    case SensorType::Gyroscope:
+      index = SeeCalSensor::GyroCal;
+      break;
+    case SensorType::GeomagneticField:
+      index = SeeCalSensor::MagCal;
+      break;
+    default:
+      index = SeeCalSensor::NumCalSensors;
+  }
+  return static_cast<size_t>(index);
+}
+
+const char *SeeCalHelper::getDataTypeForCalSensorIndex(size_t calSensorIndex) {
+  switch (static_cast<SeeCalSensor>(calSensorIndex)) {
+    case SeeCalSensor::AccelCal:
+      return "accel_cal";
+    case SeeCalSensor::GyroCal:
+      return "gyro_cal";
+    case SeeCalSensor::MagCal:
+      return "mag_cal";
+    default:
+      CHRE_ASSERT(false);
+  }
+  return nullptr;
+}
+
+size_t SeeCalHelper::getCalIndexFromSuid(const sns_std_suid& suid) const {
+  size_t i = 0;
+  for (; i < ARRAY_SIZE(mCalInfo); i++) {
+    if (mCalInfo[i].suid.has_value()
+        && suidsMatch(suid, mCalInfo[i].suid.value())) {
+      break;
+    }
+  }
+  return i;
+}
+
+}  // namespace chre
diff --git a/platform/slpi/see/see_helper.cc b/platform/slpi/see/see_helper.cc
index 7f49190..d2c8334 100644
--- a/platform/slpi/see/see_helper.cc
+++ b/platform/slpi/see/see_helper.cc
@@ -24,11 +24,16 @@
 #include "sns_proximity.pb.h"
 #include "sns_rc.h"
 #include "sns_remote_proc_state.pb.h"
+#include "sns_resampler.pb.h"
 #include "sns_std.pb.h"
 #include "sns_std_sensor.pb.h"
 #include "stringl.h"
 #include "timer.h"
 
+#ifdef CHRE_SLPI_UIMG_ENABLED
+#include "sns_qmi_client.h"
+#endif
+
 #include <algorithm>
 #include <cfloat>
 #include <cinttypes>
@@ -93,7 +98,6 @@
   size_t totalSamples;
   UniquePtr<uint8_t> event;
   UniquePtr<SeeHelperCallbackInterface::SamplingStatusData> status;
-  SeeCalData *cal;
   SensorType sensorType;
   bool isHostWakeSuspendEvent;
   bool isHostAwake;
@@ -108,7 +112,7 @@
   SeeDataArg *data;
   bool decodeMsgIdOnly;
   Optional<sns_std_suid> *remoteProcSuid;
-  SeeCalInfo *calInfo;
+  SeeCalHelper *calHelper;
 };
 
 //! A struct to facilitate decoding sensor attributes.
@@ -125,48 +129,6 @@
   bool initialized;
 };
 
-size_t getCalIndexFromSensorType(SensorType sensorType) {
-  SeeCalSensor index;
-  switch (sensorType) {
-    case SensorType::Accelerometer:
-      index = SeeCalSensor::AccelCal;
-      break;
-    case SensorType::Gyroscope:
-      index = SeeCalSensor::GyroCal;
-      break;
-    case SensorType::GeomagneticField:
-      index = SeeCalSensor::MagCal;
-      break;
-    default:
-      index = SeeCalSensor::NumCalSensors;
-  }
-  return static_cast<size_t>(index);
-}
-
-size_t getCalIndexFromDataType(const char *dataType) {
-  SensorType sensorType = SensorType::Unknown;
-  if (strcmp(dataType, "accel_cal") == 0) {
-    sensorType = SensorType::Accelerometer;
-  } else if (strcmp(dataType, "gyro_cal") == 0) {
-    sensorType = SensorType::Gyroscope;
-  } else if (strcmp(dataType, "mag_cal") == 0) {
-    sensorType = SensorType::GeomagneticField;
-  }
-  return getCalIndexFromSensorType(sensorType);
-}
-
-size_t getCalIndexFromSuid(const sns_std_suid& suid,
-                           const SeeCalInfo *calInfo) {
-  size_t i = 0;
-  for (; i < kNumSeeCalSensors; i++) {
-    if (calInfo[i].suid.has_value()
-        && suidsMatch(suid, calInfo[i].suid.value())) {
-      break;
-    }
-  }
-  return i;
-}
-
 /**
  * Copy an encoded pb message to a wrapper proto's field.
  */
@@ -272,6 +234,55 @@
 }
 
 /**
+ * Encodes sns_resampler_config pb message.
+ *
+ * @param request The request to be encoded.
+ * @param suid The SUID of the physical sensor to be resampled.
+ * @param msg A non-null pointer to the pb message unique pointer whose object
+ *            will be assigned here.
+ * @param msgLen A non-null pointer to the size of the encoded pb message.
+ *
+ * @return true if the pb message and length were obtained.
+ */
+bool encodeSnsResamplerConfig(const SeeSensorRequest& request,
+                              const sns_std_suid& suid,
+                              UniquePtr<pb_byte_t> *msg, size_t *msgLen) {
+  CHRE_ASSERT(msg);
+  CHRE_ASSERT(msgLen);
+  bool success = false;
+
+  // Initialize the pb message
+  sns_resampler_config req = {
+    .sensor_uid = suid,
+    .resampled_rate = request.samplingRateHz,
+    .rate_type = SNS_RESAMPLER_RATE_FIXED,
+    .filter = true,
+    .has_axis_cnt = true,
+    .axis_cnt = 3,  // TODO: set this properly.
+  };
+
+  if (!pb_get_encoded_size(msgLen, sns_resampler_config_fields, &req)) {
+    LOGE("pb_get_encoded_size failed for sns_resampler_config");
+  } else if (*msgLen == 0) {
+    LOGE("Invalid pb encoded size for sns_resampler_config");
+  } else {
+    UniquePtr<pb_byte_t> buf(static_cast<pb_byte_t *>(memoryAlloc(*msgLen)));
+    *msg = std::move(buf);
+    if (msg->isNull()) {
+      LOG_OOM();
+    } else {
+      pb_ostream_t stream = pb_ostream_from_buffer(msg->get(), *msgLen);
+
+      success = pb_encode(&stream, sns_resampler_config_fields, &req);
+      if (!success) {
+        LOG_NANOPB_ERROR(&stream);
+      }
+    }
+  }
+  return success;
+}
+
+/**
  * Encodes sns_std_sensor_config pb message.
  *
  * @param request The request to be encoded.
@@ -363,6 +374,9 @@
     req->susp_config.delivery_type = SNS_CLIENT_DELIVERY_WAKEUP;
     req->request.has_batching = batchValid;
     req->request.batching.batch_period = batchPeriodUs;
+    // TODO: remove flush_period setting after resolving b/110823194.
+    req->request.batching.has_flush_period = true;
+    req->request.batching.flush_period = batchPeriodUs + 3000000;
     req->request.payload.funcs.encode = copyPayload;
     req->request.payload.arg = data;
     req->request.has_is_passive = true,
@@ -727,20 +741,8 @@
   return success;
 }
 
-// TODO: Support compensation matrix and scaling factor calibration
-void applyThreeAxisCalibration(
-    chreSensorThreeAxisData::chreSensorThreeAxisSampleData *sample,
-    const float *val, const SeeCalData *cal) {
-  float bias[3] = {};
-  if (cal != nullptr && cal->hasBias) {
-    memcpy(bias, cal->bias, sizeof(bias));
-  }
-  sample->x = val[0] - bias[0];
-  sample->y = val[1] - bias[1];
-  sample->z = val[2] - bias[2];
-}
-
-void populateEventSample(SeeDataArg *data, const float *val) {
+void populateEventSample(SeeInfoArg *info, const float *val) {
+  SeeDataArg *data = info->data;
   size_t index = data->sampleIndex;
   if (!data->event.isNull() && index < data->totalSamples) {
     SensorSampleType sampleType = getSensorSampleTypeFromSensorType(
@@ -751,7 +753,8 @@
       case SensorSampleType::ThreeAxis: {
         auto *event = reinterpret_cast<chreSensorThreeAxisData *>(
             data->event.get());
-        applyThreeAxisCalibration(&event->readings[index], val, data->cal);
+        info->calHelper->applyCalibration(
+            data->sensorType, val, event->readings[index].values);
         timestampDelta = &event->readings[index].timestampDelta;
         break;
       }
@@ -805,6 +808,15 @@
         timestampDelta = &event->readings[index].timestampDelta;
         break;
       }
+
+      case SensorSampleType::Vendor3: {
+        auto *event = reinterpret_cast<chrexSensorVendor3Data *>(
+            data->event.get());
+        memcpy(event->readings[index].values, val,
+               sizeof(event->readings[index].values));
+        timestampDelta = &event->readings[index].timestampDelta;
+        break;
+      }
 #endif  // CHREX_SENSOR_SUPPORT
 
       default:
@@ -912,7 +924,7 @@
     LOG_NANOPB_ERROR(stream);
   } else {
     auto *info = static_cast<SeeInfoArg *>(*arg);
-    populateEventSample(info->data, sample.val);
+    populateEventSample(info, sample.val);
   }
   return success;
 }
@@ -959,31 +971,16 @@
     LOG_NANOPB_ERROR(stream);
   } else {
     auto *info = static_cast<SeeInfoArg *>(*arg);
-    SeeCalInfo *calInfo = info->calInfo;
-    size_t calIndex = getCalIndexFromSuid(info->suid, calInfo);
-    if (calIndex >= kNumSeeCalSensors) {
-      LOGW("Cal sensor index out of bounds 0x%" PRIx64 " %" PRIx64,
-           info->suid.suid_high, info->suid.suid_low);
-    } else {
-      SeeCalData *cal = &calInfo[calIndex].cal;
+    SeeCalHelper *calHelper = info->calHelper;
 
-      cal->hasBias = (offset.index == 3);
-      if (cal->hasBias) {
-        memcpy(cal->bias, offset.val, sizeof(cal->bias));
-      }
+    bool hasBias = (offset.index == 3);
+    bool hasScale = (scale.index == 3);
+    bool hasMatrix = (matrix.index == 9);
+    uint8_t accuracy = static_cast<uint8_t>(event.status);
 
-      cal->hasScale = (scale.index == 3);
-      if (cal->hasScale) {
-        memcpy(cal->scale, scale.val, sizeof(cal->scale));
-      }
-
-      cal->hasMatrix = (matrix.index == 9);
-      if (cal->hasScale) {
-        memcpy(cal->matrix, matrix.val, sizeof(cal->matrix));
-      }
-
-      cal->accuracy = static_cast<uint8_t>(event.status);
-    }
+    calHelper->updateCalibration(
+        info->suid, hasBias, offset.val, hasScale, scale.val,
+        hasMatrix, matrix.val, accuracy);
   }
   return success;
 }
@@ -1017,7 +1014,7 @@
   } else {
     float value = static_cast<float>(event.proximity_event_type);
     auto *info = static_cast<SeeInfoArg *>(*arg);
-    populateEventSample(info->data, &value);
+    populateEventSample(info, &value);
   }
   return success;
 }
@@ -1041,6 +1038,41 @@
   return success;
 }
 
+bool decodeSnsResamplerConfigEvent(pb_istream_t *stream,
+                                   const pb_field_t *field, void **arg) {
+  sns_resampler_config_event event = {};
+
+  bool success = pb_decode(stream, sns_resampler_config_event_fields, &event);
+  if (!success) {
+    LOG_NANOPB_ERROR(stream);
+  } else {
+    auto *info = static_cast<SeeInfoArg *>(*arg);
+    LOGD("SensorType %" PRIu8 " resampler quality %" PRIu8,
+         static_cast<uint8_t>(info->data->sensorType),
+         static_cast<uint8_t>(event.quality));
+  }
+  return success;
+}
+
+/**
+ * Decode messages defined in sns_resampler.proto
+ */
+bool decodeSnsResamplerProtoEvent(pb_istream_t *stream, const pb_field_t *field,
+                                  void **arg) {
+  bool success = false;
+
+  auto *info = static_cast<SeeInfoArg *>(*arg);
+  switch (info->msgId) {
+    case SNS_RESAMPLER_MSGID_SNS_RESAMPLER_CONFIG_EVENT:
+      success = decodeSnsResamplerConfigEvent(stream, field, arg);
+      break;
+
+    default:
+      LOGW("Unhandled sns_resampler.proto msg ID %" PRIu32, info->msgId);
+  }
+  return success;
+}
+
 bool decodeSnsRemoteProcStateEvent(
     pb_istream_t *stream, const pb_field_t *field, void **arg) {
   sns_remote_proc_state_event event = sns_remote_proc_state_event_init_default;
@@ -1107,6 +1139,10 @@
         payload->funcs.decode = decodeSnsProximityProtoEvent;
         break;
 
+      case SNS_RESAMPLER_MSGID_SNS_RESAMPLER_CONFIG_EVENT:
+        payload->funcs.decode = decodeSnsResamplerProtoEvent;
+        break;
+
       default:
         success = false;
         LOGW("Unhandled msg ID %" PRIu32, info->msgId);
@@ -1192,12 +1228,6 @@
   if (suidFound) {
     LOGE("Unmatched client: %p, SUID 0x%016" PRIx64 " %016" PRIx64,
              client, suid.suid_high, suid.suid_low);
-    // TODO: remove after b/79993302 is resolved.
-    for (const auto& sensorInfo : sensorInfos) {
-      LOGE("  %p, 0x%016" PRIx64 " %016" PRIx64,
-           sensorInfo.client,
-           sensorInfo.suid.suid_high, sensorInfo.suid.suid_low);
-    }
 
     // Return SensorType in the other sns_client that matches the SUID as a
     // backup plan.
@@ -1245,6 +1275,10 @@
     case SensorSampleType::Vendor2:
       sampleSize = sizeof(chrexSensorVendor2SampleData);
       break;
+
+    case SensorSampleType::Vendor3:
+      sampleSize = sizeof(chrexSensorVendor3SampleData);
+      break;
 #endif  // CHREX_SENSOR_SUPPORT
 
     default:
@@ -1300,6 +1334,25 @@
   .sns_client_send   = sns_client_send,
 };
 
+#ifdef CHRE_SLPI_UIMG_ENABLED
+const SeeHelper::SnsClientApi BigImageSeeHelper::kQmiApi = {
+  .sns_client_init   = sns_qmi_client_init,
+  .sns_client_deinit = sns_qmi_client_deinit,
+  .sns_client_send   = sns_qmi_client_send,
+};
+#endif  // CHRE_SLPI_UIMG_ENABLED
+
+SeeHelper::SeeHelper() {
+  mCalHelper = memoryAlloc<SeeCalHelper>();
+  if (mCalHelper == nullptr) {
+    FATAL_ERROR("Failed to allocate SeeCalHelper");
+  }
+  mOwnsCalHelper = true;
+}
+
+SeeHelper::SeeHelper(SeeCalHelper *calHelper)
+    : mCalHelper(calHelper), mOwnsCalHelper(false) {}
+
 SeeHelper::~SeeHelper() {
   for (auto *client : mSeeClients) {
     int status = mSnsClientApi->sns_client_deinit(client);
@@ -1307,6 +1360,11 @@
       LOGE("Failed to release sensor client: %d", status);
     }
   }
+
+  if (mOwnsCalHelper) {
+    mCalHelper->~SeeCalHelper();
+    memoryFree(mCalHelper);
+  }
 }
 
 void SeeHelper::handleSnsClientEventMsg(
@@ -1336,7 +1394,7 @@
     data->info.data = &data->dataArg;
     data->info.decodeMsgIdOnly = true;
     data->info.remoteProcSuid = &mRemoteProcSuid;
-    data->info.calInfo = &mCalInfo[0];
+    data->info.calHelper = mCalHelper;
     data->event.events.funcs.decode = decodeSnsClientEventMsg;
     data->event.events.arg = &data->info;
 
@@ -1346,7 +1404,6 @@
     } else {
       data->info.suid = data->event.suid;
       data->info.decodeMsgIdOnly = false;
-      data->info.data->cal = getCalDataFromSuid(data->info.suid);
       data->info.data->sensorType = getSensorTypeFromSensorInfo(
           data->info.client, data->info.suid, mSensorInfos);
 
@@ -1480,7 +1537,8 @@
   return success;
 }
 
-bool SeeHelper::init(SeeHelperCallbackInterface *cbIf, Microseconds timeout) {
+bool SeeHelper::init(SeeHelperCallbackInterface *cbIf, Microseconds timeout,
+                     bool skipDefaultSensorInit) {
   CHRE_ASSERT(cbIf);
 
   mCbIf = cbIf;
@@ -1489,8 +1547,10 @@
   // Initialize cal/remote_proc_state sensors before making sensor data request.
   return (waitForService(&client, timeout)
           && mSeeClients.push_back(client)
-          && initCalSensors()
-          && initRemoteProcSensor());
+          && initResamplerSensor()
+          && (skipDefaultSensorInit
+              || (mCalHelper->registerForCalibrationUpdates(*this)
+                  && initRemoteProcSensor())));
 }
 
 bool SeeHelper::makeRequest(const SeeSensorRequest& request) {
@@ -1510,8 +1570,14 @@
       msgId = SNS_CLIENT_MSGID_SNS_CLIENT_DISABLE_REQ;
       success = true;
     } else if (sensorTypeIsContinuous(request.sensorType)) {
-      msgId = SNS_STD_SENSOR_MSGID_SNS_STD_SENSOR_CONFIG;
-      success = encodeSnsStdSensorConfig(request, &msg, &msgLen);
+      if (suidsMatch(sensorInfo->suid, mResamplerSuid.value())) {
+        msgId = SNS_RESAMPLER_MSGID_SNS_RESAMPLER_CONFIG;
+        success = encodeSnsResamplerConfig(
+            request, sensorInfo->physicalSuid, &msg, &msgLen);
+      } else {
+        msgId = SNS_STD_SENSOR_MSGID_SNS_STD_SENSOR_CONFIG;
+        success = encodeSnsStdSensorConfig(request, &msg, &msgLen);
+      }
     } else {
       msgId = SNS_STD_SENSOR_MSGID_SNS_STD_ON_CHANGE_CONFIG;
       // No sample rate needed to configure on-change or one-shot sensors.
@@ -1529,15 +1595,15 @@
   return success;
 }
 
-const sns_std_suid& SeeHelper::getCalSuidFromSensorType(
-    SensorType sensorType) const {
-  static sns_std_suid suid = sns_suid_sensor_init_zero;
-
-  size_t calIndex = getCalIndexFromSensorType(sensorType);
-  if (calIndex < kNumSeeCalSensors && mCalInfo[calIndex].suid.has_value()) {
-    suid = mCalInfo[calIndex].suid.value();
-  }
-  return suid;
+bool SeeHelper::configureOnChangeSensor(const sns_std_suid& suid, bool enable) {
+  uint32_t msgId = (enable)
+      ? SNS_STD_SENSOR_MSGID_SNS_STD_ON_CHANGE_CONFIG
+      : SNS_CLIENT_MSGID_SNS_CLIENT_DISABLE_REQ;
+  return sendReq(
+      suid, nullptr /* syncData */, nullptr /* syncDataType */,
+      msgId, nullptr /* msg */, 0 /* msgLen */,
+      false /* batchValid */, 0 /* batchPeriodUs */,
+      false /* passive */, false /* waitForIndication */);
 }
 
 /**
@@ -1582,11 +1648,19 @@
         if (!waitSuccess) {
           LOGE("SEE resp timed out after %" PRIu64 " ms",
                Milliseconds(timeoutResp).getMilliseconds());
-        } else if (mRespError != SNS_STD_ERROR_NO_ERROR) {
-          LOGE("SEE txn ID %" PRIu32 " failed with error %d",
-               mCurrentTxnId, mRespError);
+
+          if (++mNumMissingResp >= kSeeNumMissingResp) {
+            FATAL_ERROR("%" PRIu32 " consecutive missing responses",
+                        mNumMissingResp);
+          }
         } else {
-          success = true;
+          mNumMissingResp = 0;
+          if (mRespError != SNS_STD_ERROR_NO_ERROR) {
+            LOGE("SEE txn ID %" PRIu32 " failed with error %d",
+                 mCurrentTxnId, mRespError);
+          } else {
+            success = true;
+          }
         }
       }
       mWaitingOnResp = false;
@@ -1674,44 +1748,54 @@
 }
 
 bool SeeHelper::registerSensor(
-    SensorType sensorType, const sns_std_suid& suid, bool *prevRegistered) {
+    SensorType sensorType, const sns_std_suid& suid, bool resample,
+    bool *prevRegistered) {
   CHRE_ASSERT(sensorType != SensorType::Unknown);
   CHRE_ASSERT(prevRegistered != nullptr);
   bool success = false;
 
-  // Check whether the SUID/SensorType pair has been previously registered.
-  // Also count how many other SensorTypes the SUID has been registered with.
-  *prevRegistered = false;
-  size_t suidRegCount = 0;
-  for (const auto& sensorInfo : mSensorInfos) {
-    if (suidsMatch(suid, sensorInfo.suid)) {
-      suidRegCount++;
-      if (sensorInfo.sensorType == sensorType) {
-        *prevRegistered = true;
+  bool doResample = resample && sensorTypeIsContinuous(sensorType);
+  if (doResample && !mResamplerSuid.has_value()) {
+    LOGE("Unable to use resampler without its SUID");
+  } else {
+    // The SUID to make request to.
+    const sns_std_suid& reqSuid = doResample ? mResamplerSuid.value() : suid;
+
+    // Check whether the SUID/SensorType pair has been previously registered.
+    // Also count how many other SensorTypes the SUID has been registered with.
+    *prevRegistered = false;
+    size_t suidRegCount = 0;
+    for (const auto& sensorInfo : mSensorInfos) {
+      if (suidsMatch(reqSuid, sensorInfo.suid)) {
+        suidRegCount++;
+        if (sensorInfo.sensorType == sensorType) {
+          *prevRegistered = true;
+        }
       }
     }
-  }
 
-  // Initialize another SEE client if the SUID has been previously
-  // registered with more SensorTypes than the number of SEE clients can
-  // disambiguate.
-  bool clientAvailable = true;
-  if (mSeeClients.size() <= suidRegCount) {
-    sns_client *client;
-    clientAvailable = waitForService(&client);
-    if (clientAvailable) {
-      clientAvailable = mSeeClients.push_back(client);
+    // Initialize another SEE client if the SUID has been previously
+    // registered with more SensorTypes than the number of SEE clients can
+    // disambiguate.
+    bool clientAvailable = true;
+    if (mSeeClients.size() <= suidRegCount) {
+      sns_client *client;
+      clientAvailable = waitForService(&client);
+      if (clientAvailable) {
+        clientAvailable = mSeeClients.push_back(client);
+      }
     }
-  }
 
-  // Add a new entry only if this SUID/SensorType pair hasn't been registered.
-  if (!*prevRegistered && clientAvailable) {
-    SensorInfo sensorInfo = {
-      .suid = suid,
-      .sensorType = sensorType,
-      .client = mSeeClients[suidRegCount],
-    };
-    success = mSensorInfos.push_back(sensorInfo);
+    // Add a new entry only if this SUID/SensorType pair hasn't been registered.
+    if (!*prevRegistered && clientAvailable) {
+      SensorInfo sensorInfo = {
+        .suid = reqSuid,
+        .sensorType = sensorType,
+        .client = mSeeClients[suidRegCount],
+        .physicalSuid = suid,
+      };
+      success = mSensorInfos.push_back(sensorInfo);
+    }
   }
   return success;
 }
@@ -1737,50 +1821,6 @@
   return success;
 }
 
-bool SeeHelper::initCalSensors() {
-  bool success = true;
-
-  // Zero out mCalInfo to avoid accidental suid and data match.
-  memset(mCalInfo, 0, sizeof(mCalInfo));
-
-  const char *kCalTypes[] = {
-    "accel_cal",
-    "gyro_cal",
-    "mag_cal",
-  };
-
-  // Find the cal sensor's SUID, assign it to mCalInfo, and make cal sensor data
-  // request.
-  DynamicVector<sns_std_suid> suids;
-  for (size_t i = 0; i < ARRAY_SIZE(kCalTypes); i++) {
-    const char *calType = kCalTypes[i];
-    if (!findSuidSync(calType, &suids)) {
-      success = false;
-      LOGE("Failed to find sensor '%s'", calType);
-    } else {
-      size_t index = getCalIndexFromDataType(calType);
-      if (index >= kNumSeeCalSensors) {
-        success = false;
-        LOGE("Cal sensor '%s' index out of bounds", calType);
-      } else {
-        mCalInfo[index].suid = suids[0];
-
-        if (!sendReq(suids[0], nullptr /* syncData */,
-                     nullptr /* syncDataType */,
-                     SNS_STD_SENSOR_MSGID_SNS_STD_ON_CHANGE_CONFIG,
-                     nullptr /* msg */, 0 /* msgLen */,
-                     false /* batchValid */, 0 /* batchPeriodUs */,
-                     false /* passive */, false /* waitForIndication */)) {
-          success = false;
-          LOGE("Failed to request '%s' data", calType);
-        }
-      }
-    }
-  }
-
-  return success;
-}
-
 bool SeeHelper::initRemoteProcSensor() {
   bool success = false;
 
@@ -1811,9 +1851,18 @@
   return success;
 }
 
-SeeCalData *SeeHelper::getCalDataFromSuid(const sns_std_suid& suid) {
-  size_t calIndex = getCalIndexFromSuid(suid, mCalInfo);
-  return (calIndex < kNumSeeCalSensors) ? &mCalInfo[calIndex].cal : nullptr;
+bool SeeHelper::initResamplerSensor() {
+  bool success = false;
+
+  const char *kResamplerType = "resampler";
+  DynamicVector<sns_std_suid> suids;
+  if (!findSuidSync(kResamplerType, &suids)) {
+    LOGE("Failed to find sensor '%s'", kResamplerType);
+  } else {
+    mResamplerSuid = suids[0];
+    success = true;
+  }
+  return success;
 }
 
 const SeeHelper::SensorInfo *SeeHelper::getSensorInfo(
diff --git a/util/include/chre/util/array_queue.h b/util/include/chre/util/array_queue.h
index db70634..2ff6e7a 100644
--- a/util/include/chre/util/array_queue.h
+++ b/util/include/chre/util/array_queue.h
@@ -146,6 +146,11 @@
   bool emplace(Args&&... args);
 
   /**
+   * Removes all the elements of the queue.
+   */
+  void clear();
+
+  /**
    * A template class that implements a forward iterator for the array queue.
    */
   template<typename ValueType>
diff --git a/util/include/chre/util/array_queue_impl.h b/util/include/chre/util/array_queue_impl.h
index d3ba2e8..0de6341 100644
--- a/util/include/chre/util/array_queue_impl.h
+++ b/util/include/chre/util/array_queue_impl.h
@@ -27,9 +27,7 @@
 
 template<typename ElementType, size_t kCapacity>
 ArrayQueue<ElementType, kCapacity>::~ArrayQueue() {
-  while (!empty()) {
-    pop();
-  }
+  clear();
 }
 
 template<typename ElementType, size_t kCapacity>
@@ -166,6 +164,19 @@
 }
 
 template<typename ElementType, size_t kCapacity>
+void ArrayQueue<ElementType, kCapacity>::clear() {
+  if (!std::is_trivially_destructible<ElementType>::value) {
+    while (!empty()) {
+      pop();
+    }
+  } else {
+    mSize = 0;
+    mHead = 0;
+    mTail = kCapacity - 1;
+  }
+}
+
+template<typename ElementType, size_t kCapacity>
 typename ArrayQueue<ElementType, kCapacity>::iterator
 ArrayQueue<ElementType, kCapacity>::begin() {
   // Align begin() and end() outside of the memory block when empty.
diff --git a/util/include/chre/util/nanoapp/app_id.h b/util/include/chre/util/nanoapp/app_id.h
index 9ee7b18..7057954 100644
--- a/util/include/chre/util/nanoapp/app_id.h
+++ b/util/include/chre/util/nanoapp/app_id.h
@@ -65,19 +65,20 @@
   return makeNanoappId(CHRE_VENDOR_ID_GOOGLE, appNumber);
 }
 
-constexpr uint64_t kHelloWorldAppId     = makeExampleNanoappId(1);
-constexpr uint64_t kMessageWorldAppId   = makeExampleNanoappId(2);
-constexpr uint64_t kTimerWorldAppId     = makeExampleNanoappId(3);
-constexpr uint64_t kSensorWorldAppId    = makeExampleNanoappId(4);
-constexpr uint64_t kGnssWorldAppId      = makeExampleNanoappId(5);
-constexpr uint64_t kWifiWorldAppId      = makeExampleNanoappId(6);
-constexpr uint64_t kWwanWorldAppId      = makeExampleNanoappId(7);
+constexpr uint64_t kHelloWorldAppId       = makeExampleNanoappId(1);
+constexpr uint64_t kMessageWorldAppId     = makeExampleNanoappId(2);
+constexpr uint64_t kTimerWorldAppId       = makeExampleNanoappId(3);
+constexpr uint64_t kSensorWorldAppId      = makeExampleNanoappId(4);
+constexpr uint64_t kGnssWorldAppId        = makeExampleNanoappId(5);
+constexpr uint64_t kWifiWorldAppId        = makeExampleNanoappId(6);
+constexpr uint64_t kWwanWorldAppId        = makeExampleNanoappId(7);
 // 8 = reserved (previously used by ImuCal)
-constexpr uint64_t kSpammerAppId        = makeExampleNanoappId(9);
-constexpr uint64_t kUnloadTesterAppId   = makeExampleNanoappId(10);
-constexpr uint64_t kAshWorldAppId       = makeExampleNanoappId(11);
-constexpr uint64_t kAudioWorldAppId     = makeExampleNanoappId(12);
-constexpr uint64_t kHostAwakeWorldAppId = makeExampleNanoappId(13);
+constexpr uint64_t kSpammerAppId          = makeExampleNanoappId(9);
+constexpr uint64_t kUnloadTesterAppId     = makeExampleNanoappId(10);
+constexpr uint64_t kAshWorldAppId         = makeExampleNanoappId(11);
+constexpr uint64_t kAudioWorldAppId       = makeExampleNanoappId(12);
+constexpr uint64_t kHostAwakeWorldAppId   = makeExampleNanoappId(13);
+constexpr uint64_t kAudioStressTestAppId  = makeExampleNanoappId(14);
 
 }  // namespace chre
 
diff --git a/util/include/chre/util/time.h b/util/include/chre/util/time.h
index 9a2c065..0d97231 100644
--- a/util/include/chre/util/time.h
+++ b/util/include/chre/util/time.h
@@ -22,10 +22,10 @@
 namespace chre {
 
 //! The number of milliseconds in one min.
-constexpr uint64_t kOneMinuteInMillisecods(60000);
+constexpr uint64_t kOneMinuteInMilliseconds(60000);
 
 //! The number of milliseconds in one second.
-constexpr uint64_t kOneSecondInMillisecods(1000);
+constexpr uint64_t kOneSecondInMilliseconds(1000);
 
 //! The number of nanoseconds in one second.
 constexpr uint64_t kOneSecondInNanoseconds(1000000000);
@@ -60,6 +60,13 @@
    */
   constexpr uint64_t toRawNanoseconds() const;
 
+  /**
+   * Obtains the number of Milliseconds stored by this time duration.
+   *
+   * @return the value of milliseconds.
+   */
+  constexpr uint64_t getMilliseconds() const;
+
  private:
   uint64_t mSeconds;
 };
diff --git a/util/include/chre/util/time_impl.h b/util/include/chre/util/time_impl.h
index 9d8adba..7e79cf8 100644
--- a/util/include/chre/util/time_impl.h
+++ b/util/include/chre/util/time_impl.h
@@ -28,10 +28,18 @@
   // Perform the simple unit conversion. Warning: overflow is caught and
   // handled by returning UINT64_MAX. A ternary expression is used because
   // constexpr requires it.
-  return mSeconds > (UINT64_MAX / kOneSecondInNanoseconds) ? UINT64_MAX
+  return (mSeconds > (UINT64_MAX / kOneSecondInNanoseconds)) ? UINT64_MAX
       : mSeconds * kOneSecondInNanoseconds;
 }
 
+constexpr uint64_t Seconds::getMilliseconds() const {
+  // Perform the simple unit conversion. Warning: overflow is caught and
+  // handled by returning UINT64_MAX. A ternary expression is used because
+  // constexpr requires it.
+  return (mSeconds > (UINT64_MAX / kOneSecondInMilliseconds)) ? UINT64_MAX
+      : mSeconds * kOneSecondInMilliseconds;
+}
+
 constexpr Milliseconds::Milliseconds()
     : mMilliseconds(0) {}
 
@@ -51,7 +59,11 @@
 }
 
 constexpr uint64_t Milliseconds::getMicroseconds() const {
-  return mMilliseconds * kOneMillisecondInMicroseconds;
+  // Perform the simple unit conversion. Warning: overflow is caught and
+  // handled by returning UINT64_MAX. A ternary expression is used because
+  // constexpr requires it.
+  return (mMilliseconds > (UINT64_MAX / kOneMillisecondInMicroseconds))
+      ? UINT64_MAX : mMilliseconds * kOneMillisecondInMicroseconds ;
 }
 
 constexpr uint64_t Milliseconds::getMilliseconds() const {
diff --git a/util/tests/array_queue_test.cc b/util/tests/array_queue_test.cc
index 8dd631e..3f425ce 100644
--- a/util/tests/array_queue_test.cc
+++ b/util/tests/array_queue_test.cc
@@ -11,6 +11,7 @@
 constexpr int kMaxTestCapacity = 10;
 int destructor_count[kMaxTestCapacity];
 int constructor_count;
+int total_destructor_count;
 
 class DummyElement {
  public:
@@ -22,6 +23,7 @@
     constructor_count++;
   };
   ~DummyElement() {
+    total_destructor_count++;
     if (val_ >= 0 && val_ < kMaxTestCapacity) {
       destructor_count[val_]++;
     }
@@ -466,3 +468,44 @@
       std::is_same<traits::iterator_category, std::forward_iterator_tag>::value,
       "ArrayQueueIterator should be a forward iterator");
 }
+
+TEST(ArrayQueueTest, ArrayClear) {
+  ArrayQueue<size_t, 4> q;
+
+  q.clear();
+  EXPECT_TRUE(q.empty());
+
+  for (size_t i = 0; i < 4; i++) {
+    q.push(i);
+  }
+
+  q.clear();
+  EXPECT_TRUE(q.empty());
+
+  // Make sure that insertion/access still work after a clear.
+  for (size_t i = 0; i < 4; i++) {
+    q.push(i);
+  }
+  for (size_t i = 0; i < 4; i++) {
+    EXPECT_EQ(q[i], i);
+  }
+}
+
+TEST(ArrayQueueTest, ElementsDestructedArrayClear) {
+  for (size_t i = 0; i < kMaxTestCapacity; ++i) {
+    destructor_count[i] = 0;
+  }
+  total_destructor_count = 0;
+
+  ArrayQueue<DummyElement, 4> q;
+  for (size_t i = 0; i < 3; ++i) {
+    q.emplace(i);
+  }
+
+  q.clear();
+
+  for (size_t i = 0; i < 3; ++i) {
+    EXPECT_EQ(1, destructor_count[i]);
+  }
+  EXPECT_EQ(3, total_destructor_count);
+}
diff --git a/util/tests/time_test.cc b/util/tests/time_test.cc
index 94d1032..96616e4 100644
--- a/util/tests/time_test.cc
+++ b/util/tests/time_test.cc
@@ -22,10 +22,11 @@
 using chre::Milliseconds;
 using chre::Microseconds;
 using chre::Nanoseconds;
+using chre::kOneSecondInMilliseconds;
 using chre::kOneSecondInNanoseconds;
+using chre::kOneMillisecondInMicroseconds;
 using chre::kOneMillisecondInNanoseconds;
 using chre::kOneMicrosecondInNanoseconds;
-using chre::kOneMillisecondInMicroseconds;
 
 // Tests for Time constants
 TEST(Time, CheckTimeConversionConstants) {
@@ -46,6 +47,16 @@
   EXPECT_EQ(t.toRawNanoseconds(), UINT64_MAX);
 }
 
+TEST(Time, ConvertSecToMillisec) {
+  Seconds t(5);
+  EXPECT_EQ(t.getMilliseconds(), 5 * kOneSecondInMilliseconds);
+}
+
+TEST(Time, ConvertSecToMillisecOverflowIsUint64Max) {
+  Seconds t(UINT64_MAX / kOneSecondInMilliseconds + 1);
+  EXPECT_EQ(t.getMilliseconds(), UINT64_MAX);
+}
+
 // Tests for Milliseconds
 TEST(Time, DefaultMillisecIsZero) {
   Milliseconds t;
@@ -68,6 +79,11 @@
   EXPECT_EQ(t.getMicroseconds(), 5 * kOneMillisecondInMicroseconds);
 }
 
+TEST(Time, ConvertMillisecToMicrosecOverflowIsUint64Max) {
+  Milliseconds t(UINT64_MAX / kOneMillisecondInMicroseconds + 1);
+  EXPECT_EQ(t.getMicroseconds(), UINT64_MAX);
+}
+
 TEST(Time, ConvertMillisecToNanosec) {
   Milliseconds t(5);
   EXPECT_EQ(t.toRawNanoseconds(), 5 * kOneMillisecondInNanoseconds);