DO NOT MERGE: Merge Oreo MR1 into master

Exempt-From-Owner-Approval: Changes already landed internally
Change-Id: Ie577b7dd4c610be47d2c78cf65e9f85a712226f8
diff --git a/Android.bp b/Android.bp
index b578f16..70f8382 100644
--- a/Android.bp
+++ b/Android.bp
@@ -18,7 +18,6 @@
     name: "chre_client",
     vendor: true,
     export_include_dirs: [
-        "external/flatbuffers/include",
         "host/common/include",
         "platform/shared/include",
         "util/include",
@@ -28,6 +27,8 @@
         "host/common/host_protocol_host.cc",
         "platform/shared/host_protocol_common.cc",
     ],
+    header_libs: ["chre_flatbuffers"],
+    export_header_lib_headers: ["chre_flatbuffers"],
     shared_libs: [
         "libcutils",
         "liblog",
@@ -76,3 +77,23 @@
     static_libs: ["chre_client"],
     tags: ["optional"],
 }
+
+cc_library_headers {
+    name: "chre_api",
+    vendor: true,
+    export_include_dirs: [
+        "chre_api/include/chre_api",
+    ]
+}
+
+cc_library_headers {
+    name: "chre_flatbuffers",
+    vendor: true,
+    export_include_dirs: [
+        "external/flatbuffers/include",
+    ],
+}
+
+subdirs = [
+    "apps/wifi_offload",
+]
diff --git a/Makefile b/Makefile
index 386094f..1a9bdd5 100644
--- a/Makefile
+++ b/Makefile
@@ -54,8 +54,11 @@
 HEXAGON_CFLAGS += -I$(SLPI_PREFIX)/platform/rtld/inc
 HEXAGON_CFLAGS += -I$(SLPI_PREFIX)/Sensors/api
 HEXAGON_CFLAGS += -I$(SLPI_PREFIX)/Sensors/common/idl/inc
+HEXAGON_CFLAGS += -I$(SLPI_PREFIX)/Sensors/common/inc
+HEXAGON_CFLAGS += -I$(SLPI_PREFIX)/Sensors/common/smr/inc
 HEXAGON_CFLAGS += -I$(SLPI_PREFIX)/Sensors/common/util/mathtools/inc
 HEXAGON_CFLAGS += -I$(SLPI_PREFIX)/Sensors/goog/api
+HEXAGON_CFLAGS += -I$(SLPI_PREFIX)/Sensors/pm/inc
 
 # Makefile Includes ############################################################
 
@@ -80,5 +83,6 @@
 # google_cm4_nanohub as Nanohub itself is a CHRE implementation.
 include $(CHRE_PREFIX)/build/variant/google_hexagonv60_slpi.mk
 include $(CHRE_PREFIX)/build/variant/google_hexagonv62_slpi.mk
+include $(CHRE_PREFIX)/build/variant/google_hexagonv62_slpi-uimg.mk
 include $(CHRE_PREFIX)/build/variant/google_x86_linux.mk
 include $(CHRE_PREFIX)/build/variant/google_x86_googletest.mk
diff --git a/NOTICE b/NOTICE
new file mode 100644
index 0000000..6b4fac5
--- /dev/null
+++ b/NOTICE
@@ -0,0 +1,219 @@
+
+   Copyright (c) 2016-2017, The Android Open Source Project
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+
+   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.
+
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+
+Support for running dynamic modules in uImage for SLPI:
+
+Copyright (c) 2017, The Linux Foundation. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+    * Neither the name of The Linux Foundation nor the names of its
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/README.md b/README.md
index 8229adc..c0ba2f0 100644
--- a/README.md
+++ b/README.md
@@ -22,6 +22,12 @@
 
 #### Linux Build/Run
 
+The simulator uses TCLAP for command-line argument parsing. It must be available
+on the system path of the simulator. Here is an example of how to install it for
+Ubuntu:
+
+    sudo apt-get install libtclap-dev
+
 The build target for x86 linux is ``google_x86_linux``. You can build/run the
 simulator with the following command:
 
diff --git a/apps/chqts/README.md b/apps/chqts/README.md
new file mode 100644
index 0000000..9266f7d
--- /dev/null
+++ b/apps/chqts/README.md
@@ -0,0 +1,4 @@
+The subdirectories here contain the source code for nanoapps used for Context
+Hub Qualification Test Suite (CHQTS). CHQTS is a test suite used to validate
+the proper functionality of a CHRE implementation as specified in the API
+methods.
diff --git a/apps/chqts/build/busy_startup/Makefile b/apps/chqts/build/busy_startup/Makefile
new file mode 100644
index 0000000..64e8ff9
--- /dev/null
+++ b/apps/chqts/build/busy_startup/Makefile
@@ -0,0 +1,8 @@
+NANOAPP_NAME := busy_startup
+NANOAPP_SRC_FILES := busy_startup.cc
+
+NANOAPP_ID := 0x476f6f6754fffffc
+NANOAPP_VERSION := 0
+NANOAPP_NAME_STRING := \"CHQTS\ Busy\ Startup\"
+
+include ../shared_make.mk
diff --git a/apps/chqts/build/busy_startup/header b/apps/chqts/build/busy_startup/header
new file mode 100644
index 0000000..dd238c9
--- /dev/null
+++ b/apps/chqts/build/busy_startup/header
Binary files differ
diff --git a/apps/chqts/build/do_nothing/Makefile b/apps/chqts/build/do_nothing/Makefile
new file mode 100644
index 0000000..39f0417
--- /dev/null
+++ b/apps/chqts/build/do_nothing/Makefile
@@ -0,0 +1,8 @@
+NANOAPP_NAME := do_nothing
+NANOAPP_SRC_FILES := do_nothing.cc
+
+NANOAPP_ID := 0x476f6f6754fffffd
+NANOAPP_VERSION := 0
+NANOAPP_NAME_STRING := \"CHQTS\ Do\ Nothing\"
+
+include ../shared_make.mk
diff --git a/apps/chqts/build/do_nothing/header b/apps/chqts/build/do_nothing/header
new file mode 100644
index 0000000..f44a12d
--- /dev/null
+++ b/apps/chqts/build/do_nothing/header
Binary files differ
diff --git a/apps/chqts/build/fail_startup/Makefile b/apps/chqts/build/fail_startup/Makefile
new file mode 100644
index 0000000..8abfb0e
--- /dev/null
+++ b/apps/chqts/build/fail_startup/Makefile
@@ -0,0 +1,8 @@
+NANOAPP_NAME := fail_startup
+NANOAPP_SRC_FILES := fail_startup.cc
+
+NANOAPP_ID := 0x476f6f6754fffffe
+NANOAPP_VERSION := 0
+NANOAPP_NAME_STRING := \"CHQTS\ Fail\ Startup\"
+
+include ../shared_make.mk
diff --git a/apps/chqts/build/fail_startup/header b/apps/chqts/build/fail_startup/header
new file mode 100644
index 0000000..91fcc95
--- /dev/null
+++ b/apps/chqts/build/fail_startup/header
Binary files differ
diff --git a/apps/chqts/build/general_test/Makefile b/apps/chqts/build/general_test/Makefile
new file mode 100644
index 0000000..cf0f9d7
--- /dev/null
+++ b/apps/chqts/build/general_test/Makefile
@@ -0,0 +1,10 @@
+include ../general_test_sources.mk
+
+NANOAPP_NAME := general_test
+NANOAPP_SRC_FILES := $(GENERAL_TEST_SRC_FILES)
+
+NANOAPP_ID := 0x476f6f6754000000
+NANOAPP_VERSION := 0
+NANOAPP_NAME_STRING := \"CHQTS\ General\ Test\"
+
+include ../shared_make.mk
diff --git a/apps/chqts/build/general_test/header b/apps/chqts/build/general_test/header
new file mode 100644
index 0000000..32d6d61
--- /dev/null
+++ b/apps/chqts/build/general_test/header
Binary files differ
diff --git a/apps/chqts/build/general_test2/Makefile b/apps/chqts/build/general_test2/Makefile
new file mode 100644
index 0000000..498c47d
--- /dev/null
+++ b/apps/chqts/build/general_test2/Makefile
@@ -0,0 +1,11 @@
+include ../general_test_sources.mk
+
+NANOAPP_NAME := general_test2
+NANOAPP_DIR_NAME := general_test
+NANOAPP_SRC_FILES := $(GENERAL_TEST_SRC_FILES)
+
+NANOAPP_ID := 0x476f6f6754000001
+NANOAPP_VERSION := 0
+NANOAPP_NAME_STRING := \"CHQTS\ General\ Test\"
+
+include ../shared_make.mk
diff --git a/apps/chqts/build/general_test2/header b/apps/chqts/build/general_test2/header
new file mode 100644
index 0000000..aed6d1c
--- /dev/null
+++ b/apps/chqts/build/general_test2/header
Binary files differ
diff --git a/apps/chqts/build/general_test_sources.mk b/apps/chqts/build/general_test_sources.mk
new file mode 100644
index 0000000..91a30df
--- /dev/null
+++ b/apps/chqts/build/general_test_sources.mk
@@ -0,0 +1,40 @@
+# Since we add source files to this app somewhat regularly, we prefer
+# a central definition shared across all target platforms.
+
+GENERAL_TEST_SRC_FILES = \
+    app.cc \
+    basic_sensor_test_base.cc \
+    basic_sensor_tests.cc \
+    cell_info_base.cc \
+    cell_info_cdma.cc \
+    cell_info_gsm.cc \
+    cell_info_lte.cc \
+    cell_info_tdscdma.cc \
+    cell_info_wcdma.cc \
+    estimated_host_time_test.cc \
+    event_between_apps_test.cc \
+    get_time_test.cc \
+    gnss_capabilities_test.cc \
+    heap_alloc_stress_test.cc \
+    heap_exhaustion_stability_test.cc \
+    hello_world_test.cc \
+    logging_sanity_test.cc \
+    nanoapp_info.cc \
+    nanoapp_info_by_app_id_test.cc \
+    nanoapp_info_by_instance_id_test.cc \
+    nanoapp_info_events_test_observer.cc \
+    nanoapp_info_events_test_performer.cc \
+    running_info.cc \
+    send_event_test.cc \
+    send_event_stress_test.cc \
+    send_message_to_host_test.cc \
+    sensor_info_test.cc \
+    simple_heap_alloc_test.cc \
+    test.cc \
+    timer_cancel_test.cc \
+    timer_set_test.cc \
+    timer_stress_test.cc \
+    version_sanity_test.cc \
+    wifi_capabilities_test.cc \
+    wwan_capabilities_test.cc \
+    wwan_cell_info_test.cc
diff --git a/apps/chqts/build/shared_make.mk b/apps/chqts/build/shared_make.mk
new file mode 100644
index 0000000..37a8388
--- /dev/null
+++ b/apps/chqts/build/shared_make.mk
@@ -0,0 +1,38 @@
+ifndef NANOAPP_NAME
+$(error NANOAPP_NAME unset)
+endif
+
+ifndef NANOAPP_SRC_FILES
+$(error NANOAPP_SRC_FILES unset)
+endif
+
+ifndef ANDROID_BUILD_TOP
+$(error Must set Android build environment first)
+endif
+
+NANOAPP_DIR_NAME ?= $(NANOAPP_NAME)
+
+# This path is actually relative to one level deeper as this file
+# gets included from Makefile of each test subdirectory
+NANOAPP_SRC_PATH = ../../src
+
+SHARED_LIB_FILES = abort.cc \
+  dumb_allocator.cc \
+  nano_endian.cc \
+  nano_string.cc \
+  send_message.cc
+
+COMMON_SRCS += \
+  $(addprefix $(NANOAPP_SRC_PATH)/$(NANOAPP_DIR_NAME)/, $(NANOAPP_SRC_FILES)) \
+  $(addprefix $(NANOAPP_SRC_PATH)/shared/, $(SHARED_LIB_FILES))
+
+COMMON_CFLAGS += -DCHRE_NO_ENDIAN_H \
+  -D__LITTLE_ENDIAN=1 \
+  -D__BYTE_ORDER=1 \
+  -D__BIG_ENDIAN=2
+
+COMMON_CFLAGS += -I$(NANOAPP_SRC_PATH)
+
+OPT_LEVEL=2
+
+include ${ANDROID_BUILD_TOP}/system/chre/build/nanoapp/app.mk
diff --git a/apps/chqts/src/busy_startup/busy_startup.cc b/apps/chqts/src/busy_startup/busy_startup.cc
new file mode 100644
index 0000000..cf2c05e
--- /dev/null
+++ b/apps/chqts/src/busy_startup/busy_startup.cc
@@ -0,0 +1,223 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+/**
+ * Nanoapp which performs a number of operations within nanoappStart().
+ *
+ * This nanoapp is to confirm a number of CHRE methods can be invoked from
+ * within nanoappStart().  There are other tests which test each of these
+ * CHRE methods more in depth.  We're just doing a sanity check that calling
+ * from nanoappStart() works at all.
+ *
+ * Specifically, we're testing:
+ * o chreHeapAlloc() and chreHeapFree()
+ * o chreGetInstanceId()
+ * o chreSendEvent() [*]
+ * o chreTimerSet() [*]
+ * o chreSensorFindDefault() and chreSensorConfigure() [*]
+ * o chreSendMessageToHost() [**]
+ *
+ * [*] These require nanoappHandleEvent() to be called successfully in order
+ *     to confirm.
+ * [**] This is confirmed by the host receiving this message.
+ *
+ * This isn't a "general" test, so it doesn't have a standard communication
+ * protocol.  Notably, the Host doesn't send any messages to this nanoapp.
+ *
+ * Protocol:
+ * Nanoapp to Host: kContinue
+ * Nanoapp to Host: kSuccess
+ */
+
+#include <cinttypes>
+
+#include <chre.h>
+
+#include <shared/send_message.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;
+static uint32_t gSensorHandle;
+
+constexpr size_t kSelfEventStage = 0;
+constexpr size_t kTimerStage = 1;
+constexpr size_t kSensorStage = 2;
+constexpr size_t kStageCount = 3;
+
+constexpr uint32_t kAllFinished = (1 << kStageCount) - 1;
+static uint32_t gFinishedBitmask = 0;
+
+constexpr uint16_t kEventType = CHRE_EVENT_FIRST_USER_VALUE;
+
+static void markSuccess(uint32_t stage) {
+  chreLog(CHRE_LOG_DEBUG, "Stage %" PRIu32 " succeeded", stage);
+  uint32_t finishedBit = (1 << stage);
+  if ((kAllFinished & finishedBit) == 0) {
+    sendFatalFailureToHost("markSuccess bad stage", &stage);
+  }
+  gFinishedBitmask |= finishedBit;
+  if (gFinishedBitmask == kAllFinished) {
+    sendSuccessToHost();
+  }
+}
+
+static void checkSelfEvent(uint16_t eventType, const uint32_t *eventData) {
+  if (eventType != kEventType) {
+    uint32_t e = eventType;
+    sendFatalFailureToHost("Event from self, bad event type:", &e);
+  }
+  if (eventData == nullptr) {
+    sendFatalFailureToHost("Event from self, null data");
+  }
+  if (*eventData != gInstanceId) {
+    sendFatalFailureToHost("Event from self, bad data:", eventData);
+  }
+  markSuccess(kSelfEventStage);
+}
+
+static void checkTimerEvent(const uint32_t *eventData) {
+  if (eventData == nullptr) {
+    sendFatalFailureToHost("TimerEvent, null data");
+  }
+  if (*eventData != gInstanceId) {
+    sendFatalFailureToHost("TimerEvent, bad data:", eventData);
+  }
+  markSuccess(kTimerStage);
+}
+
+static void checkSensorEvent(const void *eventData) {
+  const chreSensorDataHeader* header =
+      static_cast<const chreSensorDataHeader *>(eventData);
+  if (header == nullptr) {
+    sendFatalFailureToHost("sensorEvent, null data");
+  }
+  if (header->sensorHandle != gSensorHandle) {
+    sendFatalFailureToHost("sensorEvent for wrong handle",
+                           &header->sensorHandle);
+  }
+  if (header->readingCount == 0) {
+    sendFatalFailureToHost("sensorEvent has readingCount of 0");
+  }
+  if ((header->reserved[0] != 0) || (header->reserved[1] != 0)) {
+    sendFatalFailureToHost("sensorEvent has non-zero reserved bytes");
+  }
+  markSuccess(kSensorStage);
+}
+
+extern "C" void nanoappHandleEvent(uint32_t senderInstanceId,
+                                   uint16_t eventType,
+                                   const void* eventData) {
+  if (gInMethod) {
+    sendFatalFailureToHost("CHRE reentered nanoapp");
+  }
+  gInMethod = true;
+  const uint32_t *intData = static_cast<const uint32_t *>(eventData);
+  if (senderInstanceId == gInstanceId) {
+    checkSelfEvent(eventType, intData);
+
+  } else if (senderInstanceId == CHRE_INSTANCE_ID) {
+    if (eventType == CHRE_EVENT_TIMER) {
+      checkTimerEvent(intData);
+    } else if (eventType == CHRE_EVENT_SENSOR_ACCELEROMETER_DATA) {
+      checkSensorEvent(eventData);
+    } else if (eventType == CHRE_EVENT_SENSOR_SAMPLING_CHANGE) {
+      // This could have been generated when we configured the
+      // sensor.  We just ignore it.
+    } else {
+      uint32_t e = eventType;
+      sendFatalFailureToHost("Unexpected event from CHRE:", &e);
+    }
+  } else {
+    sendFatalFailureToHost("Unexpected senderInstanceId",
+                           &senderInstanceId);
+  }
+  gInMethod = false;
+}
+
+extern "C" bool nanoappStart(void) {
+  gInMethod = true;
+  void *ptr = chreHeapAlloc(15);
+  if (ptr == nullptr) {
+    // TODO(b/32326854): We're not able to send messages from
+    //     nanoappStart(), so we just use chreLog() here, and make
+    //     the user look through the logs to determine why this failed.
+    chreLog(CHRE_LOG_ERROR, "Unable to malloc in start");
+    return false;
+  }
+  gInstanceId = chreGetInstanceId();
+  if (gInstanceId == CHRE_INSTANCE_ID) {
+    chreLog(CHRE_LOG_ERROR, "Got bad instance ID in start");
+    return false;
+  }
+
+  // Send an event to ourself.
+  if (!chreSendEvent(kEventType, &gInstanceId, nullptr, gInstanceId)) {
+    chreLog(CHRE_LOG_ERROR, "Failed chreSendEvent in start");
+    return false;
+  }
+
+  // One shot timer that should trigger very quickly.
+  gTimerId = chreTimerSet(1, &gInstanceId, true);
+  if (gTimerId == CHRE_TIMER_INVALID) {
+    chreLog(CHRE_LOG_ERROR, "Failed chreTimerSet in start");
+    return false;
+  }
+
+  // We don't have a way to confirm the 'free' worked, we'll just look
+  // to see that we didn't crash.  We intentionally move this 'free' to
+  // be not immediately after the 'alloc', and still before we're done
+  // calling other methods.
+  chreHeapFree(ptr);
+
+  // Confirm we can find and configure a sensor.
+  if (!chreSensorFindDefault(CHRE_SENSOR_TYPE_ACCELEROMETER,
+                             &gSensorHandle)) {
+    chreLog(CHRE_LOG_ERROR, "Failed sensorFindDefault in start");
+    return false;
+  }
+  if (!chreSensorConfigure(gSensorHandle,
+                           CHRE_SENSOR_CONFIGURE_MODE_CONTINUOUS,
+                           CHRE_SENSOR_INTERVAL_DEFAULT,
+                           CHRE_SENSOR_LATENCY_ASAP)) {
+    chreLog(CHRE_LOG_ERROR, "Failed sensorConfigure in start");
+    return false;
+  }
+
+  // TODO(b/32326854): Confirm we can send a message to the host.
+
+  gInMethod = false;
+  return true;
+}
+
+extern "C" void nanoappEnd(void) {
+  if (!chreSensorConfigureModeOnly(gSensorHandle,
+                                   CHRE_SENSOR_CONFIGURE_MODE_DONE)) {
+    sendFatalFailureToHost("Unable to configure sensor mode to DONE");
+  }
+
+  if (gInMethod) {
+    // This message won't be noticed by the host; but hopefully the
+    // fatal failure prevents a clean unload of the app and fails the test.
+    sendFatalFailureToHost("nanoappEnd called in reentrant manner");
+  }
+}
diff --git a/apps/chqts/src/do_nothing/do_nothing.cc b/apps/chqts/src/do_nothing/do_nothing.cc
new file mode 100644
index 0000000..cbe68c5
--- /dev/null
+++ b/apps/chqts/src/do_nothing/do_nothing.cc
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * The most trivial possible nanoapp.
+ *
+ * This successfully loads, and does nothing else.  If a CHRE implementation
+ * can't load and unload this, there's no point in testing anything else yet.
+ */
+
+#include <cstdint>
+
+#include <chre.h>
+
+extern "C" void nanoappHandleEvent(uint32_t /* senderInstanceId */,
+                                   uint16_t /* eventType */,
+                                   const void* /* eventData */) {
+  // Intentionally do nothing.
+}
+
+extern "C" bool nanoappStart(void) {
+  return true;
+}
+
+extern "C" void nanoappEnd(void) {
+  // Intentionally do nothing.
+}
diff --git a/apps/chqts/src/fail_startup/fail_startup.cc b/apps/chqts/src/fail_startup/fail_startup.cc
new file mode 100644
index 0000000..168f90b
--- /dev/null
+++ b/apps/chqts/src/fail_startup/fail_startup.cc
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Trivial nanoapp which fails on startup.
+ *
+ * Non-trivial nanoapp tests may indicate early failure by failing the
+ * nanoappStart() method.  This test assures that those failures are
+ * properly reported by the CHRE, so we know our other test failures will
+ * be caught.
+ */
+
+#include <cstdint>
+
+#include <chre.h>
+
+#include <shared/abort.h>
+
+extern "C" void nanoappHandleEvent(uint32_t /* senderInstanceId */,
+                                   uint16_t /* eventType */,
+                                   const void* /* eventData */) {
+  // Intentionally do nothing.
+}
+
+extern "C" bool nanoappStart(void) {
+  // Return failure
+  return false;
+}
+
+extern "C" void nanoappEnd(void) {
+  // It is an error for the CHRE to call this method.
+  nanoapp_testing::abort(nanoapp_testing::AbortBlame::kChreInNanoappEnd);
+}
diff --git a/apps/chqts/src/general_test/app.cc b/apps/chqts/src/general_test/app.cc
new file mode 100644
index 0000000..743bf95
--- /dev/null
+++ b/apps/chqts/src/general_test/app.cc
@@ -0,0 +1,287 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <cstddef>  // max_align_t
+#include <cstdint>
+#include <new>  // placement new
+
+#include <chre.h>
+
+#include <general_test/basic_sensor_tests.h>
+#include <general_test/estimated_host_time_test.h>
+#include <general_test/event_between_apps_test.h>
+#include <general_test/get_time_test.h>
+#include <general_test/gnss_capabilities_test.h>
+#include <general_test/heap_alloc_stress_test.h>
+#include <general_test/heap_exhaustion_stability_test.h>
+#include <general_test/hello_world_test.h>
+#include <general_test/logging_sanity_test.h>
+#include <general_test/nanoapp_info_by_app_id_test.h>
+#include <general_test/nanoapp_info_by_instance_id_test.h>
+#include <general_test/nanoapp_info_events_test_observer.h>
+#include <general_test/nanoapp_info_events_test_performer.h>
+#include <general_test/send_event_test.h>
+#include <general_test/send_event_stress_test.h>
+#include <general_test/send_message_to_host_test.h>
+#include <general_test/sensor_info_test.h>
+#include <general_test/simple_heap_alloc_test.h>
+#include <general_test/test.h>
+#include <general_test/test_names.h>
+#include <general_test/timer_cancel_test.h>
+#include <general_test/timer_set_test.h>
+#include <general_test/timer_stress_test.h>
+#include <general_test/version_sanity_test.h>
+#include <general_test/wifi_capabilities_test.h>
+#include <general_test/wwan_capabilities_test.h>
+#include <general_test/wwan_cell_info_test.h>
+#include <shared/abort.h>
+#include <shared/nano_endian.h>
+#include <shared/nano_string.h>
+#include <shared/send_message.h>
+
+using nanoapp_testing::AbortBlame;
+using nanoapp_testing::MessageType;
+using nanoapp_testing::sendFatalFailureToHost;
+using nanoapp_testing::sendInternalFailureToHost;
+
+
+namespace general_test {
+
+// The size of this array is checked at compile time by the static_assert
+// in getNew().
+alignas(alignof(max_align_t)) static uint8_t gGetNewBackingMemory[128];
+
+template<typename N>
+static N *getNew() {
+  // We intentionally avoid using chreHeapAlloc() to reduce dependencies
+  // for our tests, especially things like HelloWorld.  This obviously
+  // cannot be called more than once, but our usage doesn't require it.
+  static_assert(sizeof(gGetNewBackingMemory) >= sizeof(N),
+                "getNew() backing memory is undersized");
+
+  return new(gGetNewBackingMemory) N();
+}
+
+// TODO(b/32114261): Remove this variable.
+bool gUseNycMessageHack = true;
+
+class App {
+ public:
+  App() : mConstructionCookie(kConstructed),
+          mCurrentTest(nullptr) {}
+
+  ~App() {
+    // Yes, it's very odd to actively set a value in our destructor.
+    // However, since we're making a static instance of this class,
+    // the space for this class will stick around (unlike heap memory
+    // which might get reused), so we can still use this to perform
+    // some testing.
+    mConstructionCookie = kDestructed;
+  }
+
+  bool wasConstructed() const { return mConstructionCookie == kConstructed; }
+  bool wasDestructed() const { return mConstructionCookie == kDestructed; }
+
+  void handleEvent(uint32_t senderInstanceId, uint16_t eventType,
+                   const void *eventData);
+
+  void createTest(const void *eventData);
+  void freeTest();
+
+ private:
+  uint32_t mConstructionCookie;
+  Test *mCurrentTest;
+
+  static constexpr uint32_t kConstructed = UINT32_C(0x51501984);
+  static constexpr uint32_t kDestructed = UINT32_C(0x19845150);
+
+  // TODO(b/32114261): Remove this method.
+  chreMessageFromHostData
+      adjustHostMessageForNYC(const chreMessageFromHostData *data);
+};
+
+
+// In the NYC version of the CHRE, the "reservedMessageType" isn't
+// assured to be sent correctly from the host.  But we want our
+// tests to be written using this field (it's cleaner).  So in NYC
+// the host prefixes this value in the first four bytes of 'message',
+// and here we reconstruct the message to be correct.
+// TODO(b/32114261): Remove this method.
+chreMessageFromHostData
+App::adjustHostMessageForNYC(const chreMessageFromHostData *data) {
+  if (!gUseNycMessageHack) {
+    return *data;
+  }
+  chreMessageFromHostData ret;
+
+  if (data->messageSize < sizeof(uint32_t)) {
+    sendFatalFailureToHost("Undersized message in adjustHostMessageForNYC");
+  }
+  const uint8_t *messageBytes = static_cast<const uint8_t*>(data->message);
+  nanoapp_testing::memcpy(&(ret.reservedMessageType), messageBytes,
+                          sizeof(ret.reservedMessageType));
+  nanoapp_testing::littleEndianToHost(&(ret.reservedMessageType));
+  ret.messageSize = data->messageSize - sizeof(ret.reservedMessageType);
+  ret.message = messageBytes + sizeof(ret.reservedMessageType);
+  return ret;
+}
+
+
+void App::handleEvent(uint32_t senderInstanceId, uint16_t eventType,
+                      const void* eventData) {
+  // TODO: When we get an API that fixes the reservedMessageType,
+  //       then we should only use our adjustedData hack on APIs
+  //       prior to it being fixed.  Eventually, we should remove
+  //       this altogether.
+  chreMessageFromHostData adjustedData;
+  if (eventType == CHRE_EVENT_MESSAGE_FROM_HOST) {
+    auto data = static_cast<const chreMessageFromHostData *>(eventData);
+    adjustedData = adjustHostMessageForNYC(data);
+    eventData = &adjustedData;
+  }
+
+  if (mCurrentTest != nullptr) {
+    // Our test is in progress, so let it take control.
+    mCurrentTest->testHandleEvent(senderInstanceId, eventType, eventData);
+    return;
+  }
+
+  // No test in progress, so we expect this message to be the host telling
+  // us which test to run.  We fail if it's anything else.
+  if (eventType != CHRE_EVENT_MESSAGE_FROM_HOST) {
+    uint32_t localEventType = eventType;
+    sendFatalFailureToHost(
+        "Unexpected event type with no established test:",
+        &localEventType);
+  }
+  if (senderInstanceId != CHRE_INSTANCE_ID) {
+    sendFatalFailureToHost(
+        "Got MESSAGE_FROM_HOST not from CHRE_INSTANCE_ID:",
+        &senderInstanceId);
+  }
+  createTest(eventData);
+}
+
+void App::createTest(const void *eventData) {
+  if (mCurrentTest != nullptr) {
+    sendInternalFailureToHost(
+        "Got to createTest() with non-null mCurrentTest");
+  }
+
+  auto data = static_cast<const chreMessageFromHostData *>(eventData);
+  switch (static_cast<TestNames>(data->reservedMessageType)) {
+    using namespace general_test;
+
+#define CASE(testName, className) \
+    case TestNames::testName: \
+         mCurrentTest = getNew<className>(); \
+         break;
+
+    CASE(kHelloWorld, HelloWorldTest);
+    CASE(kSimpleHeapAlloc, SimpleHeapAllocTest);
+    CASE(kHeapAllocStress, HeapAllocStressTest);
+    CASE(kGetTime, GetTimeTest);
+    CASE(kEventBetweenApps0, EventBetweenApps0);
+    CASE(kEventBetweenApps1, EventBetweenApps1);
+    CASE(kSendEvent, SendEventTest);
+    CASE(kBasicAccelerometer, BasicAccelerometerTest);
+    CASE(kBasicInstantMotionDetect, BasicInstantMotionDetectTest);
+    CASE(kBasicStationaryDetect, BasicStationaryDetectTest);
+    CASE(kBasicGyroscope, BasicGyroscopeTest);
+    CASE(kBasicMagnetometer, BasicMagnetometerTest);
+    CASE(kBasicBarometer, BasicBarometerTest);
+    CASE(kBasicLightSensor, BasicLightSensorTest);
+    CASE(kBasicProximity, BasicProximityTest);
+    CASE(kVersionSanity, VersionSanityTest);
+    CASE(kLoggingSanity, LoggingSanityTest);
+    CASE(kSendMessageToHost, SendMessageToHostTest);
+    CASE(kTimerSet, TimerSetTest);
+    CASE(kTimerCancel, TimerCancelTest);
+    CASE(kTimerStress, TimerStressTest);
+    CASE(kSendEventStress, SendEventStressTest);
+    CASE(kHeapExhaustionStability, HeapExhaustionStabilityTest);
+    CASE(kGnssCapabilities, GnssCapabilitiesTest);
+    CASE(kWifiCapabilities, WifiCapabilitiesTest);
+    CASE(kWwanCapabilities, WwanCapabilitiesTest);
+    CASE(kSensorInfo, SensorInfoTest);
+    CASE(kWwanCellInfoTest, WwanCellInfoTest);
+    CASE(kEstimatedHostTime, EstimatedHostTimeTest);
+    CASE(kNanoappInfoByAppId, NanoappInfoByAppIdTest);
+    CASE(kNanoappInfoByInstanceId, NanoappInfoByInstanceIdTest);
+    CASE(kNanoAppInfoEventsPerformer, NanoAppInfoEventsTestPerformer);
+    CASE(kNanoAppInfoEventsObserver, NanoAppInfoEventsTestObserver);
+
+#undef CASE
+
+    default:
+    sendFatalFailureToHost("Unexpected message type:",
+                           &(data->reservedMessageType));
+  }
+
+  if (mCurrentTest != nullptr) {
+    mCurrentTest->testSetUp(data->messageSize, data->message);
+  } else {
+    sendInternalFailureToHost("createTest() ended with null mCurrentTest");
+  }
+}
+
+void App::freeTest() {
+  if (mCurrentTest == nullptr) {
+    sendInternalFailureToHost("Nanoapp unloading without running any test");
+  }
+  mCurrentTest->~Test();
+}
+
+}  // namespace general_test
+
+static general_test::App gApp;
+
+
+extern "C" void nanoappHandleEvent(uint32_t senderInstanceId,
+                                   uint16_t eventType,
+                                   const void* eventData) {
+  gApp.handleEvent(senderInstanceId, eventType, eventData);
+}
+
+static uint32_t zeroedBytes[13];
+
+extern "C" bool nanoappStart(void) {
+  // zeroedBytes is in the BSS and needs to be zero'd out.
+  for (size_t i = 0; i < sizeof(zeroedBytes)/sizeof(zeroedBytes[0]); i++) {
+    if (zeroedBytes[i] != 0) {
+      return false;
+    }
+  }
+
+  // A CHRE is required to call the constructor of our class prior to
+  // reaching this point.
+  return gApp.wasConstructed();
+}
+
+extern "C" void nanoappEnd(void) {
+  if (gApp.wasDestructed()) {
+    // It's not legal for us to send a message from here.  The best
+    // we can do is abort, although there's no means for the end user
+    // to see such a failure.
+    // TODO: Figure out how to have this failure noticed.
+    nanoapp_testing::abort(AbortBlame::kChreInNanoappEnd);
+  }
+  gApp.freeTest();
+
+  // TODO: Unclear how we can test the global destructor being called,
+  //     but that would be good to test.  Since it's supposed to happen
+  //     after this call completes, it's difficult to test.
+}
diff --git a/apps/chqts/src/general_test/basic_sensor_test_base.cc b/apps/chqts/src/general_test/basic_sensor_test_base.cc
new file mode 100644
index 0000000..3024eda
--- /dev/null
+++ b/apps/chqts/src/general_test/basic_sensor_test_base.cc
@@ -0,0 +1,450 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <general_test/basic_sensor_test_base.h>
+
+#include <cinttypes>
+#include <cstddef>
+
+#include <shared/send_message.h>
+
+#include <chre.h>
+
+using nanoapp_testing::MessageType;
+using nanoapp_testing::sendFatalFailureToHost;
+using nanoapp_testing::sendInternalFailureToHost;
+using nanoapp_testing::sendStringToHost;
+using nanoapp_testing::sendSuccessToHost;
+
+/*
+ * Our general test flow is as follows:
+ *
+ * Constructor: Send startEvent to self to start.
+ * StartEvent: Get default sensor and perform various sanity checks.  Configure
+ *    the sensor.
+ *
+ * At this point, it depends what kind of sensor we have for how we proceed
+ * with the test.
+ *
+ * One-shot: finishTest()
+ * On-change: Wait for one data event from sensor.  Then finishTest().
+ * Continuous: Wait for two data events from sensor.  Then finishTest().
+ *
+ * We also look for and perform basic sanity checking on sampling status
+ * change events, as well as bias data reports.
+ */
+
+
+namespace general_test {
+
+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
+
+uint64_t getEventDuration(const chreSensorThreeAxisData *event) {
+  uint64_t duration = 0;
+  for (size_t i = 0; i < event->header.readingCount; i++) {
+    duration += event->readings[i].timestampDelta;
+  }
+
+  return duration;
+}
+} // anonymous namespace
+
+BasicSensorTestBase::BasicSensorTestBase()
+  : Test(CHRE_API_VERSION_1_0),
+    mInMethod(true),
+    mExternalSamplingStatusChange(false),
+    mDoneWithPassiveConfigure(false),
+    mState(State::kPreStart),
+    mInstanceId(chreGetInstanceId())
+    /* All other members initialized later */ {
+}
+
+void BasicSensorTestBase::setUp(uint32_t messageSize,
+                                const void * /* message */) {
+  if (messageSize != 0) {
+    sendFatalFailureToHost(
+        "Beginning message expects 0 additional bytes, got ",
+        &messageSize);
+  }
+  // Most tests start running in the constructor.  However, since this
+  // is a base class, and we invoke abstract methods when running our
+  // test, we don't start until after the class has been fully
+  // constructed.
+  if (!chreSendEvent(kStartEvent, nullptr, nullptr, mInstanceId)) {
+    sendFatalFailureToHost("Failed chreSendEvent to begin test");
+  }
+  mInMethod = false;
+}
+
+void BasicSensorTestBase::checkPassiveConfigure() {
+  chreSensorConfigureMode mode = isOneShotSensor() ?
+      CHRE_SENSOR_CONFIGURE_MODE_PASSIVE_ONE_SHOT :
+      CHRE_SENSOR_CONFIGURE_MODE_PASSIVE_CONTINUOUS;
+
+  if (mApiVersion == CHRE_API_VERSION_1_0) {
+    // Any attempt to make a PASSIVE call with a non-default interval
+    // or latency should fail.
+    if (chreSensorConfigure(mSensorHandle, mode,
+                            CHRE_SENSOR_INTERVAL_DEFAULT, 999)) {
+      sendFatalFailureToHost("chreSensorConfigure() allowed passive with "
+                             "different latency");
+    }
+    if (chreSensorConfigure(mSensorHandle, mode,
+                            999, CHRE_SENSOR_LATENCY_DEFAULT)) {
+      sendFatalFailureToHost("chreSensorConfigure() allowed passive with "
+                             "different interval");
+    }
+    // TODO: In a more in-depth test, we should test passive mode
+    //     receiving data.  This is somewhat complicated by the fact that
+    //     pretty much by definition, we don't control whether a sensor
+    //     we're passively listening to is enabled or not.  We could try
+    //     to control this with an additional test nanoapp toggling sensor
+    //     usage, but there's still the complication of other nanoapps in
+    //     the system.
+  } else {
+    if (!chreSensorConfigure(mSensorHandle, mode,
+                             CHRE_SENSOR_INTERVAL_DEFAULT,
+                             kNanosecondsPerSecond)) {
+      sendFatalFailureToHost("chreSensorConfigure() failed passive with "
+                             "default interval and non-default latency");
+    }
+    if (!isOneShotSensor() && !chreSensorConfigure(
+        mSensorHandle, mode, kNanosecondsPerSecond,
+        CHRE_SENSOR_LATENCY_DEFAULT)) {
+      sendFatalFailureToHost("chreSensorConfigure() failed passive with "
+                             "non-default interval and default latency");
+    }
+    if (!isOneShotSensor() && !chreSensorConfigure(
+        mSensorHandle, mode, kNanosecondsPerSecond,
+        kNanosecondsPerSecond)) {
+      sendFatalFailureToHost("chreSensorConfigure() failed passive with "
+                             "non-default interval and latency");
+    }
+  }
+}
+
+void BasicSensorTestBase::startTest() {
+  mState = State::kPreConfigure;
+  if (!chreSensorFindDefault(getSensorType(), &mSensorHandle)) {
+    if (isRequiredSensor()) {
+      sendFatalFailureToHost("Sensor is required, but no default found.");
+    }
+    sendStringToHost(MessageType::kSkipped, "No default sensor found for "
+                     "optional sensor.");
+    return;
+  }
+
+  chreSensorInfo info;
+  if (!chreGetSensorInfo(mSensorHandle, &info)) {
+    sendFatalFailureToHost("GetSensorInfo() call failed");
+  }
+  if (info.sensorName == nullptr) {
+    sendFatalFailureToHost("chreSensorInfo::sensorName is NULL");
+  }
+  if (info.sensorType != getSensorType()) {
+    uint32_t type = info.sensorType;
+    sendFatalFailureToHost("chreSensorInfo::sensorType is not expected "
+                           "value, is:", &type);
+  }
+  if (info.isOnChange != isOnChangeSensor()) {
+    sendFatalFailureToHost("chreSensorInfo::isOnChange is opposite of "
+                           "what we expected");
+  }
+  if (info.isOneShot != isOneShotSensor()) {
+    sendFatalFailureToHost("chreSensorInfo::isOneShot is opposite of "
+                           "what we expected");
+  }
+
+  if (!chreGetSensorSamplingStatus(mSensorHandle, &mOriginalStatus)) {
+    sendFatalFailureToHost("chreGetSensorSamplingStatus() failed");
+  }
+
+  // Nanoapp may start getting events with a passive request. 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 */
+    CHRE_SENSOR_LATENCY_DEFAULT, /* latency */
+    true /* enabled */
+  };
+  chreSensorConfigureMode mode = isOneShotSensor() ?
+      CHRE_SENSOR_CONFIGURE_MODE_ONE_SHOT :
+      CHRE_SENSOR_CONFIGURE_MODE_CONTINUOUS;
+
+  if (!chreSensorConfigure(mSensorHandle, mode,
+                           mNewStatus.interval, mNewStatus.latency)) {
+    sendFatalFailureToHost("chreSensorConfigure() call failed with default"
+                           " interval and latency");
+  }
+  // handleEvent may start getting events, and our testing continues there.
+  // (Note: The CHRE is not allow to call handleEvent() while we're still
+  // in this method, so it's not a race to set this state here.)
+
+  // Set a new request so the test can receive events before test timeout.
+  mNewStatus = {
+    // This will be valid on all required sensors.
+    // TODO: A more in-depth test could try to change this interval
+    //     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 */
+    // 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.
+    CHRE_SENSOR_LATENCY_ASAP, /* latency */
+    true /* enabled */
+  };
+
+  // Skip one-shot sensors for non-default interval configurations.
+  if (!isOneShotSensor() && !chreSensorConfigure(
+      mSensorHandle, mode, mNewStatus.interval, mNewStatus.latency)) {
+    sendFatalFailureToHost("chreSensorConfigure() call failed");
+  }
+
+  if (isOnChangeSensor()) {
+    // We should receive the current state of this sensor after the
+    // configure call.  However, we're not assured additional events,
+    // since we don't know if this is going to change.  Thus, we jump
+    // our testing state to waiting for the last event.
+    mState = State::kExpectingLastDataEvent;
+  } else if (isOneShotSensor()) {
+    // There's no assurance we'll get any events from a one-shot
+    // sensor, so we'll just skip to the end of the test.
+    finishTest();
+  } else {
+    mState = State::kExpectingInitialDataEvent;
+  }
+}
+
+void BasicSensorTestBase::finishTest() {
+  if (!chreSensorConfigureModeOnly(mSensorHandle,
+                                   CHRE_SENSOR_CONFIGURE_MODE_DONE)) {
+    sendFatalFailureToHost("Unable to configure sensor mode to DONE");
+  }
+  mDoneTimestamp = chreGetTime();
+  chreSensorSamplingStatus status;
+  if (!chreGetSensorSamplingStatus(mSensorHandle, &status)) {
+    sendFatalFailureToHost("Could not get final sensor info");
+  }
+  if (!mExternalSamplingStatusChange) {
+    // No one else changed this, so it should be what we had before.
+    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");
+    }
+  }
+  mState = State::kFinished;
+  sendSuccessToHost();
+}
+
+void BasicSensorTestBase::sanityCheckHeader(const chreSensorDataHeader* header,
+                                            bool modifyTimestamps,
+                                            uint64_t eventDuration) {
+  if (header->sensorHandle != mSensorHandle) {
+    sendFatalFailureToHost("SensorDataHeader for wrong handle",
+                           &header->sensorHandle);
+  }
+
+  if (!isOnChangeSensor()) {
+    // An on-change sensor is supposed to send its current state, which
+    // could be timestamped in the past.  Everything else should be
+    // getting recent data.
+    uint64_t *minTime = nullptr;
+    uint64_t *timeToUpdate = nullptr;
+
+    if (mState == State::kExpectingInitialDataEvent) {
+      minTime = &mPreTimestamp;
+      timeToUpdate = &mFirstEventTimestamp;
+    } else if (mState == State::kExpectingLastDataEvent) {
+      minTime = &mFirstEventTimestamp;
+      timeToUpdate = &mLastEventTimestamp;
+    } else { // State::kFinished
+      minTime = &mLastEventTimestamp;
+      // Go ahead and update this timestamp again.
+      timeToUpdate = &mLastEventTimestamp;
+    }
+
+    // If there's another CHRE client requesting batched sensor data,
+    // baseTimestamp can be before mPreTimestamp. Also allow
+    // kEventLoopSlack to handle this nanoapp before handling the sensor
+    // event.
+    uint64_t minTimeWithSlack =
+        (*minTime > eventDuration + kEventLoopSlack) ?
+        (*minTime - eventDuration - kEventLoopSlack) : 0;
+    if (header->baseTimestamp < minTimeWithSlack) {
+      chreLog(CHRE_LOG_ERROR,
+              "baseTimestamp %" PRIu64 " < minTimeWithSlack %" PRIu64
+              ": minTime %" PRIu64 " eventDuration %" PRIu64
+              " kEventLoopSlack %" PRIu64,
+              header->baseTimestamp, minTimeWithSlack,
+              *minTime, eventDuration, kEventLoopSlack);
+      sendFatalFailureToHost("SensorDataHeader is in the past");
+    }
+    if ((mState == State::kFinished) &&
+        (header->baseTimestamp > mDoneTimestamp)) {
+      sendFatalFailureToHost("SensorDataHeader is from after DONE");
+    }
+    if (modifyTimestamps) {
+      *timeToUpdate = header->baseTimestamp;
+    }
+  }
+  if (header->readingCount == 0) {
+    sendFatalFailureToHost("SensorDataHeader has readingCount of 0");
+  }
+  if ((header->reserved[0] != 0) || (header->reserved[1] != 0)) {
+    sendFatalFailureToHost("SensorDataHeader has non-zero reserved bytes");
+  }
+}
+
+
+void BasicSensorTestBase::handleBiasEvent(
+    uint16_t eventType, const chreSensorThreeAxisData *eventData) {
+  uint8_t expectedSensorType = 0;
+  uint32_t eType = eventType;
+  if (eventType == CHRE_EVENT_SENSOR_GYROSCOPE_BIAS_INFO) {
+    expectedSensorType = CHRE_SENSOR_TYPE_GYROSCOPE;
+  } else if (eventType == CHRE_EVENT_SENSOR_GEOMAGNETIC_FIELD_BIAS_INFO) {
+    expectedSensorType = CHRE_SENSOR_TYPE_GEOMAGNETIC_FIELD;
+  } else {
+    sendInternalFailureToHost("Illegal eventType in handleBiasEvent",
+                              &eType);
+  }
+
+  if (expectedSensorType != getSensorType()) {
+    sendFatalFailureToHost("Unexpected bias event:", &eType);
+  }
+  sanityCheckHeader(&eventData->header, false, getEventDuration(eventData));
+
+  // TODO: Sanity check the eventData.  This check is out-of-scope for
+  //     Android N testing.
+}
+
+void BasicSensorTestBase::handleSamplingChangeEvent(
+    const chreSensorSamplingStatusEvent* eventData) {
+  if (eventData->sensorHandle != mSensorHandle) {
+    sendFatalFailureToHost("SamplingChangeEvent for wrong sensor handle:",
+                           &eventData->sensorHandle);
+  }
+  if (mState == State::kFinished) {
+    // TODO: If we strictly define whether this event is or isn't
+    //     generated upon being DONE with a sensor, then we can perform
+    //     a strict check here.  For now, we just let this go.
+    return;
+  }
+  // Passive sensor requests do not guarantee sensors will always be enabled.
+  // Bypass 'enabled' check for passive configurations.
+  if (mDoneWithPassiveConfigure && !eventData->status.enabled) {
+    sendFatalFailureToHost("SamplingChangeEvent disabled the sensor.");
+  }
+
+  if ((mNewStatus.interval != eventData->status.interval) ||
+      (mNewStatus.latency != eventData->status.latency)) {
+    // This is from someone other than us.  Let's note that so we know
+    // our sanity checks are invalid.
+    mExternalSamplingStatusChange = true;
+  }
+}
+
+void BasicSensorTestBase::handleSensorDataEvent(const void* eventData) {
+  if ((mState == State::kPreStart) || (mState == State::kPreConfigure)) {
+    sendFatalFailureToHost("SensorDataEvent sent too early.");
+  }
+  // Note, if mState is kFinished, we could be getting batched data which
+  // hadn't been delivered yet at the time we were DONE.  We'll sanity
+  // check it, even though in theory we're done testing.
+  uint64_t eventDuration = getEventDuration(
+      static_cast<const chreSensorThreeAxisData *>(eventData));
+  sanityCheckHeader(static_cast<const chreSensorDataHeader*>(eventData),
+                    true, eventDuration);
+
+  // Send to the sensor itself for any additional checks of actual data.
+  confirmDataIsSane(eventData);
+  if (mState == State::kExpectingInitialDataEvent) {
+    mState = State::kExpectingLastDataEvent;
+  } else if (mState == State::kExpectingLastDataEvent) {
+    finishTest();
+  } else if (mState != State::kFinished) {
+    uint32_t value = static_cast<uint32_t>(mState);
+    sendInternalFailureToHost("Illegal mState in handleSensorDataEvent:",
+                              &value);
+  }
+}
+
+void BasicSensorTestBase::handleEvent(
+    uint32_t senderInstanceId, uint16_t eventType, const void* eventData) {
+  if (mInMethod) {
+    sendFatalFailureToHost("handleEvent() invoked while already in "
+                           "method.");
+  }
+  mInMethod = true;
+  const uint16_t dataEventType =
+      CHRE_EVENT_SENSOR_DATA_EVENT_BASE + getSensorType();
+
+  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);
+
+  } else if (senderInstanceId != CHRE_INSTANCE_ID) {
+    sendFatalFailureToHost("Unexpected senderInstanceId:",
+                           &senderInstanceId);
+
+  } else if (eventData == nullptr) {
+    uint32_t eType = eventType;
+    sendFatalFailureToHost("Got NULL eventData for event:", &eType);
+
+  } else if (eventType == dataEventType) {
+    handleSensorDataEvent(eventData);
+
+  } else if (eventType == CHRE_EVENT_SENSOR_SAMPLING_CHANGE) {
+    handleSamplingChangeEvent(
+        static_cast<const chreSensorSamplingStatusEvent*>(eventData));
+
+  } else if ((eventType == CHRE_EVENT_SENSOR_GYROSCOPE_BIAS_INFO) ||
+             (eventType == CHRE_EVENT_SENSOR_GEOMAGNETIC_FIELD_BIAS_INFO)) {
+    handleBiasEvent(eventType,
+                    static_cast<const chreSensorThreeAxisData*>(eventData));
+
+  } else {
+    unexpectedEvent(eventType);
+  }
+
+  mInMethod = false;
+}
+
+}  // namespace general_test
diff --git a/apps/chqts/src/general_test/basic_sensor_test_base.h b/apps/chqts/src/general_test/basic_sensor_test_base.h
new file mode 100644
index 0000000..1ad5a61
--- /dev/null
+++ b/apps/chqts/src/general_test/basic_sensor_test_base.h
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _GTS_NANOAPPS_GENERAL_TEST_BASIC_SENSOR_TEST_BASE_H_
+#define _GTS_NANOAPPS_GENERAL_TEST_BASIC_SENSOR_TEST_BASE_H_
+
+#include <general_test/test.h>
+
+#include <chre.h>
+
+namespace general_test {
+
+/**
+ * Abstract base class for basic sensor tests.
+ *
+ * This repeats a similar test for several different sensor types.  Children
+ * classes must implement the abstract methods to define details about the
+ * sensor.
+ *
+ * This uses a Simple Protocol between the Host and Nanoapp.
+ */
+class BasicSensorTestBase : public Test {
+ public:
+  BasicSensorTestBase();
+
+ protected:
+  void handleEvent(uint32_t senderInstanceId, uint16_t eventType,
+                   const void* eventData) override;
+  void setUp(uint32_t messageSize, const void *message) override;
+
+  /**
+   * Abstract method indicating which sensor type this is.
+   *
+   * @returns One of the CHRE_SENSOR_TYPE_* constants.
+   */
+  virtual uint8_t getSensorType() const = 0;
+
+  /**
+   * Abstract method indicating if this sensor is required for a valid
+   * CHRE implementation.
+   *
+   * A CHRE isn't useful without certain sensors available.
+   *
+   * @returns true if this is sensor is required; false otherwise.
+   */
+  virtual bool isRequiredSensor() const = 0;
+
+  /**
+   * Abstract method indicating if this is an on-change sensor.
+   *
+   * @returns true if this sensor is on-change; false otherwise.
+   */
+  virtual bool isOnChangeSensor() const = 0;
+
+  /**
+   * Abstract method indicating if this is a one-shot sensor.
+   *
+   * @returns true if this sensor is one-shot; false otherwise.
+   */
+  virtual bool isOneShotSensor() const = 0;
+
+  /**
+   * Abstract method which makes sure the given data is sane.
+   *
+   * This is a very loose test, and some sensors may provide no checking
+   * at all here.  But some sensor might be able to provide a basic check
+   * (for example, a barometer claiming 0 hPa is broken (assuming the tests
+   * aren't running in outer space)).
+   *
+   * @returns If the data is absurd, this function will not return (it
+   *     will trigger a fatal error report).  This function returning
+   *     is evidence the data is sane.
+   */
+  virtual void confirmDataIsSane(const void* eventData) = 0;
+
+ private:
+  enum State {
+    kPreStart,
+    kPreConfigure,
+    kExpectingInitialDataEvent,
+    kExpectingLastDataEvent,
+    kFinished
+  };
+
+  // Catch if CHRE performs reentrant calls for handleEvent()
+  bool mInMethod;
+  // 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;
+  uint16_t mInstanceId;
+  uint32_t mSensorHandle;
+  uint64_t mPreTimestamp;
+  uint64_t mFirstEventTimestamp;
+  uint64_t mLastEventTimestamp;
+  uint64_t mDoneTimestamp;
+  chreSensorSamplingStatus mOriginalStatus;
+  chreSensorSamplingStatus mNewStatus;
+
+  void startTest();
+  void finishTest();
+  void checkPassiveConfigure();
+  void handleBiasEvent(uint16_t eventType,
+                       const chreSensorThreeAxisData* eventData);
+  void handleSamplingChangeEvent(
+      const chreSensorSamplingStatusEvent* eventData);
+  void handleSensorDataEvent(const void* eventData);
+  void sanityCheckHeader(const chreSensorDataHeader* header,
+                         bool modifyTimestamps,
+                         uint64_t eventDuration);
+};
+
+}  // namespace general_test
+
+
+#endif  // _GTS_NANOAPPS_GENERAL_TEST_BASIC_SENSOR_TEST_BASE_H_
diff --git a/apps/chqts/src/general_test/basic_sensor_tests.cc b/apps/chqts/src/general_test/basic_sensor_tests.cc
new file mode 100644
index 0000000..fd1706f
--- /dev/null
+++ b/apps/chqts/src/general_test/basic_sensor_tests.cc
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <general_test/basic_sensor_tests.h>
+
+#include <shared/send_message.h>
+
+using nanoapp_testing::sendFatalFailureToHost;
+
+namespace general_test {
+
+static void checkFloat(float value, float extremeLow, float extremeHigh) {
+  if ((value < extremeLow) || (value > extremeHigh)) {
+    uint32_t i = static_cast<uint32_t>(value);
+    sendFatalFailureToHost("Value beyond extreme.  As int:", &i);
+  }
+}
+
+static void checkTimestampDelta(uint32_t delta, uint32_t index) {
+  if (index == 0) {
+    // This delta is allowed (and expected, but not required) to be 0.
+    return;
+  }
+  if (delta == 0) {
+    sendFatalFailureToHost("timestampDelta was 0 for reading index ",
+                           &index);
+  }
+}
+
+static void sanityCheckThreeAxisData(const void *eventData, float extremeLow,
+                                     float extremeHigh) {
+  auto data = static_cast<const chreSensorThreeAxisData*>(eventData);
+  for (size_t i = 0; i < data->header.readingCount; i++) {
+    checkTimestampDelta(data->readings[i].timestampDelta, i);
+    for (size_t j = 0; j < 3; j++) {
+      checkFloat(data->readings[i].values[j], extremeLow, extremeHigh);
+    }
+  }
+}
+
+static void sanityCheckFloatData(const void *eventData, float extremeLow,
+                                 float extremeHigh) {
+  auto data = static_cast<const chreSensorFloatData*>(eventData);
+  for (size_t i = 0; i < data->header.readingCount; i++) {
+    checkTimestampDelta(data->readings[i].timestampDelta, i);
+    checkFloat(data->readings[i].value, extremeLow, extremeHigh);
+  }
+}
+
+void BasicAccelerometerTest::confirmDataIsSane(const void* eventData) {
+  constexpr float kExtreme = 70.5f;  // Apollo 16 on reentry (7.19g)
+  sanityCheckThreeAxisData(eventData, -kExtreme, kExtreme);
+}
+
+void BasicInstantMotionDetectTest::confirmDataIsSane(const void* eventData) {
+  // Nothing to sanity check.
+}
+
+void BasicStationaryDetectTest::confirmDataIsSane(const void* eventData) {
+  // Nothing to sanity check.
+}
+
+void BasicGyroscopeTest::confirmDataIsSane(const void* eventData) {
+  constexpr float kExtreme = 9420.0f;  // Zippe centrifuge
+  sanityCheckThreeAxisData(eventData, -kExtreme, kExtreme);
+}
+
+void BasicMagnetometerTest::confirmDataIsSane(const void* eventData) {
+  constexpr float kExtreme = 9400000.0f;  // Strength of medical MRI
+  sanityCheckThreeAxisData(eventData, -kExtreme, kExtreme);
+}
+
+void BasicBarometerTest::confirmDataIsSane(const void* eventData) {
+  constexpr float kExtremeLow = 337.0f;  // Mount Everest summit
+  constexpr float kExtremeHigh = 1067.0f;  // Dead Sea
+  sanityCheckFloatData(eventData, kExtremeLow, kExtremeHigh);
+}
+
+void BasicLightSensorTest::confirmDataIsSane(const void* eventData) {
+  constexpr float kExtreme = 300000.0f; // 3x the Sun
+  sanityCheckFloatData(eventData, 0.0f, kExtreme);
+}
+
+void BasicProximityTest::confirmDataIsSane(const void* eventData) {
+  auto data = static_cast<const chreSensorByteData*>(eventData);
+  for (size_t i = 0; i < data->header.readingCount; i++) {
+    checkTimestampDelta(data->readings[i].timestampDelta, i);
+    // 'invalid' is a sane reading.  But our padding should be zero'd.
+    if (data->readings[i].padding0 != 0) {
+      uint32_t padding = data->readings[i].padding0;
+      sendFatalFailureToHost("padding0 is data is non-zero:", &padding);
+    }
+  }
+}
+
+
+}  // namespace general_test
diff --git a/apps/chqts/src/general_test/basic_sensor_tests.h b/apps/chqts/src/general_test/basic_sensor_tests.h
new file mode 100644
index 0000000..cdff3cf
--- /dev/null
+++ b/apps/chqts/src/general_test/basic_sensor_tests.h
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _GTS_NANOAPPS_GENERAL_TEST_BASIC_SENSOR_TESTS_H_
+#define _GTS_NANOAPPS_GENERAL_TEST_BASIC_SENSOR_TESTS_H_
+
+#include <general_test/basic_sensor_test_base.h>
+
+#include <chre.h>
+
+namespace general_test {
+
+class BasicAccelerometerTest : public BasicSensorTestBase {
+ public:
+  BasicAccelerometerTest()
+      : BasicSensorTestBase() {}
+
+ protected:
+  uint8_t getSensorType() const override {
+    return CHRE_SENSOR_TYPE_ACCELEROMETER;
+  }
+  bool isRequiredSensor() const override { return true; }
+  bool isOnChangeSensor() const override { return false; }
+  bool isOneShotSensor() const override { return false; }
+  void confirmDataIsSane(const void* eventData) override;
+};
+
+class BasicInstantMotionDetectTest : public BasicSensorTestBase {
+ public:
+  BasicInstantMotionDetectTest()
+      : BasicSensorTestBase() {}
+
+ protected:
+  uint8_t getSensorType() const override {
+    return CHRE_SENSOR_TYPE_INSTANT_MOTION_DETECT;
+  }
+  bool isRequiredSensor() const override { return true; }
+  bool isOnChangeSensor() const override { return false; }
+  bool isOneShotSensor() const override { return true; }
+  void confirmDataIsSane(const void* eventData) override;
+};
+
+class BasicStationaryDetectTest : public BasicSensorTestBase {
+ public:
+  BasicStationaryDetectTest()
+      : BasicSensorTestBase() {}
+
+ protected:
+  uint8_t getSensorType() const override {
+    return CHRE_SENSOR_TYPE_STATIONARY_DETECT;
+  }
+  bool isRequiredSensor() const override { return true; }
+  bool isOnChangeSensor() const override { return false; }
+  bool isOneShotSensor() const override { return true; }
+  void confirmDataIsSane(const void* eventData) override;
+};
+
+class BasicGyroscopeTest : public BasicSensorTestBase {
+ public:
+  BasicGyroscopeTest()
+      : BasicSensorTestBase() {}
+
+ protected:
+  uint8_t getSensorType() const override {
+    return CHRE_SENSOR_TYPE_GYROSCOPE;
+  }
+  bool isRequiredSensor() const override { return true; }
+  bool isOnChangeSensor() const override { return false; }
+  bool isOneShotSensor() const override { return false; }
+  void confirmDataIsSane(const void* eventData) override;
+};
+
+class BasicMagnetometerTest : public BasicSensorTestBase {
+ public:
+  BasicMagnetometerTest()
+      : BasicSensorTestBase() {}
+
+ protected:
+  uint8_t getSensorType() const override {
+    return CHRE_SENSOR_TYPE_GEOMAGNETIC_FIELD;
+  }
+  bool isRequiredSensor() const override { return false; }
+  bool isOnChangeSensor() const override { return false; }
+  bool isOneShotSensor() const override { return false; }
+  void confirmDataIsSane(const void* eventData) override;
+};
+
+class BasicBarometerTest : public BasicSensorTestBase {
+ public:
+  BasicBarometerTest()
+      : BasicSensorTestBase() {}
+
+ protected:
+  uint8_t getSensorType() const override {
+    return CHRE_SENSOR_TYPE_PRESSURE;
+  }
+  bool isRequiredSensor() const override { return false; }
+  bool isOnChangeSensor() const override { return false; }
+  bool isOneShotSensor() const override { return false; }
+  void confirmDataIsSane(const void* eventData) override;
+};
+
+class BasicLightSensorTest : public BasicSensorTestBase {
+ public:
+  BasicLightSensorTest()
+      : BasicSensorTestBase() {}
+
+ protected:
+  uint8_t getSensorType() const override {
+    return CHRE_SENSOR_TYPE_LIGHT;
+  }
+  bool isRequiredSensor() const override { return false; }
+  bool isOnChangeSensor() const override { return true; }
+  bool isOneShotSensor() const override { return false; }
+  void confirmDataIsSane(const void* eventData) override;
+};
+
+class BasicProximityTest : public BasicSensorTestBase {
+ public:
+  BasicProximityTest()
+      : BasicSensorTestBase() {}
+
+ protected:
+  uint8_t getSensorType() const override {
+    return CHRE_SENSOR_TYPE_PROXIMITY;
+  }
+  bool isRequiredSensor() const override { return false; }
+  bool isOnChangeSensor() const override { return true; }
+  bool isOneShotSensor() const override { return false; }
+  void confirmDataIsSane(const void* eventData) override;
+};
+
+}  // namespace general_test
+
+
+#endif  // _GTS_NANOAPPS_GENERAL_TEST_BASIC_SENSOR_TESTS_H_
diff --git a/apps/chqts/src/general_test/cell_info_base.cc b/apps/chqts/src/general_test/cell_info_base.cc
new file mode 100644
index 0000000..34b1254
--- /dev/null
+++ b/apps/chqts/src/general_test/cell_info_base.cc
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <general_test/cell_info_base.h>
+
+#include <shared/send_message.h>
+
+namespace general_test {
+
+CellInfoBase::CellInfoBase() {
+  // Empty
+}
+
+bool CellInfoBase::isBoundedInt32(int32_t value, int32_t lower,
+                                  int32_t upper, int32_t invalid) {
+  return (((value >= lower) && (value <= upper)) || (value == invalid));
+}
+
+void CellInfoBase::sendFatalFailureInt32(const char *message, int32_t value) {
+  uint32_t val = value;
+  nanoapp_testing::sendFatalFailureToHost(message, &val);
+}
+
+void CellInfoBase::sendFatalFailureUint8(const char *message, uint8_t value) {
+  uint32_t val = value;
+  nanoapp_testing::sendFatalFailureToHost(message, &val);
+}
+
+} // namespace general_test
diff --git a/apps/chqts/src/general_test/cell_info_base.h b/apps/chqts/src/general_test/cell_info_base.h
new file mode 100644
index 0000000..c7bc133
--- /dev/null
+++ b/apps/chqts/src/general_test/cell_info_base.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef _GTS_NANOAPPS_GENERAL_TEST_CELL_INFO_BASE_H_
+#define _GTS_NANOAPPS_GENERAL_TEST_CELL_INFO_BASE_H_
+
+#include <cstdint>
+
+namespace general_test {
+
+class CellInfoBase {
+ public:
+  // TODO: A quick hack to convert to a uint32_t for sending fatal failure
+  static void sendFatalFailureInt32(const char *message, int32_t value);
+  static void sendFatalFailureUint8(const char *message, uint8_t value);
+
+ private:
+  CellInfoBase();
+
+ protected:
+  static bool isBoundedInt32(int32_t value, int32_t lower,
+                             int32_t upper, int32_t invalid);
+
+};
+
+} // namespace general_test
+
+#endif // _GTS_NANOAPPS_GENERAL_TEST_CELL_INFO_BASE_H_
diff --git a/apps/chqts/src/general_test/cell_info_cdma.cc b/apps/chqts/src/general_test/cell_info_cdma.cc
new file mode 100644
index 0000000..3bd0156
--- /dev/null
+++ b/apps/chqts/src/general_test/cell_info_cdma.cc
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <general_test/cell_info_cdma.h>
+
+namespace general_test {
+
+bool CellInfoCdma::validateIdentity(
+    const struct chreWwanCellIdentityCdma identity) {
+  bool valid = false;
+  constexpr int32_t max = INT32_MAX;
+
+  if (!isBoundedInt32(identity.networkId, 0, 65535, max)) {
+    sendFatalFailureInt32(
+        "Invalid CDMA Network Id: %d", identity.networkId);
+  } else if (!isBoundedInt32(identity.systemId, 0, 32767, max)) {
+    sendFatalFailureInt32(
+        "Invalid CDMA System Id: %d", identity.systemId);
+  } else if (!isBoundedInt32(identity.basestationId, 0, 65535, max)) {
+    sendFatalFailureInt32("Invalid CDMA Base Station Id: %d",
+                          identity.basestationId);
+  } else if (!isBoundedInt32(identity.longitude, -2592000, 2592000, max)) {
+    sendFatalFailureInt32("Invalid CDMA Longitude: %d", identity.longitude);
+  } else if (!isBoundedInt32(identity.latitude, -1296000, 1296000, max)) {
+    sendFatalFailureInt32("Invalid CDMA Latitude: %d", identity.latitude);
+  } else {
+    valid = true;
+  }
+
+  return valid;
+}
+
+bool CellInfoCdma::validateSignalStrengthCdma(
+    const struct chreWwanSignalStrengthCdma strength) {
+  bool valid = false;
+
+  // TODO: Find exact limits on dbm and ecio
+  if ((strength.dbm < 0) || (strength.dbm > 160)) {
+    sendFatalFailureInt32("Invalid CDMA/CDMA dbm: %d", strength.dbm);
+  } else if ((strength.ecio < 0) || (strength.ecio > 1600)) {
+    sendFatalFailureInt32("Invalid CDMA/CDMA ecio: %d", strength.ecio);
+  } else {
+    valid = true;
+  }
+
+  return valid;
+}
+
+bool CellInfoCdma::validateSignalStrengthEvdo(
+    const struct chreWwanSignalStrengthEvdo strength) {
+  bool valid = false;
+
+  // TODO: Find exact limits on dbm and ecio
+  if ((strength.dbm < 0) || (strength.dbm > 160)) {
+    sendFatalFailureInt32("Invalid CDMA/EVDO dbm: %d", strength.dbm);
+  } else if ((strength.ecio < 0) || (strength.ecio > 1600)) {
+    sendFatalFailureInt32("Invalid CDMA/EVDO ecio: %d", strength.ecio);
+  } else if ((strength.signalNoiseRatio < 0)
+             || (strength.signalNoiseRatio > 8)) {
+    sendFatalFailureInt32("Invalid evdo signal noise ratio: %d",
+                          strength.signalNoiseRatio);
+  } else {
+    valid = true;
+  }
+
+  return valid;
+}
+
+bool CellInfoCdma::validate(const struct chreWwanCellInfoCdma& cell) {
+  return (validateIdentity(cell.cellIdentityCdma)
+          && validateSignalStrengthCdma(cell.signalStrengthCdma)
+          && validateSignalStrengthEvdo(cell.signalStrengthEvdo));
+}
+
+} // namespace general_test
diff --git a/apps/chqts/src/general_test/cell_info_cdma.h b/apps/chqts/src/general_test/cell_info_cdma.h
new file mode 100644
index 0000000..6293ccc
--- /dev/null
+++ b/apps/chqts/src/general_test/cell_info_cdma.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef _GTS_NANOAPPS_GENERAL_TEST_CELL_INFO_CDMA_H_
+#define _GTS_NANOAPPS_GENERAL_TEST_CELL_INFO_CDMA_H_
+
+#include <general_test/cell_info_base.h>
+
+#include <chre.h>
+
+namespace general_test {
+
+class CellInfoCdma : private CellInfoBase {
+ public:
+  static bool validate(const struct chreWwanCellInfoCdma& cell);
+
+ private:
+  static bool validateIdentity(const struct chreWwanCellIdentityCdma identity);
+  static bool validateSignalStrengthCdma(
+      const struct chreWwanSignalStrengthCdma strength);
+  static bool validateSignalStrengthEvdo(
+      const struct chreWwanSignalStrengthEvdo strength);
+};
+
+} // namespace general_test
+
+#endif // _GTS_NANOAPPS_GENERAL_TEST_CELL_INFO_CDMA_H_
diff --git a/apps/chqts/src/general_test/cell_info_gsm.cc b/apps/chqts/src/general_test/cell_info_gsm.cc
new file mode 100644
index 0000000..3fbd7e2
--- /dev/null
+++ b/apps/chqts/src/general_test/cell_info_gsm.cc
@@ -0,0 +1,81 @@
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <general_test/cell_info_gsm.h>
+
+namespace general_test {
+
+bool CellInfoGsm::validateIdentity(
+    const struct chreWwanCellIdentityGsm identity) {
+  bool valid = false;
+
+  if (!isBoundedInt32(identity.mcc, 0, 999, INT32_MAX)) {
+    sendFatalFailureInt32(
+        "Invalid GSM Mobile Country Code: %d", identity.mcc);
+  } else if (!isBoundedInt32(identity.mnc, 0, 999, INT32_MAX)) {
+    sendFatalFailureInt32(
+        "Invalid GSM Mobile Network Code: %d", identity.mnc);
+  } else if (!isBoundedInt32(identity.lac, 0, 65535, INT32_MAX)) {
+    sendFatalFailureInt32("Invalid GSM Location Area Code", identity.lac);
+  } else if (!isBoundedInt32(identity.cid, 0, 65535, INT32_MAX)) {
+    sendFatalFailureInt32("Invalid GSM Cell Identity: %d", identity.cid);
+  } else if (!isBoundedInt32(identity.arfcn, 0, 65535, INT32_MAX)) {
+    sendFatalFailureInt32("Invalid GSM Absolute RF Channel Number: %d",
+                          identity.arfcn);
+  } else if ((identity.bsic > 63) && (identity.bsic != UINT8_MAX)) {
+    sendFatalFailureUint8("Invalid GSM Base Station Identity Code: %d",
+                          identity.bsic);
+  } else {
+    valid = true;
+
+    for (uint8_t byte : identity.reserved) {
+      if (byte != 0) {
+        valid = false;
+        sendFatalFailureUint8("Invalid GSM reserved byte: %d",
+                              byte);
+      }
+
+      if (!valid) {
+        break;
+      }
+    }
+  }
+
+  return valid;
+}
+
+bool CellInfoGsm::validateSignalStrength(
+    const struct chreWwanSignalStrengthGsm strength) {
+  bool valid = false;
+
+  if (!isBoundedInt32(strength.signalStrength, 0, 31, 99)) {
+    sendFatalFailureInt32("Invalid GSM signal strength: %d",
+                          strength.signalStrength);
+  } else if (!isBoundedInt32(strength.bitErrorRate, 0, 7, 99)) {
+    sendFatalFailureInt32("Invalid GSM bit error rate: %d",
+                          strength.bitErrorRate);
+  } else {
+    valid = true;
+  }
+
+  return valid;
+}
+
+bool CellInfoGsm::validate(const struct chreWwanCellInfoGsm& cell) {
+  return (validateIdentity(cell.cellIdentityGsm)
+          && validateSignalStrength(cell.signalStrengthGsm));
+}
+
+} // namespace general_test
diff --git a/apps/chqts/src/general_test/cell_info_gsm.h b/apps/chqts/src/general_test/cell_info_gsm.h
new file mode 100644
index 0000000..bd28e0a
--- /dev/null
+++ b/apps/chqts/src/general_test/cell_info_gsm.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef _GTS_NANOAPPS_GENERAL_TEST_CELL_INFO_GSM_H_
+#define _GTS_NANOAPPS_GENERAL_TEST_CELL_INFO_GSM_H_
+
+#include <general_test/cell_info_base.h>
+
+#include <chre.h>
+
+namespace general_test {
+
+class CellInfoGsm : private CellInfoBase {
+ public:
+  static bool validate(const struct chreWwanCellInfoGsm& cell);
+
+ private:
+  static bool validateIdentity(const struct chreWwanCellIdentityGsm identity);
+  static bool validateSignalStrength(
+      const struct chreWwanSignalStrengthGsm strength);
+};
+
+} // namespace general_test
+
+#endif // _GTS_NANOAPPS_GENERAL_TEST_CELL_INFO_GSM_H_
diff --git a/apps/chqts/src/general_test/cell_info_lte.cc b/apps/chqts/src/general_test/cell_info_lte.cc
new file mode 100644
index 0000000..9a1b8b9
--- /dev/null
+++ b/apps/chqts/src/general_test/cell_info_lte.cc
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <general_test/cell_info_lte.h>
+
+namespace general_test {
+
+bool CellInfoLte::validateIdentity(
+    const struct chreWwanCellIdentityLte identity) {
+  bool valid = false;
+
+  if (!isBoundedInt32(identity.mcc, 0, 999, INT32_MAX)) {
+    sendFatalFailureInt32(
+        "Invalid LTE Mobile Country Code: %d", identity.mcc);
+  } else if (!isBoundedInt32(identity.mnc, 0, 999, INT32_MAX)) {
+    sendFatalFailureInt32(
+        "Invalid LTE Mobile Network Code: %d", identity.mnc);
+  } else if (!isBoundedInt32(identity.ci, 0, 268435455, INT32_MAX)) {
+    sendFatalFailureInt32("Invalid LTE Cell Identity: %d", identity.ci);
+  } else if (!isBoundedInt32(identity.pci, 0, 503, INT32_MAX)) {
+    sendFatalFailureInt32("Invalid LTE Physical Cell Id: %d", identity.pci);
+  } else if (!isBoundedInt32(identity.tac, 0, 65535, INT32_MAX)) {
+    sendFatalFailureInt32(
+        "Invalid LTE Tracking Area Code: %d", identity.tac);
+  } else if (!isBoundedInt32(identity.earfcn, 0, 262144, INT32_MAX)) {
+    sendFatalFailureInt32("Invalid LTE Absolute RF Channel Number: %d",
+                          identity.earfcn);
+  } else {
+    valid = true;
+  }
+
+  return valid;
+}
+
+bool CellInfoLte::validateSignalStrength(
+    const struct chreWwanSignalStrengthLte strength) {
+  bool valid = false;
+  constexpr int32_t max = INT32_MAX;
+
+  if (!isBoundedInt32(strength.signalStrength, 0, 31, 99)) {
+    sendFatalFailureInt32("Invalid LTE Signal Strength: %d",
+                          strength.signalStrength);
+  } else if (!isBoundedInt32(strength.rsrp, 44, 140, max)) {
+    sendFatalFailureInt32("Invalid LTE Reference Signal Receive Power: %d",
+                          strength.rsrp);
+  } else if (!isBoundedInt32(strength.rsrq, 3, 20, max)) {
+    sendFatalFailureInt32(
+        "Invalid LTE Reference Signal Receive Quality: %d",
+        strength.rsrq);
+  } else if (!isBoundedInt32(strength.rssnr, -200, 300, max)) {
+    sendFatalFailureInt32(
+        "Invalid LTE Reference Signal Signal-to-noise Ratio: %d",
+        strength.rssnr);
+  } else if (!isBoundedInt32(strength.cqi, 0, 15, max)) {
+    sendFatalFailureInt32("Invalid LTE Channel Quality Indicator: %d",
+                          strength.cqi);
+  } else if (!isBoundedInt32(strength.timingAdvance, 0, max, max)) {
+    sendFatalFailureInt32("Invalid LTE Timing Advance (ms): %d",
+                          strength.timingAdvance);
+  } else {
+    valid = true;
+  }
+
+  return valid;
+}
+
+bool CellInfoLte::validate(const struct chreWwanCellInfoLte& cell) {
+  return (validateIdentity(cell.cellIdentityLte)
+          && validateSignalStrength(cell.signalStrengthLte));
+}
+
+} // namespace general_test
diff --git a/apps/chqts/src/general_test/cell_info_lte.h b/apps/chqts/src/general_test/cell_info_lte.h
new file mode 100644
index 0000000..d9e377d
--- /dev/null
+++ b/apps/chqts/src/general_test/cell_info_lte.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef _GTS_NANOAPPS_GENERAL_TEST_CELL_INFO_LTE_H_
+#define _GTS_NANOAPPS_GENERAL_TEST_CELL_INFO_LTE_H_
+
+#include <general_test/cell_info_base.h>
+
+#include <chre.h>
+
+namespace general_test {
+
+class CellInfoLte : private CellInfoBase {
+ public:
+  static bool validate(const struct chreWwanCellInfoLte& cell);
+
+ private:
+  static bool validateIdentity(const struct chreWwanCellIdentityLte identity);
+  static bool validateSignalStrength(
+      const struct chreWwanSignalStrengthLte strength);
+};
+
+} // namespace general_test
+
+#endif // _GTS_NANOAPPS_GENERAL_TEST_CELL_INFO_LTE_H_
diff --git a/apps/chqts/src/general_test/cell_info_tdscdma.cc b/apps/chqts/src/general_test/cell_info_tdscdma.cc
new file mode 100644
index 0000000..02df3f2
--- /dev/null
+++ b/apps/chqts/src/general_test/cell_info_tdscdma.cc
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <general_test/cell_info_tdscdma.h>
+
+namespace general_test {
+
+bool CellInfoTdscdma::validateIdentity(
+    const struct chreWwanCellIdentityTdscdma identity) {
+  bool valid = false;
+
+  if (!isBoundedInt32(identity.mcc, 0, 999, INT32_MAX)) {
+    sendFatalFailureInt32(
+        "Invalid TDSCDMA Mobile Country Code: %d", identity.mcc);
+  } else if (!isBoundedInt32(identity.mnc, 0, 999, INT32_MAX)) {
+    sendFatalFailureInt32(
+        "Invalid TDSCDMA Mobile Network Code: %d", identity.mnc);
+  } else if (!isBoundedInt32(identity.lac, 0, 65535, INT32_MAX)) {
+    sendFatalFailureInt32(
+        "Invalid TDSCDMA Location Area Code: %d", identity.lac);
+  } else if (!isBoundedInt32(identity.cid, 0, 65535, INT32_MAX)) {
+    sendFatalFailureInt32(
+        "Invalid TDSCDMA Cell Identity: %d", identity.cid);
+  } else if (!isBoundedInt32(identity.cpid, 0, 127, INT32_MAX)) {
+    sendFatalFailureInt32(
+        "Invalid TDSCDMA Cell Parameters ID: %d", identity.cpid);
+  } else {
+    valid = true;
+  }
+
+  return valid;
+}
+
+bool CellInfoTdscdma::validateSignalStrength(
+    const struct chreWwanSignalStrengthTdscdma strength) {
+  bool valid = false;
+
+  if (!isBoundedInt32(strength.rscp, 25, 120, INT32_MAX)) {
+    sendFatalFailureInt32("Invalid TDSCDMA Received Signal Code Power: %d",
+                          strength.rscp);
+  } else {
+    valid = true;
+  }
+
+  return valid;
+}
+
+bool CellInfoTdscdma::validate(const struct chreWwanCellInfoTdscdma& cell) {
+  return (validateIdentity(cell.cellIdentityTdscdma)
+          && validateSignalStrength(cell.signalStrengthTdscdma));
+}
+
+} // namespace general_test
diff --git a/apps/chqts/src/general_test/cell_info_tdscdma.h b/apps/chqts/src/general_test/cell_info_tdscdma.h
new file mode 100644
index 0000000..186c3a7
--- /dev/null
+++ b/apps/chqts/src/general_test/cell_info_tdscdma.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef _GTS_NANOAPPS_GENERAL_TEST_CELL_INFO_TDSCDMA_H_
+#define _GTS_NANOAPPS_GENERAL_TEST_CELL_INFO_TDSCDMA_H_
+
+#include <general_test/cell_info_base.h>
+
+#include <chre.h>
+
+namespace general_test {
+
+class CellInfoTdscdma : private CellInfoBase {
+ public:
+  static bool validate(const struct chreWwanCellInfoTdscdma& cell);
+
+ private:
+  static bool validateIdentity(
+      const struct chreWwanCellIdentityTdscdma identity);
+  static bool validateSignalStrength(
+      const struct chreWwanSignalStrengthTdscdma strength);
+};
+
+} // namespace general_test
+
+#endif // _GTS_NANOAPPS_GENERAL_TEST_CELL_INFO_TDSCDMA_H_
diff --git a/apps/chqts/src/general_test/cell_info_wcdma.cc b/apps/chqts/src/general_test/cell_info_wcdma.cc
new file mode 100644
index 0000000..fbf2647
--- /dev/null
+++ b/apps/chqts/src/general_test/cell_info_wcdma.cc
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <general_test/cell_info_wcdma.h>
+
+namespace general_test {
+
+bool CellInfoWcdma::validateIdentity(
+    const struct chreWwanCellIdentityWcdma identity) {
+  bool valid = false;
+
+  if (!isBoundedInt32(identity.mcc, 0, 999, INT32_MAX)) {
+    sendFatalFailureInt32(
+        "Invalid WCDMA Mobile Country Code: %d", identity.mcc);
+  } else if (!isBoundedInt32(identity.mnc, 0, 999, INT32_MAX)) {
+    sendFatalFailureInt32(
+        "Invalid WCDMA Mobile Network Code: %d", identity.mnc);
+  } else if (!isBoundedInt32(identity.lac, 0, 65535, INT32_MAX)) {
+    sendFatalFailureInt32(
+        "Invalid WCDMA Location Area Code: %d", identity.lac);
+  } else if (!isBoundedInt32(identity.cid, 0, 268435455, INT32_MAX)) {
+    sendFatalFailureInt32("Invalid WCDMA Cell Identity: %d", identity.cid);
+  } else if (!isBoundedInt32(identity.psc, 0, 511, INT32_MAX)) {
+    sendFatalFailureInt32("Invalid WCDMA Primary Scrambling Code: %d",
+                          identity.psc);
+  } else if (!isBoundedInt32(identity.uarfcn, 0, 65535, INT32_MAX)) {
+    sendFatalFailureInt32("Invalid WCDMA Absolute RF Channel Number: %d",
+                          identity.uarfcn);
+  } else {
+    valid = true;
+  }
+
+  return valid;
+}
+
+bool CellInfoWcdma::validateSignalStrength(
+    const struct chreWwanSignalStrengthWcdma strength) {
+  bool valid = false;
+
+  if (!isBoundedInt32(strength.signalStrength, 0, 31, 99)) {
+    sendFatalFailureInt32("Invalid WCDMA Signal Strength: %d",
+                          strength.signalStrength);
+  } else if (!isBoundedInt32(strength.bitErrorRate, 0, 7, 99)) {
+    sendFatalFailureInt32("Invalid WCDMA Bit Error Rate: %d",
+                          strength.bitErrorRate);
+  } else {
+    valid = true;
+  }
+
+  return valid;
+}
+
+bool CellInfoWcdma::validate(const struct chreWwanCellInfoWcdma& cell) {
+  return (validateIdentity(cell.cellIdentityWcdma)
+          && validateSignalStrength(cell.signalStrengthWcdma));
+}
+
+} // namespace general_test
diff --git a/apps/chqts/src/general_test/cell_info_wcdma.h b/apps/chqts/src/general_test/cell_info_wcdma.h
new file mode 100644
index 0000000..41db5fb
--- /dev/null
+++ b/apps/chqts/src/general_test/cell_info_wcdma.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef _GTS_NANOAPPS_GENERAL_TEST_CELL_INFO_WCDMA_H_
+#define _GTS_NANOAPPS_GENERAL_TEST_CELL_INFO_WCDMA_H_
+
+#include <general_test/cell_info_base.h>
+
+#include <chre.h>
+
+namespace general_test {
+
+class CellInfoWcdma : private CellInfoBase {
+ public:
+  static bool validate(const struct chreWwanCellInfoWcdma& cell);
+
+ private:
+  static bool validateIdentity(
+      const struct chreWwanCellIdentityWcdma identity);
+  static bool validateSignalStrength(
+      const struct chreWwanSignalStrengthWcdma strength);
+};
+
+} // namespace general_test
+
+#endif // _GTS_NANOAPPS_GENERAL_TEST_CELL_INFO_WCDMA_H_
diff --git a/apps/chqts/src/general_test/estimated_host_time_test.cc b/apps/chqts/src/general_test/estimated_host_time_test.cc
new file mode 100644
index 0000000..885eb26
--- /dev/null
+++ b/apps/chqts/src/general_test/estimated_host_time_test.cc
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <general_test/estimated_host_time_test.h>
+
+#include <shared/nano_endian.h>
+#include <shared/nano_string.h>
+#include <shared/send_message.h>
+
+#include <chre.h>
+
+namespace general_test {
+
+EstimatedHostTimeTest::EstimatedHostTimeTest()
+    : Test(CHRE_API_VERSION_1_1),
+      mTimerHandle(CHRE_TIMER_INVALID),
+      mRemainingIterations(25) {
+}
+
+void EstimatedHostTimeTest::setUp(uint32_t /* messageSize */,
+                                  const void * /* message */) {
+  mPriorHostTime = chreGetEstimatedHostTime();
+
+  constexpr uint64_t timerInterval = 100000000; // 100 ms
+
+  mTimerHandle = chreTimerSet(timerInterval, &mTimerHandle,
+                              false /* oneShot */);
+
+  if (mTimerHandle == CHRE_TIMER_INVALID) {
+    nanoapp_testing::sendFatalFailureToHost(
+        "Unable to set timer for time verification");
+  }
+}
+
+void EstimatedHostTimeTest::handleEvent(uint32_t senderInstanceId,
+                                        uint16_t eventType,
+                                        const void *eventData) {
+  if (eventType == CHRE_EVENT_TIMER) {
+    verifyIncreasingTime();
+  } else {
+    // Verify application processor time is within reason
+    uint64_t currentHostTime = chreGetEstimatedHostTime();
+
+    // TODO: Estimate message RTT to allow stricter accuracy check
+    constexpr uint64_t timeDelta = 50000000; // 50 ms
+
+    uint64_t givenHostTime;
+    const void *message =
+        getMessageDataFromHostEvent(senderInstanceId, eventType,
+                                    eventData,
+                                    nanoapp_testing::MessageType::kContinue,
+                                    sizeof(givenHostTime));
+
+    nanoapp_testing::memcpy(&givenHostTime, message, sizeof(givenHostTime));
+    nanoapp_testing::littleEndianToHost(&givenHostTime);
+
+    if (currentHostTime >= givenHostTime) {
+      if ((currentHostTime - givenHostTime) <= timeDelta) {
+        nanoapp_testing::sendSuccessToHost();
+      } else {
+        nanoapp_testing::sendFatalFailureToHost(
+            "Current time is too far behind of host time");
+      }
+    } else if ((givenHostTime - currentHostTime) <= timeDelta) {
+      nanoapp_testing::sendSuccessToHost();
+    } else {
+      nanoapp_testing::sendFatalFailureToHost(
+          "Current time is too far ahead of host time");
+    }
+  }
+}
+
+void EstimatedHostTimeTest::verifyIncreasingTime() {
+  if (mRemainingIterations > 0) {
+    uint64_t currentHostTime = chreGetEstimatedHostTime();
+
+    if (currentHostTime > mPriorHostTime) {
+      chreTimerCancel(mTimerHandle);
+      nanoapp_testing::sendMessageToHost(
+          nanoapp_testing::MessageType::kContinue);
+    } else {
+      mPriorHostTime = currentHostTime;
+    }
+
+    --mRemainingIterations;
+  } else {
+    nanoapp_testing::sendFatalFailureToHost(
+        "Unable to verify increasing time");
+  }
+}
+
+} // namespace general_test
diff --git a/apps/chqts/src/general_test/estimated_host_time_test.h b/apps/chqts/src/general_test/estimated_host_time_test.h
new file mode 100644
index 0000000..58659ea
--- /dev/null
+++ b/apps/chqts/src/general_test/estimated_host_time_test.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or aggreed 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_GENERAL_TEST_ESTIMATED_HOST_TIME_TEST_H_
+#define _GTS_NANOAPPS_GENERAL_TEST_ESTIMATED_HOST_TIME_TEST_H_
+
+#include <general_test/test.h>
+
+#include <cstdint>
+
+namespace general_test {
+
+/*
+ * Verify estimated host time from nanoapp.
+ *
+ * Fundamentally, there are two phases to this test:
+ *   1) Verify that time does increase at some point
+ *   2) Verify that AP time is "close" to what nanoapp can get
+ *
+ * Protocol:
+ * host to app: ESITMATED_HOST_TIME, no data
+ * app to host: CONTINUE
+ * host to app: CONTINUE, 64-bit time
+ * app to host: SUCCESS
+ */
+class EstimatedHostTimeTest : public Test {
+ public:
+  EstimatedHostTimeTest();
+
+ protected:
+  void handleEvent(uint32_t senderInstanceId, uint16_t eventType,
+                   const void *eventData) override;
+  void setUp(uint32_t messageSize, const void *message) override;
+
+ private:
+  void verifyIncreasingTime();
+
+  uint32_t mTimerHandle;
+  uint32_t mRemainingIterations;
+  uint64_t mPriorHostTime;
+};
+
+} // namespace general_test
+
+#endif // _GTS_NANOAPPS_GENERAL_TEST_ESTIMATED_HOST_TIME_TEST_H_
diff --git a/apps/chqts/src/general_test/event_between_apps_test.cc b/apps/chqts/src/general_test/event_between_apps_test.cc
new file mode 100644
index 0000000..b79fe47
--- /dev/null
+++ b/apps/chqts/src/general_test/event_between_apps_test.cc
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <general_test/event_between_apps_test.h>
+
+#include <general_test/nanoapp_info.h>
+
+#include <cstddef>
+
+#include <shared/abort.h>
+#include <shared/nano_endian.h>
+#include <shared/nano_string.h>
+#include <shared/send_message.h>
+
+#include <chre.h>
+
+using nanoapp_testing::MessageType;
+using nanoapp_testing::sendFatalFailureToHost;
+using nanoapp_testing::sendSuccessToHost;
+
+namespace general_test {
+
+// Arbitrary, just to confirm our data is properly sent.
+const uint32_t EventBetweenApps0::kMagic = UINT32_C(0x51501984);
+
+EventBetweenApps0::EventBetweenApps0()
+    : Test(CHRE_API_VERSION_1_0), mContinueCount(0) {
+}
+
+void EventBetweenApps0::setUp(uint32_t messageSize,
+                              const void * /* message */) {
+  if (messageSize != 0) {
+    sendFatalFailureToHost(
+        "Initial message expects 0 additional bytes, got ",
+        &messageSize);
+  }
+
+  NanoappInfo info;
+  info.sendToHost();
+}
+
+void EventBetweenApps0::handleEvent(uint32_t senderInstanceId,
+                                    uint16_t eventType, const void* eventData) {
+  uint32_t app1InstanceId;
+  const void *message =
+      getMessageDataFromHostEvent(senderInstanceId, eventType, eventData,
+                                  MessageType::kContinue,
+                                  sizeof(app1InstanceId));
+  if (mContinueCount > 0) {
+    sendFatalFailureToHost("Multiple kContinue messages sent");
+  }
+
+  mContinueCount++;
+  nanoapp_testing::memcpy(&app1InstanceId, message, sizeof(app1InstanceId));
+  nanoapp_testing::littleEndianToHost(&app1InstanceId);
+  // It's safe to strip the 'const' because we're using nullptr for our
+  // free callback.
+  uint32_t *sendData = const_cast<uint32_t*>(&kMagic);
+  // Send an event to app1.  Note since app1 is on the same system, there are
+  // no endian concerns for our sendData.
+  chreSendEvent(kEventType, sendData, nullptr, app1InstanceId);
+}
+
+EventBetweenApps1::EventBetweenApps1()
+  : Test(CHRE_API_VERSION_1_0)
+    , mApp0InstanceId(CHRE_INSTANCE_ID)
+    , mReceivedInstanceId(CHRE_INSTANCE_ID) {
+}
+
+void EventBetweenApps1::setUp(uint32_t messageSize,
+                              const void * /* message */) {
+  if (messageSize != 0) {
+    sendFatalFailureToHost(
+        "Initial message expects 0 additional bytes, got ",
+        &messageSize);
+  }
+
+  NanoappInfo appInfo;
+  appInfo.sendToHost();
+}
+
+void EventBetweenApps1::handleEvent(uint32_t senderInstanceId,
+                                    uint16_t eventType, const void* eventData) {
+  if (eventType == CHRE_EVENT_MESSAGE_FROM_HOST) {
+    const void *message =
+        getMessageDataFromHostEvent(senderInstanceId, eventType, eventData,
+                                    MessageType::kContinue,
+                                    sizeof(mApp0InstanceId));
+    // We expect kContinue once, with the app0's instance ID as data.
+    if (mApp0InstanceId != CHRE_INSTANCE_ID) {
+      // We know app0's instance ID can't be CHRE_INSTANCE_ID, otherwise
+      // we would have aborted this test in commonInit().
+      sendFatalFailureToHost("Multiple kContinue messages from host.");
+    }
+    nanoapp_testing::memcpy(&mApp0InstanceId, message,
+                            sizeof(mApp0InstanceId));
+    nanoapp_testing::littleEndianToHost(&mApp0InstanceId);
+
+  } else if (eventType == EventBetweenApps0::kEventType) {
+    if (mReceivedInstanceId != CHRE_INSTANCE_ID) {
+      sendFatalFailureToHost("Multiple messages from other nanoapp.");
+    }
+    if (senderInstanceId == CHRE_INSTANCE_ID) {
+      sendFatalFailureToHost("Received event from other nanoapp with "
+                             "CHRE_INSTANCE_ID for sender");
+    }
+    mReceivedInstanceId = senderInstanceId;
+    uint32_t magic;
+    nanoapp_testing::memcpy(&magic, eventData, sizeof(magic));
+    if (magic != EventBetweenApps0::kMagic) {
+      sendFatalFailureToHost("Got incorrect magic data: ", &magic);
+    }
+
+  } else {
+    unexpectedEvent(eventType);
+  }
+
+  if ((mApp0InstanceId != CHRE_INSTANCE_ID)
+      && (mReceivedInstanceId != CHRE_INSTANCE_ID)) {
+    if (mApp0InstanceId == mReceivedInstanceId) {
+      sendSuccessToHost();
+    } else {
+      sendFatalFailureToHost("Got bad sender instance ID for nanoapp "
+                             "event: ", &mReceivedInstanceId);
+    }
+  }
+}
+
+}  // namespace general_test
diff --git a/apps/chqts/src/general_test/event_between_apps_test.h b/apps/chqts/src/general_test/event_between_apps_test.h
new file mode 100644
index 0000000..a736ff6
--- /dev/null
+++ b/apps/chqts/src/general_test/event_between_apps_test.h
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _GTS_NANOAPPS_GENERAL_TEST_EVENT_BETWEEN_APPS_TEST_H_
+#define _GTS_NANOAPPS_GENERAL_TEST_EVENT_BETWEEN_APPS_TEST_H_
+
+#include <general_test/test.h>
+#include <shared/send_message.h>
+
+namespace general_test {
+
+/**
+ * Send CHRE event to another nanoapp.
+ *
+ * Protocol:
+ * This is nanoapp app0.  This test also involves nanoapp app1.
+ * All data to/from Host is in little endian.
+ *
+ * Host to app0:  kEventBetweenApps0, no data
+ * app0 to Host:  kContinue, 64-bit app ID, 32-bit instance ID
+ * Host to app0:  kContinue, app1's 32-bit instance ID
+ * app0 to app1:  kEventType, kMagic
+ */
+class EventBetweenApps0 : public Test {
+ public:
+  EventBetweenApps0();
+
+  // Arbitrary as long as it's different from
+  // CHRE_EVENT_MESSAGE_FROM_HOST (which this value assures us).
+  static constexpr uint16_t kEventType = CHRE_EVENT_FIRST_USER_VALUE;
+
+  // NOTE: This is not constexpr, so we have storage for it.
+  static const uint32_t kMagic;
+
+ protected:
+  void handleEvent(uint32_t senderInstanceId, uint16_t eventType,
+                   const void* eventData) override;
+  void setUp(uint32_t messageSize, const void *message) override;
+
+ private:
+  int mContinueCount;
+};
+
+/**
+ * Receive CHRE event from another nanopp.
+ *
+ * Protocol:
+ * This is nanoapp app1.  This test also involves nanoapp app0.
+ * All data to/from Host is in little endian.
+ *
+ * Host to app1:  kEventBetweenApps1, no data
+ * app1 to Host:  kContinue, 64-bit app ID, 32-bit instance ID
+ * [NOTE: Next two event can happen in any order]
+ * Host to app1:  kContinue, app0's 32-bit instance ID
+ * app0 to app1:  kEventType, EventBetweenApps1::kMagic
+ * app1 to Host:  kSuccess, no data
+ */
+class EventBetweenApps1 : public Test {
+ public:
+  EventBetweenApps1();
+
+ protected:
+  void handleEvent(uint32_t senderInstanceId, uint16_t eventType,
+                   const void* eventData) override;
+  void setUp(uint32_t messageSize, const void *message) override;
+
+ private:
+  uint32_t mApp0InstanceId;
+  uint32_t mReceivedInstanceId;
+};
+
+}  // namespace general_test
+
+#endif  // _GTS_NANOAPPS_GENERAL_TEST_EVENT_BETWEEN_APPS_TEST_H_
diff --git a/apps/chqts/src/general_test/get_time_test.cc b/apps/chqts/src/general_test/get_time_test.cc
new file mode 100644
index 0000000..943c5af
--- /dev/null
+++ b/apps/chqts/src/general_test/get_time_test.cc
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <general_test/get_time_test.h>
+
+#include <cstddef>
+
+#include <shared/abort.h>
+#include <shared/nano_endian.h>
+#include <shared/send_message.h>
+
+#include <chre.h>
+
+using nanoapp_testing::MessageType;
+using nanoapp_testing::sendFatalFailureToHost;
+
+namespace general_test {
+
+GetTimeTest::GetTimeTest()
+    : Test(CHRE_API_VERSION_1_0), mContinueCount(0) {
+}
+
+void GetTimeTest::setUp(uint32_t messageSize, const void * /* message */) {
+  if (messageSize != 0) {
+    sendFatalFailureToHost(
+        "GetTime message expects 0 additional bytes, got ",
+        &messageSize);
+  }
+
+  uint64_t firstTime = chreGetTime();
+  if (firstTime == UINT64_C(0)) {
+    sendFatalFailureToHost("chreGetTime() gave 0 well after system boot.");
+  }
+
+  uint64_t baseTime = firstTime;
+  uint64_t nextTime;
+  for (size_t i = 0; i < 10; i++) {
+    nextTime = chreGetTime();
+    // We don't require this to have increased, because maybe we're
+    // on a relatively fast processor, or have a low resolution clock.
+    if (nextTime < baseTime) {
+      sendFatalFailureToHost(
+          "chreGetTime() is not monotonically increasing");
+    }
+    baseTime = nextTime;
+  }
+  // But if after ten iterations of the loop we never incremented the
+  // time, that seems highly suspicious.
+  if (nextTime == firstTime) {
+    sendFatalFailureToHost("chreGetTime() is not increasing.");
+  }
+
+  nanoapp_testing::hostToLittleEndian(&nextTime);
+  sendMessageToHost(MessageType::kContinue, &nextTime, sizeof(nextTime));
+
+  // Now we'll wait to get a 'continue' from the host.
+}
+
+void GetTimeTest::handleEvent(uint32_t senderInstanceId,
+                              uint16_t eventType, const void* eventData) {
+  // We ignore the return value, since we expect no data.
+  getMessageDataFromHostEvent(senderInstanceId, eventType, eventData,
+                              MessageType::kContinue, 0);
+  if (mContinueCount > 0) {
+    sendFatalFailureToHost("Multiple kContinue messages sent");
+  }
+
+  mContinueCount++;
+  uint64_t time = chreGetTime();
+  nanoapp_testing::hostToLittleEndian(&time);
+  sendMessageToHost(MessageType::kContinue, &time, sizeof(time));
+  // We do nothing else in the CHRE.  It's up to the Host to declare
+  // if we've passed.
+}
+
+}  // namespace general_test
diff --git a/apps/chqts/src/general_test/get_time_test.h b/apps/chqts/src/general_test/get_time_test.h
new file mode 100644
index 0000000..3af6ece
--- /dev/null
+++ b/apps/chqts/src/general_test/get_time_test.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _GTS_NANOAPPS_GENERAL_TEST_GET_TIME_TEST_H_
+#define _GTS_NANOAPPS_GENERAL_TEST_GET_TIME_TEST_H_
+
+#include <general_test/test.h>
+
+namespace general_test {
+
+/**
+ * Checks that chreGetTime() is reasonable.
+ *
+ * We check that it's monotonically increasing, and mostly in line with
+ * the application processor's notion of time passing.
+ *
+ * Protocol:
+ * Host:    kGetTimeTest, no data
+ * Nanoapp: kContinue, 64-bit timestamp (little endian)
+ * [2.5 second pause]
+ * Host:    kContinue, no data
+ * Nanoapp: kContinue, 64-bit timestamp (little endian)
+ * [Host declares pass]
+ */
+class GetTimeTest : public Test {
+ public:
+  GetTimeTest();
+
+ protected:
+  void handleEvent(uint32_t senderInstanceId, uint16_t eventType,
+                   const void* eventData) override;
+  void setUp(uint32_t messageSize, const void *message) override;
+
+ private:
+  int mContinueCount;
+};
+
+}  // namespace general_test
+
+
+#endif  // _GTS_NANOAPPS_GENERAL_TEST_GET_TIME_TEST_H_
diff --git a/apps/chqts/src/general_test/gnss_capabilities_test.cc b/apps/chqts/src/general_test/gnss_capabilities_test.cc
new file mode 100644
index 0000000..93e21c5
--- /dev/null
+++ b/apps/chqts/src/general_test/gnss_capabilities_test.cc
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <general_test/gnss_capabilities_test.h>
+
+#include <chre.h>
+
+#include <shared/send_message.h>
+
+namespace general_test {
+
+GnssCapabilitiesTest::GnssCapabilitiesTest()
+    : Test(CHRE_API_VERSION_1_1) {
+}
+
+void GnssCapabilitiesTest::setUp(uint32_t messageSize,
+                                 const void * /* message */) {
+  if (messageSize != 0) {
+    nanoapp_testing::sendFatalFailureToHost(
+        "Expected 0 byte message, got more bytes:", &messageSize);
+  } else {
+    uint32_t allCapabilities = CHRE_GNSS_CAPABILITIES_NONE;
+
+    if (mApiVersion >= CHRE_API_VERSION_1_1) {
+      allCapabilities |= CHRE_GNSS_CAPABILITIES_LOCATION
+          | CHRE_GNSS_CAPABILITIES_MEASUREMENTS;
+    }
+
+    // Call the new API
+    uint32_t capabilities = chreGnssGetCapabilities();
+
+    // Clear out known capabilities, any remaining are unknown
+    if ((capabilities & ~allCapabilities) != 0) {
+      if (mApiVersion > CHRE_API_VERSION_1_1) {
+        nanoapp_testing::sendFatalFailureToHost(
+            "New version with unknown capabilities encountered:",
+            &capabilities);
+      } else {
+        nanoapp_testing::sendFatalFailureToHost(
+            "Received unexpected capabilities:", &capabilities);
+      }
+    } else {
+      nanoapp_testing::sendSuccessToHost();
+    }
+  }
+}
+
+void GnssCapabilitiesTest::handleEvent(uint32_t /* senderInstanceId */,
+                                       uint16_t eventType,
+                                       const void * /* eventData */) {
+  unexpectedEvent(eventType);
+}
+
+} // namespace general_test
diff --git a/apps/chqts/src/general_test/gnss_capabilities_test.h b/apps/chqts/src/general_test/gnss_capabilities_test.h
new file mode 100644
index 0000000..21c89d7
--- /dev/null
+++ b/apps/chqts/src/general_test/gnss_capabilities_test.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _GTS_NANOAPPS_GENERAL_TEST_GNSS_CAPABILITIES_TEST_H_
+#define _GTS_NANOAPPS_GENERAL_TEST_GNSS_CAPABILITIES_TEST_H_
+
+#include <cstdint>
+
+#include <general_test/test.h>
+
+namespace general_test {
+
+/**
+ * Confirms that GNSS capabilities exist
+ *
+ * Simple protocol
+ * Host   : kGnssCapabilities, no data
+ * Nanoapp: kSuccess, no data
+ */
+class GnssCapabilitiesTest : public Test {
+ public:
+  GnssCapabilitiesTest();
+
+ protected:
+  void handleEvent(uint32_t senderInstanceId, uint16_t eventType,
+                   const void *eventData) override;
+  void setUp(uint32_t messageSize, const void *message) override;
+};
+
+} // namespace general_test
+
+#endif // _GTS_NANOAPPS_GENERAL_TEST_GNSS_CAPABILITIES_TEST_H_
diff --git a/apps/chqts/src/general_test/heap_alloc_stress_test.cc b/apps/chqts/src/general_test/heap_alloc_stress_test.cc
new file mode 100644
index 0000000..4240e4f
--- /dev/null
+++ b/apps/chqts/src/general_test/heap_alloc_stress_test.cc
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <general_test/heap_alloc_stress_test.h>
+
+#include <cstddef>
+
+#include <general_test/test_names.h>
+#include <shared/abort.h>
+#include <shared/send_message.h>
+
+#include <chre.h>
+
+using nanoapp_testing::sendFailureToHost;
+using nanoapp_testing::sendFatalFailureToHost;
+using nanoapp_testing::sendSuccessToHost;
+
+namespace general_test {
+
+static void tryAbsurdMalloc(uint32_t hugeSize) {
+  void *ptr = chreHeapAlloc(hugeSize);
+  if (ptr != NULL) {
+    sendFailureToHost("chreHeapAlloc claimed allocation of huge size ",
+                      &hugeSize);
+    chreHeapFree(ptr);
+    nanoapp_testing::abort();
+  }
+}
+
+HeapAllocStressTest::HeapAllocStressTest()
+  : Test(CHRE_API_VERSION_1_0) {
+}
+
+void HeapAllocStressTest::setUp(uint32_t messageSize,
+                                const void * /* message */) {
+  if (messageSize != 0) {
+    sendFatalFailureToHost(
+        "HeapAllocStress message expects 0 additional bytes, got ",
+        &messageSize);
+  }
+
+  // 1GB should be absurd on any CHRE implementation we anticipate for a
+  // while.
+  tryAbsurdMalloc(UINT32_C(0x40000000));
+
+  // Let's also make sure there's nothing treating this as signed behind
+  // the scenes and breaking things.
+  tryAbsurdMalloc(UINT32_C(-16));
+
+  // Since NULL is a valid response to chreHeapAlloc(), chreHeapFree()
+  // must accept it as an argument.
+  chreHeapFree(NULL);
+
+  // We do not test chreHeapFree() with invalid pointers, because that's
+  // an error by the caller, and there's no requirement for the CHRE
+  // implementation to handle it nicely.
+
+
+  // Now let's exhaust the heap, and make sure it properly frees up to allow
+  // things to be allocated again.
+  constexpr size_t kNumPtrs = 256;
+  void **ptrs = reinterpret_cast<void**>(
+      chreHeapAlloc(kNumPtrs * sizeof(void*)));
+  if (ptrs == NULL) {
+    // Oh, the irony.
+    sendFatalFailureToHost(
+        "Insufficient free heap to test heap exhaustion.");
+  }
+
+  size_t index;
+  uint32_t last_alloc_size = 1024 * 1024 * 256;
+  for (index = 0; (index < kNumPtrs); index++) {
+    uint32_t curr_alloc_size = last_alloc_size;
+    void *ptr = chreHeapAlloc(curr_alloc_size);
+    while (ptr == NULL) {
+      curr_alloc_size /= 2;
+      if (curr_alloc_size < 16) {
+        break;
+      }
+      ptr = chreHeapAlloc(curr_alloc_size);
+    }
+    if (ptr == NULL) {
+      break;
+    }
+    last_alloc_size = curr_alloc_size;
+    ptrs[index] = ptr;
+  }
+  if (index == 0) {
+    sendFatalFailureToHost(
+        "Failed to allocate anything for heap exhaustion");
+  }
+
+  // We should be able to free this allocation, and then obtain it again.
+  index--;
+  chreHeapFree(ptrs[index]);
+  ptrs[index] = chreHeapAlloc(last_alloc_size);
+  if (ptrs[index] == NULL) {
+    sendFatalFailureToHost(
+        "After exhausting heap and then free'ing, unable to alloc "
+        "again for size ", &last_alloc_size);
+  }
+
+  // Everything's good, let's free up our memory.
+  for (size_t i = 0; i <= index; i++) {
+    chreHeapFree(ptrs[i]);
+  }
+  chreHeapFree(ptrs);
+
+  sendSuccessToHost();
+}
+
+void HeapAllocStressTest::handleEvent(uint32_t /* senderInstanceId */,
+                                      uint16_t eventType,
+                                      const void* /* eventData */) {
+  unexpectedEvent(eventType);
+}
+
+}  // namespace general_test
diff --git a/apps/chqts/src/general_test/heap_alloc_stress_test.h b/apps/chqts/src/general_test/heap_alloc_stress_test.h
new file mode 100644
index 0000000..1aa5c5e
--- /dev/null
+++ b/apps/chqts/src/general_test/heap_alloc_stress_test.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _GTS_NANOAPPS_GENERAL_TEST_HEAP_ALLOC_STRESS_TEST_H_
+#define _GTS_NANOAPPS_GENERAL_TEST_HEAP_ALLOC_STRESS_TEST_H_
+
+#include <general_test/test.h>
+
+namespace general_test {
+
+/**
+ * Stresses the heap alloc implementation.
+ *
+ * We request extreme allocation sizes, exhaust the heap, and make
+ * sure things continue to work.
+ *
+ * Simple Protocol.
+ */
+class HeapAllocStressTest : public Test {
+ public:
+  HeapAllocStressTest();
+
+ protected:
+  void handleEvent(uint32_t senderInstanceId, uint16_t eventType,
+                   const void* eventData) override;
+  void setUp(uint32_t messageSize, const void *message) override;
+};
+
+}  // namespace general_test
+
+
+#endif  // _GTS_NANOAPPS_GENERAL_TEST_HEAP_ALLOC_STRESS_TEST_H_
diff --git a/apps/chqts/src/general_test/heap_exhaustion_stability_test.cc b/apps/chqts/src/general_test/heap_exhaustion_stability_test.cc
new file mode 100644
index 0000000..9828a3a
--- /dev/null
+++ b/apps/chqts/src/general_test/heap_exhaustion_stability_test.cc
@@ -0,0 +1,285 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <general_test/heap_exhaustion_stability_test.h>
+
+#include <cinttypes>
+#include <cstddef>
+
+#include <shared/send_message.h>
+
+#include <chre.h>
+
+using nanoapp_testing::sendFailureToHost;
+using nanoapp_testing::sendFatalFailureToHost;
+using nanoapp_testing::sendSuccessToHost;
+
+/*
+ * We set an "exhaustion timer" to go off when we're ready for the test to
+ * be over.  Then we exhaust the heap.
+ *
+ * We try a series of chre*() calls with the heap exhausted.  For many of
+ * these calls, we're less interested in them succeeding than in the system
+ * just not crashing.  However, for things which claim success, we require
+ * they succeed.
+ *
+ * To track the things which claim success, we have two "stages", kTimerStage
+ * and kEventStage.
+ *
+ * When the "exhaustion timer" fires, we free our memory, and make sure our
+ * stages have all succeeded.
+ */
+
+namespace general_test {
+
+// Note: We use pointers to the 'duration' to serve as our timer event data.
+// 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);
+
+constexpr uint16_t kEventType = CHRE_EVENT_FIRST_USER_VALUE;
+
+constexpr uint32_t kTimerStage = 0;
+constexpr uint32_t kEventStage = 1;
+
+void HeapExhaustionStabilityTest::exhaustHeap() {
+  constexpr size_t kNumPtrs = 256;
+  mExhaustionPtrs = reinterpret_cast<void**>(
+      chreHeapAlloc(kNumPtrs * sizeof(*mExhaustionPtrs)));
+  if (mExhaustionPtrs == nullptr) {
+    // Oh, the irony.
+    sendFatalFailureToHost(
+        "Insufficient free heap to exhaust the heap.");
+  }
+
+  // We start by trying to allocate massive sizes (256MB to start).
+  // When we're not able to allocate massive sizes, we cut the size in
+  // half.  We repeat until we've either done kNumPtrs allocations,
+  // or reduced our allocation size below 16 bytes.
+  uint32_t allocSize = 1024 * 1024 * 256;
+  for (mExhaustionPtrCount = 0;
+       mExhaustionPtrCount < kNumPtrs;
+       mExhaustionPtrCount++) {
+    void *ptr = chreHeapAlloc(allocSize);
+    while (ptr == nullptr) {
+      allocSize /= 2;
+      if (allocSize < 4) {
+        break;
+      }
+      ptr = chreHeapAlloc(allocSize);
+    }
+    if (ptr == nullptr) {
+      break;
+    }
+    mExhaustionPtrs[mExhaustionPtrCount] = ptr;
+  }
+  if (mExhaustionPtrCount == 0) {
+    sendFatalFailureToHost(
+        "Failed to allocate anything for heap exhaustion");
+  }
+}
+
+void HeapExhaustionStabilityTest::freeMemory() {
+  for (size_t i = 0; i < mExhaustionPtrCount; i++) {
+    chreHeapFree(mExhaustionPtrs[i]);
+  }
+  chreHeapFree(mExhaustionPtrs);
+}
+
+HeapExhaustionStabilityTest::HeapExhaustionStabilityTest()
+  : Test(CHRE_API_VERSION_1_0) {
+}
+
+void HeapExhaustionStabilityTest::setUp(uint32_t messageSize,
+                                        const void * /* message */) {
+  mInMethod = true;
+  if (messageSize != 0) {
+    sendFatalFailureToHost(
+        "HeapExhaustionStability message expects 0 additional bytes, "
+        "got ", &messageSize);
+  }
+
+  if (chreTimerSet(kExhaustionDuration, &kExhaustionDuration, true) ==
+      CHRE_TIMER_INVALID) {
+    sendFatalFailureToHost("Unable to set initial timer");
+  }
+
+  exhaustHeap();
+
+  testLog(messageSize);
+  testSetTimer();
+  testSendEvent();
+  testSensor();
+  // TODO(b/32114261): This method currently doesn't test anything.
+  testMessageToHost();
+
+  // Some of the above 'test' methods might trigger events.  Even if they
+  // don't, the kExhaustionDuration timer we set earlier should trigger
+  // eventually, and that's when we'll conclude the test.
+  mInMethod = false;
+}
+
+void HeapExhaustionStabilityTest::testLog(uint32_t zero) {
+  // This doesn't need to land in the log (and indeed we have no automated
+  // means of checking that right now anyway), but it shouldn't crash.
+  chreLog(CHRE_LOG_INFO, "Test log %s, zero: %" PRId32, "message", zero);
+}
+
+void HeapExhaustionStabilityTest::testSetTimer() {
+  if (chreTimerSet(kShortDuration, &kShortDuration, true) !=
+      CHRE_TIMER_INVALID) {
+    // CHRE claims we were able to set this timer.  We'll
+    // mark this stage a success when the timer fires.
+  } else {
+    // CHRE was not able to set this timer.  That's okay, since we're
+    // out of heap.  We'll mark this stage as a success.
+    markSuccess(kTimerStage);
+  }
+}
+
+void HeapExhaustionStabilityTest::testSendEvent() {
+  if (chreSendEvent(kEventType, nullptr, nullptr, chreGetInstanceId())) {
+    // CHRE claims we were able to send this event.  We'll make
+    // this stage a success when the event is received.
+  } else {
+    // CHRE was not able to send this event.  That's okay, since we're
+    // out of heap.  We'll mark this stage as a success.
+    markSuccess(kEventStage);
+  }
+}
+
+void HeapExhaustionStabilityTest::testSensor() {
+  static constexpr uint8_t kSensorType = CHRE_SENSOR_TYPE_ACCELEROMETER;
+  uint32_t handle;
+  if (!chreSensorFindDefault(kSensorType, &handle)) {
+    // We still expect this to succeed without any heap left.
+    sendFatalFailureToHost("chreSensorFindDefault failed");
+  }
+  chreSensorInfo info;
+  if (!chreGetSensorInfo(handle, &info)) {
+    // We still expect this to succeed, since we're supplying the memory.
+    sendFatalFailureToHost("chreGetSensorInfo failed");
+  }
+  if (info.sensorType != kSensorType) {
+    sendFatalFailureToHost("Invalid sensor info provided");
+  }
+
+  chreSensorSamplingStatus samplingStatus;
+  if (!chreGetSensorSamplingStatus(handle, &samplingStatus)) {
+    // We still expect this to succeed, since we're supplying the memory.
+    sendFatalFailureToHost("chreGetSensorSamplingStatus failed");
+  }
+
+  // TODO: We might want to consider calling chreSensorConfigure() for a
+  //     more robust test of this.  However, we don't expect sensor events to
+  //     necessarily get delivered under heap exhaustion, so it's unclear
+  //     how we'd make sure we eventually tell the system we're DONE with
+  //     the sensor (setting a timer isn't assured to work at this point).
+}
+
+void HeapExhaustionStabilityTest::testMessageToHost() {
+  // TODO(b/32114261): We should invoke sendMessageToHost() here.
+  //     Unfortunately, this is a real pain due to this bug, as we need to
+  //     duplicate much of the contents of shared/send_message.cc to
+  //     add the hack-around bytes (the method itself will internally
+  //     fail if the send attempt fails, but we're in a state where
+  //     we'll allow a failed send attempt).  Or we need to take this
+  //     off of the General test infrastructure to allow raw byte sending.
+  //     That seems not worth the effort for NYC, and just easier to wait
+  //     until OMC when this is much easier to implement.
+  // OMC Note: When we've fixed this bug, and added a send here, we'll
+  //     need to make this no longer Simple protocol, since this nanoapp
+  //     might send a message.
+}
+
+void HeapExhaustionStabilityTest::handleEvent(uint32_t senderInstanceId,
+                                              uint16_t eventType,
+                                              const void* eventData) {
+  if (mInMethod) {
+    sendFatalFailureToHost("handleEvent invoked while another nanoapp "
+                           "method is running");
+  }
+  mInMethod = true;
+
+  if (eventType == CHRE_EVENT_TIMER) {
+    handleTimer(senderInstanceId, eventData);
+  } else if (eventType == kEventType) {
+    handleSelfEvent(senderInstanceId, eventData);
+  } else {
+    unexpectedEvent(eventType);
+  }
+  mInMethod = false;
+}
+
+void HeapExhaustionStabilityTest::handleTimer(uint32_t senderInstanceId,
+                                              const void *eventData) {
+  if (senderInstanceId != CHRE_INSTANCE_ID) {
+    sendFatalFailureToHost("handleTimer with unexpected sender:",
+                           &senderInstanceId);
+  }
+  if (eventData == &kShortDuration) {
+    // This was the timer we triggered while the heap was exhausted.
+    markSuccess(kTimerStage);
+
+  } else if (eventData == &kExhaustionDuration) {
+    // Our test is done.
+    freeMemory();
+    if (mFinishedBitmask != kAllFinished) {
+      sendFatalFailureToHost("Done with test, but not all stages "
+                             "done.", &mFinishedBitmask);
+    }
+    sendSuccessToHost();
+
+  } else {
+    sendFatalFailureToHost("Unexpected timer eventData");
+  }
+}
+
+void HeapExhaustionStabilityTest::handleSelfEvent(uint32_t senderInstanceId,
+                                                  const void *eventData) {
+  if (senderInstanceId != chreGetInstanceId()) {
+    sendFatalFailureToHost("handleSelfEvent with unexpected sender:",
+                           &senderInstanceId);
+  }
+  if (eventData != nullptr) {
+    sendFatalFailureToHost("Unexpected data for event to self");
+  }
+  markSuccess(kEventStage);
+}
+
+void HeapExhaustionStabilityTest::markSuccess(uint32_t stage) {
+  chreLog(CHRE_LOG_DEBUG, "Stage %" PRIu32 " succeeded", stage);
+  uint32_t finishedBit = (1 << stage);
+  if ((kAllFinished & finishedBit) == 0) {
+    sendFatalFailureToHost("markSuccess bad stage", &stage);
+  }
+  if ((mFinishedBitmask & finishedBit) != 0) {
+    // This could be when a timer/event method returned 'false', but
+    // actually did end up triggering an event.
+    sendFatalFailureToHost("markSuccess stage triggered twice", &stage);
+  }
+  mFinishedBitmask |= finishedBit;
+  // Note that unlike many markSuccess() implementations, we do not
+  // check against kAllFinished here.  That happens when the
+  // timer for kExhaustionDuration fires.
+}
+
+
+}  // namespace general_test
diff --git a/apps/chqts/src/general_test/heap_exhaustion_stability_test.h b/apps/chqts/src/general_test/heap_exhaustion_stability_test.h
new file mode 100644
index 0000000..130f5f4
--- /dev/null
+++ b/apps/chqts/src/general_test/heap_exhaustion_stability_test.h
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _GTS_NANOAPPS_GENERAL_TEST_HEAP_EXHAUSTION_STABILITY_TEST_H_
+#define _GTS_NANOAPPS_GENERAL_TEST_HEAP_EXHAUSTION_STABILITY_TEST_H_
+
+#include <general_test/test.h>
+
+namespace general_test {
+
+/**
+ * Exhaust the heap and confirm the platform remains stable when trying
+ * various things.
+ *
+ * We don't require everything to be available when the heap is exhausted,
+ * but we do require the system is honest about its capabilities, and doesn't
+ * crash.
+ *
+ * Simple Protocol.
+ */
+class HeapExhaustionStabilityTest : public Test {
+ public:
+  HeapExhaustionStabilityTest();
+
+ protected:
+  void handleEvent(uint32_t senderInstanceId, uint16_t eventType,
+                   const void* eventData) override;
+  void setUp(uint32_t messageSize, const void *message) override;
+
+ private:
+  bool mInMethod;
+
+  void **mExhaustionPtrs;
+  size_t mExhaustionPtrCount;
+
+  static constexpr size_t kStageCount = 2;
+  static constexpr uint32_t kAllFinished = (1 << kStageCount) - 1;
+  uint32_t mFinishedBitmask;
+
+  void exhaustHeap();
+  void freeMemory();
+
+  void testLog(uint32_t zero);
+  void testSetTimer();
+  void testSendEvent();
+  void testSensor();
+  // TODO(b/32114261): This method currently doesn't test anything.
+  void testMessageToHost();
+
+  void handleTimer(uint32_t senderInstanceId, const void *eventData);
+  void handleSelfEvent(uint32_t senderInstanceId, const void *eventData);
+
+  void markSuccess(uint32_t stage);
+};
+
+}  // namespace general_test
+
+
+#endif  // _GTS_NANOAPPS_GENERAL_TEST_HEAP_EXHAUSTION_STABILITY_TEST_H_
diff --git a/apps/chqts/src/general_test/hello_world_test.cc b/apps/chqts/src/general_test/hello_world_test.cc
new file mode 100644
index 0000000..b476efd
--- /dev/null
+++ b/apps/chqts/src/general_test/hello_world_test.cc
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <general_test/hello_world_test.h>
+
+#include <shared/send_message.h>
+
+using nanoapp_testing::sendFatalFailureToHost;
+using nanoapp_testing::sendSuccessToHost;
+
+namespace general_test {
+
+HelloWorldTest::HelloWorldTest()
+    : Test(CHRE_API_VERSION_1_0) {
+}
+
+void HelloWorldTest::setUp(uint32_t messageSize, const void * /* message */) {
+  if (messageSize != 0) {
+    sendFatalFailureToHost("Expected 0 byte message, got more bytes:",
+                           &messageSize);
+  } else {
+    sendSuccessToHost();
+  }
+}
+
+void HelloWorldTest::handleEvent(uint32_t /* senderInstanceId */,
+                                 uint16_t eventType,
+                                 const void* /* eventData */) {
+  unexpectedEvent(eventType);
+}
+
+}  // namespace general_test
diff --git a/apps/chqts/src/general_test/hello_world_test.h b/apps/chqts/src/general_test/hello_world_test.h
new file mode 100644
index 0000000..20c1610
--- /dev/null
+++ b/apps/chqts/src/general_test/hello_world_test.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _GTS_NANOAPPS_GENERAL_TEST_HELLO_WORLD_TEST_H_
+#define _GTS_NANOAPPS_GENERAL_TEST_HELLO_WORLD_TEST_H_
+
+#include <general_test/test.h>
+
+namespace general_test {
+
+/**
+ * Confirms a simple message from the host makes it to the app.
+ *
+ * Simple Protocol.  That is:
+ * Host:    kHelloWorld, no data
+ * Nanoapp: kSuccess, no data
+ */
+class HelloWorldTest : public Test {
+ public:
+  HelloWorldTest();
+
+ protected:
+  void handleEvent(uint32_t senderInstanceId, uint16_t eventType,
+                   const void* eventData) override;
+  void setUp(uint32_t messageSize, const void *message) override;
+};
+
+}  // namespace general_test
+
+
+#endif  // _GTS_NANOAPPS_GENERAL_TEST_HELLO_WORLD_TEST_H_
diff --git a/apps/chqts/src/general_test/logging_sanity_test.cc b/apps/chqts/src/general_test/logging_sanity_test.cc
new file mode 100644
index 0000000..ddbd03d
--- /dev/null
+++ b/apps/chqts/src/general_test/logging_sanity_test.cc
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <general_test/logging_sanity_test.h>
+
+#include <cstddef>
+#include <limits>
+
+#include <shared/send_message.h>
+
+#include <chre.h>
+
+using nanoapp_testing::sendFatalFailureToHost;
+using nanoapp_testing::sendSuccessToHost;
+
+namespace general_test {
+
+LoggingSanityTest::LoggingSanityTest()
+    : Test(CHRE_API_VERSION_1_0) {
+}
+
+void LoggingSanityTest::setUp(uint32_t messageSize,
+                              const void * /* message */) {
+  if (messageSize != 0) {
+    sendFatalFailureToHost(
+        "LoggingSanity message expects 0 additional bytes, got ",
+        &messageSize);
+  }
+
+  // Test each warning level.
+  chreLog(CHRE_LOG_ERROR, "Level: Error");
+  chreLog(CHRE_LOG_WARN, "Level: Warn");
+  chreLog(CHRE_LOG_INFO, "Level: Info");
+  chreLog(CHRE_LOG_DEBUG, "Level: Debug");
+
+  // Now we'll just test everything with INFO.
+  constexpr chreLogLevel kInfo = CHRE_LOG_INFO;
+
+  // Empty string
+  chreLog(kInfo, "");
+
+  // Try up through 10 arguments
+  chreLog(kInfo, "%d", 1);
+  chreLog(kInfo, "%d %d", 1, 2);
+  chreLog(kInfo, "%d %d %d", 1, 2, 3);
+  chreLog(kInfo, "%d %d %d %d", 1, 2, 3, 4);
+  chreLog(kInfo, "%d %d %d %d %d", 1, 2, 3, 4, 5);
+  chreLog(kInfo, "%d %d %d %d %d %d", 1, 2, 3, 4, 5, 6);
+  chreLog(kInfo, "%d %d %d %d %d %d %d", 1, 2, 3, 4, 5, 6, 7);
+  chreLog(kInfo, "%d %d %d %d %d %d %d %d", 1, 2, 3, 4, 5, 6, 7, 8);
+  chreLog(kInfo, "%d %d %d %d %d %d %d %d %d", 1, 2, 3, 4, 5, 6, 7, 8, 9);
+  chreLog(kInfo, "%d %d %d %d %d %d %d %d %d %d", 1, 2, 3, 4, 5, 6, 7, 8, 9,
+          10);
+
+  // Various 'int' specifiers.  The value of the "%u" output depends on the
+  // size of 'int' on this machine.
+  chreLog(kInfo, "%d %u 0%o 0x%x 0x%X", -1, -1, 01234, 0xF4E, 0xF4E);
+
+  // Generic testing of all specific types.  The format string is the same
+  // as the chreLog() above us, just using the appropriate prefix for each.
+  // We also use the min() value for all these signed types, assuring that
+  // we'll get different %d vs %u output, and we'll get letters within our
+  // %x and %X output.
+#define INT_TYPES(kPrefix, type) \
+  { \
+    type value = std::numeric_limits<type>::min(); \
+    chreLog(kInfo, "%" kPrefix "d %" kPrefix "u 0%" kPrefix "o 0x%" \
+            kPrefix "x 0x%" kPrefix "X", value, value, value, value, \
+            value); \
+  }
+
+  INT_TYPES("hh", char);
+  INT_TYPES("h", short);
+  INT_TYPES("l", long);
+  INT_TYPES("ll", long long);
+  INT_TYPES("z", size_t);
+  INT_TYPES("t", ptrdiff_t);
+
+  float f = 12.34f;
+  // Other required formats, including escaping the '%'.
+  chreLog(kInfo, "%% %f %c %s %p", f, '?', "str", &f);
+
+
+  // OPTIONAL specifiers.  See chreLog() API documentation for extensive
+  // discussion of what OPTIONAL means.
+  // <width> and '-'
+  chreLog(kInfo, "(%5s) (%-5s) (%5d) (%-5d)", "str", "str", 10, 10);
+  // '+'
+  chreLog(kInfo, "(%+d) (%+d) (%+f) (%+f)", -5, 5, -5.f, 5.f);
+  // ' '
+  chreLog(kInfo, "(% d) (% d) (% f) (% f)", -5, 5, -5.f, 5.f);
+  // '#'
+  chreLog(kInfo, "%#o %#x %#X %#f", 8, 15, 15, 1.f);
+  // '0' padding
+  chreLog(kInfo, "%08d 0x%04x", 123, 0xF);
+  // '.'<precision>
+  chreLog(kInfo, "%.3d %.3d %.3f %.3f %.3s", 12, 1234, 1.5, 1.0625, "abcdef");
+
+  // TODO: In some future Android release, when chreLog() is required to
+  //     output to logcat, we'll just send a Continue to the Host and have
+  //     the Host verify this output.  But for Android N, we leave it to
+  //     the test runner to manually verify.
+  sendSuccessToHost();
+}
+
+void LoggingSanityTest::handleEvent(uint32_t senderInstanceId,
+                                    uint16_t eventType, const void* eventData) {
+  unexpectedEvent(eventType);
+}
+
+}  // namespace general_test
diff --git a/apps/chqts/src/general_test/logging_sanity_test.h b/apps/chqts/src/general_test/logging_sanity_test.h
new file mode 100644
index 0000000..9ce49bb
--- /dev/null
+++ b/apps/chqts/src/general_test/logging_sanity_test.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _GTS_NANOAPPS_GENERAL_TEST_LOGGING_SANITY_TEST_H_
+#define _GTS_NANOAPPS_GENERAL_TEST_LOGGING_SANITY_TEST_H_
+
+#include <general_test/test.h>
+
+namespace general_test {
+
+/**
+ * Invokes chreLog() in a variety of ways.
+ *
+ * Unfortunately, we're unable to automatically check that this works
+ * correctly.  At the very least, we can confirm in an automated manner
+ * that this doesn't crash.  A diligent tester will check where the
+ * chreLog() messages go for this platform to confirm the contents look
+ * correct.
+ *
+ * Simple protocol.
+ */
+class LoggingSanityTest : public Test {
+ public:
+  LoggingSanityTest();
+
+ protected:
+  void handleEvent(uint32_t senderInstanceId, uint16_t eventType,
+                   const void* eventData) override;
+  void setUp(uint32_t messageSize, const void *message) override;
+};
+
+}  // namespace general_test
+
+
+#endif  // _GTS_NANOAPPS_GENERAL_TEST_LOGGING_SANITY_TEST_H_
diff --git a/apps/chqts/src/general_test/nanoapp_info.cc b/apps/chqts/src/general_test/nanoapp_info.cc
new file mode 100644
index 0000000..6645d52
--- /dev/null
+++ b/apps/chqts/src/general_test/nanoapp_info.cc
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <general_test/nanoapp_info.h>
+
+#include <shared/nano_endian.h>
+#include <shared/send_message.h>
+
+#include <chre.h>
+
+namespace general_test {
+
+struct AppInfo {
+  uint64_t appId;
+  uint32_t instanceId;
+} __attribute__((packed));
+
+NanoappInfo::NanoappInfo()
+  : mAppId(chreGetAppId()), mInstanceId(chreGetInstanceId()) {
+    if (mInstanceId == CHRE_INSTANCE_ID) {
+      nanoapp_testing::sendFatalFailureToHost(
+          "Given CHRE_INSTANCE_ID for my instance ID");
+    }
+}
+
+void NanoappInfo::sendToHost() {
+  AppInfo info;
+  info.appId = mAppId;
+  info.instanceId = mInstanceId;
+
+  nanoapp_testing::hostToLittleEndian(&info.appId);
+  nanoapp_testing::hostToLittleEndian(&info.instanceId);
+
+  sendMessageToHost(nanoapp_testing::MessageType::kContinue,
+                    &info, sizeof(info));
+}
+
+bool NanoappInfo::validate(uint64_t appId, uint32_t instanceId) {
+  bool result = true;
+  if (appId != mAppId) {
+    nanoapp_testing::sendFatalFailureToHost(
+        "app IDs do not match");
+    result = false;
+  } else if (instanceId != mInstanceId) {
+    nanoapp_testing::sendFatalFailureToHost(
+        "instance IDs do not match");
+    result = false;
+  }
+
+  return result;
+}
+
+bool NanoappInfo::queryByAppId(struct chreNanoappInfo *info) {
+  bool result = chreGetNanoappInfoByAppId(mAppId, info);
+
+  if (!result) {
+    nanoapp_testing::sendFatalFailureToHost(
+        "Unable to get nanoapp info by app ID");
+  }
+
+  return result;
+}
+
+bool NanoappInfo::queryByInstanceId(struct chreNanoappInfo *info) {
+  bool result = chreGetNanoappInfoByInstanceId(mInstanceId, info);
+
+  if (!result) {
+    nanoapp_testing::sendFatalFailureToHost(
+        "Unable to get nanoapp info by instance ID");
+  }
+
+  return result;
+}
+
+} // namespace general_test
diff --git a/apps/chqts/src/general_test/nanoapp_info.h b/apps/chqts/src/general_test/nanoapp_info.h
new file mode 100644
index 0000000..cc8c643
--- /dev/null
+++ b/apps/chqts/src/general_test/nanoapp_info.h
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _GTS_NANOAPPS_GENERAL_TEST_NANOAPP_INFO_H_
+#define _GTS_NANOAPPS_GENERAL_TEST_NANOAPP_INFO_H_
+
+#include <cstdint>
+
+#include <chre.h>
+
+namespace general_test {
+
+class NanoappInfo {
+ public:
+  NanoappInfo();
+
+  /**
+   * Send the nanoapp information to the host
+   */
+  void sendToHost();
+
+  /**
+   * Validate that provided values match gathered data.
+   *
+   * @param appId The app ID to validate against
+   * @param instanceId The instance ID to validate against
+   * @return true if provided values match data, false otherwise
+   */
+  bool validate(uint64_t appId, uint32_t instanceId);
+
+  /**
+   * Query system for running nanoapp information by gathered application Id
+   *
+   * @param info The chreNanoappInfo to populate with query results
+   * @return true If nanoapp with application id is running on system
+   */
+  bool queryByAppId(struct chreNanoappInfo *info);
+
+  /**
+   * Query system for running nanoapp information by gathered instance Id
+   *
+   * @param info The chreNanoappInfo to populate with query results
+   * @return true If nanoapp with instance id is running on system
+   */
+  bool queryByInstanceId(struct chreNanoappInfo *info);
+
+ private:
+  uint64_t mAppId;
+  uint32_t mInstanceId;
+};
+
+} // namespace general_test
+
+#endif // _GTS_NANOAPPS_GENERAL_TEST_NANOAPP_INFO_H_
diff --git a/apps/chqts/src/general_test/nanoapp_info_by_app_id_test.cc b/apps/chqts/src/general_test/nanoapp_info_by_app_id_test.cc
new file mode 100644
index 0000000..06c4828
--- /dev/null
+++ b/apps/chqts/src/general_test/nanoapp_info_by_app_id_test.cc
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <general_test/nanoapp_info_by_app_id_test.h>
+
+#include <general_test/running_info.h>
+
+#include <shared/send_message.h>
+#include <shared/nano_endian.h>
+#include <shared/nano_string.h>
+
+#include <chre.h>
+
+namespace general_test {
+
+NanoappInfoByAppIdTest::NanoappInfoByAppIdTest()
+    : Test(CHRE_API_VERSION_1_1) {
+}
+
+void NanoappInfoByAppIdTest::setUp(uint32_t /* messageSize */,
+                                   const void * /* message */) {
+  nanoapp_testing::sendMessageToHost(nanoapp_testing::MessageType::kContinue);
+}
+
+void NanoappInfoByAppIdTest::handleEvent(uint32_t senderInstanceId,
+                                         uint16_t eventType,
+                                         const void *eventData) {
+  if (senderInstanceId == CHRE_INSTANCE_ID) {
+    uint32_t appVersion;
+    const void *message =
+        getMessageDataFromHostEvent(senderInstanceId, eventType,
+                                    eventData,
+                                    nanoapp_testing::MessageType::kContinue,
+                                    sizeof(&appVersion));
+
+    nanoapp_testing::memcpy(&appVersion, message, sizeof(appVersion));
+    nanoapp_testing::littleEndianToHost(&appVersion);
+
+    RunningInfo runningInfo;
+
+    if (runningInfo.queryByAppId() && runningInfo.validate(appVersion)) {
+      nanoapp_testing::sendSuccessToHost();
+    }
+  }
+}
+
+} // namespace general_test
diff --git a/apps/chqts/src/general_test/nanoapp_info_by_app_id_test.h b/apps/chqts/src/general_test/nanoapp_info_by_app_id_test.h
new file mode 100644
index 0000000..181463a
--- /dev/null
+++ b/apps/chqts/src/general_test/nanoapp_info_by_app_id_test.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _GTS_NANOAPPS_GENERAL_TEST_NANOAPP_INFO_BY_APP_ID_TEST_H_
+#define _GTS_NANOAPPS_GENERAL_TEST_NANOAPP_INFO_BY_APP_ID_TEST_H_
+
+#include <general_test/test.h>
+
+#include <cstdint>
+
+/**
+ * Verify chreGetNanoappInfoByAppId
+ *
+ * Simple Protocol
+ */
+namespace general_test {
+
+class NanoappInfoByAppIdTest : public Test {
+ public:
+  NanoappInfoByAppIdTest();
+
+ protected:
+  void handleEvent(uint32_t senderInstanceId, uint16_t eventType,
+                   const void *eventData) override;
+  void setUp(uint32_t messageSize, const void *message) override;
+};
+
+} // namespace general_test
+
+#endif // _GTS_NANOAPPS_GENERAL_TEST_NANOAPP_INFO_BY_APP_ID_TEST_H_
diff --git a/apps/chqts/src/general_test/nanoapp_info_by_instance_id_test.cc b/apps/chqts/src/general_test/nanoapp_info_by_instance_id_test.cc
new file mode 100644
index 0000000..9743912
--- /dev/null
+++ b/apps/chqts/src/general_test/nanoapp_info_by_instance_id_test.cc
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <general_test/nanoapp_info_by_instance_id_test.h>
+
+#include <general_test/running_info.h>
+
+#include <shared/send_message.h>
+#include <shared/nano_endian.h>
+#include <shared/nano_string.h>
+
+#include <chre.h>
+
+namespace general_test {
+
+NanoappInfoByInstanceIdTest::NanoappInfoByInstanceIdTest()
+    : Test(CHRE_API_VERSION_1_1) {
+}
+
+void NanoappInfoByInstanceIdTest::setUp(uint32_t /* messageSize */,
+                                        const void * /* message */) {
+  nanoapp_testing::sendMessageToHost(nanoapp_testing::MessageType::kContinue);
+}
+
+void NanoappInfoByInstanceIdTest::handleEvent(uint32_t senderInstanceId,
+                                              uint16_t eventType,
+                                              const void *eventData) {
+  if (senderInstanceId == CHRE_INSTANCE_ID) {
+    uint32_t appVersion;
+    const void *message =
+        getMessageDataFromHostEvent(senderInstanceId, eventType,
+                                    eventData,
+                                    nanoapp_testing::MessageType::kContinue,
+                                    sizeof(&appVersion));
+
+    nanoapp_testing::memcpy(&appVersion, message, sizeof(appVersion));
+    nanoapp_testing::littleEndianToHost(&appVersion);
+
+    RunningInfo runningInfo;
+
+    if (runningInfo.queryByInstanceId() && runningInfo.validate(appVersion)) {
+      nanoapp_testing::sendSuccessToHost();
+    }
+  }
+}
+
+} // namespace general_test
diff --git a/apps/chqts/src/general_test/nanoapp_info_by_instance_id_test.h b/apps/chqts/src/general_test/nanoapp_info_by_instance_id_test.h
new file mode 100644
index 0000000..34ee5b2
--- /dev/null
+++ b/apps/chqts/src/general_test/nanoapp_info_by_instance_id_test.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _GTS_NANOAPPS_GENERAL_TEST_NANOAPP_INFO_BY_INSTANCE_ID_TEST_H_
+#define _GTS_NANOAPPS_GENERAL_TEST_NANOAPP_INFO_BY_INSTANCE_ID_TEST_H_
+
+#include <general_test/test.h>
+
+#include <cstdint>
+
+/**
+ * Verify chreGetNanoappInfoByInstanceId
+ *
+ * Simple Protocol
+ */
+namespace general_test {
+
+class NanoappInfoByInstanceIdTest : public Test {
+ public:
+  NanoappInfoByInstanceIdTest();
+
+ protected:
+  void handleEvent(uint32_t senderInstanceId, uint16_t eventType,
+                   const void *eventData) override;
+  void setUp(uint32_t messageSize, const void *message) override;
+};
+
+} // namespace general_test
+
+#endif // _GTS_NANOAPPS_GENERAL_TEST_NANOAPP_INFO_BY_INSTANCE_ID_TEST_H_
diff --git a/apps/chqts/src/general_test/nanoapp_info_events_test_observer.cc b/apps/chqts/src/general_test/nanoapp_info_events_test_observer.cc
new file mode 100644
index 0000000..56733de
--- /dev/null
+++ b/apps/chqts/src/general_test/nanoapp_info_events_test_observer.cc
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <general_test/nanoapp_info_events_test_observer.h>
+
+#include <shared/nano_endian.h>
+#include <shared/nano_string.h>
+
+namespace general_test {
+
+NanoAppInfoEventsTestObserver::NanoAppInfoEventsTestObserver()
+    : Test(CHRE_API_VERSION_1_1) {
+}
+
+void NanoAppInfoEventsTestObserver::setUp(uint32_t /* messageSize */,
+                                          const void * /* message */) {
+  chreConfigureNanoappInfoEvents(true /* enable */);
+  nanoapp_testing::sendMessageToHost(nanoapp_testing::MessageType::kContinue);
+}
+
+void NanoAppInfoEventsTestObserver::handleEvent(uint32_t senderInstanceId,
+                                                uint16_t eventType,
+                                                const void *eventData) {
+  if ((senderInstanceId == CHRE_INSTANCE_ID)
+      && ((eventType == CHRE_EVENT_NANOAPP_STARTED)
+          || (eventType == CHRE_EVENT_NANOAPP_STOPPED))) {
+
+    const struct chreNanoappInfo *nanoAppInfo =
+        static_cast<const struct chreNanoappInfo *>(eventData);
+
+    mStartStopHistory[mHistoryIndex].instanceId =
+        nanoAppInfo->instanceId;
+
+    mStartStopHistory[mHistoryIndex].eventType = eventType;
+    mHistoryIndex = (mHistoryIndex + 1) % kHistorySize;
+  } else if ((senderInstanceId == CHRE_INSTANCE_ID)
+             && (eventType == CHRE_EVENT_MESSAGE_FROM_HOST)) {
+    uint32_t performerInstanceId;
+
+    const void *message = getMessageDataFromHostEvent(
+        senderInstanceId, eventType, eventData,
+        nanoapp_testing::MessageType::kContinue, sizeof(performerInstanceId));
+
+    nanoapp_testing::memcpy(&performerInstanceId, message,
+                            sizeof(performerInstanceId));
+    nanoapp_testing::littleEndianToHost(&performerInstanceId);
+
+    processStartStopHistory(performerInstanceId);
+  } else {
+    unexpectedEvent(eventType);
+  }
+}
+
+void NanoAppInfoEventsTestObserver::processStartStopHistory(
+    uint32_t performerInstanceId) {
+  uint32_t startCount = 0;
+  uint32_t stopCount = 0;
+  bool seenFirstEvent = false;
+  bool eventsOrdered = false;
+
+  // The oldest data (if present) is at the insertion point in the
+  // circular array (i.e. mHistoryIndex)
+  for (uint32_t i = 0; i < kHistorySize; ++i) {
+    HostActionMetadata& data =
+        mStartStopHistory[(mHistoryIndex + i) % kHistorySize];
+
+    if (data.instanceId == performerInstanceId) {
+      if (data.eventType == CHRE_EVENT_NANOAPP_STARTED) {
+        ++startCount;
+      } else {
+        ++stopCount;
+      }
+
+      if (!seenFirstEvent) {
+        eventsOrdered = (data.eventType == CHRE_EVENT_NANOAPP_STARTED);
+        seenFirstEvent = true;
+      }
+    }
+  }
+
+  if (startCount > 1) {
+    nanoapp_testing::sendFatalFailureToHost(
+        "Received too many Start events");
+  } else if (startCount == 0) {
+    nanoapp_testing::sendFatalFailureToHost(
+        "Did not receive Start event");
+  } else if (stopCount > 1) {
+    nanoapp_testing::sendFatalFailureToHost(
+        "Received too many Stop events");
+  } else if (stopCount == 0) {
+    nanoapp_testing::sendFatalFailureToHost(
+        "Did not receive Stop event");
+  } else if (!eventsOrdered) {
+    nanoapp_testing::sendFatalFailureToHost(
+        "Start and Stop events were not in order");
+  } else {
+    nanoapp_testing::sendSuccessToHost();
+  }
+}
+
+} // namespace general_test
diff --git a/apps/chqts/src/general_test/nanoapp_info_events_test_observer.h b/apps/chqts/src/general_test/nanoapp_info_events_test_observer.h
new file mode 100644
index 0000000..7bbcdcf
--- /dev/null
+++ b/apps/chqts/src/general_test/nanoapp_info_events_test_observer.h
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtian a copy of the License at
+ *
+ *      http://www.apache.org/licensed/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_GENERAL_TEST_NANOAPP_INFO_EVENTS_TEST_OBSERVER_H_
+#define _GTS_NANOAPPS_GENERAL_TEST_NANOAPP_INFO_EVENTS_TEST_OBSERVER_H_
+
+#include <general_test/test.h>
+
+#include <cstdint>
+
+#include <chre.h>
+
+namespace general_test {
+
+/**
+ * Monitor CHRE_EVENT_NANOAPP_STARTED/CHRE_EVENT_NANOAPP_STOPPED events.
+ *
+ * This is the OBSERVER nanoapp for ContextHubNanoAppInfoEventsNanoAppTest
+ *
+ * Protocol:
+ * Host to observer: NANOAPP_INFO_EVENT, no data
+ * observer to Host: CONTINUE
+ * [Host starts performer]
+ * ...
+ * [Host stops performer]
+ * Host to observer: CONTINUE, performer's 32-bit instance ID
+ * observer to host: SUCCESS
+ */
+struct HostActionMetadata {
+  uint32_t instanceId;
+  uint16_t eventType;
+};
+
+class NanoAppInfoEventsTestObserver : public Test {
+ public:
+  NanoAppInfoEventsTestObserver();
+
+ protected:
+  void handleEvent(uint32_t senderInstanceId, uint16_t eventType,
+                   const void *eventData) override;
+  void setUp(uint32_t messageSize, const void *message) override;
+
+ private:
+  /**
+   * Search the Start/Stop message history looking for a Start/Stop
+   * pair from a given instance id
+   *
+   * @param performerInstanceId The instance Id to look for
+   */
+  void processStartStopHistory(uint32_t performerInstanceId);
+
+  static constexpr uint32_t kHistorySize = 8;
+  HostActionMetadata mStartStopHistory[kHistorySize];
+  uint32_t mHistoryIndex = 0;
+};
+
+} // namespace general_test
+
+#endif // _GTS_NANOAPPS_GENERAL_TEST_NANOAPP_INFO_EVENTS_TEST_OBSERVER_H_
diff --git a/apps/chqts/src/general_test/nanoapp_info_events_test_performer.cc b/apps/chqts/src/general_test/nanoapp_info_events_test_performer.cc
new file mode 100644
index 0000000..86c6b9f
--- /dev/null
+++ b/apps/chqts/src/general_test/nanoapp_info_events_test_performer.cc
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <general_test/nanoapp_info_events_test_performer.h>
+
+#include <general_test/nanoapp_info.h>
+
+#include <shared/nano_endian.h>
+
+namespace general_test {
+
+NanoAppInfoEventsTestPerformer::NanoAppInfoEventsTestPerformer()
+    : Test(CHRE_API_VERSION_1_1) {
+}
+
+void NanoAppInfoEventsTestPerformer::setUp(uint32_t /* messageSize */,
+                                           const void * /* message */) {
+  NanoappInfo info;
+  info.sendToHost();
+}
+
+void NanoAppInfoEventsTestPerformer::handleEvent(uint32_t senderInstanceId,
+                                                 uint16_t eventType,
+                                                 const void *eventdata) {
+  // Do nothing
+}
+
+} // namespace general_test
diff --git a/apps/chqts/src/general_test/nanoapp_info_events_test_performer.h b/apps/chqts/src/general_test/nanoapp_info_events_test_performer.h
new file mode 100644
index 0000000..1fc1110
--- /dev/null
+++ b/apps/chqts/src/general_test/nanoapp_info_events_test_performer.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _GTS_NANOAPPS_GENERAL_TEST_NANOAPP_INFO_EVENTS_TEST_PERFORMER_H_
+#define _GTS_NANOAPPS_GENERAL_TEST_NANOAPP_INFO_EVENTS_TEST_PERFORMER_H_
+
+#include <general_test/test.h>
+
+#include <cstdint>
+
+namespace general_test {
+
+/**
+ * Monitor CHRE_EVENT_NANOAPP_STARTED/CHRE_EVENT_NANOAPP_STOPPED events.
+ *
+ * This is the PERFORMER nanoapp for ContextHubNanoAppInfoEventsNanoAppTest
+ *
+ * Protocol:
+ * Host to performer: NANOAPP_INFO_EVENTS_PERFORMER, no data
+ * performer to Host: CONTINUE, 64-bit app ID, 32-bit instance ID
+ */
+class NanoAppInfoEventsTestPerformer : public Test {
+ public:
+  NanoAppInfoEventsTestPerformer();
+
+ protected:
+  void handleEvent(uint32_t senderInstanceId, uint16_t eventType,
+                   const void *eventData) override;
+  void setUp(uint32_t messageSize, const void *message) override;
+};
+
+} // namespace general_test
+
+#endif // _GTS_NANOAPPS_GENERAL_TEST_NANOAPP_INFO_EVENTS_TEST_PERFORMER_H_
diff --git a/apps/chqts/src/general_test/running_info.cc b/apps/chqts/src/general_test/running_info.cc
new file mode 100644
index 0000000..9410d0f
--- /dev/null
+++ b/apps/chqts/src/general_test/running_info.cc
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <general_test/running_info.h>
+
+#include <shared/send_message.h>
+
+namespace general_test {
+
+bool RunningInfo::queryByAppId() {
+  return mPlatformInfo.queryByAppId(&mRunningInfo);
+}
+
+bool RunningInfo::queryByInstanceId() {
+  return mPlatformInfo.queryByInstanceId(&mRunningInfo);
+}
+
+bool RunningInfo::validate(uint32_t appVersion) {
+  bool result;
+  if (mRunningInfo.version != appVersion) {
+    nanoapp_testing::sendFatalFailureToHost(
+        "Running info version does not match app version");
+    result = false;
+  } else if (mRunningInfo.appId != NANOAPP_ID) {
+    nanoapp_testing::sendFatalFailureToHost(
+        "Running info appId does not match build constant");
+    result = false;
+  } else if (mRunningInfo.version != NANOAPP_VERSION) {
+    nanoapp_testing::sendFatalFailureToHost(
+        "Running info version does not match build constant");
+    result = false;
+  } else {
+    result = mPlatformInfo.validate(mRunningInfo.appId,
+                                    mRunningInfo.instanceId);
+  }
+
+  return result;
+}
+
+} // namespace general_test
diff --git a/apps/chqts/src/general_test/running_info.h b/apps/chqts/src/general_test/running_info.h
new file mode 100644
index 0000000..818e997
--- /dev/null
+++ b/apps/chqts/src/general_test/running_info.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _GTS_NANOAPPS_GENERAL_TEST_RUNNING_INFO_H_
+#define _GTS_NANOAPPS_GENERAL_TEST_RUNNING_INFO_H_
+
+#include <general_test/nanoapp_info.h>
+
+#include <cstdint>
+
+#include <chre.h>
+
+namespace general_test {
+
+class RunningInfo {
+ public:
+  bool queryByAppId();
+  bool queryByInstanceId();
+
+  /**
+   * Validate that provided values match gathered data.
+   *
+   * @param appVersion The app version to validate against
+   * @return true if provided value matches data, false otherwise
+   */
+  bool validate(uint32_t appVersion);
+
+ private:
+  NanoappInfo mPlatformInfo;
+  struct chreNanoappInfo mRunningInfo;
+};
+
+} // namespace general_test
+
+#endif // _GTS_NANOAPPS_GENERAL_TEST_RUNNING_INFO_H_
diff --git a/apps/chqts/src/general_test/send_event_stress_test.cc b/apps/chqts/src/general_test/send_event_stress_test.cc
new file mode 100644
index 0000000..2fe49a1
--- /dev/null
+++ b/apps/chqts/src/general_test/send_event_stress_test.cc
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <general_test/send_event_stress_test.h>
+
+#include <cstddef>
+
+#include <shared/send_message.h>
+
+#include <chre.h>
+
+using nanoapp_testing::sendFatalFailureToHost;
+using nanoapp_testing::sendSuccessToHost;
+
+/*
+ * We stress the system by sending more and more events until it runs out.
+ * Then we wait for all the events to be delivered, and all the completion
+ * callbacks to be invoked.
+ */
+
+constexpr uint16_t kEventType = CHRE_EVENT_FIRST_USER_VALUE;
+void *const kEventData = reinterpret_cast<void *>(-1);
+
+// If the system keeps claiming it can send more events, we don't let it
+// continue forever.  Instead, we'll cut it off at this limit.  And then
+// we'll call its bluff, and make sure that all of these events get
+// delivered.  While it won't be an actual exhaustion test (we never took the
+// system down to no more events available), it will still give us confidence
+// that this CHRE can properly handle any semi-reasonable event load properly.
+// 1030 is an arbitrary number, slightly over 2^10.  The hope is this
+// balances between catching incorrect behavior and the test taking too long.
+constexpr int32_t kMaxEventsToSend = INT32_C(1030);
+
+namespace general_test {
+
+bool SendEventStressTest::sInMethod = false;
+bool SendEventStressTest::sInitTime = false;
+
+int32_t SendEventStressTest::sEventsLeft = 0;
+int32_t SendEventStressTest::sCompleteCallbacksLeft = 0;
+
+SendEventStressTest::SendEventStressTest()
+    : Test(CHRE_API_VERSION_1_0) {
+}
+
+void SendEventStressTest::setUp(uint32_t messageSize,
+                                const void * /* message */) {
+  sInMethod = true;
+
+  if (messageSize != 0) {
+    sendFatalFailureToHost(
+        "SendEventStress message expects 0 additional bytes, got ",
+        &messageSize);
+  }
+
+  mInstanceId = chreGetInstanceId();
+
+  // When our chreSendEvent() call fails, the CHRE is allowed to
+  // directly invoke our completeCallback.  We special case this
+  // with sInitTime, so we can ignore sInMethod for that case only.
+  sCompleteCallbacksLeft = 1;
+  sInitTime = true;
+
+  // We anticipate most CHREs will not reach kMaxEventsToSend.
+  while ((sEventsLeft < kMaxEventsToSend) &&
+         chreSendEvent(kEventType, kEventData, completeCallback,
+                       mInstanceId)) {
+    sEventsLeft++;
+  }
+  sInitTime = false;
+
+  // We want at least 2 events for this to pretend to be an exhaustion test.
+  if (sEventsLeft < 2) {
+    sendFatalFailureToHost("Insufficient events available");
+  }
+
+  // sCompleteCallbacksLeft may be 0 or 1 at this point.  We don't care.
+  // We just know we also expect all the sEventsLeft to have callbacks.
+  sCompleteCallbacksLeft += sEventsLeft;
+
+  sInMethod = false;
+}
+
+void SendEventStressTest::handleEvent(uint32_t senderInstanceId,
+                                      uint16_t eventType,
+                                      const void* eventData) {
+  if (sInMethod) {
+    sendFatalFailureToHost("handleEvent invoked while another nanoapp "
+                           "method is running");
+  }
+  sInMethod = true;
+  if (senderInstanceId != mInstanceId) {
+    sendFatalFailureToHost("handleEvent got event from unexpected sender:",
+                           &senderInstanceId);
+  }
+  sanityCheck(eventType, eventData, 0);
+
+  --sEventsLeft;
+  if (sEventsLeft < 0) {
+    sendFatalFailureToHost("Too many events delivered");
+  }
+
+  sInMethod = false;
+}
+
+void SendEventStressTest::sanityCheck(uint16_t eventType, const void *data,
+                                      uint32_t num) {
+  if (eventType != kEventType) {
+    unexpectedEvent(eventType);
+  }
+  if (data != kEventData) {
+    // 0: handleEvent, 1: completeCallback
+    sendFatalFailureToHost("bad event data:", &num);
+  }
+}
+
+
+void SendEventStressTest::completeCallback(uint16_t eventType, void *data) {
+  if (sInitTime) {
+    // The CHRE must be directly calling this from within
+    // chreSendEvent(), after it failed.  We only allow a
+    // single one of these calls.
+    sInitTime = false;
+    sanityCheck(eventType, data, 1);
+    sCompleteCallbacksLeft--;
+    return;
+  }
+
+  if (sInMethod) {
+    sendFatalFailureToHost("completeCallback invoked while another nanoapp "
+                           "method is running");
+  }
+  sanityCheck(eventType, data, 1);
+
+  --sCompleteCallbacksLeft;
+  if (sCompleteCallbacksLeft == 0) {
+    if (sEventsLeft != 0) {
+      sendFatalFailureToHost("completeCallbacks delivered before events");
+    }
+    sendSuccessToHost();
+  } else if (sCompleteCallbacksLeft < 0) {
+    // It's too late for the Host to catch this failure, but perhaps
+    // the abort will screw up our unload, and trigger a failure that way.
+    sendFatalFailureToHost("completeCallback called too many times");
+  }
+}
+
+
+}  // namespace general_test
diff --git a/apps/chqts/src/general_test/send_event_stress_test.h b/apps/chqts/src/general_test/send_event_stress_test.h
new file mode 100644
index 0000000..8176cf4
--- /dev/null
+++ b/apps/chqts/src/general_test/send_event_stress_test.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _GTS_NANOAPPS_GENERAL_TEST_SEND_EVENT_STRESS_TEST_H_
+#define _GTS_NANOAPPS_GENERAL_TEST_SEND_EVENT_STRESS_TEST_H_
+
+#include <general_test/test.h>
+
+namespace general_test {
+
+/**
+ * Sends events until we can't anymore, and makes sure things still work.
+ *
+ * Simple Protocol.
+ */
+class SendEventStressTest : public Test {
+ public:
+  SendEventStressTest();
+
+ protected:
+  void handleEvent(uint32_t senderInstanceId, uint16_t eventType,
+                   const void* eventData) override;
+  void setUp(uint32_t messageSize, const void *message) override;
+
+ private:
+  uint32_t mInstanceId;
+  static bool sInMethod;
+  static bool sInitTime;
+
+  static int32_t sEventsLeft;
+  static int32_t sCompleteCallbacksLeft;
+
+  static void completeCallback(uint16_t eventType, void *data);
+
+  static void sanityCheck(uint16_t eventType, const void *data, uint32_t num);
+};
+
+}  // namespace general_test
+
+
+#endif  // _GTS_NANOAPPS_GENERAL_TEST_SEND_EVENT_STRESS_TEST_H_
diff --git a/apps/chqts/src/general_test/send_event_test.cc b/apps/chqts/src/general_test/send_event_test.cc
new file mode 100644
index 0000000..b07f89d
--- /dev/null
+++ b/apps/chqts/src/general_test/send_event_test.cc
@@ -0,0 +1,250 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <general_test/send_event_test.h>
+
+#include <cstddef>
+
+#include <shared/abort.h>
+#include <shared/array_length.h>
+#include <shared/send_message.h>
+
+#include <chre.h>
+
+using nanoapp_testing::sendFatalFailureToHost;
+using nanoapp_testing::sendSuccessToHost;
+
+/*
+ * In a properly running test, we'll invoke chreSendEvent() a total of 12 times.
+ * We initially send eight events upon startup.  And then for each of our four
+ * events which has a non-nullptr completeCallback, we call chreSendEvent()
+ * from that callback.
+ *
+ * For our first eight events, they will either be kEventType0 or kEventType1.
+ * They will either use completeCallback0 or completeCallback1.  They have
+ * various data.  This table describes them all:
+ *
+ * num | eventType | data       | Callback
+ * ----|-----------|------------|---------
+ * 0   | 0         | ptr to num | 0
+ * 1   | 0         | ptr to num | 1
+ * 2   | 1         | ptr to num | 0
+ * 3   | 1         | ptr to num | 1
+ * 4   | 0         | ptr to num | nullptr
+ * 5   | 1         | ptr to num | nullptr
+ * 6   | 0         | nullptr    | nullptr
+ * 7   | 1         | kOddData   | nullptr
+ *
+ * The other four events are all kEventTypeCallback with nullptr data and
+ * nullptr callback.
+ */
+
+constexpr uint16_t kEventType0 = CHRE_EVENT_FIRST_USER_VALUE + 0;
+constexpr uint16_t kEventType1 = CHRE_EVENT_FIRST_USER_VALUE + 1;
+constexpr uint16_t kEventTypeCallback = CHRE_EVENT_FIRST_USER_VALUE + 2;
+
+// NOTE: This is not allowed to be constexpr, even if some version of g++/clang
+//     allow it.
+static void *kOddData = reinterpret_cast<void*>(-1);
+
+namespace general_test {
+
+bool SendEventTest::sInMethod = false;
+uint8_t SendEventTest::sCallbacksInvoked = 0;
+
+template<uint8_t kCallbackIndex>
+void SendEventTest::completeCallback(uint16_t eventType, void *data) {
+  if (sInMethod) {
+    sendFatalFailureToHost("completeCallback called while another nanoapp "
+                           "method is running.");
+  }
+  sInMethod = true;
+  if ((data == nullptr) || (data == kOddData)) {
+    sendFatalFailureToHost(
+        "completeCallback called with nullptr or odd data.");
+  }
+  uint32_t num = *(reinterpret_cast<uint32_t*>(data));
+  uint16_t expectedEventType = 0xFFFF;
+  uint8_t expectedCallbackIndex = 0xFF;
+  switch (num) {
+    case 0:
+      expectedEventType = kEventType0;
+      expectedCallbackIndex = 0;
+      break;
+    case 1:
+      expectedEventType = kEventType0;
+      expectedCallbackIndex = 1;
+      break;
+    case 2:
+      expectedEventType = kEventType1;
+      expectedCallbackIndex = 0;
+      break;
+    case 3:
+      expectedEventType = kEventType1;
+      expectedCallbackIndex = 1;
+      break;
+    default:
+      sendFatalFailureToHost("completeCallback given bad data.", &num);
+  }
+  if (expectedEventType != eventType) {
+    sendFatalFailureToHost("completeCallback bad/eventType mismatch.");
+  }
+  if (expectedCallbackIndex != kCallbackIndex) {
+    sendFatalFailureToHost("Incorrect callback function called.");
+  }
+  uint8_t mask = 1 << num;
+  if ((sCallbacksInvoked & mask) != 0) {
+    sendFatalFailureToHost("Complete callback invoked multiple times for ",
+                           &num);
+  }
+  sCallbacksInvoked |= mask;
+
+  if (!chreSendEvent(kEventTypeCallback, nullptr, nullptr,
+                     chreGetInstanceId())) {
+    sendFatalFailureToHost("Failed chreSendEvent in callback.");
+  }
+  sInMethod = false;
+}
+
+void SendEventTest::completeCallback0(uint16_t eventType, void *data) {
+  completeCallback<0>(eventType, data);
+}
+
+void SendEventTest::completeCallback1(uint16_t eventType, void *data) {
+  completeCallback<1>(eventType, data);
+}
+
+SendEventTest::SendEventTest()
+  : Test(CHRE_API_VERSION_1_0) , mNextNum(0) {
+}
+
+void SendEventTest::setUp(uint32_t messageSize, const void * /* message */) {
+  sInMethod = true;
+  if (messageSize != 0) {
+    sendFatalFailureToHost(
+        "SendEvent message expects 0 additional bytes, got ",
+        &messageSize);
+  }
+
+  const uint32_t id = chreGetInstanceId();
+  for (uint32_t i = 0; i < arrayLength(mData); i++) {
+    mData[i] = i;
+  }
+
+  // num: 0
+  if (!chreSendEvent(kEventType0, &mData[0], completeCallback0, id)) {
+    sendFatalFailureToHost("Failed chreSendEvent num 0");
+  }
+
+  // num: 1
+  if (!chreSendEvent(kEventType0, &mData[1], completeCallback1, id)) {
+    sendFatalFailureToHost("Failed chreSendEvent num 1");
+  }
+
+  // num: 2
+  if (!chreSendEvent(kEventType1, &mData[2], completeCallback0, id)) {
+    sendFatalFailureToHost("Failed chreSendEvent num 2");
+  }
+
+  // num: 3
+  if (!chreSendEvent(kEventType1, &mData[3], completeCallback1, id)) {
+    sendFatalFailureToHost("Failed chreSendEvent num 3");
+  }
+
+  // num: 4
+  if (!chreSendEvent(kEventType0, &mData[4], nullptr, id)) {
+    sendFatalFailureToHost("Failed chreSendEvent num 4");
+  }
+
+  // num: 5
+  if (!chreSendEvent(kEventType1, &mData[5], nullptr, id)) {
+    sendFatalFailureToHost("Failed chreSendEvent num 5");
+  }
+
+  // num: 6
+  if (!chreSendEvent(kEventType0, nullptr, nullptr, id)) {
+    sendFatalFailureToHost("Failed chreSendEvent num 6");
+  }
+
+  // num: 7
+  if (!chreSendEvent(kEventType1, kOddData, nullptr, id)) {
+    sendFatalFailureToHost("Failed chreSendEvent num 7");
+  }
+
+  sInMethod = false;
+}
+
+void SendEventTest::handleEvent(uint32_t senderInstanceId,
+                                uint16_t eventType, const void* eventData) {
+  if (sInMethod) {
+    sendFatalFailureToHost("handleEvent invoked while another nanoapp "
+                           "method is running");
+  }
+  sInMethod = true;
+  if (senderInstanceId != chreGetInstanceId()) {
+    sendFatalFailureToHost("handleEvent got event from unexpected sender:",
+                           &senderInstanceId);
+  }
+
+  if (mNextNum < 8) {
+    void *expectedData;
+    if (mNextNum < 6) {
+      expectedData = &mData[mNextNum];
+    } else if (mNextNum == 6) {
+      expectedData = nullptr;
+    } else {
+      expectedData = kOddData;
+    }
+
+    uint16_t expectedEventType = 0xFFFF;
+    switch (mNextNum) {
+      case 0:
+      case 1:
+      case 4:
+      case 6:
+        expectedEventType = kEventType0;
+        break;
+      case 2:
+      case 3:
+      case 5:
+      case 7:
+        expectedEventType = kEventType1;
+        break;
+    }
+
+    if (expectedEventType != eventType) {
+      sendFatalFailureToHost("Incorrect event type sent for num ",
+                             &mNextNum);
+    }
+    if (expectedData != eventData) {
+      sendFatalFailureToHost("Incorrect data sent for num ", &mNextNum);
+    }
+
+  } else {
+    if (eventType != kEventTypeCallback) {
+      sendFatalFailureToHost("Unexpected event type for num ", &mNextNum);
+    }
+    if (mNextNum == 11) {
+      // This was our last callback.  Everything is good.
+      sendSuccessToHost();
+    }
+  }
+
+  mNextNum++;
+  sInMethod = false;
+}
+
+}  // namespace general_test
diff --git a/apps/chqts/src/general_test/send_event_test.h b/apps/chqts/src/general_test/send_event_test.h
new file mode 100644
index 0000000..51830cf
--- /dev/null
+++ b/apps/chqts/src/general_test/send_event_test.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _GTS_NANOAPPS_GENERAL_TEST_SEND_EVENT_TEST_H_
+#define _GTS_NANOAPPS_GENERAL_TEST_SEND_EVENT_TEST_H_
+
+#include <general_test/test.h>
+
+namespace general_test {
+
+/**
+ * Checks that chreSendEvent() works by sending events to ourself.
+ *
+ * We send a number of events with different combinations of types
+ * and data and callbacks, and make sure things come through as we
+ * expect.
+ *
+ * Simple Protocol.
+ */
+class SendEventTest : public Test {
+ public:
+  SendEventTest();
+
+ protected:
+  void handleEvent(uint32_t senderInstanceId, uint16_t eventType,
+                   const void* eventData) override;
+  void setUp(uint32_t messageSize, const void *message) override;
+
+ private:
+  uint32_t mNextNum;
+  uint32_t mData[6];
+
+  static bool sInMethod;
+  static uint8_t sCallbacksInvoked;
+
+  template<uint8_t kCallbackIndex>
+  static void completeCallback(uint16_t eventType, void *data);
+
+  static void completeCallback0(uint16_t eventType, void *data);
+  static void completeCallback1(uint16_t eventType, void *data);
+};
+
+}  // namespace general_test
+
+
+#endif  // _GTS_NANOAPPS_GENERAL_TEST_SEND_EVENT_TEST_H_
diff --git a/apps/chqts/src/general_test/send_message_to_host_test.cc b/apps/chqts/src/general_test/send_message_to_host_test.cc
new file mode 100644
index 0000000..ac0deb3
--- /dev/null
+++ b/apps/chqts/src/general_test/send_message_to_host_test.cc
@@ -0,0 +1,368 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <general_test/send_message_to_host_test.h>
+
+#include <cinttypes>
+#include <cstddef>
+
+#include <shared/nano_endian.h>
+#include <shared/nano_string.h>
+#include <shared/send_message.h>
+
+#include <chre.h>
+
+using nanoapp_testing::MessageType;
+using nanoapp_testing::sendFatalFailureToHost;
+using nanoapp_testing::sendInternalFailureToHost;
+using nanoapp_testing::sendSuccessToHost;
+
+
+/*
+ * Our test essentially has nine stages.  The first eight stages all involve
+ * sending data to the Host.  Here is a table describing them:
+ *
+ * Stage | Data length | Callback
+ * ------|-------------|--------------
+ * 0     | small       | smallMessage0
+ * 1     | small       | smallMessage1
+ * 2     | small       | nullptr
+ * 3     | small       | smallMessage0
+ * 4     | nullptr     | nullptr
+ * 5     | 4 bytes     | nullptr
+ * 6     | MAX + 1     | largeMessage
+ * 7     | MAX         | largeMessage
+ *
+ * Stage 8 involves waiting for an incoming zero-sized message from the Host.
+ *
+ * The focus of the first four stages is making sure the correct callback
+ * gets invoked and a nullptr callback works.
+ *
+ * Stage 4 tests sending a null message to the Host (that should send).
+ *
+ * Stage 5 is not testing anything, but it's necessary to get data
+ * to the host to confirm the message in stage 7 is correct.
+ *
+ * Stage 6 tests that we properly reject oversized messages.  This
+ * data should _not_ make it to the host.
+ *
+ * Stage 7 tests that we can send the maximum claimed size to the host.
+ *
+ * Every single stage which has a non-null callback is not considered a
+ * "success" until that callback has been invoked.  There is no CHRE
+ * requirement in terms of the order in which these callbacks are
+ * invoked, which is why the markSuccess() method uses a bitmask and
+ * checks for overall success every time we gets success from a single
+ * stage.
+ *
+ * We consider the test successful only when all stages have reported success.
+ * Note that the Host will not perform Stage 8 until after it has received
+ * all the expected messages from the nanoapp.  That's how we can confirm
+ * all messages actually made it through to the Host.
+ */
+
+// TODO(b/32114261): Remove this and actually test a variety of message types.
+constexpr uint32_t kUntestedMessageType = UINT32_C(0x51501984);
+
+namespace general_test {
+
+// TODO(b/32114261): Remove this variable.
+extern bool gUseNycMessageHack;
+
+uint8_t SendMessageToHostTest::sSmallMessageData[kSmallMessageTestCount][kSmallMessageSize];
+void *SendMessageToHostTest::sLargeMessageData[2];
+constexpr size_t SendMessageToHostTest::kLargeSizes[2];
+
+bool SendMessageToHostTest::sInMethod = false;
+uint32_t SendMessageToHostTest::sFinishedBitmask = 0;
+
+template<uint8_t kCallbackIndex>
+void SendMessageToHostTest::smallMessageCallback(void *message,
+                                                 size_t messageSize) {
+  if (sInMethod) {
+    sendFatalFailureToHost("smallMessageCallback called while another "
+                           "nanoapp method is running");
+  }
+  sInMethod = true;
+  if (message == nullptr) {
+    sendFatalFailureToHost("smallMessageCallback given null message");
+  }
+  if (messageSize != kSmallMessageSize) {
+    uint32_t size = static_cast<uint32_t>(messageSize);
+    sendFatalFailureToHost("smallMessageCallback given bad messageSize:",
+                           &size);
+  }
+  const uint8_t *msg = static_cast<const uint8_t*>(message);
+  for (size_t i = 0; i < messageSize; i++) {
+    if (msg[i] != kDataByte) {
+      sendFatalFailureToHost("Corrupt data in smallMessageCallback");
+    }
+  }
+
+  uint32_t stage = getSmallDataIndex(msg);
+  uint8_t expectedCallbackIndex = 2;
+  switch (stage) {
+    case 0:  // fall-through
+    case 3:
+      expectedCallbackIndex = 0;
+      break;
+    case 1:
+      expectedCallbackIndex = 1;
+      break;
+    case 2:
+      sendFatalFailureToHost("callback invoked when null callback "
+                             "given");
+      break;
+    default:
+      sendInternalFailureToHost("Invalid index", &stage);
+  }
+  if (expectedCallbackIndex != kCallbackIndex) {
+    sendFatalFailureToHost("Incorrect callback function called.");
+  }
+
+  markSuccess(stage);
+  sInMethod = false;
+}
+
+void SendMessageToHostTest::smallMessageCallback0(void *message,
+                                                  size_t messageSize) {
+  smallMessageCallback<0>(message, messageSize);
+}
+
+void SendMessageToHostTest::smallMessageCallback1(void *message,
+                                                  size_t messageSize) {
+  smallMessageCallback<1>(message, messageSize);
+}
+
+uint32_t SendMessageToHostTest::getSmallDataIndex(const uint8_t *data) {
+  // O(N) is fine.  N is small and this is test code.
+  for (uint32_t i = 0; i < kSmallMessageTestCount; i++) {
+    if (data == sSmallMessageData[i]) {
+      return i;
+    }
+  }
+  sendFatalFailureToHost("Bad memory sent to smallMessageCallback");
+  // We should never get here.
+  return kSmallMessageTestCount;
+}
+
+void SendMessageToHostTest::largeMessageCallback(void *message,
+                                                 size_t messageSize) {
+  if (sInMethod) {
+    sendFatalFailureToHost("largeMessageCallback called while another "
+                           "nanoapp method is running");
+  }
+  sInMethod = true;
+  if (message == nullptr) {
+    sendFatalFailureToHost("largeMessageCallback given null message");
+  }
+  uint32_t index = 2;
+  if (message == sLargeMessageData[0]) {
+    index = 0;
+  } else if (message == sLargeMessageData[1]) {
+    index = 1;
+  } else {
+    sendFatalFailureToHost("largeMessageCallback given bad message");
+  }
+  if (messageSize != kLargeSizes[index]) {
+    sendFatalFailureToHost("largeMessageCallback given incorrect "
+                           "messageSize");
+  }
+  const uint8_t *msg = static_cast<const uint8_t*>(message);
+  for (size_t i = 0; i < messageSize; i++) {
+    if (msg[i] != kDataByte) {
+      sendFatalFailureToHost("Corrupt data in largeMessageCallback");
+    }
+  }
+  chreHeapFree(sLargeMessageData[index]);
+  // index 0 == stage 6, index 1 == stage 7
+  markSuccess(index + 6);
+
+  sInMethod = false;
+}
+
+void SendMessageToHostTest::markSuccess(uint32_t stage) {
+  chreLog(CHRE_LOG_DEBUG, "Stage %" PRIu32 " succeeded", stage);
+  uint32_t finishedBit = (1 << stage);
+  if (sFinishedBitmask & finishedBit) {
+    sendFatalFailureToHost("callback called multiple times for stage:",
+                           &stage);
+  }
+  if ((kAllFinished & finishedBit) == 0) {
+    sendFatalFailureToHost("markSuccess bad stage", &stage);
+  }
+  sFinishedBitmask |= finishedBit;
+  if (sFinishedBitmask == kAllFinished) {
+    sendSuccessToHost();
+  }
+}
+
+void SendMessageToHostTest::prepTestMemory() {
+  nanoapp_testing::memset(sSmallMessageData, kDataByte,
+                          sizeof(sSmallMessageData));
+
+  for (size_t i = 0; i < 2; i++) {
+    sLargeMessageData[i] = chreHeapAlloc(kLargeSizes[i]);
+    if (sLargeMessageData[i] == nullptr) {
+      sendFatalFailureToHost("Insufficient heap memory for test");
+    }
+    nanoapp_testing::memset(sLargeMessageData[i], kDataByte,
+                            kLargeSizes[i]);
+  }
+}
+
+void SendMessageToHostTest::sendMessageMaxSize() {
+  // Our focus here is just sending this data; we're not trying to
+  // test anything.  So we use the helper function.
+  uint32_t maxSize = CHRE_MESSAGE_TO_HOST_MAX_SIZE;
+  nanoapp_testing::hostToLittleEndian(&maxSize);
+  // TODO(b/32114261): We intentionally don't have a namespace using
+  //     declaration for sendMessageToHost because it's generally
+  //     incorrect to use while we're working around this bug.  When the
+  //     bug is fixed, we'll add this declaration, and use the method
+  //     widely.
+  nanoapp_testing::sendMessageToHost(MessageType::kContinue,
+                                     &maxSize, sizeof(maxSize));
+}
+
+// Wrapper for chreSendMessageToHost() that sets sInMethod to false during its
+// execution, to allow for inline callbacks (this CHRE API is allowed to call
+// the free callback either within the function, or at an unspecified later time
+// when this nanoapp is not otherwise executing).
+bool SendMessageToHostTest::sendMessageToHost(
+    void *message, uint32_t messageSize, uint32_t reservedMessageType,
+    chreMessageFreeFunction *freeCallback) {
+  sInMethod = false;
+  bool success = chreSendMessageToHost(message, messageSize,
+                                       reservedMessageType, freeCallback);
+  sInMethod = true;
+
+  return success;
+}
+
+SendMessageToHostTest::SendMessageToHostTest()
+  : Test(CHRE_API_VERSION_1_0) {
+}
+
+void SendMessageToHostTest::setUp(uint32_t messageSize,
+                                  const void * /* message */) {
+  // TODO(b/32114261): We need this hackery so we can get the raw bytes
+  //     from the host, without the test infrastructure trying to
+  //     interpret them.  This won't be necessary when messageType is
+  //     properly sent.
+  gUseNycMessageHack = false;
+
+  sInMethod = true;
+  if (messageSize != 0) {
+    sendFatalFailureToHost(
+        "SendMessageToHost message expects 0 additional bytes, got ",
+        &messageSize);
+  }
+
+  prepTestMemory();
+
+  // stage: 0
+  if (!sendMessageToHost(sSmallMessageData[0], kSmallMessageSize,
+                         kUntestedMessageType, smallMessageCallback0)) {
+    sendFatalFailureToHost("Failed chreSendMessageToHost stage 0");
+  }
+
+  // stage: 1
+  if (!sendMessageToHost(sSmallMessageData[1], kSmallMessageSize,
+                         kUntestedMessageType, smallMessageCallback1)) {
+    sendFatalFailureToHost("Failed chreSendMessageToHost stage 1");
+  }
+
+  // stage: 2
+  if (!sendMessageToHost(sSmallMessageData[2], kSmallMessageSize,
+                         kUntestedMessageType, nullptr)) {
+    sendFatalFailureToHost("Failed chreSendMessageToHost stage 2");
+  }
+  // There's no callback, so we mark this as a success.
+  markSuccess(2);
+
+  // stage: 3
+  if (!sendMessageToHost(sSmallMessageData[3], kSmallMessageSize,
+                         kUntestedMessageType, smallMessageCallback0)) {
+    sendFatalFailureToHost("Failed chreSendMessageToHost stage 3");
+  }
+
+  // stage: 4
+  if (!sendMessageToHost(nullptr, 0, kUntestedMessageType, nullptr)) {
+    sendFatalFailureToHost("Failed chreSendMessageToHost stage 4");
+  }
+  // There's no callback, so we mark this as a success.
+  markSuccess(4);
+
+  // stage: 5
+  sendMessageMaxSize();
+  // There's no callback, so we mark this as a success.
+  markSuccess(5);
+
+  // stage: 6
+  if (sendMessageToHost(sLargeMessageData[0], kLargeSizes[0],
+                        kUntestedMessageType, largeMessageCallback)) {
+    sendFatalFailureToHost("Oversized data to chreSendMessageToHost "
+                           "claimed success");
+  }
+
+  // stage: 7
+  if (!sendMessageToHost(sLargeMessageData[1], kLargeSizes[1],
+                         kUntestedMessageType, largeMessageCallback)) {
+    sendFatalFailureToHost("Failed chreSendMessageToHost stage 7");
+  }
+
+  sInMethod = false;
+}
+
+void SendMessageToHostTest::handleEvent(uint32_t senderInstanceId,
+                                        uint16_t eventType,
+                                        const void* eventData) {
+  if (sInMethod) {
+    sendFatalFailureToHost("handleEvent invoked while another nanoapp "
+                           "method is running");
+  }
+  sInMethod = true;
+
+  // TODO(b/32114261): Use getMessageDataFromHostEvent().  We can't do
+  //     that now because our messageType is probably wrong.
+  if (senderInstanceId != CHRE_INSTANCE_ID) {
+    sendFatalFailureToHost("handleEvent got event from unexpected sender:",
+                           &senderInstanceId);
+  }
+  if (eventType != CHRE_EVENT_MESSAGE_FROM_HOST) {
+    unexpectedEvent(eventType);
+  }
+
+  auto dataStruct = static_cast<const chreMessageFromHostData *>(eventData);
+  // TODO(b/32114261): Test the message type.
+  if (dataStruct->messageSize != 0) {
+    sendFatalFailureToHost("handleEvent got non-zero message size",
+                           &dataStruct->messageSize);
+  }
+  // We don't test dataStruct->message.  We don't require this to be
+  // nullptr.  If a CHRE choses to deal in 0-sized memory blocks, that's
+  // acceptable.
+
+  // Stage 8 was successful.  Note that other stages might still be waiting
+  // for freeCallbacks.  So we don't send success to the host, but just
+  // mark our stage as a success.
+  markSuccess(8);
+
+  sInMethod = false;
+}
+
+}  // namespace general_test
diff --git a/apps/chqts/src/general_test/send_message_to_host_test.h b/apps/chqts/src/general_test/send_message_to_host_test.h
new file mode 100644
index 0000000..3b1fb90
--- /dev/null
+++ b/apps/chqts/src/general_test/send_message_to_host_test.h
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _GTS_NANOAPPS_GENERAL_TEST_SEND_MESSAGE_TO_HOST_TEST_H_
+#define _GTS_NANOAPPS_GENERAL_TEST_SEND_MESSAGE_TO_HOST_TEST_H_
+
+#include <general_test/test.h>
+
+#include <chre.h>
+
+namespace general_test {
+
+/**
+ * Check chreSendMessageToHost() works, along with an empty message from the
+ * host to the nanoapp.
+ *
+ * TODO(b/32114261): This test is way more complicated than it should be.
+ *     Specifically, the standard workaround for this bug involves
+ *     putting more data within the 'message' to/from host/nanoapp.  But
+ *     since we're specifically testing that data, we can't use that
+ *     workaround.  When that workaround is gone, we can make this test
+ *     much simpler.
+ *
+ * Protocol:
+ * Host:    kSendMessageToHostTest, no data
+ * Nanoapp: 3 bytes of 0xFE
+ * Nanoapp: 3 bytes of 0xFE
+ * Nanoapp: 3 bytes of 0xFE
+ * Nanoapp: 3 bytes of 0xFE
+ * Nanoapp: 0 bytes
+ * Nanoapp: kContinue, 4 bytes (little endian) with <MessageMaxSize>
+ * Nanoapp: <MessageMaxSize> bytes of 0xFE
+ * Host:    0 bytes
+ * [nanoapp waits for all 'freeCallback's to have been invoked]
+ * Nanoapp: kSuccess
+ */
+class SendMessageToHostTest : public Test {
+ public:
+  SendMessageToHostTest();
+
+ protected:
+  void handleEvent(uint32_t senderInstanceId, uint16_t eventType,
+                   const void* eventData) override;
+  void setUp(uint32_t messageSize, const void *message) override;
+
+ private:
+  // Note, most of our data and methods are static, because much of our test
+  // logic happens in callbacks which must be static members.  This class
+  // instance is a singleton, so there's no issue with this approach.
+
+  static constexpr uint8_t kDataByte = UINT8_C(0xFE);
+
+  static constexpr size_t kSmallMessageSize = 3;
+  static constexpr size_t kSmallMessageTestCount = 4;
+  static uint8_t sSmallMessageData[kSmallMessageTestCount][kSmallMessageSize];
+
+  static constexpr size_t kLargeSizes[2] = {
+    CHRE_MESSAGE_TO_HOST_MAX_SIZE + 1,
+    CHRE_MESSAGE_TO_HOST_MAX_SIZE
+  };
+  static void *sLargeMessageData[2];
+
+  // Catch if CHRE implementation illegally reenters nanoapp code.
+  static bool sInMethod;
+
+  // We have nine stages.  We set a bit in our finishedBitmask
+  // when each has succeeded.
+  static constexpr uint32_t kAllFinished = (1 << 9) - 1;
+  static uint32_t sFinishedBitmask;
+
+  template<uint8_t kCallbackIndex>
+  static void smallMessageCallback(void *message, size_t messageSize);
+
+  static void smallMessageCallback0(void *message, size_t messageSize);
+  static void smallMessageCallback1(void *message, size_t messageSize);
+
+  static void largeMessageCallback(void *message, size_t messageSize);
+
+  static uint32_t getSmallDataIndex(const uint8_t *data);
+
+  static void markSuccess(uint32_t stage);
+
+  static bool sendMessageToHost(void *message, uint32_t messageSize,
+                                uint32_t reservedMessageType,
+                                chreMessageFreeFunction *freeCallback);
+
+  void prepTestMemory();
+  void sendMessageMaxSize();
+};
+
+}  // namespace general_test
+
+
+#endif  // _GTS_NANOAPPS_GENERAL_TEST_SEND_MESSAGE_TO_HOST_TEST_H_
diff --git a/apps/chqts/src/general_test/sensor_info_test.cc b/apps/chqts/src/general_test/sensor_info_test.cc
new file mode 100644
index 0000000..de34b82
--- /dev/null
+++ b/apps/chqts/src/general_test/sensor_info_test.cc
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <general_test/sensor_info_test.h>
+
+#include <shared/send_message.h>
+
+namespace general_test {
+
+SensorInfoTest::SensorInfoTest()
+    : Test(CHRE_API_VERSION_1_1) {
+}
+
+void SensorInfoTest::setUp(uint32_t messageSize, const void * /* message */) {
+  if (messageSize != 0) {
+    nanoapp_testing::sendFatalFailureToHost(
+        "Expected 0 byte message, got more bytes:", &messageSize);
+  } else if (!chreSensorFindDefault(CHRE_SENSOR_TYPE_ACCELEROMETER,
+                                    &mSensorHandle)) {
+    nanoapp_testing::sendFatalFailureToHost(
+        "CHRE implementation does not have an accelerometer");
+  } else {
+    struct chreSensorInfo info;
+
+    if (!chreGetSensorInfo(mSensorHandle, &info)) {
+      nanoapp_testing::sendFatalFailureToHost(
+          "Failed to gather sensor info");
+    } else {
+      mCompleted = true;
+      validateSensorInfo(info);
+    }
+  }
+}
+
+void SensorInfoTest::validateSensorInfo(const struct chreSensorInfo& info) const {
+  if ((mApiVersion < CHRE_API_VERSION_1_1) && (info.minInterval != 0)) {
+    nanoapp_testing::sendFatalFailureToHost(
+        "Sensor minimum interval is non-zero");
+  } else if (info.minInterval == 0) {
+    nanoapp_testing::sendFatalFailureToHost(
+        "Sensor minimum interval is unknown");
+  } else if (!chreSensorConfigure(mSensorHandle,
+                                  CHRE_SENSOR_CONFIGURE_MODE_CONTINUOUS,
+                                  info.minInterval,
+                                  CHRE_SENSOR_LATENCY_DEFAULT)) {
+    nanoapp_testing::sendFatalFailureToHost(
+        "Sensor failed configuration with minimum interval");
+  } else if (!chreSensorConfigureModeOnly(mSensorHandle,
+                                          CHRE_SENSOR_CONFIGURE_MODE_DONE)) {
+    nanoapp_testing::sendFatalFailureToHost(
+        "Unable to configure sensor mode to DONE");
+  } else {
+    nanoapp_testing::sendSuccessToHost();
+  }
+}
+
+void SensorInfoTest::handleEvent(uint32_t /* senderInstanceId */,
+                                 uint16_t eventType,
+                                 const void * /* eventData */) {
+  if (!mCompleted) {
+    unexpectedEvent(eventType);
+  }
+}
+
+} // namespace general_test
diff --git a/apps/chqts/src/general_test/sensor_info_test.h b/apps/chqts/src/general_test/sensor_info_test.h
new file mode 100644
index 0000000..6743a81
--- /dev/null
+++ b/apps/chqts/src/general_test/sensor_info_test.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef _GTS_NANOAPPS_GENERAL_TEST_SENSOR_INFO_TEST_H_
+#define _GTS_NANOAPPS_GENERAL_TEST_SENSOR_INFO_TEST_H_
+
+#include <general_test/test.h>
+
+#include <cstdint>
+
+#include <chre.h>
+
+namespace general_test {
+
+class SensorInfoTest : public Test {
+ public:
+  SensorInfoTest();
+
+ protected:
+  void handleEvent(uint32_t senderInstanceId, uint16_t eventType,
+                   const void *eventData) override;
+  void setUp(uint32_t messageSize, const void *message) override;
+
+ private:
+  void validateSensorInfo(const struct chreSensorInfo& info) const;
+  uint32_t mSensorHandle;
+  bool mCompleted = false;
+};
+
+} // namespace general_test
+
+#endif // _GTS_NANOAPPS_GENERAL_TEST_SENSOR_INFO_TEST_H_
diff --git a/apps/chqts/src/general_test/simple_heap_alloc_test.cc b/apps/chqts/src/general_test/simple_heap_alloc_test.cc
new file mode 100644
index 0000000..d425370
--- /dev/null
+++ b/apps/chqts/src/general_test/simple_heap_alloc_test.cc
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <general_test/simple_heap_alloc_test.h>
+
+#include <cstddef>
+
+#include <general_test/test_names.h>
+#include <shared/abort.h>
+#include <shared/array_length.h>
+#include <shared/nano_string.h>
+#include <shared/send_message.h>
+
+#include <chre.h>
+
+using nanoapp_testing::MessageType;
+using nanoapp_testing::sendFatalFailureToHost;
+using nanoapp_testing::sendMessageToHost;
+using nanoapp_testing::sendSuccessToHost;
+
+namespace general_test {
+
+// For most platforms, we expect that what the compiler toolchain claims
+// is the maximum alignment needed for any type is accurate.  However, we
+// do support one CHRE implementation where it is configured for a lower
+// max alignment than what the toolchain claims.
+// To support this, we allow for a compiler define set for building this
+// test.  For the most part, we need to just trust the CHRE implementation
+// that this number is correct.  However, we make a basic sanity check of
+// this in testMaxAlignment().
+
+constexpr size_t kMaxAlignment =
+#ifdef CHRE_CUSTOM_MAX_ALIGNMENT
+    CHRE_CUSTOM_MAX_ALIGNMENT;
+#else
+    alignof(max_align_t);
+#endif  // else CHRE_CUSTOM_MAX_ALIGNMENT
+
+#ifdef CHRE_CUSTOM_MAX_ALIGNMENT
+// We only test this when a CHRE implementation claims a custom max aligment.
+// We use an argument here to try to keep the compiler from performing any
+// of these calculations at compile-time, so they're forced to happen at
+// runtime.  We do a mixture of multiplication and division, to force
+// various instructions which might have alignment constraints.
+static void testMaxAlignment(uint32_t zero) {
+  // It's not sufficient to use alignas(kMaxAlignment).  Say kMaxAlignment
+  // is 4.  Then alignas(4) could legally give something aligned on 32 bytes,
+  // and we wouldn't be testing what we hoped to test.  So we ask for double
+  // the alignment (alignas(8), in our example), and then offset into that
+  // to assure that we're at exactly kMaxAlignment, and no more.
+
+#ifdef CHRE_NO_DOUBLE_SUPPORT
+  typedef float MyFloat;
+#define FLOAT_C(value) value##f
+#else
+  typedef long double myFloat;
+#define FLOAT_C(value) value
+#endif
+
+  alignas(kMaxAlignment * 2) uint8_t
+      myFloatMemory[sizeof(MyFloat) * 3 + kMaxAlignment];
+  MyFloat *mfArray =
+      reinterpret_cast<MyFloat*>(myFloatMemory + kMaxAlignment);
+  mfArray[0] = static_cast<MyFloat>(zero) + FLOAT_C(1.0);
+  mfArray[1] = static_cast<MyFloat>(zero) + FLOAT_C(3.0);
+  mfArray[2] = mfArray[0] / mfArray[1];
+  if ((mfArray[0] * mfArray[1] + mfArray[2]) / FLOAT_C(3.0) == FLOAT_C(1.0)) {
+    sendFatalFailureToHost("Float math is wrong");
+  }
+
+  constexpr size_t kUllSize = sizeof(unsigned long long);
+  static_assert(kUllSize >= 8, "Size of long long violates spec");
+  alignas(kMaxAlignment * 2) uint8_t
+      longlongMemory[kUllSize * 3 + kMaxAlignment];
+  unsigned long long *ullArray =
+      reinterpret_cast<unsigned long long*>(longlongMemory + kMaxAlignment);
+  ullArray[0] = static_cast<unsigned long long>(zero) +
+      (1ULL << (kUllSize * 8 - 4));
+  ullArray[1] = static_cast<unsigned long long>(zero) + (1ULL << 3);
+  ullArray[2] = ullArray[0] * ullArray[1];
+  constexpr unsigned long long kExpected = 747134227367742ULL;
+  unsigned long long result = ullArray[2] / 12345ULL;
+  if (((kUllSize == 8) && (result != kExpected)) ||
+      ((kUllSize > 8) && (result <= kExpected))) {
+    sendFatalFailureToHost("Long long math is wrong");
+  }
+}
+#endif  // CHRE_CUSTOM_MAX_ALIGNMENT
+
+
+SimpleHeapAllocTest::SimpleHeapAllocTest()
+  : Test(CHRE_API_VERSION_1_0), mHasFreed(false) {
+}
+
+void SimpleHeapAllocTest::setUp(uint32_t messageSize,
+                                const void * /* message */) {
+  nanoapp_testing::memset(mPtrs, 0, sizeof(mPtrs));
+
+  if (messageSize != 0) {
+    sendFatalFailureToHost(
+        "SimpleHeapAlloc message expects 0 additional bytes, got ",
+        &messageSize);
+  }
+
+  // Allocate random small-ish sizes.
+  static constexpr size_t kSizes[5] = {
+    16, 53, 2, 32, 40 };
+
+  mPtrs[0] = chreHeapAlloc(kSizes[0]);
+  mPtrs[1] = chreHeapAlloc(kSizes[1]);
+  // For mPtrs[2] we do _not_ use kSizes[2], because we're going to free
+  // this in a moment, and intentionally want a different size.
+  mPtrs[2] = chreHeapAlloc(23);
+  mPtrs[3] = chreHeapAlloc(kSizes[3]);
+  // We want to mix in a free among the allocs, just to make sure there
+  // isn't some issue there.
+  if (mPtrs[2] == nullptr) {
+    sendFatalFailureToHost("Failed first allocation of mPtrs[2]");
+  } else {
+    chreHeapFree(mPtrs[2]);
+  }
+  mPtrs[4] = chreHeapAlloc(kSizes[4]);
+  mPtrs[2] = chreHeapAlloc(kSizes[2]);
+
+  for (uint32_t i = 0; i < arrayLength(mPtrs); i++) {
+    if (mPtrs[i] == nullptr) {
+      // If we're getting this failure, but convinced the CHRE is
+      // correct, make sure that we're actually performing an allocation
+      // for each element of mPtrs.
+      sendFatalFailureToHost("Failed to allocate index ", &i);
+    }
+    const uintptr_t ptrValue = reinterpret_cast<uintptr_t>(mPtrs[i]);
+    if ((ptrValue & (kMaxAlignment - 1)) != 0) {
+      sendFatalFailureToHost("Misaligned allocation at index ", &i);
+    }
+    // Make sure all of the bytes are addressable.  Our assumption
+    // is we'll crash here if that's not the case.  Not the most
+    // friendly test, but it's better than allowing a bad CHRE.
+    // TODO: If we convince ourselves that chreLog() should be
+    //     safe enough to use here, we could log an 'info' message
+    //     prior to each memset attempt.
+    nanoapp_testing::memset(mPtrs[i], 0xFF, kSizes[i]);
+  }
+#ifdef CHRE_CUSTOM_MAX_ALIGNMENT
+  testMaxAlignment(messageSize);
+#endif  // CHRE_CUSTOM_MAX_ALIGNMENT
+  sendMessageToHost(MessageType::kContinue);
+}
+
+void SimpleHeapAllocTest::handleEvent(uint32_t senderInstanceId,
+                                      uint16_t eventType,
+                                      const void* eventData) {
+  // We ignore the return value, since we expect no data.
+  getMessageDataFromHostEvent(senderInstanceId, eventType, eventData,
+                              MessageType::kContinue, 0);
+  if (mHasFreed) {
+    sendFatalFailureToHost("Multiple kContinue messages sent");
+  }
+
+  chreHeapFree(mPtrs[3]);
+  chreHeapFree(mPtrs[1]);
+  chreHeapFree(mPtrs[2]);
+  chreHeapFree(mPtrs[0]);
+  chreHeapFree(mPtrs[4]);
+  mHasFreed = true;
+
+  sendSuccessToHost();
+}
+
+}  // namespace general_test
diff --git a/apps/chqts/src/general_test/simple_heap_alloc_test.h b/apps/chqts/src/general_test/simple_heap_alloc_test.h
new file mode 100644
index 0000000..2fa3d55
--- /dev/null
+++ b/apps/chqts/src/general_test/simple_heap_alloc_test.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _GTS_NANOAPPS_GENERAL_TEST_SIMPLE_HEAP_ALLOC_TEST_H_
+#define _GTS_NANOAPPS_GENERAL_TEST_SIMPLE_HEAP_ALLOC_TEST_H_
+
+#include <general_test/test.h>
+
+namespace general_test {
+
+/**
+ * Performs simple allocation and freeing from the heap.
+ *
+ * Requires the host to send an additional message to tell us to free.
+ *
+ * Protocol:
+ * Host:    kSimpleHeapAlloc, no data
+ * Nanoapp: kContinue, no data
+ * Host:    kContinue, no data
+ * Nanoapp: kSuccess, no data
+ */
+class SimpleHeapAllocTest : public Test {
+ public:
+  SimpleHeapAllocTest();
+
+ protected:
+  void handleEvent(uint32_t senderInstanceId, uint16_t eventType,
+                   const void* eventData) override;
+  void setUp(uint32_t messageSize, const void *message) override;
+
+ private:
+  bool mHasFreed;
+  void *mPtrs[5];
+};
+
+}  // namespace general_test
+
+
+#endif  // _GTS_NANOAPPS_GENERAL_TEST_SIMPLE_HEAP_ALLOC_TEST_H_
diff --git a/apps/chqts/src/general_test/test.cc b/apps/chqts/src/general_test/test.cc
new file mode 100644
index 0000000..0b65067
--- /dev/null
+++ b/apps/chqts/src/general_test/test.cc
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <general_test/test.h>
+
+#include <shared/abort.h>
+#include <shared/send_message.h>
+
+#include <chre.h>
+
+using nanoapp_testing::sendFatalFailureToHost;
+
+namespace general_test {
+
+Test::Test(uint32_t minSupportedVersion)
+    : mApiVersion(chreGetApiVersion())
+      , mIsSupported(mApiVersion >= minSupportedVersion) {
+}
+
+void Test::testSetUp(uint32_t messageSize, const void *message) {
+  if (mIsSupported) {
+    setUp(messageSize, message);
+  } else {
+    sendMessageToHost(nanoapp_testing::MessageType::kSkipped);
+  }
+}
+
+void Test::testHandleEvent(uint32_t senderInstanceId, uint16_t eventType,
+                           const void *eventData) {
+  if (mIsSupported) {
+    handleEvent(senderInstanceId, eventType, eventData);
+  }
+}
+
+void Test::unexpectedEvent(uint16_t eventType) {
+  uint32_t localEvent = eventType;
+  sendFatalFailureToHost("Test received unexpected event:", &localEvent);
+}
+
+const void *Test::getMessageDataFromHostEvent(uint32_t senderInstanceId,
+                                              uint16_t eventType, const void* eventData,
+                                              nanoapp_testing::MessageType expectedMessageType,
+                                              uint32_t expectedMessageSize) {
+  if (senderInstanceId != CHRE_INSTANCE_ID) {
+    sendFatalFailureToHost("Unexpected sender ID:", &senderInstanceId);
+  }
+  if (eventType != CHRE_EVENT_MESSAGE_FROM_HOST) {
+    unexpectedEvent(eventType);
+  }
+  if (eventData == nullptr) {
+    sendFatalFailureToHost("NULL eventData given");
+  }
+  auto data = static_cast<const chreMessageFromHostData*>(eventData);
+  if (data->reservedMessageType != uint32_t(expectedMessageType)) {
+    sendFatalFailureToHost("Unexpected reservedMessageType:",
+                           &(data->reservedMessageType));
+  }
+  if (data->messageSize != expectedMessageSize) {
+    sendFatalFailureToHost("Unexpected messageSize:", &(data->messageSize));
+  }
+  return data->message;
+}
+
+
+}  // namespace general_test
diff --git a/apps/chqts/src/general_test/test.h b/apps/chqts/src/general_test/test.h
new file mode 100644
index 0000000..83d164c
--- /dev/null
+++ b/apps/chqts/src/general_test/test.h
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _GTS_NANOAPPS_GENERAL_TEST_TEST_H_
+#define _GTS_NANOAPPS_GENERAL_TEST_TEST_H_
+
+#include <shared/send_message.h>
+
+#include <chre.h>
+
+namespace general_test {
+
+/**
+ * Abstract base for all test cases.
+ */
+class Test {
+ public:
+  Test(uint32_t minSupportedVersion);
+  virtual ~Test() {}
+
+  void testHandleEvent(uint32_t senderInstanceId, uint16_t eventType,
+                       const void *eventData);
+
+  void testSetUp(uint32_t messageSize, const void *message);
+
+ protected:
+  /**
+   * Report a test-ending error due to an unexpectedEvent.
+   *
+   * @param eventType  The event type
+   * @returns Never.  This method aborts execution.
+   */
+  static void unexpectedEvent(uint16_t eventType);
+
+  /**
+   * Get the message data sent from the host, after performing sanity checks.
+   *
+   * The method centralizes a number of common sanity checks that tests
+   * will perform in taking the given CHRE event data and extracting out
+   * the raw data payload sent by the host.  This method is still useful
+   * when no message data is expected from the host, as we'll still
+   * perform the sanity checks.
+   *
+   * This method will end the test in failure if any of the following happen:
+   * o 'senderInstanceId' != CHRE_INSTANCE_ID
+   * o 'eventType' != CHRE_EVENT_MESSAGE_FROM_HOST
+   * o 'eventData'->reservedMessageType != expectedMessageType
+   * o 'eventData'->messageSize != expectedMessageSize
+   *
+   * @param senderInstanceId  From handleEvent()
+   * @param eventType  From handleEvent()
+   * @param eventData  From handleEvent()
+   * @param expectedMessageType  The expected 'reservedMessageType' field
+   *     when 'eventData' is seen as a chreMessageFromHostData.
+   * @param expectedMessageSize  The expected 'messageSize' field
+   *     when 'eventData' is seen as a chreMessageFromHostData.
+   * @returns 'eventData'->message, assuming all the sanity checks pass.
+   */
+  static const void *getMessageDataFromHostEvent(
+      uint32_t senderInstanceId, uint16_t eventType, const void* eventData,
+      nanoapp_testing::MessageType expectedMessageType,
+      uint32_t expectedMessageSize);
+
+  virtual void handleEvent(uint32_t senderInstanceId, uint16_t eventType,
+                           const void* eventData) = 0;
+  virtual void setUp(uint32_t messageSize, const void *message) = 0;
+
+  /**
+   * The platform reported CHRE API version.
+   *
+   * Nanoapps may use this to determine what version they are running
+   * on and perform any version specific behaviours.
+   */
+  const uint32_t mApiVersion;
+
+ private:
+  /**
+   * Is the nanoapp supported by the platform reported CHRE API version.
+   *
+   * Nanoapps specify the minimum CHRE API version required during
+   * construction. If it is at least the version that is being reported
+   * by the platform then mIsSupported will be true. Else, the nanoapp
+   * will skip the test.
+   */
+  const bool mIsSupported;
+};
+
+}  // namespace general_test
+
+
+#endif  // _GTS_NANOAPPS_GENERAL_TEST_TEST_H_
diff --git a/apps/chqts/src/general_test/test_names.h b/apps/chqts/src/general_test/test_names.h
new file mode 100644
index 0000000..1411c88
--- /dev/null
+++ b/apps/chqts/src/general_test/test_names.h
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _GTS_NANOAPPS_GENERAL_TEST_TEST_NAMES_H_
+#define _GTS_NANOAPPS_GENERAL_TEST_TEST_NAMES_H_
+
+#include <cstdint>
+
+/**
+ * NOTE: These values are manually synced in the GTS Java's
+ *     ContextHubTestConstants.java.  If you make a change here, be sure
+ *     to update ContextHubTestContants.java as well.
+ */
+
+namespace general_test {
+
+/**
+ * Names of the tests we support.
+ */
+enum class TestNames : uint32_t {
+  /**
+   * Value which should never be used.
+   *
+   * This starts at CONTEXT_HUB_TYPE_PRIVATE_MSG_BASE.
+   */
+  kInvalidTest = 0x0400,
+
+  /**
+   * Test: HelloWorldTest
+   */
+  kHelloWorld = 0x0401,
+
+  /**
+   * Test: SimpleHeapAllocTest
+   */
+  kSimpleHeapAlloc = 0x0402,
+
+  /**
+   * Test: HeapAllocStressTest
+   */
+  kHeapAllocStress = 0x0403,
+
+  /**
+   * Test: GetTimeTest
+   */
+  kGetTime = 0x0404,
+
+  /**
+   * Test: EventBetweenApps0
+   */
+  kEventBetweenApps0 = 0x0405,
+
+  /**
+   * Test: EventBetweenApps1
+   */
+  kEventBetweenApps1 = 0x0406,
+
+  /**
+   * Test: SendEventTest
+   */
+  kSendEvent = 0x0407,
+
+  /**
+   * Test: BasicAccelerometerTest
+   */
+  kBasicAccelerometer = 0x0408,
+
+  /**
+   * Test: BasicInstantMotionDetectTest
+   */
+  kBasicInstantMotionDetect = 0x0409,
+
+  /**
+   * Test: BasicStationaryDetectTest
+   */
+  kBasicStationaryDetect = 0x040A,
+
+  /**
+   * Test: BasicGyroscopeTest
+   */
+  kBasicGyroscope = 0x040B,
+
+  /**
+   * Test: BasicMagnetometerTest
+   */
+  kBasicMagnetometer = 0x040C,
+
+  /**
+   * Test: BasicBarometerTest
+   */
+  kBasicBarometer = 0x040D,
+
+  /**
+   * Test: BasicLightSensorTest
+   */
+  kBasicLightSensor = 0x040E,
+
+  /**
+   * Test: BasicProximityTest
+   */
+  kBasicProximity = 0x040F,
+
+  /**
+   * Test: VersionSanityTest
+   */
+  kVersionSanity = 0x0410,
+
+  /**
+   * Test: LoggingSanityTest
+   */
+  kLoggingSanity = 0x0411,
+
+  /**
+   * Test: SendMessageToHostTest
+   */
+  kSendMessageToHost = 0x0412,
+
+  /**
+   * Test: TimerSetTest
+   */
+  kTimerSet = 0x0413,
+
+  /**
+   * Test: TimerCancelTest
+   */
+  kTimerCancel = 0x0414,
+
+  /**
+   * Test: TimerStressTest
+   */
+  kTimerStress = 0x0415,
+
+  /**
+   * Test: SendEventStressTest
+   */
+  kSendEventStress = 0x0416,
+
+  /**
+   * Test: HeapExhaustionStabilityTest
+   */
+  kHeapExhaustionStability = 0x0417,
+
+  /**
+   * Test: GnssCapabilitiesTest
+   */
+  kGnssCapabilities = 0x0418,
+
+  /**
+   * Test: WifiCapablitiesTest
+   */
+  kWifiCapabilities = 0x0419,
+
+  /**
+   * Test: WwanCapabilitiesTest
+   */
+  kWwanCapabilities = 0x041A,
+
+  /**
+   * Test: SensorInfoTest
+   */
+  kSensorInfo = 0x041B,
+
+  /**
+   * Test: WwanCellInfoTest
+   */
+  kWwanCellInfoTest = 0x041C,
+
+  /**
+   * Test: EstimatedHostTimeTest
+   */
+  kEstimatedHostTime = 0x041D,
+
+  /**
+   * Test: NanoappInfoByAppId
+   */
+  kNanoappInfoByAppId = 0x041E,
+
+  /**
+   * Test: NanoappInfoByInstanceId
+   */
+  kNanoappInfoByInstanceId = 0x041F,
+
+  /**
+   * Test: NanoAppInfoEventsTest
+   */
+  kNanoAppInfoEventsPerformer = 0x0420,
+
+  /**
+   * Test: NanoAppInfoEventsTest
+   */
+  kNanoAppInfoEventsObserver = 0x0421,
+};
+
+}  // namespace general_test
+
+
+#endif  // _GTS_NANOAPPS_GENERAL_TEST_TEST_NAMES_H_
diff --git a/apps/chqts/src/general_test/timer_cancel_test.cc b/apps/chqts/src/general_test/timer_cancel_test.cc
new file mode 100644
index 0000000..689d868
--- /dev/null
+++ b/apps/chqts/src/general_test/timer_cancel_test.cc
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <general_test/timer_cancel_test.h>
+
+#include <cinttypes>
+#include <cstddef>
+
+#include <shared/send_message.h>
+
+#include <chre.h>
+
+using nanoapp_testing::sendFatalFailureToHost;
+using nanoapp_testing::sendInternalFailureToHost;
+using nanoapp_testing::sendSuccessToHost;
+
+/*
+ * This test has four stages where we cancel one-shot and recurring timers,
+ * before and after they're triggered.
+ *
+ * See the TimerCancelTest constructor to see which stage tests which setup.
+ *
+ * When all of our stages have succeeded, then we send success to the host.
+ */
+
+// 10 milliseconds
+static uint64_t kDuration = UINT64_C(10000000);
+
+namespace general_test {
+
+void TimerCancelTest::startStages() {
+  for (uint32_t i = 0; i < kStageCount; i++) {
+    Stage *stage = &mStages[i];
+    stage->timerId = chreTimerSet(kDuration, stage, stage->oneShot);
+    if (stage->timerId == CHRE_TIMER_INVALID) {
+      sendFatalFailureToHost("Unable to set timer:", &i);
+    }
+    if (stage->expectCallback) {
+      // Go on to the next stage.  Note this stage will markSuccess()
+      // in handleStageEvent().
+      continue;
+    }
+    if (!chreTimerCancel(stage->timerId)) {
+      sendFatalFailureToHost("Unable to cancel timer:", &i);
+    }
+    if (chreTimerCancel(stage->timerId)) {
+      sendFatalFailureToHost("Claimed success in second cancel:", &i);
+    }
+    markSuccess(i);
+  }
+}
+
+TimerCancelTest::TimerCancelTest()
+  : Test(CHRE_API_VERSION_1_0),
+    mInMethod(false),
+    mStages{
+      // expectCallback:false ==> We're canceling before the timer fires.
+      // expectCallback:true  ==> We'll cancel after the timer fires once.
+      //
+      //        stage, oneShot, expectCallback
+        Stage(0,     false,   false),
+        Stage(1,     true,    false),
+        Stage(2,     false,   true  ),
+        Stage(3,     true,    true  )},
+    mFinishedBitmask(0) {
+}
+
+void TimerCancelTest::setUp(uint32_t messageSize, const void * /* message */) {
+  mInMethod = true;
+
+  if (messageSize != 0) {
+    sendFatalFailureToHost(
+        "TimerCancel message expects 0 additional bytes, got ",
+        &messageSize);
+  }
+
+  constexpr uint32_t kUnownedTimer = 0;
+  static_assert((kUnownedTimer != CHRE_TIMER_INVALID), "Bad test");
+  if (chreTimerCancel(kUnownedTimer)) {
+    sendFatalFailureToHost("Claimed success canceling timer we don't own");
+  }
+
+  startStages();
+
+  // Now we wait for some events from the timers to fire.
+
+  mInMethod = false;
+}
+
+void TimerCancelTest::handleStageEvent(Stage *stage) {
+  if (!stage->expectCallback) {
+    sendFatalFailureToHost("Timer didn't cancel:", &stage->stage);
+  }
+  // Now we're going to cancel the timer, so we don't expect an
+  // additional call.
+  stage->expectCallback = false;
+
+  bool cancelSucceeded = chreTimerCancel(stage->timerId);
+  if (stage->oneShot) {
+    if (cancelSucceeded) {
+      sendFatalFailureToHost("Claimed success canceling one-shot after "
+                             "it fired:", &stage->stage);
+    }
+  } else {
+    if (!cancelSucceeded) {
+      sendFatalFailureToHost("Unable to cancel recurring timer:",
+                             &stage->stage);
+    }
+  }
+  if (chreTimerCancel(stage->timerId)) {
+    sendFatalFailureToHost("Claimed success in second cancel:",
+                           &stage->stage);
+  }
+  markSuccess(stage->stage);
+}
+
+void TimerCancelTest::handleEvent(uint32_t senderInstanceId,
+                                  uint16_t eventType, const void* eventData) {
+  if (mInMethod) {
+    sendFatalFailureToHost("handleEvent invoked while another nanoapp "
+                           "method is running");
+  }
+  mInMethod = true;
+  if (senderInstanceId != CHRE_INSTANCE_ID) {
+    sendFatalFailureToHost("handleEvent got event from unexpected sender:",
+                           &senderInstanceId);
+  }
+  if (eventType != CHRE_EVENT_TIMER) {
+    unexpectedEvent(eventType);
+  }
+  const Stage *stage = static_cast<const Stage*>(eventData);
+  if (stage->stage >= kStageCount) {
+    sendFatalFailureToHost("Invalid handleEvent data:", &stage->stage);
+  }
+  handleStageEvent(const_cast<Stage *>(stage));
+
+  mInMethod = false;
+}
+
+void TimerCancelTest::markSuccess(uint32_t stage) {
+  chreLog(CHRE_LOG_DEBUG, "Stage %" PRIu32 " succeeded", stage);
+  uint32_t finishedBit = (1 << stage);
+  if ((kAllFinished & finishedBit) == 0) {
+    sendFatalFailureToHost("markSuccess bad stage:", &stage);
+  }
+  if ((mFinishedBitmask & finishedBit) != 0) {
+    sendInternalFailureToHost("markSuccess multiple times:", &stage);
+  }
+  mFinishedBitmask |= finishedBit;
+  if (mFinishedBitmask == kAllFinished) {
+    sendSuccessToHost();
+  }
+}
+
+}  // namespace general_test
diff --git a/apps/chqts/src/general_test/timer_cancel_test.h b/apps/chqts/src/general_test/timer_cancel_test.h
new file mode 100644
index 0000000..0a82a6f
--- /dev/null
+++ b/apps/chqts/src/general_test/timer_cancel_test.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _GTS_NANOAPPS_GENERAL_TEST_TIMER_CANCEL_TEST_H_
+#define _GTS_NANOAPPS_GENERAL_TEST_TIMER_CANCEL_TEST_H_
+
+#include <general_test/test.h>
+#include <chre.h>
+
+namespace general_test {
+
+/**
+ * Checks that chreTimerCancel() works by trying various usages.
+ *
+ * Simple Protocol.
+ */
+class TimerCancelTest : public Test {
+ public:
+  TimerCancelTest();
+
+ protected:
+  void handleEvent(uint32_t senderInstanceId, uint16_t eventType,
+                   const void* eventData) override;
+  void setUp(uint32_t messageSize, const void *message) override;
+
+ private:
+  struct Stage {
+    uint32_t stage;
+    uint32_t timerId;
+    bool oneShot;
+    bool expectCallback;
+
+    // Leave timerId invalid.
+    Stage(uint32_t stage_, bool oneShot_, bool expectCallback_) :
+      stage(stage_), timerId(CHRE_TIMER_INVALID), oneShot(oneShot_),
+      expectCallback(expectCallback_) {}
+  };
+
+  bool mInMethod;
+
+  static constexpr size_t kStageCount = 4;
+  Stage mStages[kStageCount];
+
+  static constexpr uint32_t kAllFinished = (1 << kStageCount) - 1;
+  uint32_t mFinishedBitmask;
+
+  void startStages();
+  void handleStageEvent(Stage *stage);
+  void markSuccess(uint32_t stage);
+};
+
+}  // namespace general_test
+
+
+#endif  // _GTS_NANOAPPS_GENERAL_TEST_TIMER_CANCEL_TEST_H_
diff --git a/apps/chqts/src/general_test/timer_set_test.cc b/apps/chqts/src/general_test/timer_set_test.cc
new file mode 100644
index 0000000..fb7165b
--- /dev/null
+++ b/apps/chqts/src/general_test/timer_set_test.cc
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <general_test/timer_set_test.h>
+
+#include <cinttypes>
+#include <cstddef>
+#include <new>
+
+#include <shared/send_message.h>
+
+#include <chre.h>
+
+using nanoapp_testing::sendFatalFailureToHost;
+using nanoapp_testing::sendInternalFailureToHost;
+using nanoapp_testing::sendSuccessToHost;
+
+/*
+ * We have various "stages" for different timer setups we want to test.
+ * To speed up the test, we run all our stages simultaneously.  That
+ * requires having 6 timers available, but with a 32 timer minimum
+ * and a presumption that tests aren't going to be run alongside a lot
+ * of other nanoapps, this should be fine.
+ *
+ * See initStages() for the description of each stage.  Since these all
+ * happen in parallel, we leave it to each stage to mark itself has having
+ * succeeded, and have markSuccess() tell the Host when all stages have
+ * reported in.
+ *
+ * Note that we intentionally place the one-shot timers first, to give
+ * 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;
+
+namespace general_test {
+
+TimerSetTest::Stage::Stage(uint32_t stage, uint64_t duration,
+                           const void *cookie, bool oneShot)
+    : mSetTime(0), mDuration(duration), mStage(stage), mEventCount(0),
+      mCookie(cookie), mOneShot(oneShot) {}
+
+void TimerSetTest::Stage::start() {
+  mSetTime = chreGetTime();
+  mTimerHandle = chreTimerSet(mDuration, mCookie, mOneShot);
+  if (mTimerHandle == CHRE_TIMER_INVALID) {
+    sendFatalFailureToHost("Unable to set timer ", &mStage);
+  }
+  if (mSetTime == 0) {
+    sendFatalFailureToHost("chreGetTime() gave 0");
+  }
+}
+
+void TimerSetTest::Stage::processEvent(uint64_t timestamp, TimerSetTest *test) {
+  if (mSetTime == 0) {
+    sendInternalFailureToHost("Didn't initialize mSetTime");
+  }
+  mEventCount++;
+
+  uint64_t expectedTime = mSetTime + (mEventCount * mDuration);
+  if (timestamp < expectedTime) {
+    sendFatalFailureToHost("Timer triggered too soon ", &mStage);
+  }
+  // TODO(b/32179037): Make this check much stricter.
+  if (timestamp > (expectedTime + kOneSecond)) {
+    sendFatalFailureToHost("Timer triggered over a second late ", &mStage);
+  }
+
+  if (mOneShot) {
+    if (mEventCount > 1) {
+      sendFatalFailureToHost("One shot timer called multiple times ",
+                             &mStage);
+    } else {
+      test->markSuccess(mStage);
+    }
+  } else if (mEventCount == 3) {
+    // We mark recurring timers as successful on their third firing, if we
+    // can cancel it.
+    if (chreTimerCancel(mTimerHandle)) {
+      test->markSuccess(mStage);
+    } else {
+      sendFatalFailureToHost("Could not cancel recurring timer", &mStage);
+    }
+  }
+}
+
+void TimerSetTest::initStages() {
+  // To avoid fragmentation, we do one large allocation, and use
+  // placement new to initialize it.
+  mStages = static_cast<Stage*>(chreHeapAlloc(sizeof(*mStages) *
+                                              kStageCount));
+  if (mStages == nullptr) {
+    sendFatalFailureToHost("Insufficient heap");
+  }
+
+#define COOKIE(num) reinterpret_cast<const void*>(num)
+
+  // Stage 0: Test NULL cookie
+  new(&mStages[0]) Stage(0, kShortDuration, nullptr, true);
+  // Stage 1: Test (void*)-1 cookie
+  new(&mStages[1]) Stage(1, kShortDuration, COOKIE(-1), true);
+  // Stage 2: Test one shot with short duration
+  new(&mStages[2]) Stage(2, kShortDuration, COOKIE(2), true);
+  // Stage 3: Test one shot with long duration
+  new(&mStages[3]) Stage(3, kLongDuration,  COOKIE(3), true);
+  // Stage 4: Test recurring with long duration
+  new(&mStages[4]) Stage(4, kLongDuration,  COOKIE(4), false);
+  // Stage 5: Test recurring with short duration
+  new(&mStages[5]) Stage(5, kShortDuration, COOKIE(5), false);
+  static_assert((5 + 1) == kStageCount, "Missized array");
+
+#undef COOKIE
+}
+
+TimerSetTest::TimerSetTest()
+  : Test(CHRE_API_VERSION_1_0), mInMethod(false), mFinishedBitmask(0) {
+}
+
+void TimerSetTest::setUp(uint32_t messageSize, const void * /* message */) {
+  mInMethod = true;
+
+  if (messageSize != 0) {
+    sendFatalFailureToHost(
+        "TimerSet message expects 0 additional bytes, got ",
+        &messageSize);
+  }
+
+  initStages();
+  for (size_t i = 0; i < kStageCount; i++) {
+    mStages[i].start();
+  }
+
+  mInMethod = false;
+}
+
+TimerSetTest::~TimerSetTest() {
+  chreHeapFree(mStages);
+}
+
+void TimerSetTest::handleEvent(uint32_t senderInstanceId,
+                               uint16_t eventType, const void* eventData) {
+  uint64_t timestamp = chreGetTime();
+  if (mInMethod) {
+    sendFatalFailureToHost("handleEvent invoked while another nanoapp "
+                           "method is running");
+  }
+  mInMethod = true;
+  if (senderInstanceId != CHRE_INSTANCE_ID) {
+    sendFatalFailureToHost("handleEvent got event from unexpected sender:",
+                           &senderInstanceId);
+  }
+  if (eventType != CHRE_EVENT_TIMER) {
+    unexpectedEvent(eventType);
+  }
+  Stage *stage = getStageFromCookie(eventData);
+  if (stage == nullptr) {
+    sendFatalFailureToHost("handleEvent got invalid eventData");
+  }
+  stage->processEvent(timestamp, this);
+
+  mInMethod = false;
+}
+
+void TimerSetTest::markSuccess(uint32_t stage) {
+  chreLog(CHRE_LOG_DEBUG, "Stage %" PRIu32 " succeeded", stage);
+  uint32_t finishedBit = (1 << stage);
+  if ((kAllFinished & finishedBit) == 0) {
+    sendFatalFailureToHost("markSuccess bad stage", &stage);
+  }
+  mFinishedBitmask |= finishedBit;
+  if (mFinishedBitmask == kAllFinished) {
+    sendSuccessToHost();
+  }
+}
+
+TimerSetTest::Stage *TimerSetTest::getStageFromCookie(const void *cookie) {
+  Stage *ret = nullptr;
+  for (size_t i = 0; i < kStageCount; i++) {
+    if (mStages[i].getCookie() == cookie) {
+      if (ret != nullptr) {
+        sendInternalFailureToHost("Multiple stages with the same "
+                                  "cookie");
+      }
+      ret = &mStages[i];
+      // It's cheap enough to go through the whole array, and will
+      // catch if we screw up this test setup by duplicating a cookie.
+    }
+  }
+  return ret;
+}
+
+}  // namespace general_test
diff --git a/apps/chqts/src/general_test/timer_set_test.h b/apps/chqts/src/general_test/timer_set_test.h
new file mode 100644
index 0000000..504f615
--- /dev/null
+++ b/apps/chqts/src/general_test/timer_set_test.h
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _GTS_NANOAPPS_GENERAL_TEST_TIMER_SET_TEST_H_
+#define _GTS_NANOAPPS_GENERAL_TEST_TIMER_SET_TEST_H_
+
+#include <general_test/test.h>
+
+namespace general_test {
+
+/**
+ * Checks that chreTimerSet() works by trying various timers.
+ *
+ * Simple Protocol.
+ */
+class TimerSetTest : public Test {
+ public:
+  TimerSetTest();
+  ~TimerSetTest();
+
+  void markSuccess(uint32_t stage);
+
+ protected:
+  void handleEvent(uint32_t senderInstanceId, uint16_t eventType,
+                   const void* eventData) override;
+  void setUp(uint32_t messageSize, const void *message) override;
+
+ private:
+  class Stage {
+   public:
+    Stage(uint32_t stage, uint64_t duration, const void *cookie,
+          bool oneShot);
+    void start();
+    // We take 'test' so we can call markSuccess if appropriate.
+    void processEvent(uint64_t timestamp, TimerSetTest *test);
+
+    const void *getCookie() const { return mCookie; }
+
+   private:
+    uint64_t mSetTime;
+    uint64_t mDuration;
+    uint32_t mStage;
+    uint32_t mEventCount;
+    const void *mCookie;
+    bool mOneShot;
+    uint32_t mTimerHandle;
+  };
+  // In the interest in keeping our static memory usage low (since we
+  // are just one of many, many tests in this nanoapp), we get this
+  // memory from the heap instead of statically declaring an array here.
+  Stage *mStages;
+  static constexpr size_t kStageCount = 6;
+
+  bool mInMethod;
+  static constexpr uint32_t kAllFinished = (1 << kStageCount) - 1;
+  uint32_t mFinishedBitmask;
+
+  void initStages();
+  Stage *getStageFromCookie(const void *cookie);
+};
+
+}  // namespace general_test
+
+
+#endif  // _GTS_NANOAPPS_GENERAL_TEST_TIMER_SET_TEST_H_
diff --git a/apps/chqts/src/general_test/timer_stress_test.cc b/apps/chqts/src/general_test/timer_stress_test.cc
new file mode 100644
index 0000000..055622d
--- /dev/null
+++ b/apps/chqts/src/general_test/timer_stress_test.cc
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <general_test/timer_stress_test.h>
+
+#include <cinttypes>
+#include <cstddef>
+
+#include <shared/send_message.h>
+
+#include <chre.h>
+
+using nanoapp_testing::sendFatalFailureToHost;
+using nanoapp_testing::sendInternalFailureToHost;
+using nanoapp_testing::sendSuccessToHost;
+
+/*
+ * We stress the system by setting more and more timers until the system
+ * runs out.  We then cancel one (CT) and set a new timer post-cancel (NT).
+ * We make sure all the timers we set fire.
+ *
+ * Our stages are:
+ * Stage 0: Successfully cancel CT.
+ * Stage 1: All of our "exhaustion" timers fire.
+ * Stage 2: The new timer, NT, fires.
+ *
+ * After all of our stages have succeeded, we send success to the host.  Note
+ * there is no system requirement that Stage 2 happens after Stage 1, so
+ * we use markSuccess() to track this.
+ */
+
+// Allow 1000ms to create the large number of timers specified below. This
+// equates to approximately 1ms per timer which should give ample time for
+// timer creation to complete.
+constexpr uint64_t kDuration = UINT64_C(1000000000);
+
+// If the system keeps claiming it can set more timers, we don't let it
+// continue forever.  Instead, we'll cut it off at this limit.  And then
+// we'll call its bluff, and make sure that all of these timers legitimately
+// fire.  While it won't be an actual exhaustion test (we never took the
+// system down to no more timers available), it will still give us confidence
+// that this CHRE can properly handle any semi-reasonable timer load properly.
+// 1030 is an arbitrary number, slightly over 2^10.  The hope is this
+// balances between catching incorrect behavior and the test taking too long.
+constexpr int32_t kMaxTimersToSet = INT32_C(1030);
+
+namespace general_test {
+
+#define COOKIE(num) reinterpret_cast<const void*>(num)
+
+void TimerStressTest::startStages() {
+  uint32_t cancelId = chreTimerSet(kDuration, COOKIE(0), true);
+  if (cancelId == CHRE_TIMER_INVALID) {
+    sendFatalFailureToHost("No timers available");
+  }
+
+  mStage1CallbacksLeft = 0;
+  // We anticipate most CHREs will not reach kMaxTimersToSet.
+  while (mStage1CallbacksLeft < kMaxTimersToSet) {
+    if (chreTimerSet(kDuration, COOKIE(1), true) == CHRE_TIMER_INVALID) {
+      break;
+    }
+    mStage1CallbacksLeft++;
+  }
+  if (mStage1CallbacksLeft == 0) {
+    sendFatalFailureToHost("Insufficient timers available");
+  }
+  if (!chreTimerCancel(cancelId)) {
+    sendFatalFailureToHost("Unable to cancel timer");
+  }
+  markSuccess(0);
+  if (chreTimerSet(kDuration, COOKIE(2), true) == CHRE_TIMER_INVALID) {
+    sendFatalFailureToHost("Unable to set new timer after successful "
+                           "cancel.");
+  }
+}
+
+#undef COOKIE
+
+TimerStressTest::TimerStressTest()
+  : Test(CHRE_API_VERSION_1_0),
+    mInMethod(false),
+    mFinishedBitmask(0),
+    mStage1CallbacksLeft(0) {
+}
+
+void TimerStressTest::setUp(uint32_t messageSize, const void * /* message */) {
+  mInMethod = true;
+
+  if (messageSize != 0) {
+    sendFatalFailureToHost(
+        "TimerStress message expects 0 additional bytes, got ",
+        &messageSize);
+  }
+
+  startStages();
+
+  mInMethod = false;
+}
+
+void TimerStressTest::handleStageEvent(uint32_t index) {
+  switch (index) {
+    case 0:
+      sendFatalFailureToHost("Canceled timer fired:", &index);
+      break;
+
+    case 1:
+      --mStage1CallbacksLeft;
+      if (mStage1CallbacksLeft <= 0) {
+        markSuccess(index);
+      }
+      break;
+
+    case 2:
+      markSuccess(index);
+      break;
+
+    default:
+      sendFatalFailureToHost("Unexpected event stage:", &index);
+  }
+}
+
+void TimerStressTest::handleEvent(uint32_t senderInstanceId,
+                                  uint16_t eventType, const void* eventData) {
+  if (mInMethod) {
+    sendFatalFailureToHost("handleEvent invoked while another nanoapp "
+                           "method is running");
+  }
+  mInMethod = true;
+  if (senderInstanceId != CHRE_INSTANCE_ID) {
+    sendFatalFailureToHost("handleEvent got event from unexpected sender:",
+                           &senderInstanceId);
+  }
+  if (eventType != CHRE_EVENT_TIMER) {
+    unexpectedEvent(eventType);
+  }
+  handleStageEvent(reinterpret_cast<uint32_t>(eventData));
+
+  mInMethod = false;
+}
+
+void TimerStressTest::markSuccess(uint32_t stage) {
+  chreLog(CHRE_LOG_DEBUG, "Stage %" PRIu32 " succeeded", stage);
+  uint32_t finishedBit = (1 << stage);
+  if ((kAllFinished & finishedBit) == 0) {
+    sendFatalFailureToHost("markSuccess bad stage:", &stage);
+  }
+  if ((mFinishedBitmask & finishedBit) != 0) {
+    sendFatalFailureToHost("timer over-triggered:", &stage);
+  }
+  mFinishedBitmask |= finishedBit;
+  if (mFinishedBitmask == kAllFinished) {
+    sendSuccessToHost();
+  }
+}
+
+
+}  // namespace general_test
diff --git a/apps/chqts/src/general_test/timer_stress_test.h b/apps/chqts/src/general_test/timer_stress_test.h
new file mode 100644
index 0000000..d142d90
--- /dev/null
+++ b/apps/chqts/src/general_test/timer_stress_test.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _GTS_NANOAPPS_GENERAL_TEST_TIMER_STRESS_TEST_H_
+#define _GTS_NANOAPPS_GENERAL_TEST_TIMER_STRESS_TEST_H_
+
+#include <general_test/test.h>
+
+namespace general_test {
+
+/**
+ * Exhausts all of our system timers and makes sure things still work.
+ *
+ * Simple Protocol.
+ */
+class TimerStressTest : public Test {
+ public:
+  TimerStressTest();
+
+ protected:
+  void handleEvent(uint32_t senderInstanceId, uint16_t eventType,
+                   const void* eventData) override;
+  void setUp(uint32_t messageSize, const void *message) override;
+
+ private:
+  bool mInMethod;
+
+  static constexpr size_t kStageCount = 3;
+  static constexpr uint32_t kAllFinished = (1 << kStageCount) - 1;
+  uint32_t mFinishedBitmask;
+
+  int32_t mStage1CallbacksLeft;
+
+  void startStages();
+  void handleStageEvent(uint32_t index);
+  void markSuccess(uint32_t stage);
+};
+
+}  // namespace general_test
+
+
+#endif  // _GTS_NANOAPPS_GENERAL_TEST_TIMER_STRESS_TEST_H_
diff --git a/apps/chqts/src/general_test/version_sanity_test.cc b/apps/chqts/src/general_test/version_sanity_test.cc
new file mode 100644
index 0000000..ffe9705
--- /dev/null
+++ b/apps/chqts/src/general_test/version_sanity_test.cc
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <general_test/version_sanity_test.h>
+
+#include <cstddef>
+
+#include <shared/send_message.h>
+
+#include <chre.h>
+
+using nanoapp_testing::sendFatalFailureToHost;
+using nanoapp_testing::sendSuccessToHost;
+
+namespace general_test {
+
+VersionSanityTest::VersionSanityTest()
+    : Test(CHRE_API_VERSION_1_0) {
+}
+
+void VersionSanityTest::setUp(uint32_t messageSize, const void * /* message */) {
+  if (messageSize != 0) {
+    sendFatalFailureToHost(
+        "VersionSanity message expects 0 additional bytes, got ",
+        &messageSize);
+  }
+
+  if (mApiVersion < CHRE_API_VERSION_1_0) {
+    sendFatalFailureToHost("API version less than 1.0", &mApiVersion);
+  }
+  if ((mApiVersion & UINT32_C(0xFFFF)) != 0) {
+    sendFatalFailureToHost("API version has two LSB set", &mApiVersion);
+  }
+  uint32_t platformVersion = chreGetVersion();
+  constexpr uint32_t kMajorMinorMask = UINT32_C(0xFFFF0000);
+  // Both mApiVersion and platformVersion refer to what the CHRE was
+  // built against, so they must agree in major and minor version.
+  if ((platformVersion & kMajorMinorMask) != (mApiVersion & kMajorMinorMask)) {
+    sendFatalFailureToHost("API and platform version mismatch",
+                           &platformVersion);
+  }
+
+  // Confirm that our app (CHRE_API_VERSION) and CHRE (mApiVersion) were
+  // both compiled against the same major version.  They're allowed to
+  // differ in minor version.
+  constexpr uint32_t kMajorMask = UINT32_C(0xFF000000);
+  if ((mApiVersion & kMajorMask) != (CHRE_API_VERSION & kMajorMask)) {
+    uint32_t appVersion = CHRE_API_VERSION;
+    sendFatalFailureToHost("App built against different major version",
+                           &appVersion);
+  }
+
+  uint64_t platformId = chreGetPlatformId();
+  if ((platformId == UINT64_C(0)) || (platformId == UINT64_C(-1))) {
+    sendFatalFailureToHost("Bogus platform ID");
+  }
+  // TODO(b/30077401): We should send the Platform ID back to the Host, and
+  //     have the Host confirm this is the Platform ID we used for loading
+  //     the nanoapp.
+
+  sendSuccessToHost();
+}
+
+void VersionSanityTest::handleEvent(uint32_t senderInstanceId,
+                                    uint16_t eventType, const void* eventData) {
+  unexpectedEvent(eventType);
+}
+
+}  // namespace general_test
diff --git a/apps/chqts/src/general_test/version_sanity_test.h b/apps/chqts/src/general_test/version_sanity_test.h
new file mode 100644
index 0000000..f04deb9
--- /dev/null
+++ b/apps/chqts/src/general_test/version_sanity_test.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _GTS_NANOAPPS_GENERAL_TEST_VERSION_SANITY_TEST_H_
+#define _GTS_NANOAPPS_GENERAL_TEST_VERSION_SANITY_TEST_H_
+
+#include <general_test/test.h>
+
+namespace general_test {
+
+/**
+ * Checks that the chre_version.h methods give sane responses.
+ *
+ * Simple protocol.
+ *
+ * (Note: TODO(b/30077401): This test will have a non-simple protocol.)
+ */
+class VersionSanityTest : public Test {
+ public:
+  VersionSanityTest();
+
+ protected:
+  void handleEvent(uint32_t senderInstanceId, uint16_t eventType,
+                   const void* eventData) override;
+  void setUp(uint32_t messageSize, const void *message) override;
+};
+
+}  // namespace general_test
+
+
+#endif  // _GTS_NANOAPPS_GENERAL_TEST_VERSION_SANITY_TEST_H_
diff --git a/apps/chqts/src/general_test/wifi_capabilities_test.cc b/apps/chqts/src/general_test/wifi_capabilities_test.cc
new file mode 100644
index 0000000..f878baf
--- /dev/null
+++ b/apps/chqts/src/general_test/wifi_capabilities_test.cc
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <general_test/wifi_capabilities_test.h>
+
+#include <chre.h>
+
+#include <shared/send_message.h>
+
+namespace general_test {
+
+WifiCapabilitiesTest::WifiCapabilitiesTest()
+    : Test(CHRE_API_VERSION_1_1) {
+}
+
+void WifiCapabilitiesTest::setUp(uint32_t messageSize,
+                                 const void * /* message */) {
+  if (messageSize != 0) {
+    nanoapp_testing::sendFatalFailureToHost(
+        "Expected 0 byte message, got more bytes:", &messageSize);
+  } else {
+    uint32_t allCapabilities = CHRE_WIFI_CAPABILITIES_NONE;
+
+    if (mApiVersion >= CHRE_API_VERSION_1_1) {
+      allCapabilities |= CHRE_WIFI_CAPABILITIES_SCAN_MONITORING
+          | CHRE_WIFI_CAPABILITIES_ON_DEMAND_SCAN;
+    }
+
+    // Call the new API
+    uint32_t capabilities = chreWifiGetCapabilities();
+
+    // Clear out known capabilities, any remaining are unknown
+    if ((capabilities & ~allCapabilities) != 0) {
+      if (mApiVersion > CHRE_API_VERSION_1_1) {
+        nanoapp_testing::sendFatalFailureToHost(
+            "New version with unknown capabilities encountered:",
+            &capabilities);
+      } else {
+        nanoapp_testing::sendFatalFailureToHost(
+            "Received unexpected capabilities:", &capabilities);
+      }
+    } else {
+      nanoapp_testing::sendSuccessToHost();
+    }
+  }
+}
+
+void WifiCapabilitiesTest::handleEvent(uint32_t /* senderInstanceId */,
+                                       uint16_t eventType,
+                                       const void * /* eventData */) {
+  unexpectedEvent(eventType);
+}
+
+} // namespace general_test
diff --git a/apps/chqts/src/general_test/wifi_capabilities_test.h b/apps/chqts/src/general_test/wifi_capabilities_test.h
new file mode 100644
index 0000000..787af3c
--- /dev/null
+++ b/apps/chqts/src/general_test/wifi_capabilities_test.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _GTS_NANOAPPS_GENERAL_TEST_WIFI_CAPABILITIES_TEST_H_
+#define _GTS_NANOAPPS_GENERAL_TEST_WIFI_CAPABILITIES_TEST_H_
+
+#include <cstdint>
+
+#include <general_test/test.h>
+
+namespace general_test {
+
+/**
+ * Confirms that WiFi capabilities exist
+ *
+ * Simple protocol
+ * Host   : kWiFiCapabilities, no data
+ * Nanoapp: kSuccess, no data
+ */
+class WifiCapabilitiesTest : public Test {
+ public:
+  WifiCapabilitiesTest();
+
+ protected:
+  void handleEvent(uint32_t senderInstanceId, uint16_t eventType,
+                   const void *eventData) override;
+  void setUp(uint32_t messageSize, const void *message) override;
+};
+
+} // namespace general_test
+
+#endif // _GTS_NANOAPPS_GENERAL_TEST_WIFI_CAPABILITIES_TEST_H_
diff --git a/apps/chqts/src/general_test/wwan_capabilities_test.cc b/apps/chqts/src/general_test/wwan_capabilities_test.cc
new file mode 100644
index 0000000..c443466
--- /dev/null
+++ b/apps/chqts/src/general_test/wwan_capabilities_test.cc
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <general_test/wwan_capabilities_test.h>
+
+#include <chre.h>
+
+#include <shared/send_message.h>
+
+namespace general_test {
+
+WwanCapabilitiesTest::WwanCapabilitiesTest()
+    : Test(CHRE_API_VERSION_1_1) {
+}
+
+void WwanCapabilitiesTest::setUp(uint32_t messageSize,
+                                 const void * /* message */) {
+  if (messageSize != 0) {
+    nanoapp_testing::sendFatalFailureToHost(
+        "Expected 0 byte message, got more bytes:", &messageSize);
+  } else {
+    uint32_t allCapabilities = CHRE_WWAN_CAPABILITIES_NONE;
+
+    if (mApiVersion >= CHRE_API_VERSION_1_1) {
+      allCapabilities |= CHRE_WWAN_GET_CELL_INFO;
+    }
+
+    // Call the new API
+    uint32_t capabilities = chreWwanGetCapabilities();
+
+    // Clear out known capabilities, any remaining are unknown
+    if ((capabilities & ~allCapabilities) != 0) {
+      if (mApiVersion > CHRE_API_VERSION_1_1) {
+        nanoapp_testing::sendFatalFailureToHost(
+            "New version with unknown capabilities encountered:",
+            &capabilities);
+      } else {
+        nanoapp_testing::sendFatalFailureToHost(
+            "Received unexpected capabilities:", &capabilities);
+      }
+    } else {
+      nanoapp_testing::sendSuccessToHost();
+    }
+  }
+}
+
+void WwanCapabilitiesTest::handleEvent(uint32_t /* senderInstanceId */,
+                                       uint16_t eventType,
+                                       const void * /* eventData */) {
+  unexpectedEvent(eventType);
+}
+
+} // namespace general_test
diff --git a/apps/chqts/src/general_test/wwan_capabilities_test.h b/apps/chqts/src/general_test/wwan_capabilities_test.h
new file mode 100644
index 0000000..9636daf
--- /dev/null
+++ b/apps/chqts/src/general_test/wwan_capabilities_test.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _GTS_NANOAPPS_GENERAL_TEST_WWAN_CAPABILITIES_TEST_H_
+#define _GTS_NANOAPPS_GENERAL_TEST_WWAN_CAPABILITIES_TEST_H_
+
+#include <cstdint>
+
+#include <general_test/test.h>
+
+namespace general_test {
+
+/**
+ * Confirms that WWAN capabilities exist
+ *
+ * Simple protocol
+ * Host   : kWWANCapabilities, no data
+ * Nanoapp: kSuccess, no data
+ */
+class WwanCapabilitiesTest : public Test {
+ public:
+  WwanCapabilitiesTest();
+
+ protected:
+  void handleEvent(uint32_t senderInstanceId, uint16_t eventType,
+                   const void *eventData) override;
+  void setUp(uint32_t messageSize, const void *message) override;
+};
+
+} // namespace general_test
+
+#endif // _GTS_NANOAPPS_GENERAL_TEST_WWAN_CAPABILITIES_TEST_H_
diff --git a/apps/chqts/src/general_test/wwan_cell_info_test.cc b/apps/chqts/src/general_test/wwan_cell_info_test.cc
new file mode 100644
index 0000000..470dbb8
--- /dev/null
+++ b/apps/chqts/src/general_test/wwan_cell_info_test.cc
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <general_test/wwan_cell_info_test.h>
+
+#include <general_test/cell_info_base.h>
+#include <general_test/cell_info_cdma.h>
+#include <general_test/cell_info_gsm.h>
+#include <general_test/cell_info_lte.h>
+#include <general_test/cell_info_tdscdma.h>
+#include <general_test/cell_info_wcdma.h>
+
+#include <shared/send_message.h>
+
+/*
+ * General philosophy behind this test:
+ *   Make a call to chreWwanGetCellInfoAsync and then ensure the following:
+ *     1) Data is received within CHRE_ASYNC_RESULT_TIMEOUT_NS + small buffer.
+ *     2) Various fields in the returned data are correct.
+ */
+
+namespace general_test {
+
+WwanCellInfoTest::WwanCellInfoTest()
+    : Test(CHRE_API_VERSION_1_1) {
+}
+
+void WwanCellInfoTest::setUp(uint32_t messageSize, const void * /* message */) {
+  if ((chreWwanGetCapabilities() & CHRE_WWAN_GET_CELL_INFO) == 0) {
+    sendMessageToHost(nanoapp_testing::MessageType::kSkipped);
+  } else if (!chreWwanGetCellInfoAsync(&mTimerHandle)) {
+    nanoapp_testing::sendFatalFailureToHost(
+        "chreWwanGetCellInfo failed unexpectedly");
+  } else {
+    mTimerHandle = chreTimerSet(CHRE_ASYNC_RESULT_TIMEOUT_NS,
+                                &mTimerHandle, true /* oneShot */);
+
+    if (mTimerHandle == CHRE_TIMER_INVALID) {
+      nanoapp_testing::sendFatalFailureToHost(
+          "Unable to set timer for automatic failure");
+    }
+  }
+}
+
+WwanCellInfoTest::~WwanCellInfoTest() {
+  // Ensure the timer is cancelled
+  cancelTimer();
+}
+
+void WwanCellInfoTest::handleEvent(uint32_t senderInstanceId,
+                                   uint16_t eventType,
+                                   const void *eventData) {
+  // The only expected message is from the async call
+  if (senderInstanceId != CHRE_INSTANCE_ID) {
+    nanoapp_testing::sendFatalFailureToHost(
+        "handleEvent received event from unexpected sender:",
+        &senderInstanceId);
+  } else if (eventType == CHRE_EVENT_WWAN_CELL_INFO_RESULT) {
+    cancelTimer();
+    validateCellInfoResult(eventData);
+  } else if (eventType == CHRE_EVENT_TIMER) {
+    nanoapp_testing::sendFatalFailureToHost(
+        "chreWwanGetCellInfo did not return data in time");
+  } else {
+    uint32_t type = eventType;
+    nanoapp_testing::sendFatalFailureToHost(
+        "handleEvent received an unexpected eventType:", &type);
+  }
+}
+
+void WwanCellInfoTest::cancelTimer() {
+  if (mTimerHandle != CHRE_TIMER_INVALID) {
+    chreTimerCancel(mTimerHandle);
+    mTimerHandle = CHRE_TIMER_INVALID;
+  }
+}
+
+void WwanCellInfoTest::validateCellInfo(uint8_t count,
+                                        const struct chreWwanCellInfo *cells) const {
+  bool valid = true;
+
+  for (int i = 0; (i < count) && valid; ++i) {
+    if (cells[i].reserved != 0) {
+      valid = false;
+      CellInfoBase::sendFatalFailureUint8(
+          "Invalid reserved CellInfo field: %d", cells[i].reserved);
+    }
+
+    for (uint8_t byte : cells[i].reserved2) {
+      if (byte != 0) {
+        valid = false;
+        CellInfoBase::sendFatalFailureUint8(
+            "Invalid reserved2 field: %d", byte);
+      }
+    }
+
+    if ((cells[i].timeStampType != CHRE_WWAN_CELL_TIMESTAMP_TYPE_UNKNOWN)
+        && (cells[i].timeStampType
+            != CHRE_WWAN_CELL_TIMESTAMP_TYPE_ANTENNA)
+        && (cells[i].timeStampType
+            != CHRE_WWAN_CELL_TIMESTAMP_TYPE_MODEM)
+        && (cells[i].timeStampType
+            != CHRE_WWAN_CELL_TIMESTAMP_TYPE_OEM_RIL)
+        && (cells[i].timeStampType
+            != CHRE_WWAN_CELL_TIMESTAMP_TYPE_JAVA_RIL)) {
+      valid = false;
+      CellInfoBase::sendFatalFailureUint8(
+          "Invalid timeStampType: %d", cells[i].timeStampType);
+    }
+
+    if (cells[i].cellInfoType == CHRE_WWAN_CELL_INFO_TYPE_GSM) {
+      valid &= CellInfoGsm::validate(cells[i].CellInfo.gsm);
+    } else if (cells[i].cellInfoType == CHRE_WWAN_CELL_INFO_TYPE_CDMA) {
+      valid &= CellInfoCdma::validate(cells[i].CellInfo.cdma);
+    } else if (cells[i].cellInfoType == CHRE_WWAN_CELL_INFO_TYPE_LTE) {
+      valid &= CellInfoLte::validate(cells[i].CellInfo.lte);
+    } else if (cells[i].cellInfoType == CHRE_WWAN_CELL_INFO_TYPE_WCDMA) {
+      valid &= CellInfoWcdma::validate(cells[i].CellInfo.wcdma);
+    } else if (cells[i].cellInfoType == CHRE_WWAN_CELL_INFO_TYPE_TD_SCDMA) {
+      valid &= CellInfoTdscdma::validate(cells[i].CellInfo.tdscdma);
+    } else {
+      valid = false;
+      CellInfoBase::sendFatalFailureUint8(
+          "Invalid cellInfoType: %d", cells[i].cellInfoType);
+    }
+  }
+
+  if (valid) {
+    nanoapp_testing::sendSuccessToHost();
+  }
+}
+
+void WwanCellInfoTest::validateCellInfoResult(const void *eventData) const {
+  const struct chreWwanCellInfoResult *result =
+      static_cast<const chreWwanCellInfoResult *>(eventData);
+
+  if (eventData == nullptr) {
+    nanoapp_testing::sendFatalFailureToHost("Received eventData is null");
+  } else if (result->version != CHRE_WWAN_CELL_INFO_RESULT_VERSION) {
+    nanoapp_testing::sendFatalFailureToHost(
+        "Received version is unexpected value");
+  } else if (result->reserved != 0) {
+    nanoapp_testing::sendFatalFailureToHost(
+        "Received reserved field non-zero");
+  } else {
+    const uint32_t *receivedCookie =
+        static_cast<const uint32_t *>(result->cookie);
+
+    if (receivedCookie != &mTimerHandle) {
+      nanoapp_testing::sendFatalFailureToHost(
+          "Received cookie does not match");
+    } else if (result->cellInfoCount != 0) {
+      validateCellInfo(result->cellInfoCount, result->cells);
+    } else {
+      nanoapp_testing::sendSuccessToHost();
+    }
+  }
+}
+
+} // namespace general_test
diff --git a/apps/chqts/src/general_test/wwan_cell_info_test.h b/apps/chqts/src/general_test/wwan_cell_info_test.h
new file mode 100644
index 0000000..7360575
--- /dev/null
+++ b/apps/chqts/src/general_test/wwan_cell_info_test.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef _GTS_NANOAPPS_GENERAL_TEST_WWAN_CELL_INFO_TEST_H_
+#define _GTS_NANOAPPS_GENERAL_TEST_WWAN_CELL_INFO_TEST_H_
+
+#include <general_test/test.h>
+
+#include <cstdint>
+
+#include <chre.h>
+
+namespace general_test {
+
+class WwanCellInfoTest : public Test {
+ public:
+  WwanCellInfoTest();
+  ~WwanCellInfoTest();
+
+ protected:
+  void handleEvent(uint32_t senderInstanceId, uint16_t eventType,
+                   const void *eventData) override;
+  void setUp(uint32_t messageSize, const void *message) override;
+
+ private:
+  void cancelTimer();
+  void validateCellInfo(uint8_t count, const struct chreWwanCellInfo *cells) const;
+  void validateCellInfoResult(const void *eventData) const;
+
+  uint32_t mTimerHandle = CHRE_TIMER_INVALID;
+};
+
+} // namespace general_test
+
+#endif // _GTS_NANOAPPS_GENERAL_TEST_WWAN_CELL_INFO_TEST_H_
diff --git a/apps/chqts/src/shared/Android.mk b/apps/chqts/src/shared/Android.mk
new file mode 100644
index 0000000..4a3d628
--- /dev/null
+++ b/apps/chqts/src/shared/Android.mk
@@ -0,0 +1,50 @@
+# Copyright (C) 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# While it's arguably overkill to have unit test for our testing code,
+# this testing code runs under an embedded environment which is much more
+# difficult to test.  To the extent that we have platform-independent
+# code here, it seems a long-term time saver to have Linux tests for
+# what we can easily test.
+
+LOCAL_PATH := $(call my-dir)
+
+FILES_UNDER_TEST := \
+    dumb_allocator.cc \
+    nano_endian.cc \
+    nano_string.cc \
+
+TESTING_SOURCE := \
+    dumb_allocator_test.cc \
+    nano_endian_be_test.cc \
+    nano_endian_le_test.cc \
+    nano_endian_test.cc \
+    nano_string_test.cc \
+
+ifeq ($(HOST_OS),linux)
+include $(CLEAR_VARS)
+LOCAL_CPP_EXTENSION := .cc
+LOCAL_C_INCLUDES := $(LOCAL_PATH)/..
+
+LOCAL_SRC_FILES := $(FILES_UNDER_TEST) $(TESTING_SOURCE)
+
+LOCAL_MODULE := nanoapp_chqts_shared_tests
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_CPPFLAGS += -Wall -Wextra -Werror
+LOCAL_CPPFLAGS += -DINTERNAL_TESTING
+
+include $(BUILD_HOST_NATIVE_TEST)
+
+endif  # HOST_OS == linux
diff --git a/apps/chqts/src/shared/abort.cc b/apps/chqts/src/shared/abort.cc
new file mode 100644
index 0000000..9340918
--- /dev/null
+++ b/apps/chqts/src/shared/abort.cc
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <shared/abort.h>
+
+#include <cstdint>
+
+#include <chre.h>
+
+namespace nanoapp_testing {
+
+void abort(AbortBlame reason) {
+  uint32_t abortCode = UINT32_C(0x10000) + static_cast<uint16_t>(reason);
+  chreAbort(abortCode);
+
+  // We should never get here.  But in the case that the CHRE has a bug
+  // in its chreAbort() implementation, let's try to blow things up.
+  // Some compilers are smart about not wanting to crash like this, so
+  // we need to fool them via indirection.
+  uint16_t zero = static_cast<uint16_t>(reason)
+      - static_cast<uint8_t>(reason);
+  uint8_t *badPointer = reinterpret_cast<uint8_t*>(zero);
+  *badPointer = 1;
+
+  // If we're here, we have a buggy CHRE on a platform where it's okay
+  // to write to nullptr.  At the very least, let's not return from this
+  // function.
+  while (true) {}
+}
+
+}  // namespace nanoapp_testing
diff --git a/apps/chqts/src/shared/abort.h b/apps/chqts/src/shared/abort.h
new file mode 100644
index 0000000..b76f2b3
--- /dev/null
+++ b/apps/chqts/src/shared/abort.h
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _GTS_NANOAPPS_SHARED_ABORT_H_
+#define _GTS_NANOAPPS_SHARED_ABORT_H_
+
+#include <cstdint>
+
+namespace nanoapp_testing {
+
+/**
+ * Enumeration of what should be blamed for why the test was aborted.
+ */
+enum class AbortBlame : uint16_t {
+  /**
+   * Most common failure mode, a presumed issue within the CHRE
+   * implementation.
+   *
+   * The test should have sent out an error message prior to aborting.
+   */
+  kChre = 0,
+
+  /**
+   * A presumed issue within the CHRE implementation, discovered while
+   * in the nanoappEnd() method of the nanoapp.
+   *
+   * This differs from kChre in that we're not able to send out an
+   * error message prior to aborting, so we want a distinct value.
+   */
+  kChreInNanoappEnd = 1,
+
+  /**
+   * A presumed bug in the test framework itself.
+   *
+   * The test should have sent out a kInternalError message prior to
+   * aborting.
+   */
+  kTestFramework = 2
+};
+
+/**
+ * This should fatally abort the nanoapp, and hence the current test.
+ *
+ * This is useful in cases where we cannot proceed due to previous issues,
+ * or in cases where we want to make unambigously clear that we've
+ * experienced a failure.
+ *
+ * The presumption is that code will send out a message prior to calling
+ * this method, if at all practical.
+ *
+ * This will invoke chreAbort() with the code (0x10000 + uint16_t(reason)),
+ * preserving the lower two bytes for tests to use for testing of
+ * chreAbort().
+ *
+ * @param reason  Optional.  The appropriate AbortBlame.  Defaults to kChre.
+ * @returns This function should never return.
+ */
+void abort(AbortBlame reason = AbortBlame::kChre);
+
+}  // namespace nanoapp_testing
+
+#endif  // _GTS_NANOAPPS_SHARED_ABORT_H_
diff --git a/apps/chqts/src/shared/array_length.h b/apps/chqts/src/shared/array_length.h
new file mode 100644
index 0000000..73ac436
--- /dev/null
+++ b/apps/chqts/src/shared/array_length.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _GTS_NANOAPPS_SHARED_ARRAY_LENGTH_H_
+#define _GTS_NANOAPPS_SHARED_ARRAY_LENGTH_H_
+
+#include <stddef.h>
+
+// We chose to have this at the global name scope, as this method
+// isn't exclusive to nanoapp testing, and hopefully someday will
+// move to some place more central.
+
+/**
+ * Determine the length of a statically-sized array at compile time using
+ * template trickery which makes sure we only match statically-sized arrays.
+ */
+template <typename T, size_t N>
+constexpr size_t arrayLength(T (&)[N]) { return N; }
+
+
+#endif  // _GTS_NANOAPPS_SHARED_ARRAY_LENGTH_H_
diff --git a/apps/chqts/src/shared/dumb_allocator.cc b/apps/chqts/src/shared/dumb_allocator.cc
new file mode 100644
index 0000000..1b40f48
--- /dev/null
+++ b/apps/chqts/src/shared/dumb_allocator.cc
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <shared/dumb_allocator.h>
+
+#include <shared/nano_string.h>
+
+namespace nanoapp_testing {
+
+DumbAllocatorBase::DumbAllocatorBase(size_t allocSize, size_t slotCount,
+                                     uint8_t *rawMemory)
+    : mAllocSize(allocSize),
+      mSlotCount(slotCount),
+      mRawMemory(rawMemory),
+      mAllocatedSlots(0) {
+  // We're not worried about runtime efficiency, since this is testing
+  // code.  In case of an issue within the tests, though, we do want
+  // to have consistent behavior.  Thus, we initialize this memory to
+  // aid tracking problems.
+  memset(mRawMemory, 0xCD, mSlotCount * mAllocSize);
+}
+
+void *DumbAllocatorBase::alloc(size_t bytes) {
+  if (bytes > mAllocSize) {
+    // Oversized for our allocator.
+    return nullptr;
+  }
+  size_t slot = 0;
+  for (uint32_t mask = 1; slot < mSlotCount; slot++, mask <<= 1) {
+    if ((mAllocatedSlots & mask) == 0) {
+      // This space is available, let's claim it.
+      mAllocatedSlots |= mask;
+      break;
+    }
+  }
+  if (slot == mSlotCount) {
+    // We're out of space.
+    return nullptr;
+  }
+  return mRawMemory + (slot * mAllocSize);
+}
+
+bool DumbAllocatorBase::free(void *pointer) {
+  size_t slot;
+  if (!getSlot(pointer, &slot)) {
+    return false;
+  }
+  mAllocatedSlots &= ~(1 << slot);
+  return true;
+}
+
+bool DumbAllocatorBase::contains(const void *pointer) const {
+  size_t slot;
+  return getSlot(pointer, &slot);
+}
+
+bool DumbAllocatorBase::getSlot(const void *pointer, size_t *slot) const {
+  const uint8_t *ptr = static_cast<const uint8_t *>(pointer);
+  if (ptr < mRawMemory) {
+    // Out of range.
+    return false;
+  }
+  *slot = (ptr - mRawMemory) / mAllocSize;
+  if (*slot >= mSlotCount) {
+    // Out of range.
+    return false;
+  }
+  // Also confirm alignment.
+  return ((mRawMemory + (*slot * mAllocSize)) == ptr);
+}
+
+
+}  // namespace nanoapp_testing
diff --git a/apps/chqts/src/shared/dumb_allocator.h b/apps/chqts/src/shared/dumb_allocator.h
new file mode 100644
index 0000000..06ce081
--- /dev/null
+++ b/apps/chqts/src/shared/dumb_allocator.h
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _GTS_NANOAPPS_SHARED_DUMB_ALLOCATOR_H_
+#define _GTS_NANOAPPS_SHARED_DUMB_ALLOCATOR_H_
+
+#include <cstddef>
+#include <cstdint>
+
+namespace nanoapp_testing {
+
+// Implementation Note: We chose the pattern of having DumbAllocatorBase to
+// reduce the code duplication from multiple instances of DumbAllocator with
+// different template parameters.
+// See DumbAllocator below for usage and API documentation.
+class DumbAllocatorBase {
+ protected:
+  DumbAllocatorBase(size_t allocSize, size_t slotCount, uint8_t *rawMemory);
+
+  void *alloc(size_t bytes);
+  bool free(void *ptr);
+  bool contains(const void *ptr) const;
+
+  static constexpr size_t MaxSlotCount() {
+    // Our mAllocatedSlots is treated as a bit array, so we get 8 slots for
+    // each byte it has.
+    return (sizeof(mAllocatedSlots) * 8);
+  }
+
+ private:
+  const size_t mAllocSize;
+  const size_t mSlotCount;
+  uint8_t * const mRawMemory;
+  uint32_t mAllocatedSlots;
+
+  bool getSlot(const void *ptr, size_t *slot) const;
+};
+
+
+/**
+ * This dumb allocator is designed to allow us to easily get chunks of
+ * memory without needing to go through heap allocation.  The idea is to
+ * reduce our dependency on CHRE for some aspects of our tests.
+ *
+ * This allocator is non-reentrant.  It's also inefficient and a bad idea
+ * for shipping code, but useful for reducing dependencies during testing.
+ *
+ * This will allow up to kSlotCount allocations of up to kAllocSize bytes
+ * each, and costs (kSlotCount * kAllocSize) bytes of underlying storage.
+ */
+template<size_t kAllocSize, size_t kSlotCount>
+class DumbAllocator : DumbAllocatorBase {
+ public:
+  DumbAllocator()
+      : DumbAllocatorBase(kAllocSize, kSlotCount, mRawMemoryArray) {}
+
+  /**
+   * If "bytes" <= kAllocSize, and there are less than kSlotCount allocations,
+   * return a valid pointer.  Otherwise, nullptr.
+   *
+   * Reminder this is non-reentrant.
+   */
+  void *alloc(size_t bytes) {
+    return DumbAllocatorBase::alloc(bytes);
+  }
+
+  /**
+   * If contains(ptr) is true, free the allocation and return true.
+   * Otherwise, do nothing and return false.
+   *
+   * Reminder this is non-reentrant.
+   */
+  bool free(void *ptr) {
+    return DumbAllocatorBase::free(ptr);
+  }
+
+  /**
+   * If "ptr" was a non-null pointer returned from alloc() on this instance,
+   * return true.  Otherwise, do nothing and return false.
+   */
+  bool contains(const void *ptr) const {
+    return DumbAllocatorBase::contains(ptr);
+  }
+
+ private:
+  uint8_t mRawMemoryArray[kAllocSize * kSlotCount];
+
+  static_assert(kSlotCount <= MaxSlotCount(), "kSlotCount is too high");
+};
+
+
+}  // namespace nanoapp_testing
+
+#endif  // _GTS_NANOAPPS_SHARED_DUMB_ALLOCATOR_H_
diff --git a/apps/chqts/src/shared/dumb_allocator_test.cc b/apps/chqts/src/shared/dumb_allocator_test.cc
new file mode 100644
index 0000000..5d28538
--- /dev/null
+++ b/apps/chqts/src/shared/dumb_allocator_test.cc
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <shared/dumb_allocator.h>
+
+#include <gtest/gtest.h>
+
+static constexpr size_t kAllocSize = 128;
+static constexpr size_t kSlotCount = 5;
+
+typedef nanoapp_testing::DumbAllocator<kAllocSize, kSlotCount> DA;
+
+static void ExpectGoodAlloc(const DA &da, const void *ptr) {
+  EXPECT_NE(nullptr, ptr);
+  EXPECT_TRUE(da.contains(ptr));
+}
+
+TEST(DumbAllocatorTests, SimpleAlloc) {
+  DA da;
+  void *ptr = da.alloc(kAllocSize);
+  ExpectGoodAlloc(da, ptr);
+  EXPECT_TRUE(da.free(ptr));
+}
+
+TEST(DumbAllocatorTests, AllocsAfterFree) {
+  DA da;
+  void *ptrs[kSlotCount];
+  for (size_t i = 0; i < kSlotCount; i++) {
+    ptrs[i] = da.alloc(kAllocSize);
+    ExpectGoodAlloc(da, ptrs[i]);
+    // Also confirm we're not doubly allocating the same pointer.
+    for (size_t j = 0; j < i; j++) {
+      EXPECT_NE(ptrs[j], ptrs[i]);
+    }
+  }
+  // Out of slots, allocation should fail.
+  EXPECT_EQ(nullptr, da.alloc(kAllocSize));
+
+  constexpr size_t kFreeIndex = kSlotCount / 2;
+  EXPECT_TRUE(da.free(ptrs[kFreeIndex]));
+  ptrs[kFreeIndex] = nullptr;
+
+  // Now our allocation should succeed.
+  void *newPtr = da.alloc(kAllocSize);
+  ExpectGoodAlloc(da, newPtr);
+  for (size_t i = 0; i < kSlotCount; i++) {
+    EXPECT_NE(newPtr, ptrs[i]);
+  }
+}
+
+TEST(DumbAllocatorTests, ContainsIsFalseForBadPtrs) {
+  DA da;
+  uint8_t *ptr = static_cast<uint8_t*>(da.alloc(kAllocSize));
+  ASSERT_NE(nullptr, ptr);
+  EXPECT_FALSE(da.contains(ptr - 1));
+  EXPECT_FALSE(da.contains(ptr + 1));
+  EXPECT_FALSE(da.contains(nullptr));
+}
+
+TEST(DumbAllocatorTests, FailLargeAllocations) {
+  DA da;
+  EXPECT_EQ(nullptr, da.alloc(kAllocSize + 1));
+  EXPECT_EQ(nullptr, da.alloc(kAllocSize * 2));
+}
+
+TEST(DumbAllocatorTests, SucceedSmallAllocations) {
+  DA da;
+  ExpectGoodAlloc(da, da.alloc(kAllocSize - 1));
+  ExpectGoodAlloc(da, da.alloc(1));
+  ExpectGoodAlloc(da, da.alloc(0));
+}
diff --git a/platform/shared/memory.cc b/apps/chqts/src/shared/nano_endian.cc
similarity index 67%
copy from platform/shared/memory.cc
copy to apps/chqts/src/shared/nano_endian.cc
index c137eb8..68d39e9 100644
--- a/platform/shared/memory.cc
+++ b/apps/chqts/src/shared/nano_endian.cc
@@ -14,18 +14,16 @@
  * limitations under the License.
  */
 
-#include "chre/platform/memory.h"
+#include <shared/nano_endian.h>
 
-#include <stdlib.h>
+namespace nanoapp_testing {
 
-namespace chre {
-
-void *memoryAlloc(size_t size) {
-  return malloc(size);
+void swapBytes(uint8_t *bytes, size_t size) {
+  for (size_t front = 0, end = size - 1; front < end; front++, end--) {
+    uint8_t tmp = bytes[front];
+    bytes[front] = bytes[end];
+    bytes[end] = tmp;
+  }
 }
 
-void memoryFree(void *pointer) {
-  free(pointer);
-}
-
-}  // namespace chre
+}  // namespace nanoapp_testing
diff --git a/apps/chqts/src/shared/nano_endian.h b/apps/chqts/src/shared/nano_endian.h
new file mode 100644
index 0000000..3684ac6
--- /dev/null
+++ b/apps/chqts/src/shared/nano_endian.h
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _GTS_NANOAPPS_SHARED_NANO_ENDIAN_H_
+#define _GTS_NANOAPPS_SHARED_NANO_ENDIAN_H_
+
+// If the platform has no endian.h, then have the build system set
+// CHRE_NO_ENDIAN_H, and set __BYTE_ORDER, __LITTLE_ENDIAN, and
+// __BIG_ENDIAN appropriately.
+#ifndef CHRE_NO_ENDIAN_H
+#include <endian.h>
+#endif
+
+#include <cstddef>
+#include <cstdint>
+
+
+#if !(defined(__BYTE_ORDER) && defined(__LITTLE_ENDIAN) && \
+      defined(__BIG_ENDIAN))
+#error "Need to define the preprocessor defines __BYTE_ORDER, __LITTLE_ENDIAN" \
+       " and __BIG_ENDIAN."
+#endif
+
+#if __LITTLE_ENDIAN == __BIG_ENDIAN
+#error "__LITTLE_ENDIAN and __BIG_ENDIAN must have different values."
+#endif
+
+#if ((__BYTE_ORDER != __LITTLE_ENDIAN) && (__BYTE_ORDER != __BIG_ENDIAN))
+#error "__BYTE_ORDER must be either __LITTLE_ENDIAN or __BIG_ENDIAN."
+#endif
+
+
+namespace nanoapp_testing {
+
+void swapBytes(uint8_t *bytes, size_t size);
+
+// Note: The 'static' with these 'inline' methods is required for our
+// unit tests to work, since they compile this header with different
+// endianness.  Without the 'static', we'll just use the first version
+// of this we compile with, and fail some of the tests.
+
+template<typename T>
+static inline void hostToLittleEndian(T *value) {
+#if (__BYTE_ORDER == __BIG_ENDIAN)
+  swapBytes(reinterpret_cast<uint8_t*>(value), sizeof(T));
+#else
+  // Nothing to do for little endian machines.
+  (void)value;
+#endif
+}
+
+template<typename T>
+static inline void littleEndianToHost(T *value) {
+  // This has identical behavior to hostToLittleEndian.  We provide both
+  // so code reads more cleanly.
+  hostToLittleEndian(value);
+}
+
+}  // namespace nanoapp_testing
+
+#endif  // _GTS_NANOAPPS_SHARED_NANO_ENDIAN_H_
diff --git a/apps/chqts/src/shared/nano_endian_be_test.cc b/apps/chqts/src/shared/nano_endian_be_test.cc
new file mode 100644
index 0000000..3702f88
--- /dev/null
+++ b/apps/chqts/src/shared/nano_endian_be_test.cc
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// For this test, we pretend we're a big endian platform, even if we don't
+// happen to be.
+#define CHRE_NO_ENDIAN_H 1
+#define __LITTLE_ENDIAN 0
+#define __BIG_ENDIAN 1
+#define __BYTE_ORDER __BIG_ENDIAN
+
+#include <shared/nano_endian.h>
+
+#include <cstdint>
+#include <cstring>
+
+#include <gtest/gtest.h>
+#include <shared/array_length.h>
+
+
+static constexpr uint8_t kLittleEndianRepresentation[4] = {
+  0x01, 0x02, 0x03, 0x04 };
+static constexpr uint8_t kBigEndianRepresentation[4] = {
+  0x04, 0x03, 0x02, 0x01 };
+
+TEST(EndianTest, LittleEndianToBigEndianHost) {
+  alignas(alignof(uint32_t)) uint8_t bytes[4];
+  ::memcpy(bytes, kLittleEndianRepresentation, sizeof(bytes));
+  uint32_t *valuePtr = reinterpret_cast<uint32_t*>(bytes);
+
+  nanoapp_testing::littleEndianToHost(valuePtr);
+  EXPECT_EQ(0, ::memcmp(bytes, kBigEndianRepresentation, sizeof(bytes)));
+}
+
+TEST(EndianTest, BigEndianHostToLittleEndian) {
+  alignas(alignof(uint32_t)) uint8_t bytes[4];
+  ::memcpy(bytes, kBigEndianRepresentation, sizeof(bytes));
+  uint32_t *valuePtr = reinterpret_cast<uint32_t*>(bytes);
+
+  nanoapp_testing::hostToLittleEndian(valuePtr);
+  EXPECT_EQ(0, ::memcmp(bytes, kLittleEndianRepresentation, sizeof(bytes)));
+}
diff --git a/apps/chqts/src/shared/nano_endian_le_test.cc b/apps/chqts/src/shared/nano_endian_le_test.cc
new file mode 100644
index 0000000..4dbe202
--- /dev/null
+++ b/apps/chqts/src/shared/nano_endian_le_test.cc
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// For this test, we pretend we're a little endian platform, even if we don't
+// happen to be.
+#define CHRE_NO_ENDIAN_H 1
+#define __LITTLE_ENDIAN 0
+#define __BIG_ENDIAN 1
+#define __BYTE_ORDER __LITTLE_ENDIAN
+
+#include <shared/nano_endian.h>
+
+#include <cstdint>
+#include <cstring>
+
+#include <gtest/gtest.h>
+#include <shared/array_length.h>
+
+
+static constexpr uint8_t kLittleEndianRepresentation[4] = {
+  0x01, 0x02, 0x03, 0x04 };
+
+TEST(EndianTest, LittleEndianToLittleEndianHost) {
+  alignas(alignof(uint32_t)) uint8_t bytes[4];
+  ::memcpy(bytes, kLittleEndianRepresentation, sizeof(bytes));
+  uint32_t *valuePtr = reinterpret_cast<uint32_t*>(bytes);
+
+  nanoapp_testing::littleEndianToHost(valuePtr);
+  EXPECT_EQ(0, ::memcmp(bytes, kLittleEndianRepresentation, sizeof(bytes)));
+}
+
+TEST(EndianTest, LittleEndianHostToLittleEndian) {
+  alignas(alignof(uint32_t)) uint8_t bytes[4];
+  ::memcpy(bytes, kLittleEndianRepresentation, sizeof(bytes));
+  uint32_t *valuePtr = reinterpret_cast<uint32_t*>(bytes);
+
+  nanoapp_testing::hostToLittleEndian(valuePtr);
+  EXPECT_EQ(0, ::memcmp(bytes, kLittleEndianRepresentation, sizeof(bytes)));
+}
diff --git a/apps/chqts/src/shared/nano_endian_test.cc b/apps/chqts/src/shared/nano_endian_test.cc
new file mode 100644
index 0000000..6314ad8
--- /dev/null
+++ b/apps/chqts/src/shared/nano_endian_test.cc
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <shared/nano_endian.h>
+
+#include <cstdint>
+#include <cstring>
+
+#include <gtest/gtest.h>
+#include <shared/array_length.h>
+
+
+template<size_t kByteCount>
+static void swapByteTest() {
+  uint8_t bytes[] = {
+    0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
+    0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10 };
+  static constexpr uint8_t postSwap[] = {
+    0x10, 0x0F, 0x0E, 0x0D, 0x0C, 0x0B, 0x0A, 0x09,
+    0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01 };
+
+#ifdef __clang__
+  // This static_assert crashes g++, but it's legit and works in clang.
+  static_assert(arrayLength(bytes) == arrayLength(postSwap),
+                "Mismatched arrays");
+#endif
+  static_assert((kByteCount > 0) && (kByteCount <= arrayLength(bytes)),
+                "Invalid test");
+
+  constexpr const uint8_t *kExpected =
+      postSwap + (arrayLength(postSwap) - kByteCount);
+  nanoapp_testing::swapBytes(bytes, kByteCount);
+  EXPECT_EQ(0, ::memcmp(bytes, kExpected, kByteCount));
+
+  if (arrayLength(bytes) < kByteCount) {
+    // Confirm that we didn't modify out of bounds.
+    EXPECT_EQ(kByteCount + 1, bytes[kByteCount]);
+  }
+}
+
+
+TEST(EndianTest, SwapBytes1) {
+  swapByteTest<1>();
+}
+
+TEST(EndianTest, SwapBytes2) {
+  swapByteTest<2>();
+}
+
+TEST(EndianTest, SwapBytes4) {
+  swapByteTest<4>();
+}
+
+TEST(EndianTest, SwapBytes8) {
+  swapByteTest<8>();
+}
+
+TEST(EndianTest, SwapBytes16) {
+  swapByteTest<16>();
+}
+
+
+// These tests should work regardless of which endian platform this
+// test happens to be built and running on.
+
+static constexpr uint32_t kValue = UINT32_C(0x04030201);
+static constexpr uint8_t kLittleEndianRepresentation[4] = {
+  0x01, 0x02, 0x03, 0x04 };
+
+TEST(EndianTest, LittleEndianToHost) {
+  alignas(alignof(uint32_t)) uint8_t bytes[4];
+  ::memcpy(bytes, kLittleEndianRepresentation, sizeof(bytes));
+  uint32_t *valuePtr = reinterpret_cast<uint32_t*>(bytes);
+
+  nanoapp_testing::littleEndianToHost(valuePtr);
+  EXPECT_EQ(kValue, *valuePtr);
+}
+
+TEST(EndianTest, HostToLittleEndian) {
+  uint32_t value = kValue;
+  nanoapp_testing::hostToLittleEndian(&value);
+
+  const uint8_t *bytes = reinterpret_cast<uint8_t*>(&value);
+  EXPECT_EQ(0, ::memcmp(kLittleEndianRepresentation, bytes,
+                        sizeof(kLittleEndianRepresentation)));
+}
diff --git a/apps/chqts/src/shared/nano_string.cc b/apps/chqts/src/shared/nano_string.cc
new file mode 100644
index 0000000..9a74d80
--- /dev/null
+++ b/apps/chqts/src/shared/nano_string.cc
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <shared/nano_string.h>
+
+#ifndef INTERNAL_TESTING
+
+#include <shared/send_message.h>
+#define REPORT_INTERNAL_ERROR(msg) \
+    sendInternalFailureToHost(msg)
+
+#else
+
+#include <gtest/gtest.h>
+#define REPORT_INTERNAL_ERROR(msg) FAIL() << msg
+
+#endif
+
+
+namespace nanoapp_testing {
+
+void memset(void *mem, int val, size_t count) {
+  uint8_t *bytes = static_cast<uint8_t*>(mem);
+  for (size_t i = 0; i < count; i++) {
+    bytes[i] = val;
+  }
+}
+
+void memcpy(void *d, const void *s, size_t bytes) {
+  uint8_t *dst = static_cast<uint8_t*>(d);
+  const uint8_t *src = static_cast<const uint8_t*>(s);
+  for (size_t i = 0; i < bytes; i++) {
+    dst[i] = src[i];
+  }
+}
+
+size_t strlen(char const *str) {
+  size_t ret = 0;
+  for (; str[ret] != '\0'; ret++) {}
+  return ret;
+}
+
+char *strncpy(char *dest, const char *src, size_t len) {
+  size_t i;
+  for (i = 0; (i < len) && (src[i] != '\0'); i++) {
+    dest[i] = src[i];
+  }
+  for (; i < len; i++) {
+    dest[i] = '\0';
+  }
+  return dest;
+}
+
+void uint32ToHexAscii(char *buffer, size_t buffer_len, uint32_t value) {
+  constexpr char lookup[16] = {
+    '0', '1', '2', '3', '4', '5', '6', '7',
+    '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
+  if (buffer_len < kUint32ToHexAsciiBufferMinLen) {
+    // We chose not to send our buffer_len here, as that would invoke
+    // another call to this method and risk infinite recursion if something
+    // was really screwed up.
+    REPORT_INTERNAL_ERROR("uint32ToHexAscii got undersized buffer_len");
+    return;
+  }
+  buffer[0] = '0';
+  buffer[1] = 'x';
+  for (size_t i = 0, shift = 28; i < 8; i++, shift -= 4) {
+    buffer[2 + i] = lookup[(value >> shift) & 0xF];
+  }
+}
+
+}  // namespace nanoapp_testing
diff --git a/apps/chqts/src/shared/nano_string.h b/apps/chqts/src/shared/nano_string.h
new file mode 100644
index 0000000..d502c8f
--- /dev/null
+++ b/apps/chqts/src/shared/nano_string.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _GTS_NANOAPPS_SHARED_NANO_STRING_H_
+#define _GTS_NANOAPPS_SHARED_NANO_STRING_H_
+
+#include <cstddef>
+#include <cstdint>
+
+namespace nanoapp_testing {
+
+void memset(void *mem, int val, size_t count);
+
+void memcpy(void *dst, const void *src, size_t bytes);
+
+size_t strlen(char const *str);
+
+char *strncpy(char *dest, const char *src, size_t len);
+
+// Does not NUL terminate.  buffer_len must be >= kUint32ToHexAsciiBufferMinLen.
+// Example: Given 'value' of 1234, 'buffer' will be filled with "0x000004D2".
+void uint32ToHexAscii(char *buffer, size_t buffer_len, uint32_t value);
+
+constexpr size_t kUint32ToHexAsciiBufferMinLen = 10;
+
+}  // namespace nanoapp_testing
+
+#endif  // _GTS_NANOAPPS_SHARED_NANO_STRING_H_
diff --git a/apps/chqts/src/shared/nano_string_test.cc b/apps/chqts/src/shared/nano_string_test.cc
new file mode 100644
index 0000000..7ad3c37
--- /dev/null
+++ b/apps/chqts/src/shared/nano_string_test.cc
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <shared/nano_string.h>
+
+#include <string.h>  // brings strlen, strncpy, and memset into scope.
+#include <stdint.h>
+
+#include <gtest/gtest.h>
+#include <shared/array_length.h>
+
+// This "using directive" intentionally makes the symbols 'strlen', 'strncpy',
+// and 'memset' "ambigious" at this point.  This means that every use of these
+// needs to be fully qualified in our tests below.  That's as desired for
+// clarity and to avoid accidentally invoking the wrong version.
+// Note that a leading bare "::" is the fully qualified version of the
+// C library methods.
+using namespace nanoapp_testing;
+
+static constexpr size_t kMemsetBufferLen = 16;
+static constexpr int kUnsetValue = 0x5F;
+static constexpr int kNewValue   = 0xB8;
+
+template<size_t kLenToSet>
+static void testMemset() {
+  uint8_t expected[kMemsetBufferLen];
+  uint8_t actual[arrayLength(expected)];
+
+  static_assert(kLenToSet <= arrayLength(expected), "Bad test invocation");
+
+  ::memset(expected, kUnsetValue, sizeof(expected));
+  ::memset(actual, kUnsetValue, sizeof(actual));
+
+  ::memset(expected, kNewValue, kLenToSet);
+  nanoapp_testing::memset(actual, kNewValue, kLenToSet);
+
+  EXPECT_EQ(0, memcmp(expected, actual, sizeof(expected)));
+}
+
+TEST(NanoStringsTest, MemsetZeroBytes) {
+  testMemset<0>();
+}
+
+TEST(NanoStringsTest, MemsetPartialArray) {
+  testMemset<(kMemsetBufferLen / 2) - 1>();
+}
+
+TEST(NanoStringsTest, MemsetFullArray) {
+  testMemset<kMemsetBufferLen>();
+}
+
+
+static constexpr size_t kMemcpyBufferLen = 8;
+static constexpr uint8_t kMemcpySrc[kMemcpyBufferLen] = {
+  0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08 };
+
+template<size_t kLenToCopy>
+static void testMemcpy() {
+  uint8_t expected[arrayLength(kMemcpySrc)];
+  uint8_t actual[arrayLength(expected)];
+
+  static_assert(kLenToCopy <= arrayLength(kMemcpySrc), "Bad test invocation");
+
+  ::memset(expected, kUnsetValue, sizeof(expected));
+  ::memset(actual, kUnsetValue, sizeof(actual));
+
+  ::memcpy(expected, kMemcpySrc, kLenToCopy);
+  nanoapp_testing::memcpy(actual, kMemcpySrc, kLenToCopy);
+
+  EXPECT_EQ(0, memcmp(expected, actual, sizeof(expected)));
+}
+
+TEST(NanoStringsTest, MemcpyZeroBytes) {
+  testMemcpy<0>();
+}
+
+TEST(NanoStringsTest, MemcpyPartialArray) {
+  testMemcpy<(kMemcpyBufferLen / 2) - 1>();
+}
+
+TEST(NanoStringsTest, MemcpyFullArray) {
+  testMemcpy<kMemcpyBufferLen>();
+}
+
+
+TEST(NanoStringsTest, StrlenEmptyString) {
+  const char *str = "";
+  EXPECT_EQ(::strlen(str), nanoapp_testing::strlen(str));
+}
+
+TEST(NanoStringsTest, StrlenNormal) {
+  const char *str = "random string\n";
+  EXPECT_EQ(::strlen(str), nanoapp_testing::strlen(str));
+}
+
+static constexpr size_t kStrncpyMax = 10;
+static constexpr char kShortString[] = "short";
+static constexpr char kLongString[] = "Kind of long string";
+static constexpr char kExactString[] = "0123456789";
+
+static void testStrncpy(const char *str, size_t len) {
+  char expected[kStrncpyMax];
+  char actual[arrayLength(expected)];
+
+  ::memset(expected, kUnsetValue, sizeof(expected));
+  ::memset(actual, kUnsetValue, sizeof(actual));
+
+  ::strncpy(expected, str, len);
+  nanoapp_testing::strncpy(actual, str, len);
+
+  EXPECT_EQ(0, memcmp(expected, actual, sizeof(expected)));
+}
+
+TEST(NanoStringsTest, Strncpy) {
+  testStrncpy(kShortString, ::strlen(kShortString));
+}
+
+TEST(NanoStringsTest, StrncpySetsTrailingBytes) {
+  ASSERT_LT(::strlen(kShortString), kStrncpyMax);
+  testStrncpy(kShortString, kStrncpyMax);
+}
+
+TEST(NanoStringsTest, StrncpyMax) {
+  ASSERT_GT(::strlen(kLongString), kStrncpyMax);
+  testStrncpy(kLongString, kStrncpyMax);
+}
+
+TEST(NanoStringsTest, StrncpyNothing) {
+  testStrncpy(kLongString, 0);
+}
+
+TEST(NanoStringsTest, StrncpyExactFit) {
+  ASSERT_EQ(::strlen(kExactString), kStrncpyMax);
+  testStrncpy(kExactString, kStrncpyMax);
+}
+
+
+static void testHexAscii(uint32_t value, const char *str) {
+  static constexpr size_t kAsciiLen =
+      nanoapp_testing::kUint32ToHexAsciiBufferMinLen;
+
+  char array[kAsciiLen + 1];
+  array[kAsciiLen] = kUnsetValue;
+  uint32ToHexAscii(array, sizeof(array), value);
+  EXPECT_EQ(kUnsetValue, array[kAsciiLen]);
+  array[kAsciiLen] = '\0';
+  EXPECT_STREQ(str, array);
+}
+
+TEST(NanoStringsTest, Uint32ToHexAscii) {
+  testHexAscii(0x1234ABCD, "0x1234ABCD");
+}
+
+TEST(NanoStringsTest, Uint32ToHexAsciiMin) {
+  testHexAscii(0, "0x00000000");
+}
+
+TEST(NanoStringsTest, Uint32ToHexAsciiMax) {
+  testHexAscii(0xFFFFFFFF, "0xFFFFFFFF");
+}
+
diff --git a/apps/chqts/src/shared/send_message.cc b/apps/chqts/src/shared/send_message.cc
new file mode 100644
index 0000000..f535dd5
--- /dev/null
+++ b/apps/chqts/src/shared/send_message.cc
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <shared/send_message.h>
+
+#include <inttypes.h>
+
+#include <shared/abort.h>
+#include <shared/dumb_allocator.h>
+#include <shared/nano_endian.h>
+#include <shared/nano_string.h>
+
+#include <chre.h>
+
+namespace nanoapp_testing {
+
+constexpr size_t kAllocSize = 128;
+
+static DumbAllocator<kAllocSize, 4> gDumbAlloc;
+
+static void freeDumbAllocMessage(void *message, size_t messageSize) {
+  if (messageSize > kAllocSize) {
+    uint32_t localSize = uint32_t(messageSize);
+    sendFatalFailureToHost("freeDumbAllocMessage given oversized message:",
+                           &localSize);
+  }
+  if (!gDumbAlloc.free(message)) {
+    uint32_t localPtr =
+        reinterpret_cast<size_t>(message) & UINT32_C(0xFFFFFFFF);
+    sendFatalFailureToHost("freeDumbAllocMessage given bad pointer:",
+                           &localPtr);
+  }
+}
+
+static void freeHeapMessage(void *message, size_t /* messageSize */) {
+  if (gDumbAlloc.contains(message)) {
+    uint32_t localPtr =
+        reinterpret_cast<size_t>(message) & UINT32_C(0xFFFFFFFF);
+    sendFatalFailureToHost("freeHeapMessage given DumbAlloc pointer:",
+                           &localPtr);
+  }
+  chreHeapFree(message);
+}
+
+static void fatalError() {
+  // Attempt to send a context-less failure message, in the hopes that
+  // might get through.
+  chreSendMessageToHost(nullptr, 0,
+                        static_cast<uint32_t>(MessageType::kFailure),
+                        nullptr);
+  // Whether or not that made it through, unambigiously fail this test
+  // by aborting.
+  nanoapp_testing::abort();
+}
+
+// TODO(b/32114261): Remove this method.
+static bool needToPrependMessageType() {
+  // TODO: When we have a new API that properly send the messageType,
+  //     this method should get the API version and return appropriately.
+  //     Eventually we should remove this hacky method.
+  return true;
+}
+
+static void *getMessageMemory(size_t *size, bool *dumbAlloc) {
+  if (needToPrependMessageType()) {
+    *size += sizeof(uint32_t);
+  }
+  void *ret = gDumbAlloc.alloc(*size);
+  if (ret != nullptr) {
+    *dumbAlloc = true;
+  } else {
+    // Not expected, but possible if the CHRE is lagging in freeing
+    // these messages, or if we're sending a huge message.
+    *dumbAlloc = false;
+    ret = chreHeapAlloc(*size);
+    if (ret == nullptr) {
+      fatalError();
+    }
+  }
+  return ret;
+}
+
+// TODO(b/32114261): Remove this method.
+static void *prependMessageType(MessageType messageType, void *memory) {
+  if (!needToPrependMessageType()) {
+    return memory;
+  }
+  uint32_t type = static_cast<uint32_t>(messageType);
+  nanoapp_testing::hostToLittleEndian(&type);
+  memcpy(memory, &type, sizeof(type));
+  uint8_t *ptr = static_cast<uint8_t*>(memory);
+  ptr += sizeof(type);
+  return ptr;
+}
+
+static void internalSendMessage(MessageType messageType, void *data,
+                                size_t dataSize, bool dumbAlloc) {
+  // Note that if the CHRE implementation occasionally drops a message
+  // here, then tests will become flaky.  For now, we consider that to
+  // be a flaky CHRE implementation which should fail testing.
+  if (!chreSendMessageToHost(data, dataSize,
+                             static_cast<uint32_t>(messageType),
+                             dumbAlloc ? freeDumbAllocMessage :
+                             freeHeapMessage)) {
+    fatalError();
+  }
+}
+
+void sendMessageToHost(MessageType messageType, const void *data,
+                       size_t dataSize) {
+  if ((dataSize == 0) && (data != nullptr)) {
+    sendInternalFailureToHost("Bad sendMessageToHost args");
+  }
+  bool dumbAlloc = true;
+  size_t fullMessageSize = dataSize;
+  void *myMessageBase = getMessageMemory(&fullMessageSize, &dumbAlloc);
+  void *ptr = prependMessageType(messageType, myMessageBase);
+  memcpy(ptr, data, dataSize);
+  internalSendMessage(messageType, myMessageBase, fullMessageSize, dumbAlloc);
+}
+
+void sendStringToHost(MessageType messageType, const char *message,
+                      const uint32_t *value) {
+  if (message == nullptr) {
+    sendInternalFailureToHost("sendStringToHost 'message' is NULL");
+  }
+  bool dumbAlloc = true;
+  const size_t messageStrlen = strlen(message);
+  size_t myMessageLen = messageStrlen;
+  if (value != nullptr) {
+    myMessageLen += kUint32ToHexAsciiBufferMinLen;
+  }
+  // Add null terminator
+  myMessageLen++;
+
+  size_t fullMessageLen = myMessageLen;
+  char *fullMessage =
+      static_cast<char*>(getMessageMemory(&fullMessageLen, &dumbAlloc));
+  char *ptr = static_cast<char*>(prependMessageType(messageType,
+                                                    fullMessage));
+  memcpy(ptr, message, messageStrlen);
+  ptr += messageStrlen;
+  if (value != nullptr) {
+    uint32ToHexAscii(ptr, fullMessageLen - (ptr - fullMessage), *value);
+  }
+  // Add the terminator.
+  fullMessage[fullMessageLen - 1] = '\0';
+
+  internalSendMessage(messageType, fullMessage, fullMessageLen, dumbAlloc);
+}
+
+// Before we abort the nanoapp, we also put this message in the chreLog().
+// We have no assurance our message will make it to the Host (not required
+// for CHRE implementations), but this will at least make sure our message
+// hits the log.
+static void logFatalMessage(const char *message, const uint32_t *value) {
+  if (value != nullptr) {
+    chreLog(CHRE_LOG_ERROR, "TEST ABORT: %s0x%08" PRIX32, message, *value);
+  } else {
+    chreLog(CHRE_LOG_ERROR, "TEST ABORT: %s", message);
+  }
+}
+
+void sendFatalFailureToHost(const char *message, const uint32_t *value,
+                            AbortBlame reason) {
+  sendFailureToHost(message, value);
+  logFatalMessage(message, value);
+  nanoapp_testing::abort(reason);
+}
+
+void sendInternalFailureToHost(const char *message, const uint32_t *value,
+                               AbortBlame reason) {
+  sendStringToHost(MessageType::kInternalFailure, message, value);
+  logFatalMessage(message, value);
+  nanoapp_testing::abort(reason);
+}
+
+}  // namespace nanoapp_testing
diff --git a/apps/chqts/src/shared/send_message.h b/apps/chqts/src/shared/send_message.h
new file mode 100644
index 0000000..18919ac
--- /dev/null
+++ b/apps/chqts/src/shared/send_message.h
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _GTS_NANOAPPS_SHARED_SEND_MESSAGE_H_
+#define _GTS_NANOAPPS_SHARED_SEND_MESSAGE_H_
+
+#include <cstddef>
+#include <cstdint>
+
+#include <shared/abort.h>
+
+/**
+ * NOTE: The MessageType values are manually synced in the GTS Java's
+ *     ContextHubTestConstants.java.  If you make a change here, be sure
+ *     to update ContextHubTestContants.java as well.
+ */
+
+namespace nanoapp_testing {
+
+/**
+ * Messages types which are sent between Nanoapps and the Java Host testing
+ * code.
+ */
+enum class MessageType : uint32_t {
+  /**
+   * Value which should never be used.
+   *
+   * This value starts at CONTEXT_HUB_TYPE_PRIVATE_MSG_BASE.
+   *
+   * This type should never be sent by Host or Nanoapp code.
+   */
+  kInvalidMessageType = 0x0400,
+
+  /**
+   * Test has completed in success.
+   *
+   * This type should only be sent by the Nanoapp code.
+   */
+  kSuccess = 0x0401,
+
+  /**
+   * Indicates a failure in the CHRE implementation.
+   *
+   * This should be followed by null-terminated string
+   * giving details of failure.
+   *
+   * This type should only be sent by the Nanoapp code.
+   */
+  kFailure = 0x0402,
+
+  /**
+   * Indicate a failure within the testing infrastructure.
+   *
+   * This should be followed by null-terminated string
+   * giving details of failure.
+   *
+   * This type should only be sent by the Nanoapp code.
+   */
+  kInternalFailure = 0x0403,
+
+  /**
+   * Indicate a test is being skipped.
+   *
+   * This should be followed by null-terminated string
+   * giving an explanation of why this test was skipped.
+   *
+   * This type should only be sent by the Nanoapp code.
+   */
+  kSkipped = 0x0404,
+
+  /**
+   * A generic message indicating that the test should continue.
+   *
+   * The meaning of this generic message depends on the specific test.
+   * In general, it means something along the lines of "The test is
+   * successful thus far, please proceed to the next stage."
+   *
+   * This type can be sent by the Host or Nanoapp code.
+   */
+  kContinue = 0x0405,
+
+  // Tests wanting to add custom message types for their protocols should
+  // add them below.  Remember to update ContextHubTestConstants.java as
+  // well (see NOTE at the top of this header).
+};
+
+/**
+ * Sends a message to the host with the given data.
+ *
+ * This method will make a copy of 'data', so there's no need for the
+ * caller to keep that alive after this call.
+ *
+ * Note it may often be more convenient to use one the other methods
+ * when sending a text string.
+ *
+ * @param messageType  The type of the message.
+ * @param data  The data to send.  This can be nullptr, but then 'dataSize'
+ *     must be 0.
+ * @param dataSize  The number of bytes of 'data' to send.  If 'data' is
+ *     not 'nullptr', then this must be non-zero.
+ */
+void sendMessageToHost(MessageType messageType, const void *data = nullptr,
+                       size_t dataSize = 0);
+
+/**
+ * Sends a message to the host, optionally with the 'value' appended as in
+ * hex.
+ *
+ * This method will make a copy of 'message' and 'value', so there's no
+ * need for the caller to keep those alive after this call.
+ *
+ * Note it may often be more convenient to use one of the other methods
+ * below.
+ *
+ * @param messageType  The type of the message.
+ * @param message  The text of the message.  This cannot be nullptr.
+ * @param value  Optional, defaults to nullptr.  If non-null, this value will
+ *     be output as hexadecimal at the end of the message to the host.
+ */
+void sendStringToHost(MessageType messageType, const char *message,
+                      const uint32_t *value = nullptr);
+
+/**
+ * Same as sendStringToHost(), but using MessageType::kFailure for the 'status'.
+ */
+inline void sendFailureToHost(const char *message,
+                              const uint32_t *value = nullptr) {
+  sendStringToHost(MessageType::kFailure, message, value);
+}
+
+/**
+ * Same as sendFailureToHost(), but aborts the test with the given 'reason',
+ * and never returns.
+ */
+void sendFatalFailureToHost(const char *message,
+                            const uint32_t *value = nullptr,
+                            AbortBlame reason = AbortBlame::kChre);
+
+/**
+ * Same as sendStringToHost(), but uses MessageType::kInternalFailure for the
+ * 'status', and aborts the test with the given 'reason' and never returns.
+ */
+void sendInternalFailureToHost(const char *message,
+                               const uint32_t *value = nullptr,
+                               AbortBlame reason = AbortBlame::kTestFramework);
+
+/**
+ * Invoke sendMessageToHost with MessageType::kSuccess and no other information.
+ */
+inline void sendSuccessToHost() {
+  sendMessageToHost(MessageType::kSuccess);
+}
+
+
+}  // namespace nanoapp_testing
+
+
+#endif  // _GTS_NANOAPPS_SHARED_SEND_MESSAGE_H_
diff --git a/apps/gnss_world/gnss_world.cc b/apps/gnss_world/gnss_world.cc
index 78d9691..f324efb 100644
--- a/apps/gnss_world/gnss_world.cc
+++ b/apps/gnss_world/gnss_world.cc
@@ -17,6 +17,7 @@
 #include <chre.h>
 #include <cinttypes>
 
+#include "chre/util/macros.h"
 #include "chre/util/nanoapp/log.h"
 #include "chre/util/time.h"
 
@@ -27,29 +28,99 @@
 namespace {
 #endif  // CHRE_NANOAPP_INTERNAL
 
-//! A dummy cookie to pass into the location session start async request.
-const uint32_t kLocationSessionStartCookie = 0x1337;
-
-//! The interval between location updates.
-constexpr Milliseconds kLocationInterval(Seconds(10));
+//! A dummy cookie to pass into the location session async request.
+const uint32_t kLocationSessionCookie = 0x1337;
 
 //! The minimum time to the next fix for a location.
 constexpr Milliseconds kLocationMinTimeToNextFix(0);
 
+//! The interval in seconds between location updates.
+const uint32_t kLocationIntervals[] = {
+  30,
+  15,
+  30,
+  15,
+  0,
+  10,
+};
+
+//! Whether Gnss Location capability is supported by the platform
+bool gLocationSupported = false;
+
+uint32_t gTimerHandle;
+uint32_t gTimerCount = 0;
+
+//! Whether an async result has been received.
+bool gAsyncResultReceived = false;
+
+void makeLocationRequest() {
+  uint32_t interval = kLocationIntervals[gTimerCount++];
+  LOGI("Modifying location update interval to %" PRIu32 " sec", interval);
+
+  if (interval > 0) {
+    if (chreGnssLocationSessionStartAsync(
+          interval * 1000,
+          kLocationMinTimeToNextFix.getMilliseconds(),
+          &kLocationSessionCookie)) {
+      LOGI("Location session start request sent");
+    } else {
+      LOGE("Error sending location session start request");
+    }
+  } else {
+    if (chreGnssLocationSessionStopAsync(
+          &kLocationSessionCookie)) {
+      LOGI("Location session stop request sent");
+    } else {
+      LOGE("Error sending location session stop request");
+    }
+  }
+
+  // set a timer to verify reception of async result.
+  gTimerHandle = chreTimerSet(
+      CHRE_GNSS_ASYNC_RESULT_TIMEOUT_NS, /* 5 sec in CHRE 1.1 */
+      nullptr /* data */, true /* oneShot */);
+}
+
+void handleTimerEvent(const void *eventData) {
+  LOGI("Timer event received, count %d", gTimerCount);
+  if (!gAsyncResultReceived) {
+    LOGE("Async result not received!");
+  }
+  gAsyncResultReceived = false;
+
+  if (gLocationSupported && gTimerCount < ARRAY_SIZE(kLocationIntervals)) {
+    makeLocationRequest();
+  }
+}
+
 void handleGnssAsyncResult(const chreAsyncResult *result) {
   if (result->requestType == CHRE_GNSS_REQUEST_TYPE_LOCATION_SESSION_START) {
     if (result->success) {
       LOGI("Successfully requested a GNSS location session");
+      gAsyncResultReceived = true;
     } else {
       LOGE("Error requesting GNSS scan monitoring with %" PRIu8,
            result->errorCode);
     }
 
-    if (result->cookie != &kLocationSessionStartCookie) {
+    if (result->cookie != &kLocationSessionCookie) {
       LOGE("Location session start request cookie mismatch");
     }
+  } else if (result->requestType
+             == CHRE_GNSS_REQUEST_TYPE_LOCATION_SESSION_STOP) {
+    if (result->success) {
+      LOGI("Successfully stopped a GNSS location session");
+      gAsyncResultReceived = true;
+    } else {
+      LOGE("Error stoppinging GNSS scan monitoring with %" PRIu8,
+           result->errorCode);
+    }
+
+    if (result->cookie != &kLocationSessionCookie) {
+      LOGE("Location session stop request cookie mismatch");
+    }
   } else {
-    LOGE("Received invalid async result");
+    LOGE("Received invalid async result %" PRIu8, result->requestType);
   }
 }
 
@@ -73,9 +144,11 @@
     case CHRE_GNSS_CAPABILITIES_LOCATION
         | CHRE_GNSS_CAPABILITIES_MEASUREMENTS:
       gnssCapabilitiesStr = "LOCATION | MEASUREMENTS";
+      gLocationSupported = true;
       break;
     case CHRE_GNSS_CAPABILITIES_LOCATION:
       gnssCapabilitiesStr = "LOCATION";
+      gLocationSupported = true;
       break;
     case CHRE_GNSS_CAPABILITIES_MEASUREMENTS:
       gnssCapabilitiesStr = "MEASUREMENTS";
@@ -90,15 +163,8 @@
   LOGI("Detected GNSS support as: %s (%" PRIu32 ")",
        gnssCapabilitiesStr, gnssCapabilities);
 
-  if (gnssCapabilities & CHRE_GNSS_CAPABILITIES_LOCATION) {
-    if (chreGnssLocationSessionStartAsync(
-        kLocationInterval.getMilliseconds(),
-        kLocationMinTimeToNextFix.getMilliseconds(),
-        &kLocationSessionStartCookie)) {
-      LOGI("Location session start request sent");
-    } else {
-      LOGE("Error sending location session request");
-    }
+  if (gLocationSupported) {
+    makeLocationRequest();
   }
 
   return true;
@@ -115,6 +181,9 @@
       handleGnssLocationEvent(
           static_cast<const chreGnssLocationEvent *>(eventData));
       break;
+    case CHRE_EVENT_TIMER:
+      handleTimerEvent(eventData);
+      break;
     default:
       LOGW("Unhandled event type %" PRIu16, eventType);
   }
diff --git a/apps/imu_cal/imu_cal.cc b/apps/imu_cal/imu_cal.cc
index abaca97..f2cb5e2 100644
--- a/apps/imu_cal/imu_cal.cc
+++ b/apps/imu_cal/imu_cal.cc
@@ -64,7 +64,7 @@
   [SENSOR_INDEX_TEMP] = {
     .type = CHRE_SENSOR_TYPE_ACCELEROMETER_TEMPERATURE,
     .enable = true,
-    .interval = Seconds(1).toRawNanoseconds(),
+    .interval = Milliseconds(500).toRawNanoseconds(),
     .highPerformanceLatency = 0,
     // TODO(b/63908396): this sensor should be disabled in stand-by mode
     .standByLatency = Seconds(60).toRawNanoseconds(),
diff --git a/apps/imu_cal/imu_cal.mk b/apps/imu_cal/imu_cal.mk
index 3b97639..1c6f1af 100644
--- a/apps/imu_cal/imu_cal.mk
+++ b/apps/imu_cal/imu_cal.mk
@@ -34,7 +34,6 @@
 COMMON_CFLAGS += -DNANO_SENSOR_CAL_DBG_ENABLED
 
 # Debug options.
-#COMMON_CFLAGS += -DGYRO_CAL_DBG_TUNE_ENABLED
 #COMMON_CFLAGS += -DOVERTEMPCAL_DBG_LOG_TEMP
 #COMMON_CFLAGS += -DIMU_TEMP_DBG_ENABLED
 
diff --git a/apps/imu_cal/nano_calibration.cc b/apps/imu_cal/nano_calibration.cc
index 627dbf4..c2dc5b0 100644
--- a/apps/imu_cal/nano_calibration.cc
+++ b/apps/imu_cal/nano_calibration.cc
@@ -24,6 +24,7 @@
 
 #include "calibration/util/cal_log.h"
 #include "chre/util/nanoapp/log.h"
+#include "common/math/macros.h"
 
 namespace nano_calibration {
 
@@ -56,15 +57,22 @@
   chreLogNull(format, ##__VA_ARGS__)
 #endif  // NANO_SENSOR_CAL_DBG_ENABLED
 
+// Indicates and invalid sensor temperature.
+constexpr float kInvalidTemperatureCelsius = -274.0f;
+
+#ifdef GYRO_CAL_ENABLED
 // Limits NanoSensorCal gyro notifications to once every minute.
-constexpr uint64_t kNanoSensorCalMessageIntervalNanos = 60e9;
+constexpr uint64_t kNanoSensorCalMessageIntervalNanos = MIN_TO_NANOS(1);
+#endif  // GYRO_CAL_ENABLED
+
+#ifdef MAG_CAL_ENABLED
+// Unit conversion from nanoseconds to microseconds.
+constexpr float kNanoToMicroseconds = 1e-3f;
+#endif  // MAG_CAL_ENABLED
 
 #ifdef SPHERE_FIT_ENABLED
 constexpr size_t kSamplesToAverageForOdrEstimateMag = 10;
 
-// Unit conversion: nanoseconds to seconds.
-constexpr float kNanosToSec = 1.0e-9f;
-
 // Helper function that estimates the ODR based on the incoming data timestamp.
 void SamplingRateEstimate(struct SampleRateData *sample_rate_data,
                           float *mean_sampling_rate_hz,
@@ -79,7 +87,7 @@
       *mean_sampling_rate_hz =
           sample_rate_data->num_samples /
           (static_cast<float>(sample_rate_data->time_delta_accumulator) *
-           kNanosToSec);
+           NANOS_TO_SEC);
     } else {
       // Not enough samples to compute a valid sample rate estimate. Indicate
       // this with a -1 value.
@@ -296,6 +304,9 @@
   ResetCalParams(&accel_cal_params_);
   ResetCalParams(&gyro_cal_params_);
   ResetCalParams(&mag_cal_params_);
+
+  // Initializes sensor temperature.
+  temperature_celsius_ = kInvalidTemperatureCelsius;
 }
 
 void NanoSensorCal::Initialize() {
@@ -323,21 +334,25 @@
   // Initializes the gyroscope offset calibration algorithm.
   gyroCalInit(
       &gyro_cal_,
-      1.4e9,                    // min stillness period = 1.4 seconds
-      1.5e9,                    // max stillness period = 1.5 seconds
-      0, 0, 0,                  // initial bias offset calibration
-      0,                        // time stamp of initial bias calibration
-      0.5e9f,                   // analysis window length = 0.5 seconds
-      3.0e-5f,                  // gyroscope variance threshold [rad/sec]^2
-      3.0e-6f,                  // gyroscope confidence delta [rad/sec]^2
-      9.0e-3f,                  // accelerometer variance threshold [m/sec^2]^2
-      9.0e-4f,                  // accelerometer confidence delta [m/sec^2]^2
-      5.0f,                     // magnetometer variance threshold [uT]^2
-      1.0f,                     // magnetometer confidence delta [uT]^2
-      0.95f,                    // stillness threshold [0,1]
-      60.0f * kPi / 180.0f,     // stillness mean variation limit [rad/sec]
-      1.5f,   // maximum temperature deviation during stillness [C]
-      true);  // gyro calibration enable
+      SEC_TO_NANOS(1.4f),       // Min stillness period = 1.4 seconds
+      SEC_TO_NANOS(1.4f),       // Max stillness period = 1.5 seconds (NOTE 1)
+      0, 0, 0,                  // Initial bias offset calibration
+      0,                        // Time stamp of initial bias calibration
+      SEC_TO_NANOS(0.5f),       // Analysis window length = 0.5 seconds
+      3.0e-5f,                  // Gyroscope variance threshold [rad/sec]^2
+      3.0e-6f,                  // Gyroscope confidence delta [rad/sec]^2
+      4.5e-3f,                  // Accelerometer variance threshold [m/sec^2]^2
+      9.0e-4f,                  // Accelerometer confidence delta [m/sec^2]^2
+      5.0f,                     // Magnetometer variance threshold [uT]^2
+      1.0f,                     // Magnetometer confidence delta [uT]^2
+      0.95f,                    // Stillness threshold [0,1]
+      60.0f * MDEG_TO_RAD,      // Stillness mean variation limit [rad/sec]
+      1.5f,                     // Max temperature delta during stillness [C]
+      true);                    // Gyro calibration enable
+  // NOTE 1: This parameter is set to 1.4 seconds to achieve a max stillness
+  // period of 1.5 seconds and avoid buffer boundary conditions that could push
+  // the max stillness to the next multiple of the analysis window length
+  // (i.e., 2.0 seconds).
 
 #ifdef OVERTEMPCAL_GYRO_ENABLED
   // Initializes the over-temperature compensated gyroscope (OTC-Gyro) offset
@@ -345,14 +360,14 @@
   overTempCalInit(
       &over_temp_gyro_cal_,
       5,                        // Min num of points to enable model update
-      100000000,                // Min temperature update interval [nsec]
+      SEC_TO_NANOS(0.1f),       // Min temperature update interval [nsec]
       0.75f,                    // Temperature span of bin method [C]
-      40.0e-3f * kPi / 180.0f,  // Jump tolerance [rad/sec]
-      250.0e-3f * kPi / 180.0f, // Outlier rejection tolerance [rad/sec]
-      172800000000000,          // Model data point age limit [nsec]
-      250.0e-3f * kPi / 180.0f, // Limit for temp. sensitivity [rad/sec/C]
-      8.0f * kPi / 180.0f,      // Limit for model intercept parameter [rad/sec]
-      0.1e-3f * kPi / 180.0f,   // Significant offset change [rad/sec]
+      40.0f * MDEG_TO_RAD,      // Jump tolerance [rad/sec]
+      100.0f * MDEG_TO_RAD,     // Outlier rejection tolerance [rad/sec]
+      DAYS_TO_NANOS(2),         // Model data point age limit [nsec]
+      250.0f * MDEG_TO_RAD,     // Limit for temp. sensitivity [rad/sec/C]
+      8.0e3f * MDEG_TO_RAD,     // Limit for model intercept parameter [rad/sec]
+      0.1f * MDEG_TO_RAD,       // Significant offset change [rad/sec]
       true);                    // Over-temp compensation enable
 #endif  // OVERTEMPCAL_GYRO_ENABLED
 
@@ -371,18 +386,18 @@
   // TODO: Replace function parameters with a struct, to avoid swapping them per
   // accident.
   initMagCalSphere(&mag_cal_sphere_,
-                   0.0f, 0.0f, 0.0f,            // bias x, y, z.
-                   1.0f, 0.0f, 0.0f,            // c00, c01, c02.
-                   0.0f, 1.0f, 0.0f,            // c10, c11, c12.
-                   0.0f, 0.0f, 1.0f,            // c20, c21, c22.
+                   0.0f, 0.0f, 0.0f,            // Bias x, y, z
+                   1.0f, 0.0f, 0.0f,            // c00, c01, c02
+                   0.0f, 1.0f, 0.0f,            // c10, c11, c12
+                   0.0f, 0.0f, 1.0f,            // c20, c21, c22
                    7357000,                     // min_batch_window_in_micros
-                   15,                          // min_num_diverse_vectors.
-                   1,                           // max_num_max_distance.
-                   5.0f,                        // var_threshold.
-                   8.0f,                        // max_min_threshold.
-                   48.f,                        // local_field.
-                   0.49f,                       // threshold_tuning_param.
-                   2.5f);                       // max_distance_tuning_param.
+                   15,                          // min_num_diverse_vectors
+                   1,                           // max_num_max_distance
+                   5.0f,                        // var_threshold
+                   8.0f,                        // max_min_threshold
+                   48.f,                        // local_field
+                   0.49f,                       // threshold_tuning_param
+                   2.5f);                       // max_distance_tuning_param
   magCalSphereOdrUpdate(&mag_cal_sphere_, 50 /* Default sample rate Hz */);
 
   // ODR init.
@@ -505,8 +520,8 @@
 // TODO: Factor common code to shorten function and improve readability.
 void NanoSensorCal::HandleSensorSamplesGyroCal(
     uint16_t event_type, const chreSensorThreeAxisData *event_data) {
-    uint64_t timestamp_nanos = 0;
 #ifdef GYRO_CAL_ENABLED
+    uint64_t timestamp_nanos = 0;
     // Only updates the gyroscope calibration algorithm when measured
     // temperature is valid.
     if (temperature_celsius_ <= kInvalidTemperatureCelsius) {
diff --git a/apps/imu_cal/nano_calibration.h b/apps/imu_cal/nano_calibration.h
index badfdea..8c195a0 100644
--- a/apps/imu_cal/nano_calibration.h
+++ b/apps/imu_cal/nano_calibration.h
@@ -57,16 +57,6 @@
 
 namespace nano_calibration {
 
-// TODO: Clean up, unifying constexpr variables location.
-// Indicates and invalid sensor temperature.
-constexpr float kInvalidTemperatureCelsius = -274.0f;
-
-// The mathematical constant, Pi.
-constexpr float kPi = 3.141592653589793238f;
-
-// Unit conversion from nanoseconds to microseconds.
-constexpr float kNanoToMicroseconds = 1e-3f;
-
 // Data struct for sample rate estimate fuction. Visible for the class in order
 // to allow usage in all algorithms.
 struct SampleRateData {
@@ -155,6 +145,9 @@
 #ifdef GYRO_CAL_ENABLED
   // Gyroscope runtime calibration.
   struct GyroCal gyro_cal_;
+
+  // Used to limit the rate of gyro debug notification messages.
+  uint64_t gyro_notification_time_check_ = 0;
 #ifdef OVERTEMPCAL_GYRO_ENABLED
   // Gyroscope over-temperature runtime calibration.
   struct OverTempCal over_temp_gyro_cal_;
@@ -171,9 +164,6 @@
 #endif  // SPHERE_FIT_ENABLED
 #endif  // MAG_CAL_ENABLED
 
-  // Used to limit the rate of gyro debug notification messages.
-  uint64_t gyro_notification_time_check_ = 0;
-
   // Flag to indicate whether the NanoSensorCal object has been initialized.
   bool nanosensorcal_initialized_ = false;
 
@@ -183,7 +173,7 @@
   mutable bool mag_calibration_ready_ = false;
 
   // Sensor temperature.
-  float temperature_celsius_ = kInvalidTemperatureCelsius;
+  float temperature_celsius_;
 
   // Sensor calibration parameter containers.
   struct ashCalParams accel_cal_params_;
diff --git a/apps/wifi_offload/Android.bp b/apps/wifi_offload/Android.bp
new file mode 100644
index 0000000..ce2a07b
--- /dev/null
+++ b/apps/wifi_offload/Android.bp
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+cc_library_static {
+    name: "wifi_offload_types",
+    export_include_dirs: [
+        "include",
+    ],
+    srcs: [
+        "channel_histogram.cc",
+        "flatbuffers_serialization.cc",
+        "preferred_network.cc",
+        "rpc_log_record.cc",
+        "scan_config.cc",
+        "scan_filter.cc",
+        "scan_params.cc",
+        "scan_record.cc",
+        "scan_result.cc",
+        "scan_result_message.cc",
+        "scan_stats.cc",
+        "ssid.cc",
+        "utility.cc",
+    ],
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
+    shared_libs: [
+        "libcutils",
+        "liblog",
+        "libutils",
+    ],
+    header_libs: [
+        "chre_flatbuffers",
+        "chre_api",
+    ],
+    export_header_lib_headers: [
+        "chre_flatbuffers",
+        "chre_api",
+    ],
+    vendor: true,
+    proprietary: true,
+}
diff --git a/apps/wifi_offload/README.md b/apps/wifi_offload/README.md
new file mode 100644
index 0000000..305b072
--- /dev/null
+++ b/apps/wifi_offload/README.md
@@ -0,0 +1,4 @@
+This folder contains definitions for data types and serialization APIs used for
+sending messages between wifi_offload nanoapp and offload HAL. This library is
+being included by both offload HAL and wifi_offload nanoapp, and is NOT a
+standard nanoapp by itself.
diff --git a/apps/wifi_offload/channel_histogram.cc b/apps/wifi_offload/channel_histogram.cc
new file mode 100644
index 0000000..03dff89
--- /dev/null
+++ b/apps/wifi_offload/channel_histogram.cc
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "chre/apps/wifi_offload/channel_histogram.h"
+#include "chre/apps/wifi_offload/utility.h"
+
+namespace wifi_offload {
+namespace {
+
+/* Strictly increasing sequence of supported channel numbers in
+ * 2.4GHz (802.11b/g/n) and 5GHz (802.11a/h/j/n/ac) */
+constexpr uint8_t kAllChannels[] = {
+    1,   2,   3,   4,   5,   6,   7,   8,   9,   10,  11,  12,  13,  14,
+    16,  34,  36,  38,  40,  42,  44,  46,  48,  50,  52,  54,  56,  58,
+    60,  62,  64,  100, 102, 104, 106, 108, 110, 112, 114, 116, 118, 120,
+    122, 124, 126, 128, 132, 134, 136, 138, 140, 142, 144, 149, 151, 153,
+    155, 157, 159, 161, 165, 183, 184, 185, 187, 188, 189, 192, 196,
+};
+static_assert(sizeof(kAllChannels) / sizeof(kAllChannels[0]) ==
+                  ChannelHistogram::kNumChannels,
+              "some elements unspecified");
+
+/**
+ * Returns the channel number of a given frequency based on 802.11.
+ *
+ * @param frequency Frequncy of the channel in MHz
+ *
+ * @return Channel number of the given frequency. Zero if unsupported
+ *         frequency or channel number. Returned value will be in the range of
+ *         [0, 255]
+ */
+uint8_t GetChannelNumber(uint32_t frequency) {
+  int channel_number =
+      utility::Ieee80211FrequencyToChannel(static_cast<int>(frequency));
+  if (channel_number <= 0 || channel_number > 255) {
+    LOGE("Unknown channel frequency %" PRIu32 " MHz.", frequency);
+    channel_number = 0;
+  }
+  return static_cast<uint8_t>(channel_number);
+}
+
+/**
+ * Returns the index of a given channel number in kAllChannels.
+ *
+ * @param channel_number Channel number we want to map to an index
+ *
+ * @return Index of the given channel number in kAllChannels. kNumChannels if
+ *         not found. Returned value will be in the range of [0, kNumChannels]
+ */
+size_t GetChannelIndex(uint8_t channel_number) {
+  for (size_t i = 0; i < ChannelHistogram::kNumChannels; i++) {
+    if (channel_number == kAllChannels[i]) {
+      return i;
+    }
+  }
+
+  LOGE("Unsupported channel number: %" PRIu8, channel_number);
+  return ChannelHistogram::kNumChannels;
+}
+
+}  // namespace
+
+ChannelHistogram::ChannelHistogram() {
+  std::memset(scan_count_internal_high_res_, 0,
+              sizeof(scan_count_internal_high_res_));
+}
+
+bool ChannelHistogram::IsSupportedFrequency(uint32_t frequency) {
+  return GetChannelNumber(frequency) != 0;
+}
+
+uint8_t ChannelHistogram::GetChannelScanCount(uint8_t channel_number) const {
+  size_t index = GetChannelIndex(channel_number);
+  if (index == kNumChannels) {
+    return 0;
+  }
+
+  if (scan_count_internal_high_res_[index] == 0) {
+    return 0;
+  }
+
+  uint32_t max_count = 0;
+  // since there is at least one non-zero value, max_count won't be 0
+  for (const auto count : scan_count_internal_high_res_) {
+    if (max_count < count) {
+      max_count = count;
+    }
+  }
+
+  // linearly map from [1,max_count] to [1,255]
+  uint64_t scaled_value = scan_count_internal_high_res_[index];
+  scaled_value = scaled_value * 254 / max_count + 1;
+  return static_cast<uint8_t>(scaled_value);
+}
+
+bool ChannelHistogram::IncrementScanCountForFrequency(uint32_t frequency) {
+  size_t index = GetChannelIndex(GetChannelNumber(frequency));
+  if (index == kNumChannels) {
+    return false;
+  }
+
+  scan_count_internal_high_res_[index]++;
+  return true;
+}
+
+bool ChannelHistogram::IncrementScanCountForFrequencyForTest(
+    uint32_t frequency, uint32_t increase_count) {
+  return IncrementScanCountForChannelForTest(GetChannelNumber(frequency),
+                                             increase_count);
+}
+
+bool ChannelHistogram::IncrementScanCountForChannelForTest(
+    uint8_t channel, uint32_t increase_count) {
+  size_t index = GetChannelIndex(channel);
+  if (index == kNumChannels) {
+    return false;
+  }
+
+  scan_count_internal_high_res_[index] += increase_count;
+  return true;
+}
+
+bool ChannelHistogram::operator==(const ChannelHistogram &other) const {
+  if (this == &other) {
+    return true;
+  }
+
+  for (const auto channel : kAllChannels) {
+    // Compare scaled values, rather than raw values
+    if (GetChannelScanCount(channel) != other.GetChannelScanCount(channel)) {
+      return false;
+    }
+  }
+  return true;
+}
+
+flatbuffers::Offset<flatbuffers::Vector<uint8_t>> ChannelHistogram::Serialize(
+    flatbuffers::FlatBufferBuilder *builder) const {
+  uint8_t lowResScanCount[kNumChannels];
+  for (size_t i = 0; i < kNumChannels; i++) {
+    lowResScanCount[i] = GetChannelScanCount(kAllChannels[i]);
+  }
+  return builder->CreateVector(lowResScanCount, kNumChannels);
+}
+
+bool ChannelHistogram::Deserialize(
+    const flatbuffers::Vector<uint8_t> &fbs_scan_count) {
+  if (fbs_scan_count.size() != kNumChannels) {
+    LOGE("Failed to deserialize ChannelHistogram. Null or incomplete members.");
+    return false;
+  }
+
+  for (uint8_t i = 0; i < kNumChannels; i++) {
+    scan_count_internal_high_res_[i] = fbs_scan_count.Get(i);
+  }
+  return true;
+}
+
+}  // namespace wifi_offload
diff --git a/apps/wifi_offload/chre_scan_params_safe.cc b/apps/wifi_offload/chre_scan_params_safe.cc
new file mode 100644
index 0000000..8c4b460
--- /dev/null
+++ b/apps/wifi_offload/chre_scan_params_safe.cc
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <algorithm>
+
+#include "chre/apps/wifi_offload/chre_scan_params_safe.h"
+#include "chre/apps/wifi_offload/utility.h"
+
+namespace wifi_offload {
+
+ChreScanParamsSafe::ChreScanParamsSafe(const ScanParams &scan_params) {
+  // point chre_scan_params_ pointers to allocated buffers
+  chre_scan_params_.frequencyList = chre_scan_params_frequencies_;
+  chre_scan_params_.ssidList = chre_scan_params_ssids_;
+
+  UpdateFromScanParams(scan_params);
+}
+
+const chreWifiScanParams *ChreScanParamsSafe::GetChreWifiScanParams() {
+  return &chre_scan_params_;
+}
+
+void ChreScanParamsSafe::Log() const {
+  LOGI("chreWifiScanParams:");
+  LOGI("  scan type: %" PRIu8, chre_scan_params_.scanType);
+  LOGI("  max scan age (ms): %" PRIu32, chre_scan_params_.maxScanAgeMs);
+  LOGI("  frequency list length: %" PRIu16, chre_scan_params_.frequencyListLen);
+  for (size_t i = 0; i < chre_scan_params_.frequencyListLen; i++) {
+    LOGI("  frequency: %" PRIu32, chre_scan_params_.frequencyList[i]);
+  }
+  LOGI("  ssid list length: %" PRIu8, chre_scan_params_.ssidListLen);
+  for (size_t i = 0; i < chre_scan_params_.ssidListLen; i++) {
+    utility::LogSsid(chre_scan_params_.ssidList[i].ssid,
+                     chre_scan_params_.ssidList[i].ssidLen);
+  }
+}
+
+void ChreScanParamsSafe::UpdateFromScanParams(const ScanParams &scan_params) {
+  chre_scan_params_.scanType = CHRE_WIFI_SCAN_TYPE_ACTIVE_PLUS_PASSIVE_DFS;
+  chre_scan_params_.maxScanAgeMs = 0;
+
+  chre_scan_params_.frequencyListLen = std::min(
+      static_cast<uint16_t>(scan_params.frequencies_to_scan_mhz_.size()),
+      static_cast<uint16_t>(CHRE_WIFI_FREQUENCY_LIST_MAX_LEN));
+  if (chre_scan_params_.frequencyListLen <
+      scan_params.frequencies_to_scan_mhz_.size()) {
+    LOGW("Too many ScanParams frequencies %zu truncated to max %" PRIu16,
+         scan_params.frequencies_to_scan_mhz_.size(),
+         chre_scan_params_.frequencyListLen);
+  }
+
+  std::copy(scan_params.frequencies_to_scan_mhz_.begin(),
+            scan_params.frequencies_to_scan_mhz_.begin() +
+                chre_scan_params_.frequencyListLen,
+            chre_scan_params_frequencies_);
+
+  chre_scan_params_.ssidListLen =
+      std::min(static_cast<uint8_t>(scan_params.ssids_to_scan_.size()),
+               static_cast<uint8_t>(CHRE_WIFI_SSID_LIST_MAX_LEN));
+  if (chre_scan_params_.ssidListLen < scan_params.ssids_to_scan_.size()) {
+    LOGW("Too many ScanParams ssids %zu truncated to max %" PRIu8,
+         scan_params.ssids_to_scan_.size(), chre_scan_params_.ssidListLen);
+  }
+
+  for (size_t i = 0; i < chre_scan_params_.ssidListLen; i++) {
+    scan_params.ssids_to_scan_[i].ToChreWifiSsidListItem(
+        &chre_scan_params_ssids_[i]);
+  }
+}
+
+}  // namespace wifi_offload
diff --git a/apps/wifi_offload/flatbuffers_serialization.cc b/apps/wifi_offload/flatbuffers_serialization.cc
new file mode 100644
index 0000000..0f36222
--- /dev/null
+++ b/apps/wifi_offload/flatbuffers_serialization.cc
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "chre/apps/wifi_offload/flatbuffers_serialization.h"
+#include "chre/apps/wifi_offload/wifi_offload.h"
+
+namespace wifi_offload {
+namespace fbs {
+
+size_t Serialize(const wifi_offload::ScanStats &stats, uint8_t *buffer,
+                 size_t buffer_len) {
+  return Serialize(stats, buffer, buffer_len, "ScanStats");
+}
+
+bool Deserialize(const uint8_t *buffer, size_t buffer_len,
+                 wifi_offload::ScanStats *stats) {
+  return Deserialize<wifi_offload::ScanStats>(buffer, buffer_len, stats,
+                                              "ScanStats");
+}
+
+size_t Serialize(const wifi_offload::ScanConfig &config, uint8_t *buffer,
+                 size_t buffer_len) {
+  return Serialize(config, buffer, buffer_len, "ScanConfig");
+}
+
+bool Deserialize(const uint8_t *buffer, size_t buffer_len,
+                 wifi_offload::ScanConfig *config) {
+  return Deserialize<wifi_offload::ScanConfig>(buffer, buffer_len, config,
+                                               "ScanConfig");
+}
+
+size_t Serialize(const wifi_offload::Vector<wifi_offload::ScanResult> &results,
+                 uint8_t *buffer, size_t buffer_len) {
+  wifi_offload::ScanResultMessage msg;
+  msg.SetScanResults(results);
+  return Serialize(msg, buffer, buffer_len, "ScanResults");
+}
+
+bool Deserialize(const uint8_t *buffer, size_t buffer_len,
+                 wifi_offload::Vector<wifi_offload::ScanResult> *results) {
+  wifi_offload::ScanResultMessage msg;
+  if (Deserialize<wifi_offload::ScanResultMessage>(buffer, buffer_len, &msg,
+                                                   "ScanResults")) {
+    msg.GetScanResults(results);
+    return true;
+  } else {
+    return false;
+  }
+}
+
+}  // namespace fbs
+}  // namespace wifi_offload
diff --git a/apps/wifi_offload/include/chre/apps/wifi_offload/channel_histogram.h b/apps/wifi_offload/include/chre/apps/wifi_offload/channel_histogram.h
new file mode 100644
index 0000000..336362a
--- /dev/null
+++ b/apps/wifi_offload/include/chre/apps/wifi_offload/channel_histogram.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef CHRE_WIFI_OFFLOAD_CHANNEL_HISTOGRAM_H_
+#define CHRE_WIFI_OFFLOAD_CHANNEL_HISTOGRAM_H_
+
+#include "chre/apps/wifi_offload/wifi_offload.h"
+
+#include "chre/apps/wifi_offload/flatbuffers_types_generated.h"
+
+namespace wifi_offload {
+
+/**
+ * A class to keep scan count for each channel. It provides an indexer to access
+ * each channel's scan count with channel number.
+ */
+class ChannelHistogram {
+ public:
+  static constexpr uint8_t kNumChannels = 69;
+
+  ChannelHistogram();
+
+  ~ChannelHistogram() = default;
+
+  static bool IsSupportedFrequency(uint32_t frequency);
+
+  /* Gets the scaled scan count for a given channel number. All scan counts are
+   * scaled such that the largest non-zero count is always mapped to 255 */
+  uint8_t GetChannelScanCount(uint8_t channel_number) const;
+
+  bool IncrementScanCountForFrequency(uint32_t frequency);
+
+  bool IncrementScanCountForFrequencyForTest(uint32_t frequency,
+                                             uint32_t increase_count);
+
+  bool IncrementScanCountForChannelForTest(uint8_t channel,
+                                           uint32_t increase_count);
+
+  bool operator==(const ChannelHistogram &other) const;
+
+  flatbuffers::Offset<flatbuffers::Vector<uint8_t>> Serialize(
+      flatbuffers::FlatBufferBuilder *builder) const;
+
+  bool Deserialize(const flatbuffers::Vector<uint8_t> &fbs_scan_count);
+
+ private:
+  uint32_t scan_count_internal_high_res_[kNumChannels];
+};
+
+}  // namespace wifi_offload
+
+#endif  // CHRE_WIFI_OFFLOAD_CHANNEL_HISTOGRAM_H_
diff --git a/apps/wifi_offload/include/chre/apps/wifi_offload/chre_scan_params_safe.h b/apps/wifi_offload/include/chre/apps/wifi_offload/chre_scan_params_safe.h
new file mode 100644
index 0000000..c6c42c1
--- /dev/null
+++ b/apps/wifi_offload/include/chre/apps/wifi_offload/chre_scan_params_safe.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef CHRE_WIFI_OFFLOAD_CHRE_SCAN_PARAMS_SAFE_H_
+#define CHRE_WIFI_OFFLOAD_CHRE_SCAN_PARAMS_SAFE_H_
+
+#include "chre/apps/wifi_offload/scan_params.h"
+#include "chre/apps/wifi_offload/wifi_offload.h"
+
+namespace wifi_offload {
+
+/* A class to encapsulate chreWifiScanParams struct with pointers to its const
+ * fields and some useful conversion methods */
+class ChreScanParamsSafe {
+ public:
+  ChreScanParamsSafe() = delete;
+
+  ChreScanParamsSafe(const ScanParams &scan_params);
+
+  ~ChreScanParamsSafe() = default;
+
+  const chreWifiScanParams *GetChreWifiScanParams();
+
+  void Log() const;
+
+ private:
+  void UpdateFromScanParams(const ScanParams &scan_params);
+
+  chreWifiScanParams chre_scan_params_;
+  // Storage for *(chre_scan_params_.frequencyList)
+  uint32_t chre_scan_params_frequencies_[CHRE_WIFI_FREQUENCY_LIST_MAX_LEN];
+  // Storage for *(chre_scan_params_.ssidList)
+  chreWifiSsidListItem chre_scan_params_ssids_[CHRE_WIFI_SSID_LIST_MAX_LEN];
+};
+
+}  // namespace wifi_offload
+
+#endif  // CHRE_WIFI_OFFLOAD_CHRE_SCAN_PARAMS_SAFE_H_
diff --git a/apps/wifi_offload/include/chre/apps/wifi_offload/error_codes.h b/apps/wifi_offload/include/chre/apps/wifi_offload/error_codes.h
new file mode 100644
index 0000000..8d3d4ce
--- /dev/null
+++ b/apps/wifi_offload/include/chre/apps/wifi_offload/error_codes.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef CHRE_WIFI_OFFLOAD_ERROR_CODES_H_
+#define CHRE_WIFI_OFFLOAD_ERROR_CODES_H_
+
+#include "chre/apps/wifi_offload/wifi_offload.h"
+
+namespace wifi_offload {
+
+enum ErrorCode : uint32_t {
+  SUCCESS = 0,
+
+  FAILED_TO_ALLOCATE_MESSAGE_BUFFER,
+  FAILED_TO_SERIALIZE_MESSAGE,
+  FAILED_TO_SEND_MESSAGE,
+
+  FAILED_TO_DESERIALIZE_SCAN_CONFIG,
+  INVALID_SUBSCRIBE_MESSAGE_SIZE,
+  SCAN_CONFIG_NOT_INITIALIZED,
+  UNSPECIFIED_HOST_ENDPOINT,
+
+  FAILED_TO_SEND_SCAN_RESULTS,
+  FAILED_TO_SEND_SCAN_STATS,
+
+  SCAN_MONITORING_NOT_SUPPORTED,
+  FAILED_TO_START_SCAN_MONITORING,
+  FAILED_TO_STOP_SCAN_MONITORING,
+  FAILED_TO_CONFIGURE_SCAN_MONITORING_ASYNC,
+
+  ONDEMAND_SCAN_NOT_SUPPORTED,
+  FAILED_TO_SEND_ONDEMAND_SCAN_REQUEST,
+  FAILED_TO_SEND_ONDEMAND_SCAN_REQUEST_ASYNC,
+
+  OUT_OF_ORDER_SCAN_RESULTS,
+  INCOMPLETE_SCAN_RESULTS_BEFORE_SCAN_REQUEST,
+
+  FAILED_TO_SET_SCAN_TIMER,
+};
+
+}  // namespace wifi_offload
+
+#endif  // CHRE_WIFI_OFFLOAD_ERROR_CODES_H_
diff --git a/apps/wifi_offload/include/chre/apps/wifi_offload/flatbuffers_serialization.h b/apps/wifi_offload/include/chre/apps/wifi_offload/flatbuffers_serialization.h
new file mode 100644
index 0000000..639c36c
--- /dev/null
+++ b/apps/wifi_offload/include/chre/apps/wifi_offload/flatbuffers_serialization.h
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef CHRE_WIFI_OFFLOAD_FLATBUFFERS_SERIALIZATION_H_
+#define CHRE_WIFI_OFFLOAD_FLATBUFFERS_SERIALIZATION_H_
+
+#include <cstring>
+
+/**
+ * @file
+ * Serialize/deserialize API for messages passed between WifiOffload nanoapp
+ * and Offload HAL.
+ */
+
+#include "chre/apps/wifi_offload/wifi_offload.h"
+
+#include "chre/apps/wifi_offload/scan_config.h"
+#include "chre/apps/wifi_offload/scan_result.h"
+#include "chre/apps/wifi_offload/scan_result_message.h"
+#include "chre/apps/wifi_offload/scan_stats.h"
+
+namespace wifi_offload {
+namespace fbs {
+
+constexpr size_t kInitialFlatBufferSize = 256;
+
+/**
+ * Serializes the input object into a given buffer.
+ *
+ * @param stats/config/results object to be serialized
+ * @param buffer Buffer to hold the result of serialization. Caller is
+ *        responsible for allocating enough buffer size to hold the serialized
+ *        data. If buffer is not large enough, serialization will abort and
+ *        buffer will stay unmodified. If set to null, serialize function will
+ *        return the required buffer size to hold the serialized data.
+ * @param buffer_len Length of the buffer allocated by the caller
+ *
+ * @return zero if buffer is not big enough to hold the serialized data,
+ *         otherwise size of serialized data in buffer.
+ */
+size_t Serialize(const wifi_offload::ScanStats &stats, uint8_t *buffer,
+                 size_t buffer_len);
+size_t Serialize(const wifi_offload::ScanConfig &config, uint8_t *buffer,
+                 size_t buffer_len);
+size_t Serialize(const wifi_offload::Vector<wifi_offload::ScanResult> &results,
+                 uint8_t *buffer, size_t buffer_len);
+
+/**
+ * Deserializes from buffer into a given output object.
+ *
+ * @param buffer Buffer that holds the serialized data
+ * @param buffer_len Length of buffer
+ * @param stats/config/results Pointer to the output object to hold the result
+ *        of deserialization
+ *
+ * @return true if deserialized successfully, false otherwise
+ */
+bool Deserialize(const uint8_t *buffer, size_t buffer_len,
+                 wifi_offload::ScanStats *stats);
+bool Deserialize(const uint8_t *buffer, size_t buffer_len,
+                 wifi_offload::ScanConfig *config);
+bool Deserialize(const uint8_t *buffer, size_t buffer_len,
+                 wifi_offload::Vector<wifi_offload::ScanResult> *results);
+
+template <typename SerializeType>
+size_t Serialize(const SerializeType &obj, uint8_t *out_buffer,
+                 size_t out_buffer_len, const char *log_tag = "") {
+  flatbuffers::FlatBufferBuilder builder(kInitialFlatBufferSize);
+  const auto fbs_obj = obj.Serialize(&builder);
+  builder.Finish(fbs_obj);
+
+  const uint8_t *data = builder.GetBufferPointer();
+  const size_t size = builder.GetSize();
+
+  if (out_buffer == nullptr) {
+    LOGI("%s output buffer is null. Returning serialized size %zu.", log_tag,
+         size);
+    return size;
+  }
+
+  if (size > out_buffer_len) {
+    LOGE("Serialized %s size %zu too big for provided buffer %zu; dropping",
+         log_tag, size, out_buffer_len);
+    return 0;
+  }
+
+  std::memcpy(out_buffer, data, size);
+  LOGI("Serialized %s to buffer size %zu", log_tag, size);
+  return size;
+}
+
+template <typename SerializeType>
+bool Deserialize(const uint8_t *in_buffer, size_t in_buffer_len,
+                 SerializeType *obj, const char *log_tag = "") {
+  if (in_buffer == nullptr || in_buffer_len == 0) {
+    LOGE("%s deserialize buffer is null or has size zero.", log_tag);
+    return false;
+  }
+
+  if (obj == nullptr) {
+    LOGE("%s deserialize output pointer is null.", log_tag);
+    return false;
+  }
+
+  flatbuffers::Verifier verifier(in_buffer, in_buffer_len);
+  if (!verifier.VerifyBuffer<typename SerializeType::FbsType>(nullptr)) {
+    LOGE("Failed to verify %s deserialize buffer.", log_tag);
+    return false;
+  }
+
+  const auto fbs_obj =
+      flatbuffers::GetRoot<typename SerializeType::FbsType>(in_buffer);
+  if (fbs_obj == nullptr) {
+    LOGE("Deserialized %s object is null or has missing members.", log_tag);
+    return false;
+  }
+
+  return obj->Deserialize(*fbs_obj);
+}
+
+}  //  namespace fbs
+}  //  namespace wifi_offload
+
+#endif  // CHRE_WIFI_OFFLOAD_FLATBUFFERS_SERIALIZATION_H_
diff --git a/apps/wifi_offload/include/chre/apps/wifi_offload/flatbuffers_types.fbs b/apps/wifi_offload/include/chre/apps/wifi_offload/flatbuffers_types.fbs
new file mode 100644
index 0000000..5848f87
--- /dev/null
+++ b/apps/wifi_offload/include/chre/apps/wifi_offload/flatbuffers_types.fbs
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace wifi_offload.fbs;
+
+/* SSID of the Access Point, maximum 32 characters */
+table Ssid {
+  ssid:[ubyte];
+}
+
+/**
+ * Preferred network information
+ * SSID and associated security mode(s)
+ */
+table PreferredNetwork {
+  ssid:Ssid;
+  /* SecurityMode flags that are associated with this SSID
+   * More than one security mode can be supported */
+  security_modes:ubyte;
+}
+
+/**
+ * Scan Results returned by offload nanoapp
+ */
+table ScanResult {
+  ssid:Ssid;
+  security_modes:ubyte;
+  bssid:[ubyte];
+  capability:ushort;  // Multiple bits may be set
+  frequency_scanned_mhz:uint;
+  rssi_dbm:byte;  // Signal strength
+  tsf:ulong;  // TSF found in beacon/probe response
+}
+
+/**
+ * Message to send scan result(s) from nanoapp to host
+ */
+table ScanResultMessage {
+  scan_results:[ScanResult];
+}
+
+/**
+ * Parameters for performing offload scans
+ */
+table ScanParams {
+  ssids_to_scan:[Ssid];
+  frequencies_to_scan_mhz:[uint];
+  disconnected_mode_scan_interval_ms:uint;  // 0 means disable
+}
+
+/**
+ * Instruction on how to filter scan results before waking up the applications
+ * processor.
+ */
+table ScanFilter {
+  networks_to_match:[PreferredNetwork];  // emtpy means match all
+  min_rssi_threshold_dbm:byte;
+}
+
+/**
+ * Structure to send scan params and filter from host to nanoapp
+ */
+table ScanConfig {
+  scan_params:ScanParams;
+  scan_filter:ScanFilter;
+}
+
+/**
+ * Defines the structure of each scan record
+ */
+table ScanRecord {
+  time_spent_scanning_ms:uint;
+  num_channels_scanned:uint;
+  /* Number of ScanRecords aggregated into this record. Multiple ScanRecords
+   * may be combined together into a single ScanRecord by adding up values to
+   * save space if needed */
+  num_entries_aggregated:uint;
+}
+
+/**
+ * Defines the structure of each log record
+ */
+table RpcLogRecord {
+  record_type:ubyte;
+  timestamp_chre_ms:uint;  // See chreGetTime()
+}
+
+/**
+ * Structure to keep and send scan statistics from nanoapp to host
+ */
+table ScanStats {
+  num_scans_requested_by_nanoapp:uint;
+  num_scans_serviced_by_hardware:uint;
+  num_scans_serviced_by_cache:uint;
+  updated_at_chre_ms:uint;
+  sent_at_chre_ms:uint;
+  /* The duration between when the framework subscribed for scan results to min
+   * of time when the framework unsubscribed vs. current time */
+  last_subscription_duration_ms:uint;
+  channel_scan_count:[ubyte];
+  scan_records:[ScanRecord];
+  rpc_log_records:[RpcLogRecord];
+}
diff --git a/apps/wifi_offload/include/chre/apps/wifi_offload/flatbuffers_types_generated.h b/apps/wifi_offload/include/chre/apps/wifi_offload/flatbuffers_types_generated.h
new file mode 100644
index 0000000..d75ba9b
--- /dev/null
+++ b/apps/wifi_offload/include/chre/apps/wifi_offload/flatbuffers_types_generated.h
@@ -0,0 +1,780 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// automatically generated by the FlatBuffers compiler, do not modify
+
+#ifndef FLATBUFFERS_GENERATED_FLATBUFFERSTYPES_WIFI_OFFLOAD_FBS_H_
+#define FLATBUFFERS_GENERATED_FLATBUFFERSTYPES_WIFI_OFFLOAD_FBS_H_
+
+#include "flatbuffers/flatbuffers.h"
+
+namespace wifi_offload {
+namespace fbs {
+
+struct Ssid;
+
+struct PreferredNetwork;
+
+struct ScanResult;
+
+struct ScanResultMessage;
+
+struct ScanParams;
+
+struct ScanFilter;
+
+struct ScanConfig;
+
+struct ScanRecord;
+
+struct RpcLogRecord;
+
+struct ScanStats;
+
+struct Ssid FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table {
+  enum { VT_SSID = 4 };
+  const flatbuffers::Vector<uint8_t> *ssid() const {
+    return GetPointer<const flatbuffers::Vector<uint8_t> *>(VT_SSID);
+  }
+  bool Verify(flatbuffers::Verifier &verifier) const {
+    return VerifyTableStart(verifier) &&
+           VerifyField<flatbuffers::uoffset_t>(verifier, VT_SSID) &&
+           verifier.Verify(ssid()) && verifier.EndTable();
+  }
+};
+
+struct SsidBuilder {
+  flatbuffers::FlatBufferBuilder &fbb_;
+  flatbuffers::uoffset_t start_;
+  void add_ssid(flatbuffers::Offset<flatbuffers::Vector<uint8_t>> ssid) {
+    fbb_.AddOffset(Ssid::VT_SSID, ssid);
+  }
+  SsidBuilder(flatbuffers::FlatBufferBuilder &_fbb) : fbb_(_fbb) {
+    start_ = fbb_.StartTable();
+  }
+  SsidBuilder &operator=(const SsidBuilder &);
+  flatbuffers::Offset<Ssid> Finish() {
+    const auto end = fbb_.EndTable(start_, 1);
+    auto o = flatbuffers::Offset<Ssid>(end);
+    return o;
+  }
+};
+
+inline flatbuffers::Offset<Ssid> CreateSsid(
+    flatbuffers::FlatBufferBuilder &_fbb,
+    flatbuffers::Offset<flatbuffers::Vector<uint8_t>> ssid = 0) {
+  SsidBuilder builder_(_fbb);
+  builder_.add_ssid(ssid);
+  return builder_.Finish();
+}
+
+inline flatbuffers::Offset<Ssid> CreateSsidDirect(
+    flatbuffers::FlatBufferBuilder &_fbb,
+    const std::vector<uint8_t> *ssid = nullptr) {
+  return wifi_offload::fbs::CreateSsid(
+      _fbb, ssid ? _fbb.CreateVector<uint8_t>(*ssid) : 0);
+}
+
+struct PreferredNetwork FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table {
+  enum { VT_SSID = 4, VT_SECURITY_MODES = 6 };
+  const Ssid *ssid() const { return GetPointer<const Ssid *>(VT_SSID); }
+  uint8_t security_modes() const {
+    return GetField<uint8_t>(VT_SECURITY_MODES, 0);
+  }
+  bool Verify(flatbuffers::Verifier &verifier) const {
+    return VerifyTableStart(verifier) &&
+           VerifyField<flatbuffers::uoffset_t>(verifier, VT_SSID) &&
+           verifier.VerifyTable(ssid()) &&
+           VerifyField<uint8_t>(verifier, VT_SECURITY_MODES) &&
+           verifier.EndTable();
+  }
+};
+
+struct PreferredNetworkBuilder {
+  flatbuffers::FlatBufferBuilder &fbb_;
+  flatbuffers::uoffset_t start_;
+  void add_ssid(flatbuffers::Offset<Ssid> ssid) {
+    fbb_.AddOffset(PreferredNetwork::VT_SSID, ssid);
+  }
+  void add_security_modes(uint8_t security_modes) {
+    fbb_.AddElement<uint8_t>(PreferredNetwork::VT_SECURITY_MODES,
+                             security_modes, 0);
+  }
+  PreferredNetworkBuilder(flatbuffers::FlatBufferBuilder &_fbb) : fbb_(_fbb) {
+    start_ = fbb_.StartTable();
+  }
+  PreferredNetworkBuilder &operator=(const PreferredNetworkBuilder &);
+  flatbuffers::Offset<PreferredNetwork> Finish() {
+    const auto end = fbb_.EndTable(start_, 2);
+    auto o = flatbuffers::Offset<PreferredNetwork>(end);
+    return o;
+  }
+};
+
+inline flatbuffers::Offset<PreferredNetwork> CreatePreferredNetwork(
+    flatbuffers::FlatBufferBuilder &_fbb, flatbuffers::Offset<Ssid> ssid = 0,
+    uint8_t security_modes = 0) {
+  PreferredNetworkBuilder builder_(_fbb);
+  builder_.add_ssid(ssid);
+  builder_.add_security_modes(security_modes);
+  return builder_.Finish();
+}
+
+struct ScanResult FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table {
+  enum {
+    VT_SSID = 4,
+    VT_SECURITY_MODES = 6,
+    VT_BSSID = 8,
+    VT_CAPABILITY = 10,
+    VT_FREQUENCY_SCANNED_MHZ = 12,
+    VT_RSSI_DBM = 14,
+    VT_TSF = 16
+  };
+  const Ssid *ssid() const { return GetPointer<const Ssid *>(VT_SSID); }
+  uint8_t security_modes() const {
+    return GetField<uint8_t>(VT_SECURITY_MODES, 0);
+  }
+  const flatbuffers::Vector<uint8_t> *bssid() const {
+    return GetPointer<const flatbuffers::Vector<uint8_t> *>(VT_BSSID);
+  }
+  uint16_t capability() const { return GetField<uint16_t>(VT_CAPABILITY, 0); }
+  uint32_t frequency_scanned_mhz() const {
+    return GetField<uint32_t>(VT_FREQUENCY_SCANNED_MHZ, 0);
+  }
+  int8_t rssi_dbm() const { return GetField<int8_t>(VT_RSSI_DBM, 0); }
+  uint64_t tsf() const { return GetField<uint64_t>(VT_TSF, 0); }
+  bool Verify(flatbuffers::Verifier &verifier) const {
+    return VerifyTableStart(verifier) &&
+           VerifyField<flatbuffers::uoffset_t>(verifier, VT_SSID) &&
+           verifier.VerifyTable(ssid()) &&
+           VerifyField<uint8_t>(verifier, VT_SECURITY_MODES) &&
+           VerifyField<flatbuffers::uoffset_t>(verifier, VT_BSSID) &&
+           verifier.Verify(bssid()) &&
+           VerifyField<uint16_t>(verifier, VT_CAPABILITY) &&
+           VerifyField<uint32_t>(verifier, VT_FREQUENCY_SCANNED_MHZ) &&
+           VerifyField<int8_t>(verifier, VT_RSSI_DBM) &&
+           VerifyField<uint64_t>(verifier, VT_TSF) && verifier.EndTable();
+  }
+};
+
+struct ScanResultBuilder {
+  flatbuffers::FlatBufferBuilder &fbb_;
+  flatbuffers::uoffset_t start_;
+  void add_ssid(flatbuffers::Offset<Ssid> ssid) {
+    fbb_.AddOffset(ScanResult::VT_SSID, ssid);
+  }
+  void add_security_modes(uint8_t security_modes) {
+    fbb_.AddElement<uint8_t>(ScanResult::VT_SECURITY_MODES, security_modes, 0);
+  }
+  void add_bssid(flatbuffers::Offset<flatbuffers::Vector<uint8_t>> bssid) {
+    fbb_.AddOffset(ScanResult::VT_BSSID, bssid);
+  }
+  void add_capability(uint16_t capability) {
+    fbb_.AddElement<uint16_t>(ScanResult::VT_CAPABILITY, capability, 0);
+  }
+  void add_frequency_scanned_mhz(uint32_t frequency_scanned_mhz) {
+    fbb_.AddElement<uint32_t>(ScanResult::VT_FREQUENCY_SCANNED_MHZ,
+                              frequency_scanned_mhz, 0);
+  }
+  void add_rssi_dbm(int8_t rssi_dbm) {
+    fbb_.AddElement<int8_t>(ScanResult::VT_RSSI_DBM, rssi_dbm, 0);
+  }
+  void add_tsf(uint64_t tsf) {
+    fbb_.AddElement<uint64_t>(ScanResult::VT_TSF, tsf, 0);
+  }
+  ScanResultBuilder(flatbuffers::FlatBufferBuilder &_fbb) : fbb_(_fbb) {
+    start_ = fbb_.StartTable();
+  }
+  ScanResultBuilder &operator=(const ScanResultBuilder &);
+  flatbuffers::Offset<ScanResult> Finish() {
+    const auto end = fbb_.EndTable(start_, 7);
+    auto o = flatbuffers::Offset<ScanResult>(end);
+    return o;
+  }
+};
+
+inline flatbuffers::Offset<ScanResult> CreateScanResult(
+    flatbuffers::FlatBufferBuilder &_fbb, flatbuffers::Offset<Ssid> ssid = 0,
+    uint8_t security_modes = 0,
+    flatbuffers::Offset<flatbuffers::Vector<uint8_t>> bssid = 0,
+    uint16_t capability = 0, uint32_t frequency_scanned_mhz = 0,
+    int8_t rssi_dbm = 0, uint64_t tsf = 0) {
+  ScanResultBuilder builder_(_fbb);
+  builder_.add_tsf(tsf);
+  builder_.add_frequency_scanned_mhz(frequency_scanned_mhz);
+  builder_.add_bssid(bssid);
+  builder_.add_ssid(ssid);
+  builder_.add_capability(capability);
+  builder_.add_rssi_dbm(rssi_dbm);
+  builder_.add_security_modes(security_modes);
+  return builder_.Finish();
+}
+
+inline flatbuffers::Offset<ScanResult> CreateScanResultDirect(
+    flatbuffers::FlatBufferBuilder &_fbb, flatbuffers::Offset<Ssid> ssid = 0,
+    uint8_t security_modes = 0, const std::vector<uint8_t> *bssid = nullptr,
+    uint16_t capability = 0, uint32_t frequency_scanned_mhz = 0,
+    int8_t rssi_dbm = 0, uint64_t tsf = 0) {
+  return wifi_offload::fbs::CreateScanResult(
+      _fbb, ssid, security_modes,
+      bssid ? _fbb.CreateVector<uint8_t>(*bssid) : 0, capability,
+      frequency_scanned_mhz, rssi_dbm, tsf);
+}
+
+struct ScanResultMessage FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table {
+  enum { VT_SCAN_RESULTS = 4 };
+  const flatbuffers::Vector<flatbuffers::Offset<ScanResult>> *scan_results()
+      const {
+    return GetPointer<
+        const flatbuffers::Vector<flatbuffers::Offset<ScanResult>> *>(
+        VT_SCAN_RESULTS);
+  }
+  bool Verify(flatbuffers::Verifier &verifier) const {
+    return VerifyTableStart(verifier) &&
+           VerifyField<flatbuffers::uoffset_t>(verifier, VT_SCAN_RESULTS) &&
+           verifier.Verify(scan_results()) &&
+           verifier.VerifyVectorOfTables(scan_results()) && verifier.EndTable();
+  }
+};
+
+struct ScanResultMessageBuilder {
+  flatbuffers::FlatBufferBuilder &fbb_;
+  flatbuffers::uoffset_t start_;
+  void add_scan_results(
+      flatbuffers::Offset<flatbuffers::Vector<flatbuffers::Offset<ScanResult>>>
+          scan_results) {
+    fbb_.AddOffset(ScanResultMessage::VT_SCAN_RESULTS, scan_results);
+  }
+  ScanResultMessageBuilder(flatbuffers::FlatBufferBuilder &_fbb) : fbb_(_fbb) {
+    start_ = fbb_.StartTable();
+  }
+  ScanResultMessageBuilder &operator=(const ScanResultMessageBuilder &);
+  flatbuffers::Offset<ScanResultMessage> Finish() {
+    const auto end = fbb_.EndTable(start_, 1);
+    auto o = flatbuffers::Offset<ScanResultMessage>(end);
+    return o;
+  }
+};
+
+inline flatbuffers::Offset<ScanResultMessage> CreateScanResultMessage(
+    flatbuffers::FlatBufferBuilder &_fbb,
+    flatbuffers::Offset<flatbuffers::Vector<flatbuffers::Offset<ScanResult>>>
+        scan_results = 0) {
+  ScanResultMessageBuilder builder_(_fbb);
+  builder_.add_scan_results(scan_results);
+  return builder_.Finish();
+}
+
+inline flatbuffers::Offset<ScanResultMessage> CreateScanResultMessageDirect(
+    flatbuffers::FlatBufferBuilder &_fbb,
+    const std::vector<flatbuffers::Offset<ScanResult>> *scan_results =
+        nullptr) {
+  return wifi_offload::fbs::CreateScanResultMessage(
+      _fbb,
+      scan_results
+          ? _fbb.CreateVector<flatbuffers::Offset<ScanResult>>(*scan_results)
+          : 0);
+}
+
+struct ScanParams FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table {
+  enum {
+    VT_SSIDS_TO_SCAN = 4,
+    VT_FREQUENCIES_TO_SCAN_MHZ = 6,
+    VT_DISCONNECTED_MODE_SCAN_INTERVAL_MS = 8
+  };
+  const flatbuffers::Vector<flatbuffers::Offset<Ssid>> *ssids_to_scan() const {
+    return GetPointer<const flatbuffers::Vector<flatbuffers::Offset<Ssid>> *>(
+        VT_SSIDS_TO_SCAN);
+  }
+  const flatbuffers::Vector<uint32_t> *frequencies_to_scan_mhz() const {
+    return GetPointer<const flatbuffers::Vector<uint32_t> *>(
+        VT_FREQUENCIES_TO_SCAN_MHZ);
+  }
+  uint32_t disconnected_mode_scan_interval_ms() const {
+    return GetField<uint32_t>(VT_DISCONNECTED_MODE_SCAN_INTERVAL_MS, 0);
+  }
+  bool Verify(flatbuffers::Verifier &verifier) const {
+    return VerifyTableStart(verifier) &&
+           VerifyField<flatbuffers::uoffset_t>(verifier, VT_SSIDS_TO_SCAN) &&
+           verifier.Verify(ssids_to_scan()) &&
+           verifier.VerifyVectorOfTables(ssids_to_scan()) &&
+           VerifyField<flatbuffers::uoffset_t>(verifier,
+                                               VT_FREQUENCIES_TO_SCAN_MHZ) &&
+           verifier.Verify(frequencies_to_scan_mhz()) &&
+           VerifyField<uint32_t>(verifier,
+                                 VT_DISCONNECTED_MODE_SCAN_INTERVAL_MS) &&
+           verifier.EndTable();
+  }
+};
+
+struct ScanParamsBuilder {
+  flatbuffers::FlatBufferBuilder &fbb_;
+  flatbuffers::uoffset_t start_;
+  void add_ssids_to_scan(
+      flatbuffers::Offset<flatbuffers::Vector<flatbuffers::Offset<Ssid>>>
+          ssids_to_scan) {
+    fbb_.AddOffset(ScanParams::VT_SSIDS_TO_SCAN, ssids_to_scan);
+  }
+  void add_frequencies_to_scan_mhz(
+      flatbuffers::Offset<flatbuffers::Vector<uint32_t>>
+          frequencies_to_scan_mhz) {
+    fbb_.AddOffset(ScanParams::VT_FREQUENCIES_TO_SCAN_MHZ,
+                   frequencies_to_scan_mhz);
+  }
+  void add_disconnected_mode_scan_interval_ms(
+      uint32_t disconnected_mode_scan_interval_ms) {
+    fbb_.AddElement<uint32_t>(ScanParams::VT_DISCONNECTED_MODE_SCAN_INTERVAL_MS,
+                              disconnected_mode_scan_interval_ms, 0);
+  }
+  ScanParamsBuilder(flatbuffers::FlatBufferBuilder &_fbb) : fbb_(_fbb) {
+    start_ = fbb_.StartTable();
+  }
+  ScanParamsBuilder &operator=(const ScanParamsBuilder &);
+  flatbuffers::Offset<ScanParams> Finish() {
+    const auto end = fbb_.EndTable(start_, 3);
+    auto o = flatbuffers::Offset<ScanParams>(end);
+    return o;
+  }
+};
+
+inline flatbuffers::Offset<ScanParams> CreateScanParams(
+    flatbuffers::FlatBufferBuilder &_fbb,
+    flatbuffers::Offset<flatbuffers::Vector<flatbuffers::Offset<Ssid>>>
+        ssids_to_scan = 0,
+    flatbuffers::Offset<flatbuffers::Vector<uint32_t>> frequencies_to_scan_mhz =
+        0,
+    uint32_t disconnected_mode_scan_interval_ms = 0) {
+  ScanParamsBuilder builder_(_fbb);
+  builder_.add_disconnected_mode_scan_interval_ms(
+      disconnected_mode_scan_interval_ms);
+  builder_.add_frequencies_to_scan_mhz(frequencies_to_scan_mhz);
+  builder_.add_ssids_to_scan(ssids_to_scan);
+  return builder_.Finish();
+}
+
+inline flatbuffers::Offset<ScanParams> CreateScanParamsDirect(
+    flatbuffers::FlatBufferBuilder &_fbb,
+    const std::vector<flatbuffers::Offset<Ssid>> *ssids_to_scan = nullptr,
+    const std::vector<uint32_t> *frequencies_to_scan_mhz = nullptr,
+    uint32_t disconnected_mode_scan_interval_ms = 0) {
+  return wifi_offload::fbs::CreateScanParams(
+      _fbb,
+      ssids_to_scan
+          ? _fbb.CreateVector<flatbuffers::Offset<Ssid>>(*ssids_to_scan)
+          : 0,
+      frequencies_to_scan_mhz
+          ? _fbb.CreateVector<uint32_t>(*frequencies_to_scan_mhz)
+          : 0,
+      disconnected_mode_scan_interval_ms);
+}
+
+struct ScanFilter FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table {
+  enum { VT_NETWORKS_TO_MATCH = 4, VT_MIN_RSSI_THRESHOLD_DBM = 6 };
+  const flatbuffers::Vector<flatbuffers::Offset<PreferredNetwork>>
+      *networks_to_match() const {
+    return GetPointer<
+        const flatbuffers::Vector<flatbuffers::Offset<PreferredNetwork>> *>(
+        VT_NETWORKS_TO_MATCH);
+  }
+  int8_t min_rssi_threshold_dbm() const {
+    return GetField<int8_t>(VT_MIN_RSSI_THRESHOLD_DBM, 0);
+  }
+  bool Verify(flatbuffers::Verifier &verifier) const {
+    return VerifyTableStart(verifier) && VerifyField<flatbuffers::uoffset_t>(
+                                             verifier, VT_NETWORKS_TO_MATCH) &&
+           verifier.Verify(networks_to_match()) &&
+           verifier.VerifyVectorOfTables(networks_to_match()) &&
+           VerifyField<int8_t>(verifier, VT_MIN_RSSI_THRESHOLD_DBM) &&
+           verifier.EndTable();
+  }
+};
+
+struct ScanFilterBuilder {
+  flatbuffers::FlatBufferBuilder &fbb_;
+  flatbuffers::uoffset_t start_;
+  void add_networks_to_match(
+      flatbuffers::Offset<
+          flatbuffers::Vector<flatbuffers::Offset<PreferredNetwork>>>
+          networks_to_match) {
+    fbb_.AddOffset(ScanFilter::VT_NETWORKS_TO_MATCH, networks_to_match);
+  }
+  void add_min_rssi_threshold_dbm(int8_t min_rssi_threshold_dbm) {
+    fbb_.AddElement<int8_t>(ScanFilter::VT_MIN_RSSI_THRESHOLD_DBM,
+                            min_rssi_threshold_dbm, 0);
+  }
+  ScanFilterBuilder(flatbuffers::FlatBufferBuilder &_fbb) : fbb_(_fbb) {
+    start_ = fbb_.StartTable();
+  }
+  ScanFilterBuilder &operator=(const ScanFilterBuilder &);
+  flatbuffers::Offset<ScanFilter> Finish() {
+    const auto end = fbb_.EndTable(start_, 2);
+    auto o = flatbuffers::Offset<ScanFilter>(end);
+    return o;
+  }
+};
+
+inline flatbuffers::Offset<ScanFilter> CreateScanFilter(
+    flatbuffers::FlatBufferBuilder &_fbb,
+    flatbuffers::Offset<
+        flatbuffers::Vector<flatbuffers::Offset<PreferredNetwork>>>
+        networks_to_match = 0,
+    int8_t min_rssi_threshold_dbm = 0) {
+  ScanFilterBuilder builder_(_fbb);
+  builder_.add_networks_to_match(networks_to_match);
+  builder_.add_min_rssi_threshold_dbm(min_rssi_threshold_dbm);
+  return builder_.Finish();
+}
+
+inline flatbuffers::Offset<ScanFilter> CreateScanFilterDirect(
+    flatbuffers::FlatBufferBuilder &_fbb,
+    const std::vector<flatbuffers::Offset<PreferredNetwork>>
+        *networks_to_match = nullptr,
+    int8_t min_rssi_threshold_dbm = 0) {
+  return wifi_offload::fbs::CreateScanFilter(
+      _fbb,
+      networks_to_match
+          ? _fbb.CreateVector<flatbuffers::Offset<PreferredNetwork>>(
+                *networks_to_match)
+          : 0,
+      min_rssi_threshold_dbm);
+}
+
+struct ScanConfig FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table {
+  enum { VT_SCAN_PARAMS = 4, VT_SCAN_FILTER = 6 };
+  const ScanParams *scan_params() const {
+    return GetPointer<const ScanParams *>(VT_SCAN_PARAMS);
+  }
+  const ScanFilter *scan_filter() const {
+    return GetPointer<const ScanFilter *>(VT_SCAN_FILTER);
+  }
+  bool Verify(flatbuffers::Verifier &verifier) const {
+    return VerifyTableStart(verifier) &&
+           VerifyField<flatbuffers::uoffset_t>(verifier, VT_SCAN_PARAMS) &&
+           verifier.VerifyTable(scan_params()) &&
+           VerifyField<flatbuffers::uoffset_t>(verifier, VT_SCAN_FILTER) &&
+           verifier.VerifyTable(scan_filter()) && verifier.EndTable();
+  }
+};
+
+struct ScanConfigBuilder {
+  flatbuffers::FlatBufferBuilder &fbb_;
+  flatbuffers::uoffset_t start_;
+  void add_scan_params(flatbuffers::Offset<ScanParams> scan_params) {
+    fbb_.AddOffset(ScanConfig::VT_SCAN_PARAMS, scan_params);
+  }
+  void add_scan_filter(flatbuffers::Offset<ScanFilter> scan_filter) {
+    fbb_.AddOffset(ScanConfig::VT_SCAN_FILTER, scan_filter);
+  }
+  ScanConfigBuilder(flatbuffers::FlatBufferBuilder &_fbb) : fbb_(_fbb) {
+    start_ = fbb_.StartTable();
+  }
+  ScanConfigBuilder &operator=(const ScanConfigBuilder &);
+  flatbuffers::Offset<ScanConfig> Finish() {
+    const auto end = fbb_.EndTable(start_, 2);
+    auto o = flatbuffers::Offset<ScanConfig>(end);
+    return o;
+  }
+};
+
+inline flatbuffers::Offset<ScanConfig> CreateScanConfig(
+    flatbuffers::FlatBufferBuilder &_fbb,
+    flatbuffers::Offset<ScanParams> scan_params = 0,
+    flatbuffers::Offset<ScanFilter> scan_filter = 0) {
+  ScanConfigBuilder builder_(_fbb);
+  builder_.add_scan_filter(scan_filter);
+  builder_.add_scan_params(scan_params);
+  return builder_.Finish();
+}
+
+struct ScanRecord FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table {
+  enum {
+    VT_TIME_SPENT_SCANNING_MS = 4,
+    VT_NUM_CHANNELS_SCANNED = 6,
+    VT_NUM_ENTRIES_AGGREGATED = 8
+  };
+  uint32_t time_spent_scanning_ms() const {
+    return GetField<uint32_t>(VT_TIME_SPENT_SCANNING_MS, 0);
+  }
+  uint32_t num_channels_scanned() const {
+    return GetField<uint32_t>(VT_NUM_CHANNELS_SCANNED, 0);
+  }
+  uint32_t num_entries_aggregated() const {
+    return GetField<uint32_t>(VT_NUM_ENTRIES_AGGREGATED, 0);
+  }
+  bool Verify(flatbuffers::Verifier &verifier) const {
+    return VerifyTableStart(verifier) &&
+           VerifyField<uint32_t>(verifier, VT_TIME_SPENT_SCANNING_MS) &&
+           VerifyField<uint32_t>(verifier, VT_NUM_CHANNELS_SCANNED) &&
+           VerifyField<uint32_t>(verifier, VT_NUM_ENTRIES_AGGREGATED) &&
+           verifier.EndTable();
+  }
+};
+
+struct ScanRecordBuilder {
+  flatbuffers::FlatBufferBuilder &fbb_;
+  flatbuffers::uoffset_t start_;
+  void add_time_spent_scanning_ms(uint32_t time_spent_scanning_ms) {
+    fbb_.AddElement<uint32_t>(ScanRecord::VT_TIME_SPENT_SCANNING_MS,
+                              time_spent_scanning_ms, 0);
+  }
+  void add_num_channels_scanned(uint32_t num_channels_scanned) {
+    fbb_.AddElement<uint32_t>(ScanRecord::VT_NUM_CHANNELS_SCANNED,
+                              num_channels_scanned, 0);
+  }
+  void add_num_entries_aggregated(uint32_t num_entries_aggregated) {
+    fbb_.AddElement<uint32_t>(ScanRecord::VT_NUM_ENTRIES_AGGREGATED,
+                              num_entries_aggregated, 0);
+  }
+  ScanRecordBuilder(flatbuffers::FlatBufferBuilder &_fbb) : fbb_(_fbb) {
+    start_ = fbb_.StartTable();
+  }
+  ScanRecordBuilder &operator=(const ScanRecordBuilder &);
+  flatbuffers::Offset<ScanRecord> Finish() {
+    const auto end = fbb_.EndTable(start_, 3);
+    auto o = flatbuffers::Offset<ScanRecord>(end);
+    return o;
+  }
+};
+
+inline flatbuffers::Offset<ScanRecord> CreateScanRecord(
+    flatbuffers::FlatBufferBuilder &_fbb, uint32_t time_spent_scanning_ms = 0,
+    uint32_t num_channels_scanned = 0, uint32_t num_entries_aggregated = 0) {
+  ScanRecordBuilder builder_(_fbb);
+  builder_.add_num_entries_aggregated(num_entries_aggregated);
+  builder_.add_num_channels_scanned(num_channels_scanned);
+  builder_.add_time_spent_scanning_ms(time_spent_scanning_ms);
+  return builder_.Finish();
+}
+
+struct RpcLogRecord FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table {
+  enum { VT_RECORD_TYPE = 4, VT_TIMESTAMP_CHRE_MS = 6 };
+  uint8_t record_type() const { return GetField<uint8_t>(VT_RECORD_TYPE, 0); }
+  uint32_t timestamp_chre_ms() const {
+    return GetField<uint32_t>(VT_TIMESTAMP_CHRE_MS, 0);
+  }
+  bool Verify(flatbuffers::Verifier &verifier) const {
+    return VerifyTableStart(verifier) &&
+           VerifyField<uint8_t>(verifier, VT_RECORD_TYPE) &&
+           VerifyField<uint32_t>(verifier, VT_TIMESTAMP_CHRE_MS) &&
+           verifier.EndTable();
+  }
+};
+
+struct RpcLogRecordBuilder {
+  flatbuffers::FlatBufferBuilder &fbb_;
+  flatbuffers::uoffset_t start_;
+  void add_record_type(uint8_t record_type) {
+    fbb_.AddElement<uint8_t>(RpcLogRecord::VT_RECORD_TYPE, record_type, 0);
+  }
+  void add_timestamp_chre_ms(uint32_t timestamp_chre_ms) {
+    fbb_.AddElement<uint32_t>(RpcLogRecord::VT_TIMESTAMP_CHRE_MS,
+                              timestamp_chre_ms, 0);
+  }
+  RpcLogRecordBuilder(flatbuffers::FlatBufferBuilder &_fbb) : fbb_(_fbb) {
+    start_ = fbb_.StartTable();
+  }
+  RpcLogRecordBuilder &operator=(const RpcLogRecordBuilder &);
+  flatbuffers::Offset<RpcLogRecord> Finish() {
+    const auto end = fbb_.EndTable(start_, 2);
+    auto o = flatbuffers::Offset<RpcLogRecord>(end);
+    return o;
+  }
+};
+
+inline flatbuffers::Offset<RpcLogRecord> CreateRpcLogRecord(
+    flatbuffers::FlatBufferBuilder &_fbb, uint8_t record_type = 0,
+    uint32_t timestamp_chre_ms = 0) {
+  RpcLogRecordBuilder builder_(_fbb);
+  builder_.add_timestamp_chre_ms(timestamp_chre_ms);
+  builder_.add_record_type(record_type);
+  return builder_.Finish();
+}
+
+struct ScanStats FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table {
+  enum {
+    VT_NUM_SCANS_REQUESTED_BY_NANOAPP = 4,
+    VT_NUM_SCANS_SERVICED_BY_HARDWARE = 6,
+    VT_NUM_SCANS_SERVICED_BY_CACHE = 8,
+    VT_UPDATED_AT_CHRE_MS = 10,
+    VT_SENT_AT_CHRE_MS = 12,
+    VT_LAST_SUBSCRIPTION_DURATION_MS = 14,
+    VT_CHANNEL_SCAN_COUNT = 16,
+    VT_SCAN_RECORDS = 18,
+    VT_RPC_LOG_RECORDS = 20
+  };
+  uint32_t num_scans_requested_by_nanoapp() const {
+    return GetField<uint32_t>(VT_NUM_SCANS_REQUESTED_BY_NANOAPP, 0);
+  }
+  uint32_t num_scans_serviced_by_hardware() const {
+    return GetField<uint32_t>(VT_NUM_SCANS_SERVICED_BY_HARDWARE, 0);
+  }
+  uint32_t num_scans_serviced_by_cache() const {
+    return GetField<uint32_t>(VT_NUM_SCANS_SERVICED_BY_CACHE, 0);
+  }
+  uint32_t updated_at_chre_ms() const {
+    return GetField<uint32_t>(VT_UPDATED_AT_CHRE_MS, 0);
+  }
+  uint32_t sent_at_chre_ms() const {
+    return GetField<uint32_t>(VT_SENT_AT_CHRE_MS, 0);
+  }
+  uint32_t last_subscription_duration_ms() const {
+    return GetField<uint32_t>(VT_LAST_SUBSCRIPTION_DURATION_MS, 0);
+  }
+  const flatbuffers::Vector<uint8_t> *channel_scan_count() const {
+    return GetPointer<const flatbuffers::Vector<uint8_t> *>(
+        VT_CHANNEL_SCAN_COUNT);
+  }
+  const flatbuffers::Vector<flatbuffers::Offset<ScanRecord>> *scan_records()
+      const {
+    return GetPointer<
+        const flatbuffers::Vector<flatbuffers::Offset<ScanRecord>> *>(
+        VT_SCAN_RECORDS);
+  }
+  const flatbuffers::Vector<flatbuffers::Offset<RpcLogRecord>>
+      *rpc_log_records() const {
+    return GetPointer<
+        const flatbuffers::Vector<flatbuffers::Offset<RpcLogRecord>> *>(
+        VT_RPC_LOG_RECORDS);
+  }
+  bool Verify(flatbuffers::Verifier &verifier) const {
+    return VerifyTableStart(verifier) &&
+           VerifyField<uint32_t>(verifier, VT_NUM_SCANS_REQUESTED_BY_NANOAPP) &&
+           VerifyField<uint32_t>(verifier, VT_NUM_SCANS_SERVICED_BY_HARDWARE) &&
+           VerifyField<uint32_t>(verifier, VT_NUM_SCANS_SERVICED_BY_CACHE) &&
+           VerifyField<uint32_t>(verifier, VT_UPDATED_AT_CHRE_MS) &&
+           VerifyField<uint32_t>(verifier, VT_SENT_AT_CHRE_MS) &&
+           VerifyField<uint32_t>(verifier, VT_LAST_SUBSCRIPTION_DURATION_MS) &&
+           VerifyField<flatbuffers::uoffset_t>(verifier,
+                                               VT_CHANNEL_SCAN_COUNT) &&
+           verifier.Verify(channel_scan_count()) &&
+           VerifyField<flatbuffers::uoffset_t>(verifier, VT_SCAN_RECORDS) &&
+           verifier.Verify(scan_records()) &&
+           verifier.VerifyVectorOfTables(scan_records()) &&
+           VerifyField<flatbuffers::uoffset_t>(verifier, VT_RPC_LOG_RECORDS) &&
+           verifier.Verify(rpc_log_records()) &&
+           verifier.VerifyVectorOfTables(rpc_log_records()) &&
+           verifier.EndTable();
+  }
+};
+
+struct ScanStatsBuilder {
+  flatbuffers::FlatBufferBuilder &fbb_;
+  flatbuffers::uoffset_t start_;
+  void add_num_scans_requested_by_nanoapp(
+      uint32_t num_scans_requested_by_nanoapp) {
+    fbb_.AddElement<uint32_t>(ScanStats::VT_NUM_SCANS_REQUESTED_BY_NANOAPP,
+                              num_scans_requested_by_nanoapp, 0);
+  }
+  void add_num_scans_serviced_by_hardware(
+      uint32_t num_scans_serviced_by_hardware) {
+    fbb_.AddElement<uint32_t>(ScanStats::VT_NUM_SCANS_SERVICED_BY_HARDWARE,
+                              num_scans_serviced_by_hardware, 0);
+  }
+  void add_num_scans_serviced_by_cache(uint32_t num_scans_serviced_by_cache) {
+    fbb_.AddElement<uint32_t>(ScanStats::VT_NUM_SCANS_SERVICED_BY_CACHE,
+                              num_scans_serviced_by_cache, 0);
+  }
+  void add_updated_at_chre_ms(uint32_t updated_at_chre_ms) {
+    fbb_.AddElement<uint32_t>(ScanStats::VT_UPDATED_AT_CHRE_MS,
+                              updated_at_chre_ms, 0);
+  }
+  void add_sent_at_chre_ms(uint32_t sent_at_chre_ms) {
+    fbb_.AddElement<uint32_t>(ScanStats::VT_SENT_AT_CHRE_MS, sent_at_chre_ms,
+                              0);
+  }
+  void add_last_subscription_duration_ms(
+      uint32_t last_subscription_duration_ms) {
+    fbb_.AddElement<uint32_t>(ScanStats::VT_LAST_SUBSCRIPTION_DURATION_MS,
+                              last_subscription_duration_ms, 0);
+  }
+  void add_channel_scan_count(
+      flatbuffers::Offset<flatbuffers::Vector<uint8_t>> channel_scan_count) {
+    fbb_.AddOffset(ScanStats::VT_CHANNEL_SCAN_COUNT, channel_scan_count);
+  }
+  void add_scan_records(
+      flatbuffers::Offset<flatbuffers::Vector<flatbuffers::Offset<ScanRecord>>>
+          scan_records) {
+    fbb_.AddOffset(ScanStats::VT_SCAN_RECORDS, scan_records);
+  }
+  void add_rpc_log_records(
+      flatbuffers::Offset<
+          flatbuffers::Vector<flatbuffers::Offset<RpcLogRecord>>>
+          rpc_log_records) {
+    fbb_.AddOffset(ScanStats::VT_RPC_LOG_RECORDS, rpc_log_records);
+  }
+  ScanStatsBuilder(flatbuffers::FlatBufferBuilder &_fbb) : fbb_(_fbb) {
+    start_ = fbb_.StartTable();
+  }
+  ScanStatsBuilder &operator=(const ScanStatsBuilder &);
+  flatbuffers::Offset<ScanStats> Finish() {
+    const auto end = fbb_.EndTable(start_, 9);
+    auto o = flatbuffers::Offset<ScanStats>(end);
+    return o;
+  }
+};
+
+inline flatbuffers::Offset<ScanStats> CreateScanStats(
+    flatbuffers::FlatBufferBuilder &_fbb,
+    uint32_t num_scans_requested_by_nanoapp = 0,
+    uint32_t num_scans_serviced_by_hardware = 0,
+    uint32_t num_scans_serviced_by_cache = 0, uint32_t updated_at_chre_ms = 0,
+    uint32_t sent_at_chre_ms = 0, uint32_t last_subscription_duration_ms = 0,
+    flatbuffers::Offset<flatbuffers::Vector<uint8_t>> channel_scan_count = 0,
+    flatbuffers::Offset<flatbuffers::Vector<flatbuffers::Offset<ScanRecord>>>
+        scan_records = 0,
+    flatbuffers::Offset<flatbuffers::Vector<flatbuffers::Offset<RpcLogRecord>>>
+        rpc_log_records = 0) {
+  ScanStatsBuilder builder_(_fbb);
+  builder_.add_rpc_log_records(rpc_log_records);
+  builder_.add_scan_records(scan_records);
+  builder_.add_channel_scan_count(channel_scan_count);
+  builder_.add_last_subscription_duration_ms(last_subscription_duration_ms);
+  builder_.add_sent_at_chre_ms(sent_at_chre_ms);
+  builder_.add_updated_at_chre_ms(updated_at_chre_ms);
+  builder_.add_num_scans_serviced_by_cache(num_scans_serviced_by_cache);
+  builder_.add_num_scans_serviced_by_hardware(num_scans_serviced_by_hardware);
+  builder_.add_num_scans_requested_by_nanoapp(num_scans_requested_by_nanoapp);
+  return builder_.Finish();
+}
+
+inline flatbuffers::Offset<ScanStats> CreateScanStatsDirect(
+    flatbuffers::FlatBufferBuilder &_fbb,
+    uint32_t num_scans_requested_by_nanoapp = 0,
+    uint32_t num_scans_serviced_by_hardware = 0,
+    uint32_t num_scans_serviced_by_cache = 0, uint32_t updated_at_chre_ms = 0,
+    uint32_t sent_at_chre_ms = 0, uint32_t last_subscription_duration_ms = 0,
+    const std::vector<uint8_t> *channel_scan_count = nullptr,
+    const std::vector<flatbuffers::Offset<ScanRecord>> *scan_records = nullptr,
+    const std::vector<flatbuffers::Offset<RpcLogRecord>> *rpc_log_records =
+        nullptr) {
+  return wifi_offload::fbs::CreateScanStats(
+      _fbb, num_scans_requested_by_nanoapp, num_scans_serviced_by_hardware,
+      num_scans_serviced_by_cache, updated_at_chre_ms, sent_at_chre_ms,
+      last_subscription_duration_ms,
+      channel_scan_count ? _fbb.CreateVector<uint8_t>(*channel_scan_count) : 0,
+      scan_records
+          ? _fbb.CreateVector<flatbuffers::Offset<ScanRecord>>(*scan_records)
+          : 0,
+      rpc_log_records
+          ? _fbb.CreateVector<flatbuffers::Offset<RpcLogRecord>>(
+                *rpc_log_records)
+          : 0);
+}
+
+}  // namespace fbs
+}  // namespace wifi_offload
+
+#endif  // FLATBUFFERS_GENERATED_FLATBUFFERSTYPES_WIFI_OFFLOAD_FBS_H_
diff --git a/apps/wifi_offload/include/chre/apps/wifi_offload/generate_code.sh b/apps/wifi_offload/include/chre/apps/wifi_offload/generate_code.sh
new file mode 100755
index 0000000..09ab1a7
--- /dev/null
+++ b/apps/wifi_offload/include/chre/apps/wifi_offload/generate_code.sh
@@ -0,0 +1,13 @@
+#!/bin/bash -e
+
+# Path to flatc compiler
+FLATC_PATH="$ANDROID_BUILD_TOP/external/flatbuffers/flatc"
+
+# Flat buffer schema files
+FLATC_SRCS="flatbuffers_types.fbs"
+
+# Flatc arguments
+FLATC_ARGS="--cpp --no-includes"
+
+# Generate c++ code
+$FLATC_PATH $FLATC_ARGS $FLATC_SRCS
diff --git a/apps/wifi_offload/include/chre/apps/wifi_offload/host_message_types.h b/apps/wifi_offload/include/chre/apps/wifi_offload/host_message_types.h
new file mode 100644
index 0000000..67ac25a
--- /dev/null
+++ b/apps/wifi_offload/include/chre/apps/wifi_offload/host_message_types.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef CHRE_WIFI_OFFLOAD_HOST_MESSAGE_TYPES_H_
+#define CHRE_WIFI_OFFLOAD_HOST_MESSAGE_TYPES_H_
+
+namespace wifi_offload {
+
+enum HostMessageType : uint32_t {
+  HOST_CMD_BASE = 0x00001000,
+
+  HOST_CMD_CONFIG_SCANS,
+  HOST_CMD_SUBSCRIBE_SCAN_RESULTS,
+  HOST_CMD_UNSUBSCRIBE_SCAN_RESULTS,
+  HOST_CMD_GET_SCAN_STATS,
+  HOST_CMD_RESET,
+
+  HOST_MSG_BASE = 0x0005000,
+
+  HOST_MSG_SCAN_RESULTS,
+  HOST_MSG_SCAN_STATS,
+  HOST_MSG_ERROR,
+};
+
+}  // namespace wifi_offload
+
+#endif  // CHRE_WIFI_OFFLOAD_HOST_MESSAGE_TYPES_H_
diff --git a/apps/wifi_offload/include/chre/apps/wifi_offload/preferred_network.h b/apps/wifi_offload/include/chre/apps/wifi_offload/preferred_network.h
new file mode 100644
index 0000000..86ab436
--- /dev/null
+++ b/apps/wifi_offload/include/chre/apps/wifi_offload/preferred_network.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef CHRE_WIFI_OFFLOAD_PREFERRED_NETWORK_H_
+#define CHRE_WIFI_OFFLOAD_PREFERRED_NETWORK_H_
+
+#include "chre/apps/wifi_offload/wifi_offload.h"
+
+#include "chre/apps/wifi_offload/flatbuffers_types_generated.h"
+#include "chre/apps/wifi_offload/ssid.h"
+
+namespace wifi_offload {
+
+enum SecurityMode : uint8_t {
+  UNKNOWN = 0,
+  OPEN = 0x1 << 0,
+  WEP = 0x1 << 1,
+  PSK = 0x1 << 2,
+  EAP = 0x1 << 3,
+  ALL_SECURITY_MODES_MASK = OPEN | WEP | PSK | EAP,
+};
+
+class PreferredNetwork {
+ public:
+  /* Corresponding flatbuffers-generated data-type used to serialize and
+   * deserialize instances of this class */
+  using FbsType = fbs::PreferredNetwork;
+
+  PreferredNetwork();
+
+  PreferredNetwork(PreferredNetwork &&other) = default;
+
+  ~PreferredNetwork() = default;
+
+  bool operator==(const PreferredNetwork &other) const;
+
+  flatbuffers::Offset<FbsType> Serialize(
+      flatbuffers::FlatBufferBuilder *builder) const;
+
+  bool Deserialize(const FbsType &fbs_network);
+
+  void Log() const;
+
+  Ssid ssid_;
+  /* SecurityMode flags that are associated with this SSID
+   * More than one security mode can be supported, see SecurityMode */
+  uint8_t security_modes_;
+};
+
+}  // namespace wifi_offload
+
+#endif  // CHRE_WIFI_OFFLOAD_PREFERRED_NETWORK_H_
diff --git a/apps/wifi_offload/include/chre/apps/wifi_offload/rpc_log_record.h b/apps/wifi_offload/include/chre/apps/wifi_offload/rpc_log_record.h
new file mode 100644
index 0000000..fb7b949
--- /dev/null
+++ b/apps/wifi_offload/include/chre/apps/wifi_offload/rpc_log_record.h
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef CHRE_WIFI_OFFLOAD_RPC_LOG_RECORD_H_
+#define CHRE_WIFI_OFFLOAD_RPC_LOG_RECORD_H_
+
+#include "chre/apps/wifi_offload/wifi_offload.h"
+
+#include "chre/apps/wifi_offload/flatbuffers_types_generated.h"
+
+namespace wifi_offload {
+
+class RpcLogRecord {
+ public:
+  /**
+   * Enumerates the type of log that is recorded
+   */
+  enum class RpcLogRecordType : uint8_t {
+    CMD_BASE = 0x00,
+    CMD_INIT,
+    CMD_CONFIG_SCANS,
+    CMD_SUBSCRIBE_SCAN_RESULTS,
+    CMD_UNSUBSCRIBE_SCAN_RESULTS,
+    CMD_GET_SCAN_STATS,
+    CMD_RESET,
+    CMD_LAST_ITEM,
+
+    EVENT_RECVD_BASE = 0x40,
+    EVENT_RECVD_SCAN_RESULT_ASYNC,
+    EVENT_RECVD_SCAN_RESULT,
+    EVENT_RECVD_LAST_ITEM,
+
+    EVENT_SENT_BASE = 0x80,
+    EVENT_SENT_SCAN_RESULT,
+    EVENT_SENT_ABORT,
+    EVENT_SENT_ERROR,
+    EVENT_SENT_LAST_ITEM,
+
+    REQ_BASE = 0xc0,
+    REQ_SCAN,
+    REQ_LAST_ITEM,
+  };
+
+  /* Corresponding flatbuffers-generated data-type used to serialize and
+   * deserialize instances of this class */
+  using FbsType = fbs::RpcLogRecord;
+
+  RpcLogRecord();
+  ~RpcLogRecord() = default;
+
+  bool operator==(const RpcLogRecord &other) const;
+
+  flatbuffers::Offset<RpcLogRecord::FbsType> Serialize(
+      flatbuffers::FlatBufferBuilder *builder) const;
+
+  bool Deserialize(const RpcLogRecord::FbsType &fbs_record);
+
+  RpcLogRecordType record_type_;
+  uint32_t timestamp_chre_ms_;  // See chreGetTime()
+};
+
+}  // namespace wifi_offload
+
+#endif  // CHRE_WIFI_OFFLOAD_RPC_LOG_RECORD_H_
diff --git a/apps/wifi_offload/include/chre/apps/wifi_offload/scan_config.h b/apps/wifi_offload/include/chre/apps/wifi_offload/scan_config.h
new file mode 100644
index 0000000..26c2084
--- /dev/null
+++ b/apps/wifi_offload/include/chre/apps/wifi_offload/scan_config.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef CHRE_WIFI_OFFLOAD_SCAN_CONFIG_H_
+#define CHRE_WIFI_OFFLOAD_SCAN_CONFIG_H_
+
+#include "chre/apps/wifi_offload/wifi_offload.h"
+
+#include "chre/apps/wifi_offload/flatbuffers_types_generated.h"
+#include "chre/apps/wifi_offload/scan_filter.h"
+#include "chre/apps/wifi_offload/scan_params.h"
+
+namespace wifi_offload {
+
+class ScanConfig {
+ public:
+  /* Corresponding flatbuffers-generated data-type used to serialize and
+   * deserialize instances of this class */
+  using FbsType = fbs::ScanConfig;
+
+  ScanConfig() = default;
+  ~ScanConfig() = default;
+
+  bool operator==(const ScanConfig &other) const;
+
+  flatbuffers::Offset<ScanConfig::FbsType> Serialize(
+      flatbuffers::FlatBufferBuilder *builder) const;
+
+  bool Deserialize(const ScanConfig::FbsType &fbs_config);
+
+  void Log() const;
+
+  ScanParams scan_params_;
+  ScanFilter scan_filter_;
+};
+
+}  // namespace wifi_offload
+
+#endif  // CHRE_WIFI_OFFLOAD_SCAN_CONFIG_H_
diff --git a/apps/wifi_offload/include/chre/apps/wifi_offload/scan_filter.h b/apps/wifi_offload/include/chre/apps/wifi_offload/scan_filter.h
new file mode 100644
index 0000000..c49051b
--- /dev/null
+++ b/apps/wifi_offload/include/chre/apps/wifi_offload/scan_filter.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef CHRE_WIFI_OFFLOAD_SCAN_FILTER_H_
+#define CHRE_WIFI_OFFLOAD_SCAN_FILTER_H_
+
+#include "chre/apps/wifi_offload/wifi_offload.h"
+
+#include "chre/apps/wifi_offload/flatbuffers_types_generated.h"
+#include "chre/apps/wifi_offload/preferred_network.h"
+
+namespace wifi_offload {
+
+/**
+ * Instruction on how to filter scan results before waking up the applications
+ * processor.
+ */
+class ScanFilter {
+ public:
+  /* Corresponding flatbuffers-generated data-type used to serialize and
+   * deserialize instances of this class */
+  using FbsType = fbs::ScanFilter;
+
+  ScanFilter();
+
+  ~ScanFilter() = default;
+
+  bool operator==(const ScanFilter &other) const;
+
+  flatbuffers::Offset<ScanFilter::FbsType> Serialize(
+      flatbuffers::FlatBufferBuilder *builder) const;
+
+  bool Deserialize(const ScanFilter::FbsType &fbs_filter);
+
+  void Log() const;
+
+  Vector<PreferredNetwork> networks_to_match_;  // empty means match all
+  int8_t min_rssi_threshold_dbm_;
+};
+
+}  // namespace wifi_offload
+
+#endif  // CHRE_WIFI_OFFLOAD_SCAN_FILTER_H_
diff --git a/apps/wifi_offload/include/chre/apps/wifi_offload/scan_params.h b/apps/wifi_offload/include/chre/apps/wifi_offload/scan_params.h
new file mode 100644
index 0000000..e8542e3
--- /dev/null
+++ b/apps/wifi_offload/include/chre/apps/wifi_offload/scan_params.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef CHRE_WIFI_OFFLOAD_SCAN_PARAMS_H_
+#define CHRE_WIFI_OFFLOAD_SCAN_PARAMS_H_
+
+#include "chre/apps/wifi_offload/wifi_offload.h"
+
+#include "chre/apps/wifi_offload/flatbuffers_types_generated.h"
+#include "chre/apps/wifi_offload/ssid.h"
+
+namespace wifi_offload {
+
+/**
+ * Parameters for performing offload scans
+ */
+class ScanParams {
+ public:
+  /* Corresponding flatbuffers-generated data-type used to serialize and
+   * deserialize instances of this class */
+  using FbsType = fbs::ScanParams;
+
+  ScanParams();
+
+  ~ScanParams() = default;
+
+  bool operator==(const ScanParams &other) const;
+
+  flatbuffers::Offset<ScanParams::FbsType> Serialize(
+      flatbuffers::FlatBufferBuilder *builder) const;
+
+  bool Deserialize(const ScanParams::FbsType &fbs_params);
+
+  void Log() const;
+
+  Vector<Ssid> ssids_to_scan_;
+  Vector<uint32_t> frequencies_to_scan_mhz_;
+  uint32_t disconnected_mode_scan_interval_ms_;  // 0 means disable
+};
+
+}  // namespace wifi_offload
+
+#endif  // CHRE_WIFI_OFFLOAD_SCAN_PARAMS_H_
diff --git a/apps/wifi_offload/include/chre/apps/wifi_offload/scan_record.h b/apps/wifi_offload/include/chre/apps/wifi_offload/scan_record.h
new file mode 100644
index 0000000..9e9a055
--- /dev/null
+++ b/apps/wifi_offload/include/chre/apps/wifi_offload/scan_record.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef CHRE_WIFI_OFFLOAD_SCAN_RECORD_H_
+#define CHRE_WIFI_OFFLOAD_SCAN_RECORD_H_
+
+#include "chre/apps/wifi_offload/wifi_offload.h"
+
+#include "chre/apps/wifi_offload/flatbuffers_types_generated.h"
+
+namespace wifi_offload {
+
+class ScanRecord {
+ public:
+  /* Corresponding flatbuffers-generated data-type used to serialize and
+   * deserialize instances of this class */
+  using FbsType = fbs::ScanRecord;
+
+  ScanRecord();
+  ~ScanRecord() = default;
+
+  bool operator==(const ScanRecord &other) const;
+
+  flatbuffers::Offset<ScanRecord::FbsType> Serialize(
+      flatbuffers::FlatBufferBuilder *builder) const;
+
+  bool Deserialize(const ScanRecord::FbsType &fbs_record);
+
+  uint32_t time_spent_scanning_ms_;
+  uint32_t num_channels_scanned_;
+  /* Number of ScanRecords aggregated into this record. Multiple ScanRecords
+   * may be combined together into a single ScanRecord by adding up values to
+   * save space if needed */
+  uint32_t num_entries_aggregated_;
+};
+
+}  // namespace wifi_offload
+
+#endif  // CHRE_WIFI_OFFLOAD_SCAN_RECORD_H_
diff --git a/apps/wifi_offload/include/chre/apps/wifi_offload/scan_result.h b/apps/wifi_offload/include/chre/apps/wifi_offload/scan_result.h
new file mode 100644
index 0000000..c1ffbc1
--- /dev/null
+++ b/apps/wifi_offload/include/chre/apps/wifi_offload/scan_result.h
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef CHRE_WIFI_OFFLOAD_SCAN_RESULT_H_
+#define CHRE_WIFI_OFFLOAD_SCAN_RESULT_H_
+
+// First to pickup the LOG_TAG
+#include "chre/apps/wifi_offload/wifi_offload.h"
+
+#include "chre/apps/wifi_offload/flatbuffers_types_generated.h"
+#include "chre/apps/wifi_offload/preferred_network.h"
+#include "chre/apps/wifi_offload/ssid.h"
+
+namespace wifi_offload {
+
+/**
+ * Scan Results returned by offload nanoapp to the offload HAL
+ */
+class ScanResult {
+ public:
+  /**
+   * This is a bit mask describing the capabilities of a BSS.
+   * See IEEE Std 802.11: 8.4.1.4
+   */
+  enum Capability : uint16_t {
+    UNKNOWN = 0,
+    ESS = 1 << 0,
+    IBSS = 1 << 1,
+    CF_POLLABLE = 1 << 2,
+    CF_POLL_REQ = 1 << 3,
+    PRIVACY = 1 << 4,
+    SHORT_PREAMBLE = 1 << 5,
+    PBCC = 1 << 6,
+    CHANNEL_AGILITY = 1 << 7,
+    SPECTURM_MGMT = 1 << 8,
+    QOS = 1 << 9,
+    SHORT_SLOT_TIME = 1 << 10,
+    APSD = 1 << 11,
+    RADIO_MEASUREMENT = 1 << 12,
+    DSSS_OFDM = 1 << 13,
+    DELAYED_BLOCK_ACK = 1 << 14,
+    IMMEDIATE_BLOCK_ACK = 1 << 15,
+    ALL_CAPABILITIES_MASK = 0xffff,
+  };
+
+  /* Corresponding flatbuffers-generated data-type used to serialize and
+   * deserialize instances of this class */
+  using FbsType = fbs::ScanResult;
+
+  static constexpr int kBssidSize = CHRE_WIFI_BSSID_LEN;
+
+  ScanResult();
+
+  ScanResult(const ScanResult &other);
+
+  ScanResult(ScanResult &&other) = default;
+
+  explicit ScanResult(const chreWifiScanResult &chre_scan_result);
+
+  ~ScanResult() = default;
+
+  bool operator==(const ScanResult &other) const;
+
+  flatbuffers::Offset<ScanResult::FbsType> Serialize(
+      flatbuffers::FlatBufferBuilder *builder) const;
+
+  bool Deserialize(const ScanResult::FbsType &fbs_result);
+
+  void Log() const;
+
+  Ssid ssid_;
+  uint8_t security_modes_;  // SecurityMode flags, see SecurityMode
+  uint8_t bssid_[kBssidSize];
+  uint16_t capability_;  // Can have multiple bits set, see Capability
+  uint32_t frequency_scanned_mhz_;
+  int8_t rssi_dbm_;  // Signal strength
+  uint64_t tsf_;     // TSF found in beacon/probe response
+
+ private:
+  void UpdateFromChreWifiScanResult(const chreWifiScanResult &chre_scan_result);
+};
+
+}  // namespace wifi_offload
+
+#endif  // CHRE_WIFI_OFFLOAD_SCAN_RESULT_H_
diff --git a/apps/wifi_offload/include/chre/apps/wifi_offload/scan_result_message.h b/apps/wifi_offload/include/chre/apps/wifi_offload/scan_result_message.h
new file mode 100644
index 0000000..33c64fc
--- /dev/null
+++ b/apps/wifi_offload/include/chre/apps/wifi_offload/scan_result_message.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef CHRE_WIFI_OFFLOAD_SCAN_RESULT_MESSAGE_H_
+#define CHRE_WIFI_OFFLOAD_SCAN_RESULT_MESSAGE_H_
+
+#include "chre/apps/wifi_offload/wifi_offload.h"
+
+#include "chre/apps/wifi_offload/scan_result.h"
+
+namespace wifi_offload {
+
+/**
+ * Container for vector of scan results that provides serialization methods
+ */
+class ScanResultMessage {
+ public:
+  /* Corresponding flatbuffers-generated data-type used to serialize and
+   * deserialize instances of this class */
+  using FbsType = fbs::ScanResultMessage;
+
+  ScanResultMessage() = default;
+  ~ScanResultMessage() = default;
+
+  void SetScanResults(const Vector<ScanResult> &results);
+  void GetScanResults(Vector<ScanResult> *results);
+
+  flatbuffers::Offset<ScanResultMessage::FbsType> Serialize(
+      flatbuffers::FlatBufferBuilder *builder) const;
+
+  bool Deserialize(const ScanResultMessage::FbsType &fbs_result_message);
+
+ private:
+  Vector<ScanResult> scan_results_;
+};
+
+}  // namespace wifi_offload
+
+#endif  // CHRE_WIFI_OFFLOAD_SCAN_RESULT_MESSAGE_H_
diff --git a/apps/wifi_offload/include/chre/apps/wifi_offload/scan_stats.h b/apps/wifi_offload/include/chre/apps/wifi_offload/scan_stats.h
new file mode 100644
index 0000000..9b61823
--- /dev/null
+++ b/apps/wifi_offload/include/chre/apps/wifi_offload/scan_stats.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef CHRE_WIFI_OFFLOAD_SCAN_STATS_H_
+#define CHRE_WIFI_OFFLOAD_SCAN_STATS_H_
+
+#include "chre/apps/wifi_offload/wifi_offload.h"
+
+#include "chre/apps/wifi_offload/channel_histogram.h"
+#include "chre/apps/wifi_offload/flatbuffers_types_generated.h"
+#include "chre/apps/wifi_offload/rpc_log_record.h"
+#include "chre/apps/wifi_offload/scan_record.h"
+
+namespace wifi_offload {
+
+/**
+ * Defines the scan statistics to be returned to the framework
+ */
+class ScanStats {
+ public:
+  /* Corresponding flatbuffers-generated data-type used to serialize and
+   * deserialize instances of this class */
+  using FbsType = fbs::ScanStats;
+
+  ScanStats();
+  ~ScanStats() = default;
+
+  bool operator==(const ScanStats &other) const;
+
+  flatbuffers::Offset<ScanStats::FbsType> Serialize(
+      flatbuffers::FlatBufferBuilder *builder) const;
+
+  bool Deserialize(const ScanStats::FbsType &fbs_stats);
+
+  uint32_t num_scans_requested_by_nanoapp_;
+  uint32_t num_scans_serviced_by_hardware_;
+  uint32_t num_scans_serviced_by_cache_;
+  uint32_t updated_at_chre_ms_;
+  uint32_t sent_at_chre_ms_;
+  /* The duration between when the framework subscribed for scan results to min
+   * of time when the framework unsubscribed vs. current time */
+  uint32_t last_subscription_duration_ms_;
+
+  ChannelHistogram channel_histogram_;
+
+  Vector<ScanRecord> scan_records_;
+  Vector<RpcLogRecord> rpc_log_records_;
+};
+
+}  // namespace wifi_offload
+
+#endif  // CHRE_WIFI_OFFLOAD_SCAN_STATS_H_
diff --git a/apps/wifi_offload/include/chre/apps/wifi_offload/ssid.h b/apps/wifi_offload/include/chre/apps/wifi_offload/ssid.h
new file mode 100644
index 0000000..cbfd461
--- /dev/null
+++ b/apps/wifi_offload/include/chre/apps/wifi_offload/ssid.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef CHRE_WIFI_OFFLOAD_SSID_H_
+#define CHRE_WIFI_OFFLOAD_SSID_H_
+
+#include "chre/apps/wifi_offload/wifi_offload.h"
+
+#include "chre/apps/wifi_offload/flatbuffers_types_generated.h"
+
+namespace wifi_offload {
+
+/* SSID of the Access Point, maximum 32 characters */
+class Ssid {
+ public:
+  /* Corresponding flatbuffers-generated data-type used to serialize and
+   * deserialize instances of this class */
+  using FbsType = fbs::Ssid;
+
+  static constexpr size_t kMaxSsidLen = CHRE_WIFI_SSID_MAX_LEN;  // = 32
+
+  Ssid() = default;
+
+  Ssid(const Ssid &other);
+
+  Ssid(Ssid &&other) = default;
+
+  ~Ssid() = default;
+
+  void SetData(const uint8_t *buff, size_t len);
+
+  bool operator==(const Ssid &other) const;
+
+  flatbuffers::Offset<Ssid::FbsType> Serialize(
+      flatbuffers::FlatBufferBuilder *builder) const;
+
+  bool Deserialize(const Ssid::FbsType &fbs_ssid);
+
+  void Log() const;
+
+  void ToChreWifiSsidListItem(chreWifiSsidListItem *chre_ssid) const;
+
+ private:
+  Vector<uint8_t> ssid_vec_;
+};
+
+}  // namespace wifi_offload
+
+#endif  // CHRE_WIFI_OFFLOAD_SSID_H_
diff --git a/apps/wifi_offload/include/chre/apps/wifi_offload/utility.h b/apps/wifi_offload/include/chre/apps/wifi_offload/utility.h
new file mode 100644
index 0000000..f48b075
--- /dev/null
+++ b/apps/wifi_offload/include/chre/apps/wifi_offload/utility.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef CHRE_WIFI_OFFLOAD_UTILITY_H_
+#define CHRE_WIFI_OFFLOAD_UTILITY_H_
+
+#include "chre/apps/wifi_offload/wifi_offload.h"
+
+#include "chre/apps/wifi_offload/error_codes.h"
+#include "chre/apps/wifi_offload/scan_config.h"
+#include "chre/apps/wifi_offload/scan_filter.h"
+#include "chre/apps/wifi_offload/scan_params.h"
+#include "chre/apps/wifi_offload/scan_result.h"
+#include "chre/apps/wifi_offload/ssid.h"
+
+namespace wifi_offload {
+namespace utility {
+
+/**
+ * Maps channel frequency to channel number based in IEEE 802.11.
+ *
+ * @param freq Frequency in MHz we want to map to a channel number
+ *
+ * @return Channel number for a given frequency, 0 for unknown frequencies
+ */
+int Ieee80211FrequencyToChannel(int freq);
+
+void LogSsid(const uint8_t *ssid, uint8_t ssid_len);
+
+void LogBssid(const uint8_t *bssid);
+
+void LogChreScanResult(const chreWifiScanResult &result);
+
+const char *GetErrorCodeName(ErrorCode error_code);
+
+}  // namespace utility
+}  // namespace wifi_offload
+
+#endif  // CHRE_WIFI_OFFLOAD_UTILITY_H_
diff --git a/apps/wifi_offload/include/chre/apps/wifi_offload/vector_serialization.h b/apps/wifi_offload/include/chre/apps/wifi_offload/vector_serialization.h
new file mode 100644
index 0000000..2f576b1
--- /dev/null
+++ b/apps/wifi_offload/include/chre/apps/wifi_offload/vector_serialization.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef CHRE_WIFI_OFFLOAD_WIFI_VECTOR_SERIALIZATION_H_
+#define CHRE_WIFI_OFFLOAD_WIFI_VECTOR_SERIALIZATION_H_
+
+#include "chre/apps/wifi_offload/wifi_offload.h"
+
+#include "chre/apps/wifi_offload/flatbuffers_types_generated.h"
+
+namespace wifi_offload {
+
+template <typename T>
+flatbuffers::Offset<
+    flatbuffers::Vector<flatbuffers::Offset<typename T::FbsType>>>
+SerializeVector(const Vector<T> &native_vector,
+                flatbuffers::FlatBufferBuilder *builder) {
+  wifi_offload::Vector<flatbuffers::Offset<typename T::FbsType>> offset_vector;
+  offset_vector.reserve(native_vector.size());
+  for (const auto &elem : native_vector) {
+    offset_vector.push_back(elem.Serialize(builder));
+  }
+  return builder->CreateVector(offset_vector);
+}
+
+template <typename T>
+bool DeserializeVector(
+    const flatbuffers::Vector<flatbuffers::Offset<typename T::FbsType>>
+        &flatbuffer_vector,
+    Vector<T> *native_vector) {
+  if (native_vector == nullptr) {
+    return false;
+  }
+
+  native_vector->clear();
+  native_vector->reserve(flatbuffer_vector.size());
+  for (const auto &flatbuffer_elem : flatbuffer_vector) {
+    T native_elem;
+    if (flatbuffer_elem && native_elem.Deserialize(*flatbuffer_elem)) {
+      native_vector->push_back(std::move(native_elem));
+    } else {
+      return false;
+    }
+  }
+  return true;
+}
+
+}  // namespace wifi_offload
+
+#endif  // CHRE_WIFI_OFFLOAD_WIFI_VECTOR_SERIALIZATION_H_
diff --git a/apps/wifi_offload/include/chre/apps/wifi_offload/wifi_offload.h b/apps/wifi_offload/include/chre/apps/wifi_offload/wifi_offload.h
new file mode 100644
index 0000000..fef0c8d
--- /dev/null
+++ b/apps/wifi_offload/include/chre/apps/wifi_offload/wifi_offload.h
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef CHRE_WIFI_OFFLOAD_WIFI_OFFLOAD_H_
+#define CHRE_WIFI_OFFLOAD_WIFI_OFFLOAD_H_
+
+#include <cinttypes>
+#include <chre/wifi.h>
+
+#ifdef BUILD_FOR_CHRE_WIFI_OFFLOAD
+#include <chre/event.h>
+#include <chre/re.h>
+#include <chre/version.h>
+
+#define LOG_TAG "[WifiOffload]"
+
+#include "chre/util/dynamic_vector.h"
+#include "chre/util/nanoapp/log.h"
+#include "chre/util/unique_ptr.h"
+
+#undef LOGE
+#undef LOGW
+#undef LOGI
+#undef LOGD
+// Define custom logging macros to support prefixing a LOG_TAG
+#define LOGE(fmt, ...) chreLog(CHRE_LOG_ERROR, LOG_TAG " " fmt, ##__VA_ARGS__)
+#define LOGW(fmt, ...) chreLog(CHRE_LOG_WARN, LOG_TAG " " fmt, ##__VA_ARGS__)
+#define LOGI(fmt, ...) chreLog(CHRE_LOG_INFO, LOG_TAG " " fmt, ##__VA_ARGS__)
+#define LOGD(fmt, ...) chreLog(CHRE_LOG_DEBUG, LOG_TAG " " fmt, ##__VA_ARGS__)
+
+namespace wifi_offload {
+template <typename T>
+using Vector = chre::DynamicVector<T>;
+}  // namespace wifi_offload
+
+#else  // BUILD_FOR_CHRE_WIFI_OFFLOAD
+#include <android/log.h>
+#include <vector>
+
+#ifndef LOG_TAG
+#define LOG_TAG "[Offload HAL]"
+#endif
+
+// Define these to logging functions that are available for offload HAL
+#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
+#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
+#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__)
+#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
+
+namespace wifi_offload {
+template <typename T>
+using Vector = std::vector<T>;
+}  // namespace wifi_offload
+
+#endif  // BUILD_FOR_CHRE_WIFI_OFFLOAD
+
+#endif  // CHRE_WIFI_OFFLOAD_WIFI_OFFLOAD_H_
diff --git a/apps/wifi_offload/preferred_network.cc b/apps/wifi_offload/preferred_network.cc
new file mode 100644
index 0000000..43a7a63
--- /dev/null
+++ b/apps/wifi_offload/preferred_network.cc
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "chre/apps/wifi_offload/preferred_network.h"
+
+namespace wifi_offload {
+
+PreferredNetwork::PreferredNetwork() : security_modes_(SecurityMode::UNKNOWN) {}
+
+bool PreferredNetwork::operator==(const PreferredNetwork &other) const {
+  if (this == &other) {
+    return true;
+  }
+  return ssid_ == other.ssid_ && security_modes_ == other.security_modes_;
+}
+
+flatbuffers::Offset<PreferredNetwork::FbsType> PreferredNetwork::Serialize(
+    flatbuffers::FlatBufferBuilder *builder) const {
+  return fbs::CreatePreferredNetwork(*builder, ssid_.Serialize(builder),
+                                     security_modes_);
+}
+
+bool PreferredNetwork::Deserialize(
+    const PreferredNetwork::FbsType &fbs_network) {
+  const auto &fbs_ssid = fbs_network.ssid();
+  if (fbs_ssid == nullptr || !ssid_.Deserialize(*fbs_ssid)) {
+    LOGE("Failed to deserialize PreferredNetwork. Null or incomplete members.");
+    return false;
+  }
+
+  security_modes_ = fbs_network.security_modes();
+  if (security_modes_ & ~SecurityMode::ALL_SECURITY_MODES_MASK) {
+    LOGE("Failed to deserialize PreferredNetwork. Invalid security mode.");
+    return false;
+  }
+
+  return true;
+}
+
+void PreferredNetwork::Log() const {
+  ssid_.Log();
+  LOGI("  security modes: 0x%" PRIx8, security_modes_);
+}
+
+}  // namespace wifi_offload
diff --git a/apps/wifi_offload/rpc_log_record.cc b/apps/wifi_offload/rpc_log_record.cc
new file mode 100644
index 0000000..79075ac
--- /dev/null
+++ b/apps/wifi_offload/rpc_log_record.cc
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "chre/apps/wifi_offload/rpc_log_record.h"
+
+namespace wifi_offload {
+
+RpcLogRecord::RpcLogRecord()
+    : record_type_(RpcLogRecordType::CMD_BASE), timestamp_chre_ms_(0) {}
+
+bool RpcLogRecord::operator==(const RpcLogRecord &other) const {
+  if (this == &other) {
+    return true;
+  }
+  return record_type_ == other.record_type_ &&
+         timestamp_chre_ms_ == other.timestamp_chre_ms_;
+}
+
+flatbuffers::Offset<RpcLogRecord::FbsType> RpcLogRecord::Serialize(
+    flatbuffers::FlatBufferBuilder *builder) const {
+  return fbs::CreateRpcLogRecord(*builder, static_cast<uint8_t>(record_type_),
+                                 timestamp_chre_ms_);
+}
+
+bool RpcLogRecord::Deserialize(const RpcLogRecord::FbsType &fbs_record) {
+  uint8_t rec = fbs_record.record_type();
+  if ((rec > static_cast<uint8_t>(RpcLogRecordType::CMD_BASE) &&
+       rec < static_cast<uint8_t>(RpcLogRecordType::CMD_LAST_ITEM)) ||
+      (rec > static_cast<uint8_t>(RpcLogRecordType::EVENT_RECVD_BASE) &&
+       rec < static_cast<uint8_t>(RpcLogRecordType::EVENT_RECVD_LAST_ITEM)) ||
+      (rec > static_cast<uint8_t>(RpcLogRecordType::EVENT_SENT_BASE) &&
+       rec < static_cast<uint8_t>(RpcLogRecordType::EVENT_SENT_LAST_ITEM)) ||
+      (rec > static_cast<uint8_t>(RpcLogRecordType::REQ_BASE) &&
+       rec < static_cast<uint8_t>(RpcLogRecordType::REQ_LAST_ITEM))) {
+    record_type_ = static_cast<RpcLogRecordType>(fbs_record.record_type());
+  } else {
+    LOGE("Failed to deserialize RpcLogRecord. Invalid record type %" PRIu8,
+         rec);
+    return false;
+  }
+
+  timestamp_chre_ms_ = fbs_record.timestamp_chre_ms();
+  return true;
+}
+
+}  // namespace wifi_offload
diff --git a/apps/wifi_offload/scan_config.cc b/apps/wifi_offload/scan_config.cc
new file mode 100644
index 0000000..c5621d7
--- /dev/null
+++ b/apps/wifi_offload/scan_config.cc
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "chre/apps/wifi_offload/scan_config.h"
+
+namespace wifi_offload {
+
+bool ScanConfig::operator==(const ScanConfig &other) const {
+  if (this == &other) {
+    return true;
+  }
+  return scan_params_ == other.scan_params_ &&
+         scan_filter_ == other.scan_filter_;
+}
+
+flatbuffers::Offset<ScanConfig::FbsType> ScanConfig::Serialize(
+    flatbuffers::FlatBufferBuilder *builder) const {
+  auto params_offset = scan_params_.Serialize(builder);
+  auto filter_offset = scan_filter_.Serialize(builder);
+  return fbs::CreateScanConfig(*builder, params_offset, filter_offset);
+}
+
+bool ScanConfig::Deserialize(const ScanConfig::FbsType &fbs_config) {
+  if (fbs_config.scan_params() == nullptr ||
+      fbs_config.scan_filter() == nullptr) {
+    LOGE("Failed to deserialize ScanConfig. Null or incomplete members.");
+    return false;
+  }
+
+  return scan_params_.Deserialize(*fbs_config.scan_params()) &&
+         scan_filter_.Deserialize(*fbs_config.scan_filter());
+}
+
+void ScanConfig::Log() const {
+  scan_params_.Log();
+  scan_filter_.Log();
+}
+
+}  // namespace wifi_offload
diff --git a/apps/wifi_offload/scan_filter.cc b/apps/wifi_offload/scan_filter.cc
new file mode 100644
index 0000000..cca04be
--- /dev/null
+++ b/apps/wifi_offload/scan_filter.cc
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "chre/apps/wifi_offload/scan_filter.h"
+#include "chre/apps/wifi_offload/vector_serialization.h"
+
+namespace wifi_offload {
+
+ScanFilter::ScanFilter() : min_rssi_threshold_dbm_(0) {}
+
+bool ScanFilter::operator==(const ScanFilter &other) const {
+  if (this == &other) {
+    return true;
+  }
+  return networks_to_match_ == other.networks_to_match_ &&
+         min_rssi_threshold_dbm_ == other.min_rssi_threshold_dbm_;
+}
+
+flatbuffers::Offset<ScanFilter::FbsType> ScanFilter::Serialize(
+    flatbuffers::FlatBufferBuilder *builder) const {
+  auto netList = SerializeVector(networks_to_match_, builder);
+  return fbs::CreateScanFilter(*builder, netList, min_rssi_threshold_dbm_);
+}
+
+bool ScanFilter::Deserialize(const ScanFilter::FbsType &fbs_filter) {
+  const auto &fbsNetList = fbs_filter.networks_to_match();
+  if (fbsNetList == nullptr) {
+    LOGE("Failed to deserialize ScanFilter. Null or incomplete members.");
+    return false;
+  }
+
+  if (!DeserializeVector<PreferredNetwork>(*fbsNetList, &networks_to_match_)) {
+    LOGE("Failed to deserialize ScanFilter. Null or incomplete members.");
+    return false;
+  }
+
+  min_rssi_threshold_dbm_ = fbs_filter.min_rssi_threshold_dbm();
+  return true;
+}
+
+void ScanFilter::Log() const {
+  LOGI("ScanFilter:");
+  LOGI("  min rssi threshold: %" PRId8 "dBm", min_rssi_threshold_dbm_);
+  LOGI("  number of networks to match: %zu", networks_to_match_.size());
+  for (auto &net : networks_to_match_) {
+    net.Log();
+  }
+}
+
+}  // namespace wifi_offload
diff --git a/apps/wifi_offload/scan_params.cc b/apps/wifi_offload/scan_params.cc
new file mode 100644
index 0000000..67e0318
--- /dev/null
+++ b/apps/wifi_offload/scan_params.cc
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "chre/apps/wifi_offload/scan_params.h"
+#include "chre/apps/wifi_offload/channel_histogram.h"
+#include "chre/apps/wifi_offload/vector_serialization.h"
+
+namespace wifi_offload {
+
+ScanParams::ScanParams() : disconnected_mode_scan_interval_ms_(0) {}
+
+bool ScanParams::operator==(const ScanParams &other) const {
+  if (this == &other) {
+    return true;
+  }
+  return ssids_to_scan_ == other.ssids_to_scan_ &&
+         frequencies_to_scan_mhz_ == other.frequencies_to_scan_mhz_ &&
+         disconnected_mode_scan_interval_ms_ ==
+             other.disconnected_mode_scan_interval_ms_;
+}
+
+flatbuffers::Offset<ScanParams::FbsType> ScanParams::Serialize(
+    flatbuffers::FlatBufferBuilder *builder) const {
+  auto ssid_vec = SerializeVector(ssids_to_scan_, builder);
+  auto freq_vec = builder->CreateVector(frequencies_to_scan_mhz_);
+  return fbs::CreateScanParams(*builder, ssid_vec, freq_vec,
+                               disconnected_mode_scan_interval_ms_);
+}
+
+bool ScanParams::Deserialize(const ScanParams::FbsType &fbs_params) {
+  const auto &ssid_vec = fbs_params.ssids_to_scan();
+  if (ssid_vec == nullptr ||
+      !DeserializeVector<Ssid>(*ssid_vec, &ssids_to_scan_)) {
+    LOGE("Failed to deserialize ScanParams. Null or incomplete members.");
+    return false;
+  }
+
+  const auto &freq_vec = fbs_params.frequencies_to_scan_mhz();
+  if (freq_vec == nullptr) {
+    LOGE("Failed to deserialize ScanParams. Null or incomplete members.");
+    return false;
+  }
+  frequencies_to_scan_mhz_.clear();
+  frequencies_to_scan_mhz_.reserve(freq_vec->size());
+  for (const auto &freq : *freq_vec) {
+    if (!ChannelHistogram::IsSupportedFrequency(freq)) {
+      LOGE("Failed to deserialize ScanParams. Invalid frequency to scan.");
+      return false;
+    }
+    frequencies_to_scan_mhz_.push_back(freq);
+  }
+  disconnected_mode_scan_interval_ms_ =
+      fbs_params.disconnected_mode_scan_interval_ms();
+
+  return true;
+}
+
+void ScanParams::Log() const {
+  LOGI("ScanParams:");
+  LOGI("  disconnected mode scan interval (ms): %" PRIu32,
+       disconnected_mode_scan_interval_ms_);
+  LOGI("  number of ssids to scan: %zu", ssids_to_scan_.size());
+  for (const auto &ssid : ssids_to_scan_) {
+    ssid.Log();
+  }
+  LOGI("  number of frequencies to scan: %zu", frequencies_to_scan_mhz_.size());
+  for (const auto &freq : frequencies_to_scan_mhz_) {
+    LOGI("  frequency: %" PRIu32, freq);
+  }
+}
+
+}  // namespace wifi_offload
diff --git a/apps/wifi_offload/scan_record.cc b/apps/wifi_offload/scan_record.cc
new file mode 100644
index 0000000..9bbc5db
--- /dev/null
+++ b/apps/wifi_offload/scan_record.cc
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "chre/apps/wifi_offload/scan_record.h"
+
+namespace wifi_offload {
+
+ScanRecord::ScanRecord()
+    : time_spent_scanning_ms_(0),
+      num_channels_scanned_(0),
+      num_entries_aggregated_(0) {}
+
+bool ScanRecord::operator==(const ScanRecord &other) const {
+  if (this == &other) {
+    return true;
+  }
+  return time_spent_scanning_ms_ == other.time_spent_scanning_ms_ &&
+         num_channels_scanned_ == other.num_channels_scanned_ &&
+         num_entries_aggregated_ == other.num_entries_aggregated_;
+}
+
+flatbuffers::Offset<ScanRecord::FbsType> ScanRecord::Serialize(
+    flatbuffers::FlatBufferBuilder *builder) const {
+  return fbs::CreateScanRecord(*builder, time_spent_scanning_ms_,
+                               num_channels_scanned_, num_entries_aggregated_);
+}
+
+bool ScanRecord::Deserialize(const ScanRecord::FbsType &fbs_record) {
+  time_spent_scanning_ms_ = fbs_record.time_spent_scanning_ms();
+  num_channels_scanned_ = fbs_record.num_channels_scanned();
+  num_entries_aggregated_ = fbs_record.num_entries_aggregated();
+  return true;
+}
+
+}  // namespace wifi_offload
diff --git a/apps/wifi_offload/scan_result.cc b/apps/wifi_offload/scan_result.cc
new file mode 100644
index 0000000..af3731d
--- /dev/null
+++ b/apps/wifi_offload/scan_result.cc
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "chre/apps/wifi_offload/scan_result.h"
+#include "chre/apps/wifi_offload/channel_histogram.h"
+#include "chre/apps/wifi_offload/utility.h"
+
+namespace wifi_offload {
+namespace {
+
+SecurityMode ConvertSecurityModeChreToOffload(int chre_security_mode) {
+  switch (chre_security_mode) {
+    case CHRE_WIFI_SECURITY_MODE_OPEN:
+      return SecurityMode::OPEN;
+    case CHRE_WIFI_SECURITY_MODE_WEP:
+      return SecurityMode::WEP;
+    case CHRE_WIFI_SECURITY_MODE_PSK:
+      return SecurityMode::PSK;
+    case CHRE_WIFI_SECURITY_MODE_EAP:
+      return SecurityMode::EAP;
+    default:
+      return SecurityMode::UNKNOWN;
+  }
+}
+
+}  // namespace
+
+ScanResult::ScanResult()
+    : security_modes_(SecurityMode::UNKNOWN),
+      capability_(Capability::UNKNOWN),
+      frequency_scanned_mhz_(0),
+      rssi_dbm_(-128),
+      tsf_(0) {
+  std::memset(bssid_, 0, sizeof(bssid_));
+}
+
+ScanResult::ScanResult(const ScanResult &other)
+    : ssid_(other.ssid_),
+      security_modes_(other.security_modes_),
+      capability_(other.capability_),
+      frequency_scanned_mhz_(other.frequency_scanned_mhz_),
+      rssi_dbm_(other.rssi_dbm_),
+      tsf_(other.tsf_) {
+  std::memcpy(bssid_, other.bssid_, sizeof(bssid_));
+}
+
+ScanResult::ScanResult(const chreWifiScanResult &chre_scan_result) {
+  UpdateFromChreWifiScanResult(chre_scan_result);
+}
+
+bool ScanResult::operator==(const ScanResult &other) const {
+  if (this == &other) {
+    return true;
+  }
+  return std::memcmp(bssid_, other.bssid_, sizeof(bssid_)) == 0 &&
+         ssid_ == other.ssid_ && security_modes_ == other.security_modes_ &&
+         capability_ == other.capability_ &&
+         frequency_scanned_mhz_ == other.frequency_scanned_mhz_ &&
+         rssi_dbm_ == other.rssi_dbm_ && tsf_ == other.tsf_;
+}
+
+flatbuffers::Offset<ScanResult::FbsType> ScanResult::Serialize(
+    flatbuffers::FlatBufferBuilder *builder) const {
+  auto ssid_offset = ssid_.Serialize(builder);
+  auto bssid_offset = builder->CreateVector(bssid_, kBssidSize);
+  return fbs::CreateScanResult(*builder, ssid_offset, security_modes_,
+                               bssid_offset, capability_,
+                               frequency_scanned_mhz_, rssi_dbm_, tsf_);
+}
+
+bool ScanResult::Deserialize(const ScanResult::FbsType &fbs_result) {
+  if (fbs_result.ssid() == nullptr || !ssid_.Deserialize(*fbs_result.ssid())) {
+    LOGE("Failed to deserialize ScanResult. Null or incomplete members.");
+    return false;
+  }
+
+  security_modes_ = fbs_result.security_modes();
+  if (security_modes_ & ~SecurityMode::ALL_SECURITY_MODES_MASK) {
+    LOGE("Failed to deserialize ScanResult. Invalid security mode.");
+    return false;
+  }
+
+  if (fbs_result.bssid() == nullptr ||
+      fbs_result.bssid()->size() != kBssidSize) {
+    LOGE("Failed to deserialize ScanResult. Null or incomplete members.");
+    return false;
+  }
+  for (uint8_t i = 0; i < kBssidSize; i++) {
+    bssid_[i] = fbs_result.bssid()->Get(i);
+  }
+
+  capability_ = fbs_result.capability();
+  if ((capability_ == Capability::UNKNOWN) ||
+      (capability_ & ~Capability::ALL_CAPABILITIES_MASK)) {
+    LOGE("Failed to deserialize ScanResult. Invalid network capability.");
+    return false;
+  }
+
+  frequency_scanned_mhz_ = fbs_result.frequency_scanned_mhz();
+  if (!ChannelHistogram::IsSupportedFrequency(frequency_scanned_mhz_)) {
+    LOGE("Failed to deserialize ScanResult. Invalid channel frequency.");
+    return false;
+  }
+
+  rssi_dbm_ = fbs_result.rssi_dbm();
+  if (rssi_dbm_ > 0) {
+    LOGE("Failed to deserialize ScanResult. Positive rssi value.");
+    return false;
+  }
+
+  tsf_ = fbs_result.tsf();
+  return true;
+}
+
+void ScanResult::Log() const {
+  LOGI("ScanResult:");
+  ssid_.Log();
+  LOGI("  security modes: 0x%" PRIx8, security_modes_);
+  utility::LogBssid(bssid_);
+  LOGI("  capability: 0x%" PRIx16, capability_);
+  LOGI("  scanned frequency: %" PRIu32, frequency_scanned_mhz_);
+  LOGI("  rssi: %" PRId8 "dBm", rssi_dbm_);
+  LOGI("  tsf: %" PRIu64, tsf_);
+}
+
+void ScanResult::UpdateFromChreWifiScanResult(
+    const chreWifiScanResult &chre_scan_result) {
+  ssid_.SetData(chre_scan_result.ssid, chre_scan_result.ssidLen);
+
+  security_modes_ = 0;
+  for (const auto chre_security_mode :
+       {CHRE_WIFI_SECURITY_MODE_OPEN, CHRE_WIFI_SECURITY_MODE_WEP,
+        CHRE_WIFI_SECURITY_MODE_PSK, CHRE_WIFI_SECURITY_MODE_EAP}) {
+    if (chre_scan_result.securityMode & chre_security_mode) {
+      security_modes_ |= ConvertSecurityModeChreToOffload(chre_security_mode);
+    }
+  }
+
+  std::memcpy(bssid_, chre_scan_result.bssid, CHRE_WIFI_BSSID_LEN);
+  // TODO: make sure capability definition between two versions is the same
+  // (802.11:7.3.1.4 vs. 802.11:8.4.1.4)
+  capability_ = chre_scan_result.capabilityInfo;
+  if (chre_scan_result.channelWidth == CHRE_WIFI_CHANNEL_WIDTH_20_MHZ) {
+    frequency_scanned_mhz_ = chre_scan_result.primaryChannel;
+  } else {
+    // TODO: (b/62870147) Support other possible channel widths
+    LOGW("Scan result channel width not supported %" PRIu8,
+         chre_scan_result.channelWidth);
+  }
+
+  rssi_dbm_ = chre_scan_result.rssi;
+  tsf_ = 0;  // tsf value not available
+}
+
+}  // namespace wifi_offload
diff --git a/apps/wifi_offload/scan_result_message.cc b/apps/wifi_offload/scan_result_message.cc
new file mode 100644
index 0000000..e82ff0b
--- /dev/null
+++ b/apps/wifi_offload/scan_result_message.cc
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "chre/apps/wifi_offload/scan_result_message.h"
+#include "chre/apps/wifi_offload/vector_serialization.h"
+
+namespace wifi_offload {
+
+void ScanResultMessage::SetScanResults(const Vector<ScanResult> &results) {
+  scan_results_.clear();
+  scan_results_.reserve(results.size());
+  for (const auto &result : results) {
+    scan_results_.emplace_back(result);
+  }
+}
+
+void ScanResultMessage::GetScanResults(Vector<ScanResult> *results) {
+  if (results == nullptr) {
+    LOGE("ScanResultsMessage output pointer is null in GetScanResults.");
+    return;
+  }
+
+  results->clear();
+  results->reserve(scan_results_.size());
+  for (const auto &result : scan_results_) {
+    results->emplace_back(result);
+  }
+}
+
+flatbuffers::Offset<ScanResultMessage::FbsType> ScanResultMessage::Serialize(
+    flatbuffers::FlatBufferBuilder *builder) const {
+  auto results = SerializeVector(scan_results_, builder);
+  return fbs::CreateScanResultMessage(*builder, results);
+}
+
+bool ScanResultMessage::Deserialize(
+    const ScanResultMessage::FbsType &fbs_result_message) {
+  const auto &fbs_results = fbs_result_message.scan_results();
+  if (fbs_results == nullptr || fbs_results->size() == 0) {
+    LOGE(
+        "Failed to deserialize ScanResultsMessage. Null or incomplete "
+        "members.");
+    return false;
+  }
+
+  if (!DeserializeVector<ScanResult>(*fbs_results, &scan_results_)) {
+    LOGE(
+        "Failed to deserialize ScanResultMessage. Null or incomplete members.");
+    return false;
+  }
+
+  return true;
+}
+
+}  // namespace wifi_offload
diff --git a/apps/wifi_offload/scan_stats.cc b/apps/wifi_offload/scan_stats.cc
new file mode 100644
index 0000000..eb2aa0b
--- /dev/null
+++ b/apps/wifi_offload/scan_stats.cc
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "chre/apps/wifi_offload/scan_stats.h"
+#include "chre/apps/wifi_offload/vector_serialization.h"
+
+namespace wifi_offload {
+
+ScanStats::ScanStats()
+    : num_scans_requested_by_nanoapp_(0),
+      num_scans_serviced_by_hardware_(0),
+      num_scans_serviced_by_cache_(0),
+      updated_at_chre_ms_(0),
+      sent_at_chre_ms_(0),
+      last_subscription_duration_ms_(0) {}
+
+bool ScanStats::operator==(const ScanStats &other) const {
+  if (this == &other) {
+    return true;
+  }
+  return num_scans_requested_by_nanoapp_ ==
+             other.num_scans_requested_by_nanoapp_ &&
+         num_scans_serviced_by_hardware_ ==
+             other.num_scans_serviced_by_hardware_ &&
+         num_scans_serviced_by_cache_ == other.num_scans_serviced_by_cache_ &&
+         updated_at_chre_ms_ == other.updated_at_chre_ms_ &&
+         sent_at_chre_ms_ == other.sent_at_chre_ms_ &&
+         last_subscription_duration_ms_ ==
+             other.last_subscription_duration_ms_ &&
+         channel_histogram_ == other.channel_histogram_ &&
+         scan_records_ == other.scan_records_ &&
+         rpc_log_records_ == other.rpc_log_records_;
+}
+
+flatbuffers::Offset<ScanStats::FbsType> ScanStats::Serialize(
+    flatbuffers::FlatBufferBuilder *builder) const {
+  auto histo = channel_histogram_.Serialize(builder);
+  auto scan_recs = SerializeVector(scan_records_, builder);
+  auto log_recs = SerializeVector(rpc_log_records_, builder);
+  return fbs::CreateScanStats(*builder, num_scans_requested_by_nanoapp_,
+                              num_scans_serviced_by_hardware_,
+                              num_scans_serviced_by_cache_, updated_at_chre_ms_,
+                              sent_at_chre_ms_, last_subscription_duration_ms_,
+                              histo, scan_recs, log_recs);
+}
+
+bool ScanStats::Deserialize(const ScanStats::FbsType &fbs_stats) {
+  const auto &histo = fbs_stats.channel_scan_count();
+  if (histo == nullptr || !channel_histogram_.Deserialize(*histo)) {
+    LOGE("Failed to deserialize ScanStats. Null or incomplete members.");
+    return false;
+  }
+
+  const auto &scan_recs = fbs_stats.scan_records();
+  if (scan_recs == nullptr ||
+      !DeserializeVector<ScanRecord>(*scan_recs, &scan_records_)) {
+    LOGE("Failed to deserialize ScanStats. Null or incomplete members.");
+    return false;
+  }
+
+  const auto &log_recs = fbs_stats.rpc_log_records();
+  if (log_recs == nullptr ||
+      !DeserializeVector<RpcLogRecord>(*log_recs, &rpc_log_records_)) {
+    LOGE("Failed to deserialize ScanStats. Null or incomplete members.");
+    return false;
+  }
+
+  num_scans_requested_by_nanoapp_ = fbs_stats.num_scans_requested_by_nanoapp();
+  num_scans_serviced_by_hardware_ = fbs_stats.num_scans_serviced_by_hardware();
+  num_scans_serviced_by_cache_ = fbs_stats.num_scans_serviced_by_cache();
+  updated_at_chre_ms_ = fbs_stats.updated_at_chre_ms();
+  sent_at_chre_ms_ = fbs_stats.sent_at_chre_ms();
+  last_subscription_duration_ms_ = fbs_stats.last_subscription_duration_ms();
+  return true;
+}
+
+}  // namespace wifi_offload
diff --git a/apps/wifi_offload/ssid.cc b/apps/wifi_offload/ssid.cc
new file mode 100644
index 0000000..17dfc65
--- /dev/null
+++ b/apps/wifi_offload/ssid.cc
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "chre/apps/wifi_offload/ssid.h"
+#include "chre/apps/wifi_offload/utility.h"
+
+namespace wifi_offload {
+
+Ssid::Ssid(const Ssid &other) {
+  SetData(other.ssid_vec_.data(), other.ssid_vec_.size());
+}
+
+void Ssid::SetData(const uint8_t *buff, size_t len) {
+  if (len > kMaxSsidLen) {
+    LOGE("Ssid buffer len %zu larger than max ssid len %zu. Truncating.", len,
+         kMaxSsidLen);
+    len = kMaxSsidLen;
+  }
+
+  ssid_vec_.clear();
+  ssid_vec_.reserve(len);
+  for (size_t i = 0; i < len; i++) {
+    ssid_vec_.push_back(buff[i]);
+  }
+}
+
+bool Ssid::operator==(const Ssid &other) const {
+  return ssid_vec_ == other.ssid_vec_;
+}
+
+flatbuffers::Offset<Ssid::FbsType> Ssid::Serialize(
+    flatbuffers::FlatBufferBuilder *builder) const {
+  return fbs::CreateSsid(*builder, builder->CreateVector(ssid_vec_));
+}
+
+bool Ssid::Deserialize(const Ssid::FbsType &fbs_ssid) {
+  const auto &ssid_vec = fbs_ssid.ssid();
+  if (ssid_vec == nullptr) {
+    LOGE("Failed to deserialize Ssid. Null or incomplete members.");
+    return false;
+  }
+
+  if (ssid_vec->size() > kMaxSsidLen) {
+    LOGE("Failed to deserialize Ssid. Ssid size is larger than max len.");
+    return false;
+  }
+
+  SetData(ssid_vec->data(), ssid_vec->size());
+  return true;
+}
+
+void Ssid::Log() const {
+  utility::LogSsid(ssid_vec_.data(), static_cast<uint8_t>(ssid_vec_.size()));
+}
+
+void Ssid::ToChreWifiSsidListItem(chreWifiSsidListItem *chre_ssid) const {
+  if (chre_ssid == nullptr) {
+    LOGW("Failed to convert to chreWifiSsidListItem. Output pointer is null");
+    return;
+  }
+
+  std::memcpy(chre_ssid->ssid, ssid_vec_.data(), ssid_vec_.size());
+  chre_ssid->ssidLen = static_cast<uint8_t>(ssid_vec_.size());
+}
+
+}  // namespace wifi_offload
diff --git a/apps/wifi_offload/test/channelhistogram_test.cc b/apps/wifi_offload/test/channelhistogram_test.cc
new file mode 100644
index 0000000..9ec5e94
--- /dev/null
+++ b/apps/wifi_offload/test/channelhistogram_test.cc
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "gtest/gtest.h"
+
+#include "chre/apps/wifi_offload/channel_histogram.h"
+#include "include/random_generator.h"
+#include "include/utility.h"
+
+using wifi_offload_test::kNumFrequencies_Test;
+using wifi_offload_test::kAllFrequencies_Test;
+using wifi_offload_test::kAllChannels_Test;
+
+/**
+ * This file includes all the unit tests for ChannelHistogram class, except ==
+ * operator and serialize/deserialize functions which have already been covered
+ * in offloadtypes_test.cc.
+ */
+
+class ChannelHistogramTest : public testing::Test {
+ public:
+  wifi_offload_test::RandomGenerator random_gen_;
+  wifi_offload::ChannelHistogram channel_histo_;
+};
+
+TEST_F(ChannelHistogramTest, UnsupportedFrequencyAndchannel_numbersAreHandled) {
+  // some unsupported frequencies
+  channel_histo_.IncrementScanCountForFrequency(2000);
+  channel_histo_.IncrementScanCountForFrequency(42000);
+
+  // verify no count have been increased
+  for (size_t i = 0; i < kNumFrequencies_Test; i++) {
+    ASSERT_EQ(0, channel_histo_.GetChannelScanCount(kAllChannels_Test[i]));
+  }
+
+  // some unsupported channel numbers
+  EXPECT_EQ(0, channel_histo_.GetChannelScanCount(0));
+  EXPECT_EQ(0, channel_histo_.GetChannelScanCount(15));
+  EXPECT_EQ(0, channel_histo_.GetChannelScanCount(200));
+}
+
+TEST_F(ChannelHistogramTest, FrequenciesMapCorrectlyTochannel_numbers) {
+  for (size_t i = 0; i < kNumFrequencies_Test; i++) {
+    wifi_offload::ChannelHistogram histo;
+    histo.IncrementScanCountForFrequency(kAllFrequencies_Test[i]);
+
+    // verify only the increase channel is non-zero
+    for (size_t j = 0; j < kNumFrequencies_Test; j++) {
+      EXPECT_EQ(kAllChannels_Test[i] == kAllChannels_Test[j] ? 255 : 0,
+                histo.GetChannelScanCount(kAllChannels_Test[j]));
+    }
+  }
+}
+
+TEST_F(ChannelHistogramTest, IncreaseFrequencyScanCountAndGetChannelScanCount) {
+  uint32_t increase = 1;
+  for (size_t i = 0; i < 12; i++) {
+    increase *= 2;
+    channel_histo_.IncrementScanCountForFrequencyForTest(
+        kAllFrequencies_Test[i], increase);
+  }
+
+  uint32_t expected = 1;
+  for (size_t i = 0; i < 12; i++) {
+    uint8_t scaled_value =
+        channel_histo_.GetChannelScanCount(kAllChannels_Test[i]);
+
+    expected *= 2;
+    EXPECT_EQ(expected * 254 / increase + 1, scaled_value);
+  }
+
+  for (size_t i = 12; i < 14; i++) {
+    EXPECT_EQ(0, channel_histo_.GetChannelScanCount(kAllChannels_Test[i]));
+  }
+}
+
+TEST_F(ChannelHistogramTest, SetScanCountToMaxUInt32) {
+  // add some nice scan counts
+  uint32_t increase = 1;
+  for (size_t i = 0; i < 12; i++) {
+    increase *= 2;
+    channel_histo_.IncrementScanCountForFrequencyForTest(
+        kAllFrequencies_Test[i], increase);
+  }
+  /* set the next scan count to be 2 ^ 32 - 1, this will force all other counts
+   * to get mapped to 1 */
+  channel_histo_.IncrementScanCountForFrequencyForTest(kAllFrequencies_Test[12],
+                                                       0xffffffff);
+
+  for (size_t i = 0; i < 12; i++) {
+    EXPECT_EQ(1, channel_histo_.GetChannelScanCount(kAllChannels_Test[i]));
+  }
+  EXPECT_EQ(255, channel_histo_.GetChannelScanCount(kAllChannels_Test[12]));
+}
diff --git a/apps/wifi_offload/test/chrescanparamssafe_test.cc b/apps/wifi_offload/test/chrescanparamssafe_test.cc
new file mode 100644
index 0000000..ac5c89b
--- /dev/null
+++ b/apps/wifi_offload/test/chrescanparamssafe_test.cc
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "gtest/gtest.h"
+
+#include "chre/apps/wifi_offload/chre_scan_params_safe.h"
+#include "include/utility.h"
+
+class ChreScanParamsSafeTest : public testing::Test {
+ public:
+  wifi_offload_test::RandomGenerator random_gen_;
+  wifi_offload::ScanParams nanoapp_scan_params_;
+
+  void ConstructChreScanParamsSafeAndCompareWithOrigScanParams() {
+    wifi_offload::ChreScanParamsSafe chre_scan_params_safe(
+        nanoapp_scan_params_);
+    const chreWifiScanParams *chre_scan_params =
+        chre_scan_params_safe.GetChreWifiScanParams();
+
+    EXPECT_EQ(CHRE_WIFI_SCAN_TYPE_ACTIVE_PLUS_PASSIVE_DFS,
+              chre_scan_params->scanType);
+    EXPECT_EQ(0, chre_scan_params->maxScanAgeMs);
+
+    ASSERT_EQ(nanoapp_scan_params_.ssids_to_scan_.size(),
+              chre_scan_params->ssidListLen);
+    for (size_t i = 0; i < chre_scan_params->ssidListLen; i++) {
+      chreWifiSsidListItem ssid_item;
+      nanoapp_scan_params_.ssids_to_scan_[i].ToChreWifiSsidListItem(&ssid_item);
+      ASSERT_EQ(ssid_item.ssidLen, chre_scan_params->ssidList[i].ssidLen);
+      EXPECT_EQ(0,
+                std::memcmp(ssid_item.ssid, chre_scan_params->ssidList[i].ssid,
+                            chre_scan_params->ssidList[i].ssidLen));
+    }
+    ASSERT_EQ(nanoapp_scan_params_.frequencies_to_scan_mhz_.size(),
+              chre_scan_params->frequencyListLen);
+    EXPECT_EQ(
+        0, std::memcmp(nanoapp_scan_params_.frequencies_to_scan_mhz_.data(),
+                       chre_scan_params->frequencyList,
+                       chre_scan_params->frequencyListLen * sizeof(uint32_t)));
+  }
+};
+
+TEST_F(ChreScanParamsSafeTest,
+       ConstructsChreScanParamsSafeAndComparesWithOriginalScanParams) {
+  init(nanoapp_scan_params_.frequencies_to_scan_mhz_, random_gen_,
+       CHRE_WIFI_FREQUENCY_LIST_MAX_LEN - 5);
+  init(nanoapp_scan_params_.ssids_to_scan_, random_gen_,
+       CHRE_WIFI_SSID_LIST_MAX_LEN - 2);
+  ConstructChreScanParamsSafeAndCompareWithOrigScanParams();
+}
+
+TEST_F(ChreScanParamsSafeTest, ConstructsChreScanParamsSafeWithEmptyFreqList) {
+  init(nanoapp_scan_params_.ssids_to_scan_, random_gen_,
+       CHRE_WIFI_SSID_LIST_MAX_LEN - 2);
+  ConstructChreScanParamsSafeAndCompareWithOrigScanParams();
+}
+
+TEST_F(ChreScanParamsSafeTest, ConstructsChreScanParamsSafeWithEmptySsidList) {
+  init(nanoapp_scan_params_.frequencies_to_scan_mhz_, random_gen_,
+       CHRE_WIFI_FREQUENCY_LIST_MAX_LEN - 5);
+  ConstructChreScanParamsSafeAndCompareWithOrigScanParams();
+}
+
+TEST_F(ChreScanParamsSafeTest, ChreScanParamsSafeTruncatesLongLists) {
+  // initialize frequency and ssid lists to exceed limit size
+  init(nanoapp_scan_params_.frequencies_to_scan_mhz_, random_gen_,
+       CHRE_WIFI_FREQUENCY_LIST_MAX_LEN + 5);
+  init(nanoapp_scan_params_.ssids_to_scan_, random_gen_,
+       CHRE_WIFI_SSID_LIST_MAX_LEN + 2);
+
+  wifi_offload::ChreScanParamsSafe chre_scan_params_safe(nanoapp_scan_params_);
+  const chreWifiScanParams *chre_scan_params =
+      chre_scan_params_safe.GetChreWifiScanParams();
+
+  EXPECT_EQ(CHRE_WIFI_SSID_LIST_MAX_LEN, chre_scan_params->ssidListLen);
+  EXPECT_EQ(CHRE_WIFI_FREQUENCY_LIST_MAX_LEN,
+            chre_scan_params->frequencyListLen);
+}
diff --git a/apps/wifi_offload/test/flatbuffersserialization_test.cc b/apps/wifi_offload/test/flatbuffersserialization_test.cc
new file mode 100644
index 0000000..7e061ca
--- /dev/null
+++ b/apps/wifi_offload/test/flatbuffersserialization_test.cc
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <cstring>
+#include "gtest/gtest.h"
+
+#include "chre/apps/wifi_offload/flatbuffers_serialization.h"
+
+#include "include/utility.h"
+
+using wifi_offload::fbs::Serialize;
+using wifi_offload::fbs::Deserialize;
+
+template <typename TestType>
+class FlatbuffersSerializationTest : public testing::Test {
+ public:
+  // RandomGenerator used to initialize data-types with random values
+  wifi_offload_test::RandomGenerator random_gen_;
+
+  static const size_t kBufferLen = CHRE_MESSAGE_TO_HOST_MAX_SIZE;
+  uint8_t buffer[kBufferLen];
+};
+
+typedef testing::Types<wifi_offload::ScanStats, wifi_offload::ScanConfig,
+                       wifi_offload::Vector<wifi_offload::ScanResult>>
+    Implementations;
+
+TYPED_TEST_CASE(FlatbuffersSerializationTest, Implementations);
+
+TYPED_TEST(FlatbuffersSerializationTest,
+           SerializationWithNullBufferReturnsRequiredBufferSize) {
+  TypeParam test_obj;
+  init(test_obj, this->random_gen_);
+
+  size_t required_buff_size = Serialize(test_obj, nullptr, 0);
+  EXPECT_NE(0, required_buff_size);
+
+  size_t serialized_size = Serialize(test_obj, this->buffer, this->kBufferLen);
+  ASSERT_NE(0, serialized_size);
+
+  EXPECT_EQ(serialized_size, required_buff_size);
+}
+
+TYPED_TEST(FlatbuffersSerializationTest,
+           SerializationThenDeserializationCreatesEqualValue) {
+  TypeParam test_obj;
+  init(test_obj, this->random_gen_);
+
+  size_t serialized_size = Serialize(test_obj, this->buffer, this->kBufferLen);
+  ASSERT_NE(0, serialized_size);
+
+  TypeParam deserialized_obj;
+  ASSERT_TRUE(Deserialize(this->buffer, serialized_size, &deserialized_obj));
+  EXPECT_EQ(test_obj, deserialized_obj);
+}
+
+TYPED_TEST(FlatbuffersSerializationTest, NegativeTestsForSerialization) {
+  TypeParam test_obj;
+  init(test_obj, this->random_gen_);
+
+  EXPECT_EQ(0, Serialize(test_obj, this->buffer, 0));   // zero buffer size
+  EXPECT_EQ(0, Serialize(test_obj, this->buffer, 10));  // buffer too small
+}
+
+TYPED_TEST(FlatbuffersSerializationTest, NegativeTestsForDeserialization) {
+  TypeParam test_obj;
+  init(test_obj, this->random_gen_);
+
+  // The first 4 bytes in the buffer represent the position of the root
+  // table, so corrupting it should force the deserialization to fail.
+  constexpr size_t kRootTableOffsetSize = 4;
+  size_t serialized_size = Serialize(test_obj, this->buffer, this->kBufferLen);
+  ASSERT_GE(serialized_size, kRootTableOffsetSize);
+
+  TypeParam new_obj;
+  EXPECT_FALSE(Deserialize(nullptr, serialized_size, &new_obj));
+  EXPECT_FALSE(Deserialize(this->buffer, 0, &new_obj));
+
+  // Corrupt the root table's offset
+  std::memset(this->buffer, 0xff, kRootTableOffsetSize);
+  EXPECT_FALSE(Deserialize(this->buffer, serialized_size, &new_obj));
+}
diff --git a/apps/wifi_offload/test/include/random_generator.h b/apps/wifi_offload/test/include/random_generator.h
new file mode 100644
index 0000000..8ecbcf9
--- /dev/null
+++ b/apps/wifi_offload/test/include/random_generator.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef CHRE_WIFI_OFFLOAD_TEST_RANDOM_GENERATOR_H_
+#define CHRE_WIFI_OFFLOAD_TEST_RANDOM_GENERATOR_H_
+
+#include <cinttypes>
+#include <random>
+
+namespace wifi_offload_test {
+
+/**
+ * @class RandomGenerator A class to generate random values for any uint type:
+ *        (uint8_t, ..., uint64_t). Also supports resetting to its initial
+ *        state to be able to reproduce the same random sequence.
+ */
+class RandomGenerator {
+ public:
+  /**
+   * Default constructs a RandomGenerator object
+   */
+  RandomGenerator();
+
+  /**
+   * Resets the object to its initial state. Useful if we want to reproduce the
+   * exact same sequenct again.
+   */
+  void Reset();
+
+  /**
+   * Generates a random number of type IntType
+   *
+   * @return A random number of type IntType
+   */
+  template <typename IntType>
+  IntType get() {
+    return static_cast<IntType>(uniform_distribution_(random_engine_));
+  }
+
+ private:
+  /* The initial seed kept to use again when need to reset  */
+  std::random_device::result_type initial_seed_;
+
+  /* Standard mersenne_twister_engine */
+  std::mt19937 random_engine_;
+
+  /**
+   * Use uniform_distribution_ to transform the random unsigned int generated by
+   * random_engine_ into an uint64_t
+   */
+  std::uniform_int_distribution<uint64_t> uniform_distribution_;
+};
+
+}  // wifi_offload_test namespace
+
+#endif  // CHRE_WIFI_OFFLOAD_TEST_RANDOM_GENERATOR_H_
diff --git a/apps/wifi_offload/test/include/utility.h b/apps/wifi_offload/test/include/utility.h
new file mode 100644
index 0000000..7546b0d
--- /dev/null
+++ b/apps/wifi_offload/test/include/utility.h
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef CHRE_WIFI_OFFLOAD_TEST_UTILITY_H_
+#define CHRE_WIFI_OFFLOAD_TEST_UTILITY_H_
+
+#include "chre/apps/wifi_offload/wifi_offload.h"
+
+#include "chre/apps/wifi_offload/channel_histogram.h"
+#include "chre/apps/wifi_offload/preferred_network.h"
+#include "chre/apps/wifi_offload/rpc_log_record.h"
+#include "chre/apps/wifi_offload/scan_config.h"
+#include "chre/apps/wifi_offload/scan_filter.h"
+#include "chre/apps/wifi_offload/scan_params.h"
+#include "chre/apps/wifi_offload/scan_record.h"
+#include "chre/apps/wifi_offload/scan_result.h"
+#include "chre/apps/wifi_offload/scan_stats.h"
+
+#include "../include/random_generator.h"
+
+namespace wifi_offload_test {
+
+constexpr uint8_t kNumFrequencies_Test = 74;
+/* Supported frequencies in 2.4GHz (802.11b/g/n) and 5GHz (802.11a/h/j/n/ac) */
+constexpr uint16_t kAllFrequencies_Test[kNumFrequencies_Test] = {
+    2412, 2417, 2422, 2427, 2432, 2437, 2442, 2447, 2452, 2457, 2462,
+    2467, 2472, 2484, 5035, 5040, 5045, 5055, 5060, 5080, 5170, 5180,
+    5190, 5200, 5210, 5220, 5230, 5240, 5250, 5260, 5270, 5280, 5290,
+    5300, 5310, 5320, 5500, 5510, 5520, 5530, 5540, 5550, 5560, 5570,
+    5580, 5590, 5600, 5610, 5620, 5630, 5640, 5660, 5670, 5680, 5690,
+    5700, 5710, 5720, 5745, 5755, 5765, 5775, 5785, 5795, 5805, 5825,
+    4915, 4920, 4925, 4935, 4940, 4945, 4960, 4980,
+};
+
+/* Supported channel numbers that matches frequencies in
+ * kAllFrequencies_Test. 2.4GHz (802.11b/g/n) and 5GHz (802.11a/h/j/n/ac) */
+constexpr uint8_t kAllChannels_Test[kNumFrequencies_Test] = {
+    1,   2,   3,   4,   5,   6,   7,   8,   9,   10,  11,  12,  13,  14,  7,
+    8,   9,   11,  12,  16,  34,  36,  38,  40,  42,  44,  46,  48,  50,  52,
+    54,  56,  58,  60,  62,  64,  100, 102, 104, 106, 108, 110, 112, 114, 116,
+    118, 120, 122, 124, 126, 128, 132, 134, 136, 138, 140, 142, 144, 149, 151,
+    153, 155, 157, 159, 161, 165, 183, 184, 185, 187, 188, 189, 192, 196,
+};
+
+void init(uint8_t &val, RandomGenerator &rand_gen);
+void init(uint16_t &val, RandomGenerator &rand_gen);
+void init(uint32_t &val, RandomGenerator &rand_gen);
+void init(uint64_t &val, RandomGenerator &rand_gen);
+void init(uint8_t *arr, size_t len, RandomGenerator &rand_gen);
+void init(wifi_offload::Ssid &ssid, RandomGenerator &rand_gen);
+void init(wifi_offload::PreferredNetwork &net_info, RandomGenerator &rand_gen);
+void init(wifi_offload::ScanRecord &record, RandomGenerator &rand_gen);
+void init(wifi_offload::RpcLogRecord &record, RandomGenerator &rand_gen);
+void init(wifi_offload::ChannelHistogram &histo, RandomGenerator &rand_gen);
+void init(wifi_offload::ScanStats &stats, RandomGenerator &rand_gen);
+void init(wifi_offload::ScanParams &params, RandomGenerator &rand_gen);
+void init(wifi_offload::ScanFilter &filter, RandomGenerator &rand_gen);
+void init(wifi_offload::ScanConfig &config, RandomGenerator &rand_gen);
+void init(wifi_offload::ScanResult &result, RandomGenerator &rand_gen);
+void init(chreWifiScanResult &result, RandomGenerator &rand_gen);
+
+template <typename T>
+void init(wifi_offload::Vector<T> &vec, RandomGenerator &rand_gen,
+          size_t vec_len) {
+  vec.clear();
+  for (size_t i = 0; i < vec_len; i++) {
+    T new_elem;
+    init(new_elem, rand_gen);
+    vec.push_back(std::move(new_elem));
+  }
+}
+
+template <typename T>
+void init(wifi_offload::Vector<T> &vec, RandomGenerator &rand_gen) {
+  size_t vec_len = (rand_gen.get<uint8_t>() % 10) + 1;
+  init(vec, rand_gen, vec_len);
+}
+
+}  // wifioffloadtesthelper namespace
+
+#endif  // CHRE_WIFI_OFFLOAD_TEST_UTILITY_H_
diff --git a/apps/wifi_offload/test/offloadtypes_test.cc b/apps/wifi_offload/test/offloadtypes_test.cc
new file mode 100644
index 0000000..350e284
--- /dev/null
+++ b/apps/wifi_offload/test/offloadtypes_test.cc
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "gtest/gtest.h"
+
+#include "include/utility.h"
+
+template <typename TestType>
+class OffloadTypesTest : public testing::Test {
+ public:
+  // RandomGenerator used to initialize data-types with random values
+  wifi_offload_test::RandomGenerator random_gen_;
+
+  flatbuffers::FlatBufferBuilder builder;
+
+  void EqualOperatorReturnsTrueForEqualValues() {
+    TestType lhs;
+    init(lhs, random_gen_);
+
+    random_gen_.Reset();
+    TestType rhs;
+    init(rhs, random_gen_);
+
+    EXPECT_EQ(lhs, rhs);
+  }
+
+  void EqualOperatorReturnsFalseForDifferentValues() {
+    TestType lhs, rhs;
+    init(lhs, random_gen_);
+    init(rhs, random_gen_);
+
+    ASSERT_FALSE(lhs == rhs);
+  }
+
+  void SerializationThenDeserializationCreatesEqualValue() {
+    TestType test_obj;
+    init(test_obj, random_gen_);
+    builder.Finish(test_obj.Serialize(&builder));
+
+    const uint8_t *serialized_buff = builder.GetBufferPointer();
+    const size_t serialized_size = builder.GetSize();
+    ASSERT_NE(nullptr, serialized_buff);
+    ASSERT_NE(0, serialized_size);
+
+    flatbuffers::Verifier verifier(serialized_buff, serialized_size);
+    ASSERT_TRUE(verifier.VerifyBuffer<typename TestType::FbsType>(nullptr));
+
+    const auto fbs_obj =
+        flatbuffers::GetRoot<typename TestType::FbsType>(serialized_buff);
+    ASSERT_NE(nullptr, fbs_obj);
+
+    TestType deserialized_obj;
+    ASSERT_TRUE(deserialized_obj.Deserialize(*fbs_obj));
+    EXPECT_EQ(test_obj, deserialized_obj);
+  }
+};
+
+typedef testing::Types<wifi_offload::PreferredNetwork, wifi_offload::ScanResult,
+                       wifi_offload::ScanParams, wifi_offload::ScanFilter,
+                       wifi_offload::ScanConfig, wifi_offload::ScanRecord,
+                       wifi_offload::RpcLogRecord, wifi_offload::ScanStats>
+    Implementations;
+
+TYPED_TEST_CASE(OffloadTypesTest, Implementations);
+
+TYPED_TEST(OffloadTypesTest, EqualOperatorReturnsTrueForEqualValues) {
+  this->EqualOperatorReturnsTrueForEqualValues();
+}
+
+TYPED_TEST(OffloadTypesTest, EqualOperatorReturnsFalseForDifferentValues) {
+  this->EqualOperatorReturnsFalseForDifferentValues();
+}
+
+TYPED_TEST(OffloadTypesTest,
+           SerializationThenDeserializationCreatesEqualValue) {
+  this->SerializationThenDeserializationCreatesEqualValue();
+}
diff --git a/apps/wifi_offload/test/random_generator.cc b/apps/wifi_offload/test/random_generator.cc
new file mode 100644
index 0000000..9a57db0
--- /dev/null
+++ b/apps/wifi_offload/test/random_generator.cc
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "include/random_generator.h"
+
+namespace wifi_offload_test {
+namespace {
+/* use default seed (vs. random seed) to reproduce the same sequence on
+ * different runs */
+constexpr bool kUseDefaultSeed = true;
+}  // namespace
+
+RandomGenerator::RandomGenerator() {
+  if (kUseDefaultSeed) {
+    initial_seed_ = random_engine_.default_seed;
+  } else {
+    /* Used to obtain a seed for the random number engine */
+    std::random_device random_device;
+    // save the seed for future resets
+    initial_seed_ = random_device();
+  }
+
+  Reset();
+}
+
+void RandomGenerator::Reset() {
+  random_engine_.seed(initial_seed_);
+  uniform_distribution_.reset();
+}
+
+}  // wifi_offload_test namespace
diff --git a/apps/wifi_offload/test/randomgenerator_test.cc b/apps/wifi_offload/test/randomgenerator_test.cc
new file mode 100644
index 0000000..3b0237f
--- /dev/null
+++ b/apps/wifi_offload/test/randomgenerator_test.cc
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <limits>
+#include "gtest/gtest.h"
+
+#include "include/random_generator.h"
+
+template <typename TestType>
+class RandomGeneratorTest : public testing::Test {
+ public:
+  wifi_offload_test::RandomGenerator random_gen_;
+
+  void GeneratedNumbersAreSmallerOrEqualTypeMaxValue() {
+    uint64_t rand_val = random_gen_.get<TestType>();
+    uint64_t max_val = std::numeric_limits<TestType>::max();
+
+    EXPECT_TRUE(rand_val <= max_val);
+  }
+
+  void AllRandomGeneratorsGenerateTheSameSequence() {
+    constexpr size_t num_values = 10;
+    TestType rand_values[num_values];
+
+    for (size_t i = 0; i < num_values; i++) {
+      rand_values[i] = random_gen_.get<TestType>();
+    }
+
+    wifi_offload_test::RandomGenerator another_random_gen_;
+    for (size_t i = 0; i < num_values; i++) {
+      ASSERT_EQ(rand_values[i], another_random_gen_.get<TestType>());
+    }
+  }
+
+  void AfterResetGeneratesTheSameSequence() {
+    constexpr size_t num_values = 10;
+    TestType rand_values[num_values];
+
+    for (size_t i = 0; i < num_values; i++) {
+      rand_values[i] = random_gen_.get<TestType>();
+    }
+
+    random_gen_.Reset();
+    for (size_t i = 0; i < num_values; i++) {
+      ASSERT_EQ(rand_values[i], random_gen_.get<TestType>());
+    }
+  }
+
+  void GeneratesDifferentNumbersIn8Bytes() {
+    constexpr size_t num_values = 10;
+    uint64_t rand_values[num_values];
+
+    constexpr size_t repeats_to_fill_8_bytes =
+        sizeof(uint64_t) / sizeof(TestType);
+    constexpr size_t shift_size =
+        (sizeof(TestType) * 8) % (sizeof(uint64_t) * 8);
+
+    for (size_t i = 0; i < num_values; i++) {
+      rand_values[i] = 0;
+      for (size_t j = 0; j < repeats_to_fill_8_bytes; j++) {
+        rand_values[i] <<= shift_size;
+        rand_values[i] |= random_gen_.get<TestType>();
+      }
+    }
+
+    // The probability of choosing equal random numbers out of 2 ^ 64 values
+    // is "extremely" small. Smaller than the change of memory failure.
+    for (size_t i = 0; i < num_values - 1; i++) {
+      for (size_t j = i + 1; j < num_values; j++) {
+        ASSERT_NE(rand_values[i], rand_values[j]);
+      }
+    }
+  }
+};
+
+typedef testing::Types<uint8_t, uint16_t, uint32_t, uint64_t> Implementations;
+
+TYPED_TEST_CASE(RandomGeneratorTest, Implementations);
+
+TYPED_TEST(RandomGeneratorTest, GeneratedNumbersAreSmallerOrEqualTypeMaxValue) {
+  this->GeneratedNumbersAreSmallerOrEqualTypeMaxValue();
+}
+
+TYPED_TEST(RandomGeneratorTest, AllRandomGeneratorsGenerateTheSameSequence) {
+  this->AllRandomGeneratorsGenerateTheSameSequence();
+}
+
+TYPED_TEST(RandomGeneratorTest, AfterResetGeneratesTheSameSequence) {
+  this->AfterResetGeneratesTheSameSequence();
+}
+
+TYPED_TEST(RandomGeneratorTest, GeneratesDifferentNumbersIn8Bytes) {
+  this->GeneratesDifferentNumbersIn8Bytes();
+}
diff --git a/apps/wifi_offload/test/scanresult_test.cc b/apps/wifi_offload/test/scanresult_test.cc
new file mode 100644
index 0000000..c23f2cb
--- /dev/null
+++ b/apps/wifi_offload/test/scanresult_test.cc
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "gtest/gtest.h"
+
+#include "include/utility.h"
+
+TEST(ScanResultTest, ConstructOffloadScanResultBasedOnChreWifiScanResult) {
+  wifi_offload_test::RandomGenerator random_gen;
+  chreWifiScanResult chre_scan_result;
+  init(chre_scan_result, random_gen);
+  wifi_offload::ScanResult nanoapp_scan_result(chre_scan_result);
+
+  wifi_offload::Ssid ssid;
+  ssid.SetData(chre_scan_result.ssid, chre_scan_result.ssidLen);
+  ASSERT_EQ(ssid, nanoapp_scan_result.ssid_);
+  EXPECT_EQ(chre_scan_result.securityMode, nanoapp_scan_result.security_modes_);
+  EXPECT_EQ(0, std::memcmp(chre_scan_result.bssid, nanoapp_scan_result.bssid_,
+                           CHRE_WIFI_BSSID_LEN));
+  EXPECT_EQ(chre_scan_result.capabilityInfo, nanoapp_scan_result.capability_);
+  EXPECT_EQ(chre_scan_result.primaryChannel,
+            nanoapp_scan_result.frequency_scanned_mhz_);
+  EXPECT_EQ(chre_scan_result.rssi, nanoapp_scan_result.rssi_dbm_);
+  EXPECT_EQ(0, nanoapp_scan_result.tsf_);
+}
diff --git a/apps/wifi_offload/test/utility.cc b/apps/wifi_offload/test/utility.cc
new file mode 100644
index 0000000..10a43ec
--- /dev/null
+++ b/apps/wifi_offload/test/utility.cc
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "include/utility.h"
+
+using RpcLog = wifi_offload::RpcLogRecord::RpcLogRecordType;
+
+namespace wifi_offload_test {
+
+void init(uint8_t &val, RandomGenerator &rand_gen) {
+  val = rand_gen.get<uint8_t>();
+}
+
+void init(uint16_t &val, RandomGenerator &rand_gen) {
+  val = rand_gen.get<uint16_t>();
+}
+
+void init(uint32_t &val, RandomGenerator &rand_gen) {
+  val = rand_gen.get<uint32_t>();
+}
+
+void init(uint64_t &val, RandomGenerator &rand_gen) {
+  val = rand_gen.get<uint64_t>();
+}
+
+void init(uint8_t *arr, size_t len, RandomGenerator &rand_gen) {
+  for (size_t i = 0; i < len; i++) {
+    arr[i] = rand_gen.get<uint8_t>();
+  }
+}
+
+void init_security_mode(uint8_t &sec_mode, RandomGenerator &rand_gen) {
+  init(sec_mode, rand_gen);
+  sec_mode &= 0x0f;
+}
+
+void init_rssi(int8_t &rssi, RandomGenerator &rand_gen) {
+  rssi = rand_gen.get<int8_t>();
+  if (rssi > 0) {
+    rssi = -rssi;
+  }
+}
+
+void init_rpc_log_record(RpcLog &log_record, RandomGenerator &rand_gen) {
+  log_record = static_cast<RpcLog>((rand_gen.get<uint32_t>() % 6u) +
+                                   static_cast<uint32_t>(RpcLog::CMD_BASE) + 1);
+}
+
+void init_frequency(uint32_t &freq, RandomGenerator &rand_gen) {
+  freq = kAllFrequencies_Test[rand_gen.get<uint8_t>() % kNumFrequencies_Test];
+}
+
+void init_capability(uint16_t &capa, RandomGenerator &rand_gen) {
+  capa = (rand_gen.get<uint16_t>() % 0xffff) + 1;
+}
+
+void init(wifi_offload::Ssid &ssid, RandomGenerator &rand_gen) {
+  uint8_t rand_ssid[wifi_offload::Ssid::kMaxSsidLen];
+  size_t len = (rand_gen.get<uint8_t>() % wifi_offload::Ssid::kMaxSsidLen) + 1;
+  init(rand_ssid, len, rand_gen);
+  ssid.SetData(rand_ssid, len);
+}
+
+void init(wifi_offload::PreferredNetwork &net_info, RandomGenerator &rand_gen) {
+  init(net_info.ssid_, rand_gen);
+  init_security_mode(net_info.security_modes_, rand_gen);
+}
+
+void init(wifi_offload::ScanRecord &record, RandomGenerator &rand_gen) {
+  init(record.time_spent_scanning_ms_, rand_gen);
+  init(record.num_channels_scanned_, rand_gen);
+  init(record.num_entries_aggregated_, rand_gen);
+}
+
+void init(wifi_offload::RpcLogRecord &record, RandomGenerator &rand_gen) {
+  init_rpc_log_record(record.record_type_, rand_gen);
+  init(record.timestamp_chre_ms_, rand_gen);
+}
+
+void init(wifi_offload::ChannelHistogram &histo, RandomGenerator &rand_gen) {
+  for (size_t i = 0; i < kNumFrequencies_Test; i++) {
+    histo.IncrementScanCountForFrequencyForTest(kAllFrequencies_Test[i],
+                                                rand_gen.get<uint16_t>());
+  }
+}
+
+void init(wifi_offload::ScanStats &stats, RandomGenerator &rand_gen) {
+  init(stats.num_scans_requested_by_nanoapp_, rand_gen);
+  init(stats.num_scans_serviced_by_hardware_, rand_gen);
+  init(stats.num_scans_serviced_by_cache_, rand_gen);
+  init(stats.updated_at_chre_ms_, rand_gen);
+  init(stats.sent_at_chre_ms_, rand_gen);
+  init(stats.last_subscription_duration_ms_, rand_gen);
+  init(stats.channel_histogram_, rand_gen);
+  init<wifi_offload::ScanRecord>(stats.scan_records_, rand_gen);
+  init<wifi_offload::RpcLogRecord>(stats.rpc_log_records_, rand_gen);
+}
+
+void init(wifi_offload::ScanParams &params, RandomGenerator &rand_gen) {
+  init<wifi_offload::Ssid>(params.ssids_to_scan_, rand_gen);
+  // Need to init frequency vector with valid frequency values
+  // init<uint32_t>(params.frequencies_to_scan_mhz_, rand_gen);
+  params.frequencies_to_scan_mhz_.clear();
+  size_t vec_len = (rand_gen.get<uint8_t>() % 10) + 1;
+  for (size_t i = 0; i < vec_len; i++) {
+    uint32_t new_freq;
+    init_frequency(new_freq, rand_gen);
+    params.frequencies_to_scan_mhz_.push_back(new_freq);
+  }
+  init(params.disconnected_mode_scan_interval_ms_, rand_gen);
+}
+
+void init(wifi_offload::ScanFilter &filter, RandomGenerator &rand_gen) {
+  init<wifi_offload::PreferredNetwork>(filter.networks_to_match_, rand_gen);
+  init_rssi(filter.min_rssi_threshold_dbm_, rand_gen);
+}
+
+void init(wifi_offload::ScanConfig &config, RandomGenerator &rand_gen) {
+  init(config.scan_params_, rand_gen);
+  init(config.scan_filter_, rand_gen);
+}
+
+void init(wifi_offload::ScanResult &result, RandomGenerator &rand_gen) {
+  init(result.ssid_, rand_gen);
+  init_security_mode(result.security_modes_, rand_gen);
+  init(result.bssid_, wifi_offload::ScanResult::kBssidSize, rand_gen);
+  init_capability(result.capability_, rand_gen);
+  init_frequency(result.frequency_scanned_mhz_, rand_gen);
+  init_rssi(result.rssi_dbm_, rand_gen);
+  init(result.tsf_, rand_gen);
+}
+
+void init(chreWifiScanResult &result, RandomGenerator &rand_gen) {
+  init(result.ageMs, rand_gen);
+  init_capability(result.capabilityInfo, rand_gen);
+  result.ssidLen =
+      (rand_gen.get<uint8_t>() % wifi_offload::Ssid::kMaxSsidLen) + 1;
+  init(result.ssid, result.ssidLen, rand_gen);
+  init(result.bssid, CHRE_WIFI_BSSID_LEN, rand_gen);
+  init(result.flags, rand_gen);
+  init_rssi(result.rssi, rand_gen);
+  init(result.band, rand_gen);
+  init_frequency(result.primaryChannel, rand_gen);
+  init(result.centerFreqPrimary, rand_gen);
+  init(result.centerFreqSecondary, rand_gen);
+  result.channelWidth = CHRE_WIFI_CHANNEL_WIDTH_20_MHZ;
+  init_security_mode(result.securityMode, rand_gen);
+}
+
+}  // wifioffloadtesthelper namespace
diff --git a/apps/wifi_offload/test/wifioffloadutility_test.cc b/apps/wifi_offload/test/wifioffloadutility_test.cc
new file mode 100644
index 0000000..58af31f
--- /dev/null
+++ b/apps/wifi_offload/test/wifioffloadutility_test.cc
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "gtest/gtest.h"
+
+#include "chre/apps/wifi_offload/utility.h"
+#include "include/utility.h"
+
+TEST(UtilityTest, MapAllSupportedFrequenciesToAllchannel_numbers) {
+  for (size_t i = 0; i < wifi_offload_test::kNumFrequencies_Test; i++) {
+    int channel = wifi_offload::utility::Ieee80211FrequencyToChannel(
+        static_cast<int>(wifi_offload_test::kAllFrequencies_Test[i]));
+    EXPECT_EQ(wifi_offload_test::kAllChannels_Test[i],
+              static_cast<uint8_t>(channel));
+  }
+}
diff --git a/apps/wifi_offload/utility.cc b/apps/wifi_offload/utility.cc
new file mode 100644
index 0000000..617c10f
--- /dev/null
+++ b/apps/wifi_offload/utility.cc
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <algorithm>
+#include <cctype>
+
+#include "chre/apps/wifi_offload/utility.h"
+#include "chre/apps/wifi_offload/wifi_offload.h"
+
+namespace wifi_offload {
+namespace utility {
+namespace {
+
+// The length of a string SSID with null-terminator.
+constexpr size_t kMaxSsidStrLen = CHRE_WIFI_SSID_MAX_LEN + 1;
+// The length of a formatted BSSID string in XX:XX:XX:XX:XX:XX\0 format.
+constexpr size_t kBssidStrLen = 18;
+
+bool ParseSsidToStr(const uint8_t *ssid, size_t ssid_len, char *ssid_str,
+                    size_t ssid_str_len) {
+  if (ssid_str_len < ssid_len + 1) {
+    return false;
+  }
+  // Verify that the ssid is entirely printable characters and ASCII spaces.
+  for (uint8_t i = 0; i < ssid_len; i++) {
+    if (!std::isgraph(ssid[i]) && ssid[i] != ' ') {
+      return false;
+    }
+  }
+
+  std::memcpy(ssid_str, ssid, ssid_len);
+  ssid_str[ssid_len] = '\0';
+  return true;
+}
+
+bool ParseBssidToStr(const uint8_t bssid[CHRE_WIFI_BSSID_LEN], char *bssid_str,
+                     size_t bssid_str_len) {
+  if (bssid_str_len < kBssidStrLen) {
+    return false;
+  }
+
+  const char *kFormat = "%02" PRIx8 ":%02" PRIx8 ":%02" PRIx8 ":%02" PRIx8
+                        ":%02" PRIx8 ":%02" PRIx8;
+  std::snprintf(bssid_str, bssid_str_len, kFormat, bssid[0], bssid[1], bssid[2],
+                bssid[3], bssid[4], bssid[5]);
+  return true;
+}
+
+const char *ParseChreWifiBand(uint8_t band) {
+  switch (band) {
+    case CHRE_WIFI_BAND_2_4_GHZ:
+      return "2.4GHz";
+    case CHRE_WIFI_BAND_5_GHZ:
+      return "5GHz";
+    default:
+      return "<invalid>";
+  }
+}
+
+}  // namespace
+
+int Ieee80211FrequencyToChannel(int freq) {
+  /* see 802.11-2007 17.3.8.3.2 and Annex J */
+  if (freq == 2484)
+    return 14;
+  else if (freq < 2484)
+    return (freq - 2407) / 5;
+  else if (freq >= 4910 && freq <= 4980)
+    return (freq - 4000) / 5;
+  else if (freq <= 45000) /* DMG band lower limit */
+    return (freq - 5000) / 5;
+  else if (freq >= 58320 && freq <= 64800)
+    return (freq - 56160) / 2160;
+  else
+    return 0;
+}
+
+void LogSsid(const uint8_t *ssid, uint8_t ssid_len) {
+  const char *ssid_str = "<non-printable>";
+  char ssid_buffer[kMaxSsidStrLen];
+  if (ssid_len == 0) {
+    ssid_str = "<empty>";
+  } else if (ParseSsidToStr(ssid, ssid_len, ssid_buffer, kMaxSsidStrLen)) {
+    ssid_str = ssid_buffer;
+  } else {
+    // ssid has non-printable ASCII chars, parse in hex format
+    char ssid_hex_buffer[CHRE_WIFI_SSID_MAX_LEN * 3];
+    char *buf_ptr = ssid_hex_buffer;
+    for (size_t i = 0; i < ssid_len; i++) {
+      buf_ptr += std::sprintf(buf_ptr, "%02" PRIx8 ":", ssid[i]);
+    }
+    buf_ptr[-1] = '\0';
+    ssid_str = ssid_hex_buffer;
+  }
+  LOGI("  ssid: %s", ssid_str);
+}
+
+void LogBssid(const uint8_t *bssid) {
+  const char *bssid_str = "<non-printable>";
+  char bssidBuffer[kBssidStrLen];
+  if (ParseBssidToStr(bssid, bssidBuffer, kBssidStrLen)) {
+    bssid_str = bssidBuffer;
+  }
+  LOGI("  bssid: %s", bssid_str);
+}
+
+void LogChreScanResult(const chreWifiScanResult &result) {
+  LOGI("chreWifiScanResult:");
+  LogSsid(result.ssid, result.ssidLen);
+  LOGI("  age (ms): %" PRIu32, result.ageMs);
+  LOGI("  capability info: 0x%" PRIx16, result.capabilityInfo);
+  LogBssid(result.bssid);
+  LOGI("  flags: 0x%" PRIx8, result.flags);
+  LOGI("  rssi: %" PRId8 "dBm", result.rssi);
+  LOGI("  band: %s (%" PRIu8 ")", ParseChreWifiBand(result.band), result.band);
+  LOGI("  primary channel: %" PRIu32, result.primaryChannel);
+  LOGI("  center frequency primary: %" PRIu32, result.centerFreqPrimary);
+  LOGI("  center frequency secondary: %" PRIu32, result.centerFreqSecondary);
+  LOGI("  channel width: %" PRIu8, result.channelWidth);
+  LOGI("  security mode: %" PRIu8, result.securityMode);
+}
+
+const char *GetErrorCodeName(ErrorCode error_code) {
+  switch (error_code) {
+    case SUCCESS:
+      return "SUCCESS";
+    case FAILED_TO_ALLOCATE_MESSAGE_BUFFER:
+      return "FAILED_TO_ALLOCATE_MESSAGE_BUFFER";
+    case FAILED_TO_SERIALIZE_MESSAGE:
+      return "FAILED_TO_SERIALIZE_MESSAGE";
+    case FAILED_TO_SEND_MESSAGE:
+      return "FAILED_TO_SEND_MESSAGE";
+    case FAILED_TO_DESERIALIZE_SCAN_CONFIG:
+      return "FAILED_TO_DESERIALIZE_SCAN_CONFIG";
+    case INVALID_SUBSCRIBE_MESSAGE_SIZE:
+      return "INVALID_SUBSCRIBE_MESSAGE_SIZE";
+    case SCAN_CONFIG_NOT_INITIALIZED:
+      return "SCAN_CONFIG_NOT_INITIALIZED";
+    case UNSPECIFIED_HOST_ENDPOINT:
+      return "UNSPECIFIED_HOST_ENDPOINT";
+    case FAILED_TO_SEND_SCAN_RESULTS:
+      return "FAILED_TO_SEND_SCAN_RESULTS";
+    case FAILED_TO_SEND_SCAN_STATS:
+      return "FAILED_TO_SEND_SCAN_STATS";
+    case SCAN_MONITORING_NOT_SUPPORTED:
+      return "SCAN_MONITORING_NOT_SUPPORTED";
+    case FAILED_TO_START_SCAN_MONITORING:
+      return "FAILED_TO_START_SCAN_MONITORING";
+    case FAILED_TO_STOP_SCAN_MONITORING:
+      return "FAILED_TO_STOP_SCAN_MONITORING";
+    case FAILED_TO_CONFIGURE_SCAN_MONITORING_ASYNC:
+      return "FAILED_TO_CONFIGURE_SCAN_MONITORING_ASYNC";
+    case ONDEMAND_SCAN_NOT_SUPPORTED:
+      return "ONDEMAND_SCAN_NOT_SUPPORTED";
+    case FAILED_TO_SEND_ONDEMAND_SCAN_REQUEST:
+      return "FAILED_TO_SEND_ONDEMAND_SCAN_REQUEST";
+    case FAILED_TO_SEND_ONDEMAND_SCAN_REQUEST_ASYNC:
+      return "FAILED_TO_SEND_ONDEMAND_SCAN_REQUEST_ASYNC";
+    case OUT_OF_ORDER_SCAN_RESULTS:
+      return "OUT_OF_ORDER_SCAN_RESULTS";
+    case INCOMPLETE_SCAN_RESULTS_BEFORE_SCAN_REQUEST:
+      return "INCOMPLETE_SCAN_RESULTS_BEFORE_SCAN_REQUEST";
+    case FAILED_TO_SET_SCAN_TIMER:
+      return "FAILED_TO_SET_SCAN_TIMER";
+    default:
+      return "UNKNOWN_ERROR";
+  }
+}
+
+}  // namespace utility
+}  // namespace wifi_offload
diff --git a/apps/wifi_offload/wifi_offload.mk b/apps/wifi_offload/wifi_offload.mk
new file mode 100644
index 0000000..0450680
--- /dev/null
+++ b/apps/wifi_offload/wifi_offload.mk
@@ -0,0 +1,40 @@
+#
+# Wifi Offload Makefile
+#
+
+WIFI_OFFLOAD_TYPES_PREFIX = $(CHRE_PREFIX)/apps/wifi_offload
+FLAT_BUFFERS_PREFIX = $(CHRE_PREFIX)/external/flatbuffers
+
+# Common Compiler Flags ########################################################
+
+# Include paths.
+COMMON_CFLAGS += -I$(FLAT_BUFFERS_PREFIX)/include
+
+# Common Source Files ##########################################################
+
+COMMON_SRCS += $(WIFI_OFFLOAD_TYPES_PREFIX)/channel_histogram.cc
+COMMON_SRCS += $(WIFI_OFFLOAD_TYPES_PREFIX)/chre_scan_params_safe.cc
+COMMON_SRCS += $(WIFI_OFFLOAD_TYPES_PREFIX)/flatbuffers_serialization.cc
+COMMON_SRCS += $(WIFI_OFFLOAD_TYPES_PREFIX)/preferred_network.cc
+COMMON_SRCS += $(WIFI_OFFLOAD_TYPES_PREFIX)/rpc_log_record.cc
+COMMON_SRCS += $(WIFI_OFFLOAD_TYPES_PREFIX)/scan_config.cc
+COMMON_SRCS += $(WIFI_OFFLOAD_TYPES_PREFIX)/scan_filter.cc
+COMMON_SRCS += $(WIFI_OFFLOAD_TYPES_PREFIX)/scan_params.cc
+COMMON_SRCS += $(WIFI_OFFLOAD_TYPES_PREFIX)/scan_record.cc
+COMMON_SRCS += $(WIFI_OFFLOAD_TYPES_PREFIX)/scan_result.cc
+COMMON_SRCS += $(WIFI_OFFLOAD_TYPES_PREFIX)/scan_result_message.cc
+COMMON_SRCS += $(WIFI_OFFLOAD_TYPES_PREFIX)/scan_stats.cc
+COMMON_SRCS += $(WIFI_OFFLOAD_TYPES_PREFIX)/ssid.cc
+COMMON_SRCS += $(WIFI_OFFLOAD_TYPES_PREFIX)/utility.cc
+
+# GoogleTest Source Files ######################################################
+
+GOOGLETEST_SRCS += $(WIFI_OFFLOAD_TYPES_PREFIX)/test/channelhistogram_test.cc
+GOOGLETEST_SRCS += $(WIFI_OFFLOAD_TYPES_PREFIX)/test/chrescanparamssafe_test.cc
+GOOGLETEST_SRCS += $(WIFI_OFFLOAD_TYPES_PREFIX)/test/flatbuffersserialization_test.cc
+GOOGLETEST_SRCS += $(WIFI_OFFLOAD_TYPES_PREFIX)/test/offloadtypes_test.cc
+GOOGLETEST_SRCS += $(WIFI_OFFLOAD_TYPES_PREFIX)/test/random_generator.cc
+GOOGLETEST_SRCS += $(WIFI_OFFLOAD_TYPES_PREFIX)/test/randomgenerator_test.cc
+GOOGLETEST_SRCS += $(WIFI_OFFLOAD_TYPES_PREFIX)/test/scanresult_test.cc
+GOOGLETEST_SRCS += $(WIFI_OFFLOAD_TYPES_PREFIX)/test/utility.cc
+GOOGLETEST_SRCS += $(WIFI_OFFLOAD_TYPES_PREFIX)/test/wifioffloadutility_test.cc
diff --git a/ash/platform/slpi/ash.cc b/ash/platform/slpi/ash.cc
index 613da1d..c07b5ba 100644
--- a/ash/platform/slpi/ash.cc
+++ b/ash/platform/slpi/ash.cc
@@ -30,15 +30,16 @@
 #include "chre/platform/slpi/smgr_client.h"
 #include "chre_api/chre/sensor.h"
 
-using chre::getSensorServiceQmiClientHandle;
+using chre::getSmrHelper;
+using chre::getSensorServiceSmrClientHandle;
+using chre::MakeUnique;
+using chre::MakeUniqueZeroFill;
 using chre::memoryAlloc;
 using chre::memoryFree;
+using chre::UniquePtr;
 
 namespace {
 
-//! The timeout for QMI messages in milliseconds.
-constexpr uint32_t kQmiTimeoutMs = 1000;
-
 //! The constant to convert magnetometer readings from uT in Android to Gauss
 //! in SMGR.
 constexpr float kGaussPerMicroTesla = 0.01f;
@@ -135,19 +136,18 @@
     LOGE("Attempting to set calibration of sensor %" PRIu8, sensorType);
   } else {
     // Allocate request and response for sensor calibraton.
-    auto *calRequest = memoryAlloc<sns_smgr_sensor_cal_req_msg_v01>();
-    auto *calResponse = memoryAlloc<sns_smgr_sensor_cal_resp_msg_v01>();
-    if (calRequest == nullptr || calResponse == nullptr) {
+    auto calRequest = MakeUniqueZeroFill<sns_smgr_sensor_cal_req_msg_v01>();
+    auto calResponse = MakeUnique<sns_smgr_sensor_cal_resp_msg_v01>();
+    if (calRequest.isNull() || calResponse.isNull()) {
       LOGE("Failed to allocated sensor cal memory");
     } else {
-      populateCalRequest(sensorType, calInfo, calRequest);
+      populateCalRequest(sensorType, calInfo, calRequest.get());
 
-      qmi_client_error_type status = qmi_client_send_msg_sync(
-          getSensorServiceQmiClientHandle(), SNS_SMGR_CAL_REQ_V01,
-          calRequest, sizeof(*calRequest), calResponse, sizeof(*calResponse),
-          kQmiTimeoutMs);
+      smr_err status = getSmrHelper()->sendReqSync(
+          getSensorServiceSmrClientHandle(), SNS_SMGR_CAL_REQ_V01,
+          &calRequest, &calResponse);
 
-      if (status != QMI_NO_ERR) {
+      if (status != SMR_NO_ERR) {
         LOGE("Error setting sensor calibration: status %d", status);
       } else if (calResponse->Resp.sns_result_t != SNS_RESULT_SUCCESS_V01) {
         LOGE("Setting sensor calibration failed with error: %" PRIu8,
@@ -156,8 +156,6 @@
         success = true;
       }
     }
-    memoryFree(calRequest);
-    memoryFree(calResponse);
   }
   return success;
 }
diff --git a/build/app_support/qcom_nanohub/app_support_uimg.cc b/build/app_support/qcom_nanohub/app_support_uimg.cc
new file mode 100755
index 0000000..483dcb1
--- /dev/null
+++ b/build/app_support/qcom_nanohub/app_support_uimg.cc
@@ -0,0 +1 @@
+#error "This file is not implemented yet"
diff --git a/build/arch/hexagon.mk b/build/arch/hexagon.mk
index 87ca25c..f63c363 100644
--- a/build/arch/hexagon.mk
+++ b/build/arch/hexagon.mk
@@ -44,6 +44,10 @@
 # that any Qualcomm code needs it.
 TARGET_CFLAGS += -D__V_DYNAMIC__
 
+# This flag is used by some QC-supplied code to differentiate things intended to
+# run on Hexagon vs. other architectures
+TARGET_CFLAGS += -DQDSP6
+
 # Hexagon Shared Object Linker Flags ###########################################
 
 TARGET_SO_LDFLAGS += --gc-sections
diff --git a/build/common.mk b/build/common.mk
index 8f99dc5..58b2fec 100644
--- a/build/common.mk
+++ b/build/common.mk
@@ -28,6 +28,10 @@
 MAKECMDGOALS = all
 endif
 
+# Variant-specific Support Source Files ########################################
+
+SYS_SUPPORT_PATH = $(CHRE_PREFIX)/build/sys_support
+
 # Makefile Includes ############################################################
 
 # Common Includes
diff --git a/build/nanoapp/app.mk b/build/nanoapp/app.mk
index b2122f5..38cb8b3 100644
--- a/build/nanoapp/app.mk
+++ b/build/nanoapp/app.mk
@@ -62,6 +62,23 @@
 # Add the CHRE API to the include search path.
 COMMON_CFLAGS += -I$(CHRE_PREFIX)/chre_api/include/chre_api
 
+# Add util and platform/shared to the include search path.
+COMMON_CFLAGS += -I$(CHRE_PREFIX)/util/include
+COMMON_CFLAGS += -I$(CHRE_PREFIX)/platform/shared/include
+
+# Allows a nanoapp to know that is compiled separately from the CHRE system.
+COMMON_CFLAGS += -DCHRE_IS_NANOAPP_BUILD
+
+# Compile FlatBuffers in a portable way.
+COMMON_CFLAGS += -DFLATBUFFERS_CHRE
+
+# Nanoapp configuration flags.
+COMMON_CFLAGS += -DNANOAPP_ID=$(NANOAPP_ID)
+COMMON_CFLAGS += -DNANOAPP_VERSION=$(NANOAPP_VERSION)
+COMMON_CFLAGS += -DNANOAPP_VENDOR_STRING=$(NANOAPP_VENDOR_STRING)
+COMMON_CFLAGS += -DNANOAPP_NAME_STRING=$(NANOAPP_NAME_STRING)
+COMMON_CFLAGS += -DNANOAPP_IS_SYSTEM_NANOAPP=$(NANOAPP_IS_SYSTEM_NANOAPP)
+
 # Variant-specific Nanoapp Support Source Files ################################
 
 APP_SUPPORT_PATH = $(CHRE_PREFIX)/build/app_support
@@ -69,7 +86,10 @@
 
 GOOGLE_HEXAGONV60_SLPI_SRCS += $(DSO_SUPPORT_LIB_PATH)/nanoapp_support_lib_dso.c
 GOOGLE_HEXAGONV62_SLPI_SRCS += $(DSO_SUPPORT_LIB_PATH)/nanoapp_support_lib_dso.c
+GOOGLE_HEXAGONV62_SLPI-UIMG_SRCS += $(DSO_SUPPORT_LIB_PATH)/nanoapp_support_lib_dso.c
+GOOGLE_X86_LINUX_SRCS += $(DSO_SUPPORT_LIB_PATH)/nanoapp_support_lib_dso.c
 QCOM_HEXAGONV60_NANOHUB_SRCS += $(APP_SUPPORT_PATH)/qcom_nanohub/app_support.cc
+QCOM_HEXAGONV60_NANOHUB-UIMG_SRCS += $(APP_SUPPORT_PATH)/qcom_nanohub/app_support_uimg.cc
 
 # Makefile Includes ############################################################
 
@@ -80,5 +100,7 @@
 include $(CHRE_PREFIX)/build/variant/google_cm4_nanohub.mk
 include $(CHRE_PREFIX)/build/variant/google_hexagonv60_slpi.mk
 include $(CHRE_PREFIX)/build/variant/google_hexagonv62_slpi.mk
+include $(CHRE_PREFIX)/build/variant/google_hexagonv62_slpi-uimg.mk
 include $(CHRE_PREFIX)/build/variant/google_x86_linux.mk
 include $(CHRE_PREFIX)/build/variant/qcom_hexagonv60_nanohub.mk
+include $(CHRE_PREFIX)/build/variant/qcom_hexagonv60_nanohub-uimg.mk
diff --git a/build/nanoapp/google_slpi.mk b/build/nanoapp/google_slpi.mk
index 2e629bb..dfcb317 100644
--- a/build/nanoapp/google_slpi.mk
+++ b/build/nanoapp/google_slpi.mk
@@ -15,14 +15,6 @@
 #
 ################################################################################
 
-TARGET_CFLAGS += -DNANOAPP_ID=$(NANOAPP_ID)
-TARGET_CFLAGS += -DNANOAPP_VERSION=$(NANOAPP_VERSION)
-TARGET_CFLAGS += -DNANOAPP_VENDOR_STRING=$(NANOAPP_VENDOR_STRING)
-TARGET_CFLAGS += -DNANOAPP_NAME_STRING=$(NANOAPP_NAME_STRING)
-TARGET_CFLAGS += -DNANOAPP_IS_SYSTEM_NANOAPP=$(NANOAPP_IS_SYSTEM_NANOAPP)
-TARGET_CFLAGS += -I$(CHRE_PREFIX)/platform/shared/include
-TARGET_CFLAGS += -I$(CHRE_PREFIX)/util/include
-
 ifndef GOOGLE_SLPI_NANOAPP_BUILD_TEMPLATE
 define GOOGLE_SLPI_NANOAPP_BUILD_TEMPLATE
 
diff --git a/build/sys_support/qcom/uimage.lcs.toolv80 b/build/sys_support/qcom/uimage.lcs.toolv80
new file mode 100644
index 0000000..af42a34
--- /dev/null
+++ b/build/sys_support/qcom/uimage.lcs.toolv80
@@ -0,0 +1,205 @@
+/*
+Copyright (c) 2017, The Linux Foundation. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+    * Neither the name of The Linux Foundation nor the names of its
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+OUTPUT_FORMAT("elf32-littlehexagon", "elf32-bighexagon",
+	      "elf32-littlehexagon")
+OUTPUT_ARCH(hexagon)
+
+PHDRS {
+phdr1 PT_LOAD;
+phdr2 PT_LOAD;
+dynamic1 PT_DYNAMIC;
+note1 PT_NOTE;
+}
+
+ENTRY(start)
+SECTIONS
+{
+  .interp         : { *(.interp) }
+  .note.qti.uimg.dl.ver : { *(.note.qti.uimg.dl.ver) } : phdr1 : note1
+  .dynsym         :  { *(.dynsym) } : phdr1
+  .dynstr         :  { *(.dynstr) }
+  .hash           :  { *(.hash) }
+  .rela.dyn       :
+  {
+      *(.rela.text .rela.text.* .rela.gnu.linkonce.t.*)
+      *(.rela.fini)
+      *(.rela.rodata .rela.rodata.* .rela.gnu.linkonce.r.*)
+      *(.rela.data .rela.data.* .rela.gnu.linkonce.d.*)
+      *(.rela.tdata .rela.tdata.* .rela.gnu.linkonce.td.*)
+      *(.rela.tbss .rela.tbss.* .rela.gnu.linkonce.tb.*)
+      *(.rela.ctors)
+      *(.rela.dtors)
+      *(.rela.got)
+      *(.rela.sdata .rela.lit[a48] .rela.sdata.* .rela.lit[a48].* .rela.gnu.linkonce.s.* .rela.gnu.linkonce.l[a48].*)
+      *(.rela.sbss .rela.sbss.* .rela.gnu.linkonce.sb.*)
+      *(.rela.sdata2 .rela.sdata2.* .rela.gnu.linkonce.s2.*)
+      *(.rela.sbss2 .rela.sbss2.* .rela.gnu.linkonce.sb2.*)
+      *(.rela.bss .rela.bss.* .rela.gnu.linkonce.b.*)
+  }
+  .rela.plt       :
+  {
+      *(.rela.plt)
+  } : phdr1
+  . = ALIGN(64);
+  /* Code starts. */
+  .start          :
+  {
+    KEEP (*(.start))
+  } =0x00c0007f
+  . = ALIGN(64);
+  .init           :
+  {
+    KEEP (*(.init))
+  } =0x00c0007f
+  .plt            :  { *(.plt) }
+  . = ALIGN (64);
+  .text           :
+  {
+    *(.text.unlikely .text.*_unlikely)
+    *(.text.hot .text.hot.* .gnu.linkonce.t.hot.*)
+    *(.text .stub .text.* .gnu.linkonce.t.*)
+  } =0x00c0007f
+  .fini           :
+  {
+    KEEP (*(.fini))
+  } =0x00c0007f
+  PROVIDE (__etext = .);
+  PROVIDE (_etext = .);
+  PROVIDE (etext = .);
+  . = ALIGN(64);
+  /* Constants start. */
+  .rodata         :
+  {
+    *(.rodata.hot .rodata.hot.* .gnu.linkonce.r.hot.*)
+    *(.rodata .rodata.* .gnu.linkonce.r.*)
+  }
+  .eh_frame_hdr   :  { *(.eh_frame_hdr) }
+  .eh_frame       :   { KEEP (*(.eh_frame)) }
+  .gcc_except_table   :  { *(.gcc_except_table .gcc_except_table.*) }
+  _DYNAMIC = .;
+  .dynamic        :  { *(.dynamic) } : phdr1 : dynamic1
+  .got            :  { *(.got) *(.igot) } : phdr1
+  .got.plt        :  { *(.got.plt)  *(.igot.plt) }
+  . = ALIGN(64);
+  .ctors          :
+  {
+    KEEP (*crtbegin.o(.ctors))
+    KEEP (*crtbegin?.o(.ctors))
+    KEEP (*(EXCLUDE_FILE (*crtend.o *crtend?.o fini.o) .ctors))
+    KEEP (*(SORT(.ctors.*)))
+    KEEP (*(.ctors))
+  }
+  .dtors          :
+  {
+    KEEP (*crtbegin.o(.dtors))
+    KEEP (*crtbegin?.o(.dtors))
+    KEEP (*(EXCLUDE_FILE (*crtend.o *crtend?.o fini.o) .dtors))
+    KEEP (*(SORT(.dtors.*)))
+    KEEP (*(.dtors))
+  }
+  /*. = ALIGN (CONSTANT (MAXPAGESIZE)) - ((CONSTANT (MAXPAGESIZE) - .) & (CONSTANT (MAXPAGESIZE) - 1)); . = DATA_SEGMENT_ALIGN (CONSTANT (MAXPAGESIZE), CONSTANT (COMMONPAGESIZE));
+  . = ALIGN (DEFINED (DATAALIGN) ? (DATAALIGN * 1K) : CONSTANT (MAXPAGESIZE));*/
+  . = DATA_SEGMENT_RELRO_END (16, .);
+  . = ALIGN (4K);
+  .data           :
+  {
+    *(.data.hot .data.hot.* .gnu.linkonce.d.hot.*)
+    *(.data .data.* .gnu.linkonce.d.*)
+    SORT(CONSTRUCTORS)
+  } : phdr2
+  _edata = .; PROVIDE (edata = .);
+  . = ALIGN (64);
+  /* Small data start. */
+  . = ALIGN(64);
+  .sdata          :
+  {
+    PROVIDE (_SDA_BASE_ = .);
+    *(.sdata.1 .sdata.1.* .gnu.linkonce.s.1.*)
+    *(.sbss.1 .sbss.1.* .gnu.linkonce.sb.1.*)
+    *(.scommon.1 .scommon.1.*)
+    *(.sdata.2 .sdata.2.* .gnu.linkonce.s.2.*)
+    *(.sbss.2 .sbss.2.* .gnu.linkonce.sb.2.*)
+    *(.scommon.2 .scommon.2.*)
+    *(.sdata.4 .sdata.4.* .gnu.linkonce.s.4.*)
+    *(.sbss.4 .sbss.4.* .gnu.linkonce.sb.4.*)
+    *(.scommon.4 .scommon.4.*)
+    *(.lit[a4] .lit[a4].* .gnu.linkonce.l[a4].*)
+    *(.sdata.8 .sdata.8.* .gnu.linkonce.s.8.*)
+    *(.sbss.8 .sbss.8.* .gnu.linkonce.sb.8.*)
+    *(.scommon.8 .scommon.8.*)
+    *(.lit8 .lit8.* .gnu.linkonce.l8.*)
+    *(.sdata.hot .sdata.hot.* .gnu.linkonce.s.hot.*)
+    *(.sdata .sdata.* .gnu.linkonce.s.*)
+  }
+  .sbss           :
+  {
+    PROVIDE (__sbss_start = .);
+    PROVIDE (___sbss_start = .);
+    *(.dynsbss)
+    *(.sbss.hot .sbss.hot.* .gnu.linkonce.sb.hot.*)
+    *(.sbss .sbss.* .gnu.linkonce.sb.*)
+    *(.scommon .scommon.*)
+    . = ALIGN (. != 0 ? 64 : 1);
+    PROVIDE (__sbss_end = .);
+    PROVIDE (___sbss_end = .);
+  }
+  . = ALIGN (64);
+  __bss_start = .;
+  .bss            :
+  {
+   *(.dynbss)
+   *(.bss.hot .bss.hot.* .gnu.linkonce.b.hot.*)
+   *(.bss .bss.* .gnu.linkonce.b.*)
+   *(COMMON)
+  }
+  . = ALIGN (64);
+  _end = .;
+  PROVIDE (end = .);
+  .comment       0 :  { *(.comment) }
+  /* DWARF debug sections.
+     Symbols in the DWARF debugging sections are relative to the beginning
+     of the section so we begin them at 0.  */
+  /* DWARF 1 */
+  .debug          0 :  { *(.debug) }
+  .line           0 :  { *(.line) }
+  .debug_aranges  0 :  { *(.debug_aranges) }
+  .debug_pubnames 0 :  { *(.debug_pubnames) }
+  /* DWARF 2 */
+  .debug_info     0 :  { *(.debug_info .gnu.linkonce.wi.*) }
+  .debug_abbrev   0 :  { *(.debug_abbrev) }
+  .debug_line     0 :  { *(.debug_line) }
+  .debug_frame    0 :  { *(.debug_frame) }
+  .debug_str      0 :  { *(.debug_str) }
+  .debug_loc      0 :  { *(.debug_loc) }
+  /* DWARF 3 */
+  .debug_pubtypes 0 :  { *(.debug_pubtypes) }
+  .debug_ranges   0 :  { *(.debug_ranges) }
+  /DISCARD/       :  { *(.note.GNU-stack) *(.gnu_debuglink) }
+}
diff --git a/build/sys_support/qcom/uimg_dl_ver.c b/build/sys_support/qcom/uimg_dl_ver.c
new file mode 100644
index 0000000..3f1ed35
--- /dev/null
+++ b/build/sys_support/qcom/uimg_dl_ver.c
@@ -0,0 +1,69 @@
+/*
+Copyright (c) 2017, The Linux Foundation. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+    * Neither the name of The Linux Foundation nor the names of its
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#ifndef UIMG_DL_VER_MAJOR
+#define UIMG_DL_VER_MAJOR 1
+#endif
+#ifndef UIMG_DL_VER_MINOR
+#define UIMG_DL_VER_MINOR 0
+#endif
+#ifndef UIMG_DL_VER_MAINT
+#define UIMG_DL_VER_MAINT 0
+#endif
+
+#define __TOSTR(_x) #_x
+#define _TOSTR(_x) __TOSTR(_x)
+
+typedef struct note_type{
+  int sizename;
+  int sizedesc;
+  int type;
+  char name[24];
+  int desc[3];
+} note_type;
+
+#ifdef __llvm__
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wunused-variable"
+#endif
+
+const note_type uimg_dl_ver __attribute__ ((section (".note.qti.uimg.dl.ver")))
+                            __attribute__ ((visibility ("default"))) = {
+  24,
+  12,
+  0,
+  "uimg.dl.ver." _TOSTR(UIMG_DL_VER_MAJOR) "." _TOSTR(UIMG_DL_VER_MINOR) "." _TOSTR(UIMG_DL_VER_MAINT),
+  {UIMG_DL_VER_MAJOR, UIMG_DL_VER_MINOR, UIMG_DL_VER_MAINT}
+};
+
+#ifdef __llvm__
+#pragma clang diagnostic pop
+#endif
+
+
diff --git a/build/tools_config.mk b/build/tools_config.mk
index 04c56d8..89a67a1 100644
--- a/build/tools_config.mk
+++ b/build/tools_config.mk
@@ -32,7 +32,7 @@
 COMMON_DEBUG_CFLAGS += -g
 
 # Dependency Resolution
-DEP_CFLAGS = -MT $$@ -MM -MG -MP -MF $$(basename $$@).Td
+DEP_CFLAGS = -MM -MG -MP -MF $$(basename $$@).Td
 DEP_POST_COMPILE = @mv -f $$(basename $$@).Td $$(basename $$@).d && touch $$@
 
 # Compile with hidden visibility by default.
diff --git a/build/variant/google_hexagonv62_slpi-uimg.mk b/build/variant/google_hexagonv62_slpi-uimg.mk
new file mode 100644
index 0000000..2b09a59
--- /dev/null
+++ b/build/variant/google_hexagonv62_slpi-uimg.mk
@@ -0,0 +1,31 @@
+#
+# Google CHRE Reference Implementation for Hexagon v62 Architecture on SLPI
+#
+
+include $(CHRE_PREFIX)/build/clean_build_template_args.mk
+
+TARGET_NAME = google_hexagonv62_slpi-uimg
+# Sized based on the buffer allocated in the host daemon (4096 bytes), minus
+# FlatBuffer overhead (max 80 bytes), minus some extra space to make a nice
+# round number and allow for addition of new fields to the FlatBuffer
+TARGET_CFLAGS = -DCHRE_MESSAGE_TO_HOST_MAX_SIZE=4000
+TARGET_CFLAGS += -mno-pic-data-is-text-relative
+TARGET_CFLAGS += -DCHRE_SLPI_UIMG_ENABLED
+TARGET_CFLAGS += $(GOOGLE_HEXAGONV62_SLPI-UIMG_CFLAGS)
+TARGET_VARIANT_SRCS = $(GOOGLE_HEXAGONV62_SLPI-UIMG_SRCS)
+TARGET_SO_LATE_LIBS = $(GOOGLE_HEXAGONV62_SLPI-UIMG_LATE_LIBS)
+HEXAGON_ARCH = v62
+
+# Enable uImage support.
+TARGET_VARIANT_SRCS += $(SYS_SUPPORT_PATH)/qcom/uimg_dl_ver.c
+TARGET_SO_LDFLAGS += --script=$(SYS_SUPPORT_PATH)/qcom/uimage.lcs.toolv80
+
+ifneq ($(filter $(TARGET_NAME)% all, $(MAKECMDGOALS)),)
+ifneq ($(IS_NANOAPP_BUILD),)
+TARGET_SO_LATE_LIBS += $(CHRE_PREFIX)/build/app_support/google_slpi/libchre_slpi_skel.so
+include $(CHRE_PREFIX)/build/nanoapp/google_slpi.mk
+endif
+
+include $(CHRE_PREFIX)/build/arch/hexagon.mk
+include $(CHRE_PREFIX)/build/build_template.mk
+endif
diff --git a/build/variant/google_x86_googletest.mk b/build/variant/google_x86_googletest.mk
index 24310e6..f32da60 100644
--- a/build/variant/google_x86_googletest.mk
+++ b/build/variant/google_x86_googletest.mk
@@ -35,7 +35,7 @@
 TARGET_BUILD_BIN = true
 
 # Link in libraries for the final executable.
-TARGET_BIN_LDFLAGS += -lrt
+TARGET_BIN_LDFLAGS += -lrt -ldl
 TARGET_BIN_LDFLAGS += -lpthread
 
 include $(CHRE_PREFIX)/build/build_template.mk
diff --git a/build/variant/google_x86_linux.mk b/build/variant/google_x86_linux.mk
index bf5a484..4ab5d88 100644
--- a/build/variant/google_x86_linux.mk
+++ b/build/variant/google_x86_linux.mk
@@ -26,8 +26,9 @@
 # Instruct the build to link a final executable.
 TARGET_BUILD_BIN = true
 
-# Link in libraries for the final executable.
-TARGET_BIN_LDFLAGS += -lrt
+# Link in libraries for the final executable and export symbols to dynamically
+# loaded objects.
+TARGET_BIN_LDFLAGS += -lrt -ldl -Wl,--export-dynamic
 endif
 
 include $(CHRE_PREFIX)/build/arch/x86.mk
diff --git a/build/variant/qcom_hexagonv60_nanohub-uimg.mk b/build/variant/qcom_hexagonv60_nanohub-uimg.mk
new file mode 100644
index 0000000..f57e26a
--- /dev/null
+++ b/build/variant/qcom_hexagonv60_nanohub-uimg.mk
@@ -0,0 +1,28 @@
+#
+# Qualcomm CHRE Implementation for Hexagon v60, based on Nanohub
+#
+
+include $(CHRE_PREFIX)/build/clean_build_template_args.mk
+
+TARGET_NAME = qcom_hexagonv60_nanohub-uimg
+TARGET_CFLAGS = -DCHRE_MESSAGE_TO_HOST_MAX_SIZE=4080
+TARGET_CFLAGS += -mno-pic-data-is-text-relative
+TARGET_CFLAGS += $(QCOM_HEXAGONV60_NANOHUB-UIMG_CFLAGS)
+TARGET_VARIANT_SRCS = $(QCOM_HEXAGONV60_NANOHUB-UIMG_SRCS)
+TARGET_SO_LATE_LIBS = $(QCOM_HEXAGONV60_NANOHUB-UIMG_LATE_LIBS)
+HEXAGON_ARCH = v60
+
+# Enable uImage support.
+TARGET_VARIANT_SRCS += $(SYS_SUPPORT_PATH)/qcom/uimg_dl_ver.c
+TARGET_SO_LDFLAGS += --script=$(SYS_SUPPORT_PATH)/qcom/uimage.lcs.toolv80
+
+ifneq ($(filter $(TARGET_NAME)% all, $(MAKECMDGOALS)),)
+ifneq ($(IS_NANOAPP_BUILD),)
+TARGET_SO_LATE_LIBS += $(CHRE_PREFIX)/build/app_support/qcom_nanohub/chre.so
+TARGET_SO_LATE_LIBS += $(CHRE_PREFIX)/build/app_support/qcom_nanohub/chre_platform.so
+include $(CHRE_PREFIX)/build/nanoapp/qcom_nanohub.mk
+endif
+
+include $(CHRE_PREFIX)/build/arch/hexagon.mk
+include $(CHRE_PREFIX)/build/build_template.mk
+endif
diff --git a/chre_api/include/chre_api/chre/gnss.h b/chre_api/include/chre_api/chre/gnss.h
index b0c2e37..b61cc5d 100644
--- a/chre_api/include/chre_api/chre/gnss.h
+++ b/chre_api/include/chre_api/chre/gnss.h
@@ -72,7 +72,7 @@
  * an event with the result of an asynchronous request, unless specified
  * otherwise
  */
-#define CHRE_GNSS_ASYNC_RESULT_TIMEOUT_NS  (5 * CHRE_NSEC_PER_SEC_U64)
+#define CHRE_GNSS_ASYNC_RESULT_TIMEOUT_NS  (5 * CHRE_NSEC_PER_SEC)
 
 /**
  * Produce an event ID in the block of IDs reserved for GNSS
diff --git a/core/core.mk b/core/core.mk
index 36cbec8..4f97164 100644
--- a/core/core.mk
+++ b/core/core.mk
@@ -16,7 +16,6 @@
 COMMON_SRCS += core/gnss_request_manager.cc
 COMMON_SRCS += core/host_comms_manager.cc
 COMMON_SRCS += core/init.cc
-COMMON_SRCS += core/memory_manager.cc
 COMMON_SRCS += core/nanoapp.cc
 COMMON_SRCS += core/sensor.cc
 COMMON_SRCS += core/sensor_request.cc
diff --git a/core/event_loop.cc b/core/event_loop.cc
index 5ab7739..4ee27ee 100644
--- a/core/event_loop.cc
+++ b/core/event_loop.cc
@@ -109,11 +109,17 @@
     // event is delivered to all interested Nanoapps, its free callback is
     // invoked.
     if (!havePendingEvents || !mEvents.empty()) {
+      if (mEvents.size() > mMaxEventPoolUsage) {
+        mMaxEventPoolUsage = mEvents.size();
+      }
+
       // mEvents.pop() will be a blocking call if mEvents.empty()
       distributeEvent(mEvents.pop());
     }
 
     havePendingEvents = deliverEvents();
+
+    mPowerControlManager.postEventLoopProcess(mEvents.size());
   }
 
   // Deliver any events sitting in Nanoapps' own queues (we could drop them to
@@ -291,6 +297,12 @@
   for (const UniquePtr<Nanoapp>& app : mNanoapps) {
     success &= app->logStateToBuffer(buffer, bufferPos, bufferSize);
   }
+
+  success &= debugDumpPrint(buffer, bufferPos, bufferSize,
+                            "\nEvent Loop:\n");
+  success &= debugDumpPrint(buffer, bufferPos, bufferSize,
+                            "  Max event pool usage: %zu/%zu\n",
+                            mMaxEventPoolUsage, kMaxEventCount);
   return success;
 }
 
diff --git a/core/event_loop_manager.cc b/core/event_loop_manager.cc
index f0a845f..50f16e2 100644
--- a/core/event_loop_manager.cc
+++ b/core/event_loop_manager.cc
@@ -91,4 +91,7 @@
   mWwanRequestManager.init();
 }
 
+// Explicitly instantiate the EventLoopManagerSingleton to reduce codesize.
+template class Singleton<EventLoopManager>;
+
 }  // namespace chre
diff --git a/core/include/chre/core/event_loop.h b/core/include/chre/core/event_loop.h
index 9f549a0..3afee6b 100644
--- a/core/include/chre/core/event_loop.h
+++ b/core/include/chre/core/event_loop.h
@@ -22,6 +22,7 @@
 #include "chre/core/timer_pool.h"
 #include "chre/platform/mutex.h"
 #include "chre/platform/platform_nanoapp.h"
+#include "chre/platform/power_control_manager.h"
 #include "chre/util/dynamic_vector.h"
 #include "chre/util/fixed_size_blocking_queue.h"
 #include "chre/util/non_copyable.h"
@@ -231,13 +232,22 @@
   bool logStateToBuffer(char *buffer, size_t *bufferPos,
                         size_t bufferSize) const;
 
+
+  /**
+   * Returns a reference to the power control manager. This allows power
+   * controls from subsystems outside the event loops.
+   */
+  PowerControlManager& getPowerControlManager() {
+    return mPowerControlManager;
+  }
+
  private:
   //! The maximum number of events that can be active in the system.
-  static constexpr size_t kMaxEventCount = 256;
+  static constexpr size_t kMaxEventCount = 96;
 
   //! The maximum number of events that are awaiting to be scheduled. These
   //! events are in a queue to be distributed to apps.
-  static constexpr size_t kMaxUnscheduledEventCount = 256;
+  static constexpr size_t kMaxUnscheduledEventCount = 96;
 
   //! The memory pool to allocate incoming events from.
   SynchronizedMemoryPool<Event, kMaxEventCount> mEventPool;
@@ -270,6 +280,12 @@
   //! Set to the nanoapp we are in the process of unloading in unloadNanoapp()
   Nanoapp *mStoppingNanoapp = nullptr;
 
+  //! The object which manages power related controls.
+  PowerControlManager mPowerControlManager;
+
+  //! The maximum number of events ever waiting in the event pool.
+  size_t mMaxEventPoolUsage = 0;
+
   /**
    * Do one round of Nanoapp event delivery, only considering events in
    * Nanoapps' own queues (not mEvents).
diff --git a/core/include/chre/core/event_loop_manager.h b/core/include/chre/core/event_loop_manager.h
index 7d6c71b..aaa57be 100644
--- a/core/include/chre/core/event_loop_manager.h
+++ b/core/include/chre/core/event_loop_manager.h
@@ -21,10 +21,10 @@
 #include "chre/core/event_loop.h"
 #include "chre/core/gnss_request_manager.h"
 #include "chre/core/host_comms_manager.h"
-#include "chre/core/memory_manager.h"
 #include "chre/core/sensor_request_manager.h"
 #include "chre/core/wifi_request_manager.h"
 #include "chre/core/wwan_request_manager.h"
+#include "chre/platform/memory_manager.h"
 #include "chre/platform/mutex.h"
 #include "chre/util/fixed_size_vector.h"
 #include "chre/util/non_copyable.h"
@@ -221,6 +221,10 @@
 //! Provide an alias to the EventLoopManager singleton.
 typedef Singleton<EventLoopManager> EventLoopManagerSingleton;
 
+//! Extern the explicit EventLoopManagerSingleton to force non-inline method
+//! calls. This reduces codesize considerably.
+extern template class Singleton<EventLoopManager>;
+
 }  // namespace chre
 
 #endif  // CHRE_CORE_EVENT_LOOP_MANAGER_H_
diff --git a/core/include/chre/core/wifi_request_manager.h b/core/include/chre/core/wifi_request_manager.h
index 6584e4f..e5fc4bb 100644
--- a/core/include/chre/core/wifi_request_manager.h
+++ b/core/include/chre/core/wifi_request_manager.h
@@ -20,6 +20,7 @@
 #include "chre/core/nanoapp.h"
 #include "chre/platform/platform_wifi.h"
 #include "chre/util/non_copyable.h"
+#include "chre/util/time.h"
 
 namespace chre {
 
@@ -353,6 +354,9 @@
    * @param eventData a pointer to the scan event to release.
    */
   static void freeWifiScanEventCallback(uint16_t eventType, void *eventData);
+
+  //! System time when last scan request was made.
+  Nanoseconds mLastScanRequestTime;
 };
 
 }  // namespace chre
diff --git a/core/sensor_request_manager.cc b/core/sensor_request_manager.cc
index 67e5ad5..0a8c2be 100644
--- a/core/sensor_request_manager.cc
+++ b/core/sensor_request_manager.cc
@@ -242,7 +242,8 @@
   Sensor *sensorPtr = nullptr;
   if (sensorType == SensorType::Unknown
       || sensorType >= SensorType::SENSOR_TYPE_COUNT) {
-    LOGW("Attempting to get Sensor of an invalid SensorType");
+    LOGW("Attempting to get Sensor of an invalid SensorType %d",
+         static_cast<int>(sensorType));
   } else {
     size_t sensorIndex = getSensorTypeArrayIndex(sensorType);
     if (mSensorRequests[sensorIndex].sensor.has_value()) {
diff --git a/core/tests/memory_manager_test.cc b/core/tests/memory_manager_test.cc
index 2c6674e..208d508 100644
--- a/core/tests/memory_manager_test.cc
+++ b/core/tests/memory_manager_test.cc
@@ -16,7 +16,7 @@
 
 #include "gtest/gtest.h"
 
-#include "chre/core/memory_manager.h"
+#include "chre/platform/memory_manager.h"
 #include "chre/platform/memory.h"
 #include "chre/platform/log.h"
 
@@ -41,14 +41,15 @@
   EXPECT_NE(ptr, nullptr);
   EXPECT_EQ(manager.getTotalAllocatedBytes(), 1);
   EXPECT_EQ(manager.getAllocationCount(), 1);
-  manager.nanoappFree(ptr);
+  manager.nanoappFree(&app, ptr);
   EXPECT_EQ(manager.getTotalAllocatedBytes(), 0);
   EXPECT_EQ(manager.getAllocationCount(), 0);
 }
 
 TEST(MemoryManager, NullPointerFree) {
   MemoryManager manager;
-  manager.nanoappFree(nullptr);
+  Nanoapp app;
+  manager.nanoappFree(&app, nullptr);
   EXPECT_EQ(manager.getTotalAllocatedBytes(), 0);
   EXPECT_EQ(manager.getAllocationCount(), 0);
 }
@@ -89,7 +90,7 @@
   curr = head;
   for (size_t i = 0; i < maxCount; i++) {
     node *temp = curr->next;
-    manager.nanoappFree(curr);
+    manager.nanoappFree(&app, curr);
     curr = temp;
   }
   EXPECT_EQ(manager.getTotalAllocatedBytes(), 0);
diff --git a/core/wifi_request_manager.cc b/core/wifi_request_manager.cc
index 877983d..cd79208 100644
--- a/core/wifi_request_manager.cc
+++ b/core/wifi_request_manager.cc
@@ -20,6 +20,7 @@
 #include "chre/core/wifi_request_manager.h"
 #include "chre/platform/fatal_error.h"
 #include "chre/platform/log.h"
+#include "chre/platform/system_time.h"
 #include "chre/util/system/debug_dump.h"
 
 namespace chre {
@@ -80,15 +81,28 @@
                                      const void *cookie) {
   CHRE_ASSERT(nanoapp);
 
+  // TODO(b/65331248): replace with a timer to actively check response timeout
+  bool timedOut = (mScanRequestingNanoappInstanceId.has_value()
+                   && mLastScanRequestTime
+                       + Nanoseconds(CHRE_WIFI_SCAN_RESULT_TIMEOUT_NS)
+                     < SystemTime::getMonotonicTime());
+  if (timedOut) {
+    LOGE("Scan request async response timed out");
+    mScanRequestingNanoappInstanceId.reset();
+  }
+
   bool success = false;
-  if (!mScanRequestingNanoappInstanceId.has_value()) {
+  if (mScanRequestingNanoappInstanceId.has_value()) {
+     LOGE("Active wifi scan request made while a request is in flight");
+  } else {
     success = mPlatformWifi.requestScan(params);
-    if (success) {
+    if (!success) {
+      LOGE("Wifi scan request failed");
+    } else {
       mScanRequestingNanoappInstanceId = nanoapp->getInstanceId();
       mScanRequestingNanoappCookie = cookie;
+      mLastScanRequestTime = SystemTime::getMonotonicTime();
     }
-  } else {
-    LOGE("Active wifi scan request made while a request is in flight");
   }
 
   return success;
@@ -415,10 +429,25 @@
 
 void WifiRequestManager::handleScanResponseSync(bool pending,
                                                 uint8_t errorCode) {
-  CHRE_ASSERT_LOG(mScanRequestingNanoappInstanceId.has_value(),
-                  "handleScanResponseSync called with no outstanding request");
+  // TODO(b/65206783): re-enable this assertion
+  //CHRE_ASSERT_LOG(mScanRequestingNanoappInstanceId.has_value(),
+  //                "handleScanResponseSync called with no outstanding request");
+  if (!mScanRequestingNanoappInstanceId.has_value()) {
+    LOGE("handleScanResponseSync called with no outstanding request");
+  }
+
+  // TODO: raise this to CHRE_ASSERT_LOG
+  if (!pending && errorCode == CHRE_ERROR_NONE) {
+    LOGE("Invalid wifi scan response");
+    errorCode = CHRE_ERROR;
+  }
+
   if (mScanRequestingNanoappInstanceId.has_value()) {
     bool success = (pending && errorCode == CHRE_ERROR_NONE);
+    if (!success) {
+      LOGW("Wifi scan request failed: pending %d, errorCode %" PRIu8,
+           pending, errorCode);
+    }
     postScanRequestAsyncResultEventFatal(*mScanRequestingNanoappInstanceId,
                                          success, errorCode,
                                          mScanRequestingNanoappCookie);
@@ -445,35 +474,36 @@
 }
 
 void WifiRequestManager::handleScanEventSync(chreWifiScanEvent *event) {
-  if (mScanRequestResultsArePending) {
-    // Reset the event distribution logic once an entire scan event has been
-    // received.
-    mScanEventResultCountAccumulator += event->resultCount;
-    if (mScanEventResultCountAccumulator >= event->resultTotal) {
-      mScanEventResultCountAccumulator = 0;
-      mScanRequestResultsArePending = false;
-    }
-  }
-
   postScanEventFatal(event);
 }
 
 void WifiRequestManager::handleFreeWifiScanEvent(chreWifiScanEvent *scanEvent) {
-  mPlatformWifi.releaseScanEvent(scanEvent);
-
-  if (!mScanRequestResultsArePending
-      && mScanRequestingNanoappInstanceId.has_value()) {
-    Nanoapp *nanoapp = EventLoopManagerSingleton::get()->getEventLoop()
-        .findNanoappByInstanceId(*mScanRequestingNanoappInstanceId);
-    if (nanoapp == nullptr) {
-      CHRE_ASSERT_LOG(false, "Attempted to unsubscribe unknown nanoapp from "
-                      "WiFi scan events");
-    } else if (!nanoappHasScanMonitorRequest(*mScanRequestingNanoappInstanceId)) {
-      nanoapp->unregisterForBroadcastEvent(CHRE_EVENT_WIFI_SCAN_RESULT);
+  if (mScanRequestResultsArePending) {
+    // Reset the event distribution logic once an entire scan event has been
+    // received and processed by the nanoapp requesting the scan event.
+    mScanEventResultCountAccumulator += scanEvent->resultCount;
+    if (mScanEventResultCountAccumulator >= scanEvent->resultTotal) {
+      mScanEventResultCountAccumulator = 0;
+      mScanRequestResultsArePending = false;
     }
 
-    mScanRequestingNanoappInstanceId.reset();
+    if (!mScanRequestResultsArePending
+        && mScanRequestingNanoappInstanceId.has_value()) {
+      Nanoapp *nanoapp = EventLoopManagerSingleton::get()->getEventLoop()
+          .findNanoappByInstanceId(*mScanRequestingNanoappInstanceId);
+      if (nanoapp == nullptr) {
+        CHRE_ASSERT_LOG(false, "Attempted to unsubscribe unknown nanoapp from "
+                        "WiFi scan events");
+      } else if (!nanoappHasScanMonitorRequest(
+          *mScanRequestingNanoappInstanceId)) {
+        nanoapp->unregisterForBroadcastEvent(CHRE_EVENT_WIFI_SCAN_RESULT);
+      }
+
+      mScanRequestingNanoappInstanceId.reset();
+    }
   }
+
+  mPlatformWifi.releaseScanEvent(scanEvent);
 }
 
 void WifiRequestManager::freeWifiScanEventCallback(uint16_t eventType,
diff --git a/external/flatbuffers/include/flatbuffers/flatbuffers.h b/external/flatbuffers/include/flatbuffers/flatbuffers.h
index 0d87eee..6f14b23 100644
--- a/external/flatbuffers/include/flatbuffers/flatbuffers.h
+++ b/external/flatbuffers/include/flatbuffers/flatbuffers.h
@@ -49,7 +49,7 @@
   // std::function) aren't strictly required, so setting it for now.
   #define FLATBUFFERS_CPP98_STL
 
-  #include "chre/platform/assert.h"
+  #include "chre/util/container_support.h"
   #include "chre/util/dynamic_vector.h"
   #include "chre/util/unique_ptr.h"
 
@@ -593,7 +593,7 @@
 
   uoffset_t size() const {
     assert(cur_ != nullptr && buf_ != nullptr);
-    return static_cast<uoffset_t>(reserved_ - (cur_ - buf_));
+    return static_cast<uoffset_t>(reserved_ - static_cast<size_t>(cur_ - buf_));
   }
 
   uint8_t *data() const {
diff --git a/host/common/socket_client.cc b/host/common/socket_client.cc
index f6e8047..ac2394c 100644
--- a/host/common/socket_client.cc
+++ b/host/common/socket_client.cc
@@ -19,10 +19,12 @@
 #include <inttypes.h>
 
 #include <string.h>
+#include <unistd.h>
 
 #include <chrono>
 
 #include <cutils/sockets.h>
+#include <sys/socket.h>
 #include <utils/RefBase.h>
 #include <utils/StrongPointer.h>
 
@@ -220,16 +222,38 @@
 }
 
 bool SocketClient::tryConnect(bool suppressErrorLogs) {
+  bool success = false;
+
   errno = 0;
-  mSockFd = socket_local_client(mSocketName,
-                                ANDROID_SOCKET_NAMESPACE_RESERVED,
-                                SOCK_SEQPACKET);
-  if (mSockFd == INVALID_SOCKET && !suppressErrorLogs) {
-    LOGE("Couldn't create/connect client socket to '%s': %s",
-         mSocketName, strerror(errno));
+  int sockFd = socket(AF_LOCAL, SOCK_SEQPACKET, 0);
+  if (sockFd >= 0) {
+    // Set the send buffer size to 2MB to allow plenty of room for nanoapp
+    // loading
+    int sndbuf = 2 * 1024 * 1024;
+    int ret = setsockopt(
+        sockFd, SOL_SOCKET, SO_SNDBUF, &sndbuf, sizeof(sndbuf));
+    if (ret == 0) {
+      mSockFd = socket_local_client_connect(
+          sockFd, mSocketName, ANDROID_SOCKET_NAMESPACE_RESERVED,
+          SOCK_SEQPACKET);
+      if (mSockFd != INVALID_SOCKET) {
+        success = true;
+      } else if (!suppressErrorLogs) {
+        LOGE("Couldn't connect client socket to '%s': %s",
+             mSocketName, strerror(errno));
+      }
+    } else if (!suppressErrorLogs) {
+      LOGE("Failed to set SO_SNDBUF to %d: %s", sndbuf, strerror(errno));
+    }
+
+    if (!success) {
+      close(sockFd);
+    }
+  } else if (!suppressErrorLogs) {
+    LOGE("Couldn't create local socket: %s", strerror(errno));
   }
 
-  return (mSockFd != INVALID_SOCKET);
+  return success;
 }
 
 }  // namespace chre
diff --git a/host/common/test/chre_test_client.cc b/host/common/test/chre_test_client.cc
index 602a6e3..cde7af3 100644
--- a/host/common/test/chre_test_client.cc
+++ b/host/common/test/chre_test_client.cc
@@ -147,7 +147,7 @@
   uint8_t messageData[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11};
   HostProtocolHost::encodeNanoappMessage(
       builder, chre::kMessageWorldAppId, kHostEndpoint,
-      1234 /* transactionId */, messageData, sizeof(messageData));
+      1234 /* messageType */, messageData, sizeof(messageData));
 
   LOGI("Sending message to nanoapp (%" PRIu32 " bytes w/%zu bytes of payload)",
        builder.GetSize(), sizeof(messageData));
diff --git a/host/hal_generic/generic_context_hub.cc b/host/hal_generic/generic_context_hub.cc
index 28c5b3e..b8962da 100644
--- a/host/hal_generic/generic_context_hub.cc
+++ b/host/hal_generic/generic_context_hub.cc
@@ -69,6 +69,15 @@
 
 }  // anonymous namespace
 
+GenericContextHub::DeathRecipient::DeathRecipient(
+    sp<GenericContextHub> contexthub) : mGenericContextHub(contexthub){}
+
+void GenericContextHub::DeathRecipient::serviceDied(
+    uint64_t cookie, const wp<::android::hidl::base::V1_0::IBase>& /* who */) {
+  uint32_t hubId = static_cast<uint32_t>(cookie);
+  mGenericContextHub->handleServiceDeath(hubId);
+}
+
 GenericContextHub::GenericContextHub() {
   constexpr char kChreSocketName[] = "chre";
 
@@ -76,6 +85,8 @@
   if (!mClient.connectInBackground(kChreSocketName, mSocketCallbacks)) {
     ALOGE("Couldn't start socket client");
   }
+
+  mDeathRecipient = new DeathRecipient(this);
 }
 
 Return<void> GenericContextHub::debug(
@@ -162,6 +173,18 @@
   // TODO: currently we only support 1 hub behind this HAL implementation
   if (hubId == kDefaultHubId) {
     std::lock_guard<std::mutex> lock(mCallbacksLock);
+
+    if (cb != nullptr) {
+      if (mCallbacks != nullptr) {
+        ALOGD("Modifying callback for hubId %" PRIu32, hubId);
+        mCallbacks->unlinkToDeath(mDeathRecipient);
+      }
+      Return<bool> linkReturn = cb->linkToDeath(mDeathRecipient, hubId);
+      if (!linkReturn.withDefault(false)) {
+        ALOGW("Could not link death recipient to hubId %" PRIu32, hubId);
+      }
+    }
+
     mCallbacks = cb;
     result = Result::OK;
   } else {
@@ -462,6 +485,11 @@
   }
 }
 
+void GenericContextHub::handleServiceDeath(uint32_t hubId) {
+  std::lock_guard<std::mutex> lock(mCallbacksLock);
+  ALOGI("Context hub service died for hubId %" PRIu32, hubId);
+  mCallbacks.clear();
+}
 
 IContexthub* HIDL_FETCH_IContexthub(const char* /* name */) {
   return new GenericContextHub();
diff --git a/host/hal_generic/generic_context_hub.h b/host/hal_generic/generic_context_hub.h
index a09f0b9..5409d65 100644
--- a/host/hal_generic/generic_context_hub.h
+++ b/host/hal_generic/generic_context_hub.h
@@ -112,7 +112,19 @@
     void invokeClientCallback(std::function<void()> callback);
   };
 
+  class DeathRecipient : public hidl_death_recipient {
+   public:
+    DeathRecipient(const sp<GenericContextHub> contexthub);
+    void serviceDied(uint64_t cookie,
+                     const wp<::android::hidl::base::V1_0::IBase>& who)
+        override;
+
+   private:
+    sp<GenericContextHub> mGenericContextHub;
+  };
+
   sp<SocketCallbacks> mSocketCallbacks;
+  sp<DeathRecipient> mDeathRecipient;
 
   // Cached hub info used for getHubs(), and synchronization primitives to make
   // that function call synchronous if we need to query it
@@ -130,6 +142,9 @@
   // Write a string to mDebugFd
   void writeToDebugFile(const char *str);
   void writeToDebugFile(const char *str, size_t len);
+
+  // Unregisters callback when context hub service dies
+  void handleServiceDeath(uint32_t hubId);
 };
 
 extern "C" IContexthub* HIDL_FETCH_IContexthub(const char* name);
diff --git a/host/msm/daemon/chre_daemon.cc b/host/msm/daemon/chre_daemon.cc
index b6563b5..3c13f04 100644
--- a/host/msm/daemon/chre_daemon.cc
+++ b/host/msm/daemon/chre_daemon.cc
@@ -179,7 +179,7 @@
   }
 }
 
-static int64_t getTimeOffset() {
+static int64_t getTimeOffset(bool *success) {
   int64_t timeOffset = 0;
 
 #if defined(__aarch64__)
@@ -191,18 +191,34 @@
   // Use uint64_t to store since the MRS instruction uses 64 bit (X) registers
   // (http://infocenter.arm.com/help/topic/
   // com.arm.doc.den0024a/ch06s05s02.html)
-  uint64_t qTimerCount = 0, qTimerFreqKHz = 0;
+  uint64_t qTimerCount = 0, qTimerFreq = 0;
   uint64_t hostTimeNano = elapsedRealtimeNano();
   asm volatile("mrs %0, cntpct_el0" : "=r"(qTimerCount));
-  asm volatile("mrs %0, cntfrq_el0" : "=r"(qTimerFreqKHz));
-  qTimerFreqKHz /= 1000;
+  asm volatile("mrs %0, cntfrq_el0" : "=r"(qTimerFreq));
 
-  if (qTimerFreqKHz != 0) {
-    uint64_t qTimerNanos = (qTimerCount < UINT64_MAX / 1000000) ?
-        (qTimerCount * 1000000) : UINT64_MAX;
-    qTimerNanos /= qTimerFreqKHz;
+  constexpr uint64_t kOneSecondInNanoseconds = 1000000000;
+  if (qTimerFreq != 0) {
+    // Get the seconds part first, then convert the remainder to prevent
+    // overflow
+    uint64_t qTimerNanos = (qTimerCount / qTimerFreq);
+    if (qTimerNanos > UINT64_MAX / kOneSecondInNanoseconds) {
+      LOGE("CNTPCT_EL0 conversion to nanoseconds overflowed during time sync."
+           " Aborting time sync.");
+      *success = false;
+    } else {
+      qTimerNanos *= kOneSecondInNanoseconds;
 
-    timeOffset = hostTimeNano - qTimerNanos;
+      // Round the remainder portion to the nearest nanosecond
+      uint64_t remainder = (qTimerCount % qTimerFreq);
+      qTimerNanos +=
+          (remainder * kOneSecondInNanoseconds + qTimerFreq / 2) / qTimerFreq;
+
+      timeOffset = hostTimeNano - qTimerNanos;
+      *success = true;
+    }
+  } else {
+    LOGE("CNTFRQ_EL0 had 0 value. Aborting time sync.");
+    *success = false;
   }
 #else
 #error "Unsupported CPU architecture type"
@@ -212,16 +228,19 @@
 }
 
 static void sendTimeSyncMessage() {
-  int64_t timeOffset = getTimeOffset();
+  bool timeSyncSuccess = true;
+  int64_t timeOffset = getTimeOffset(&timeSyncSuccess);
 
-  flatbuffers::FlatBufferBuilder builder(64);
-  HostProtocolHost::encodeTimeSyncMessage(builder, timeOffset);
-  int success = chre_slpi_deliver_message_from_host(
-      static_cast<const unsigned char *>(builder.GetBufferPointer()),
-      static_cast<int>(builder.GetSize()));
+  if (timeSyncSuccess) {
+    flatbuffers::FlatBufferBuilder builder(64);
+    HostProtocolHost::encodeTimeSyncMessage(builder, timeOffset);
+    int success = chre_slpi_deliver_message_from_host(
+        static_cast<const unsigned char *>(builder.GetBufferPointer()),
+        static_cast<int>(builder.GetSize()));
 
-  if (success != 0) {
-    LOGE("Failed to deliver timestamp message from host to CHRE: %d", success);
+    if (success != 0) {
+      LOGE("Failed to deliver timestamp message from host to CHRE: %d", success);
+    }
   }
 }
 
diff --git a/platform/include/chre/platform/condition_variable.h b/platform/include/chre/platform/condition_variable.h
index af638ff..83cd29e 100644
--- a/platform/include/chre/platform/condition_variable.h
+++ b/platform/include/chre/platform/condition_variable.h
@@ -20,6 +20,7 @@
 #include "chre/platform/mutex.h"
 #include "chre/target_platform/condition_variable_base.h"
 #include "chre/util/non_copyable.h"
+#include "chre/util/time.h"
 
 namespace chre {
 
@@ -57,6 +58,17 @@
    * @param The currently locked mutex.
    */
   void wait(Mutex& mutex);
+
+   /**
+   * Same behavior as the wait function, but with a timeout to unblock the
+   * calling thread if not notified within the timeout period.
+   *
+   * @param mutex The currently locked mutex.
+   * @param timeout The timeout duration in nanoseconds.
+   *
+   * @return False if timed out, true if notified.
+   */
+  bool wait_for(Mutex& mutex, Nanoseconds timeout);
 };
 
 }  // namespace chre
diff --git a/platform/include/chre/platform/memory.h b/platform/include/chre/platform/memory.h
index f44b4b3..205ecdd 100644
--- a/platform/include/chre/platform/memory.h
+++ b/platform/include/chre/platform/memory.h
@@ -32,15 +32,6 @@
  */
 void memoryFree(void *pointer);
 
-/**
- * Allocates memory for an object of size T and constructs the object in the
- * newly allocated object by forwarding the provided parameters.
- */
-template<typename T, typename... Args>
-T *memoryAlloc(Args&&... args);
-
 }  // namespace chre
 
-#include "chre/platform/memory_impl.h"
-
 #endif  // CHRE_PLATFORM_MEMORY_H_
diff --git a/platform/include/chre/platform/memory_impl.h b/platform/include/chre/platform/memory_impl.h
deleted file mode 100644
index 719082d..0000000
--- a/platform/include/chre/platform/memory_impl.h
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef CHRE_PLATFORM_MEMORY_IMPL_H_
-#define CHRE_PLATFORM_MEMORY_IMPL_H_
-
-#include <new>
-#include <utility>
-
-namespace chre {
-
-template<typename T, typename... Args>
-T *memoryAlloc(Args&&... args) {
-  auto *storage = static_cast<T *>(memoryAlloc(sizeof(T)));
-  if (storage != nullptr) {
-    new(storage) T(std::forward<Args>(args)...);
-  }
-
-  return storage;
-}
-
-}  // namespace chre
-
-#endif  // CHRE_PLATFORM_MEMORY_IMPL_H_
diff --git a/core/include/chre/core/memory_manager.h b/platform/include/chre/platform/memory_manager.h
similarity index 81%
rename from core/include/chre/core/memory_manager.h
rename to platform/include/chre/platform/memory_manager.h
index 5db51e0..3c566ed 100644
--- a/core/include/chre/core/memory_manager.h
+++ b/platform/include/chre/platform/memory_manager.h
@@ -14,8 +14,8 @@
  * limitations under the License.
  */
 
-#ifndef CHRE_CORE_MEMORY_MANAGER_H_
-#define CHRE_CORE_MEMORY_MANAGER_H_
+#ifndef CHRE_PLATFORM_MEMORY_MANAGER_H_
+#define CHRE_PLATFORM_MEMORY_MANAGER_H_
 
 #include <cstddef>
 #include <cstdint>
@@ -28,30 +28,27 @@
 /**
  * The MemoryManager keeps track of heap memory allocated/deallocated by all
  * nanoapps.
- * TODO: Free memory space when nanoapps are unloaded.
- * TODO: Move this implementation to platform-specific code area.
  *
+ * TODO: Free memory space when nanoapps are unloaded.
  */
 class MemoryManager : public NonCopyable {
  public:
   /**
-   * Initializes a MemoryManager.
-   */
-  MemoryManager();
-
-  /**
    * Allocate heap memory in CHRE.
+   *
+   * @param app The pointer to the nanoapp requesting memory.
    * @param bytes The size in bytes to allocate.
-   * @param app Pointer to the nanoapp requesting memory.
    * @return the allocated memory pointer. nullptr if the allocation fails.
    */
   void *nanoappAlloc(Nanoapp *app, uint32_t bytes);
 
   /**
    * Free heap memory in CHRE.
+   *
+   * @param app The pointer to the nanoapp requesting memory free.
    * @param ptr The pointer to the memory to deallocate.
    */
-  void nanoappFree(void *ptr);
+  void nanoappFree(Nanoapp *app, void *ptr);
 
   /**
    * @return current total allocated memory in bytes.
@@ -113,18 +110,32 @@
   };
 
   //! Stores total allocated memory in bytes (not including header).
-  size_t mTotalAllocatedBytes;
+  size_t mTotalAllocatedBytes = 0;
 
   //! Stores total number of allocated memory spaces.
-  size_t mAllocationCount;
+  size_t mAllocationCount = 0;
 
   //! The maximum allowable total allocated memory in bytes for all nanoapps.
   static constexpr size_t kMaxAllocationBytes = (128 * 1024);
 
   //! The maximum allowable count of memory allocations for all nanoapps.
   static constexpr size_t kMaxAllocationCount = (8 * 1024);
+
+  /**
+   * Called by nanoappAlloc to perform the appropriate call to memory alloc.
+   *
+   * The semantics are the same as nanoappAlloc.
+   */
+  void *doAlloc(Nanoapp *app, uint32_t size);
+
+  /**
+   * Called by nanoappFree to perform the appropriate call to memory free.
+   *
+   * The sematics are the same as nanoappFree.
+   */
+  void doFree(Nanoapp *app, void *ptr);
 };
 
 }  // namespace chre
 
-#endif  // CHRE_CORE_MEMORY_MANAGER_H_
+#endif  // CHRE_PLATFORM_MEMORY_MANAGER_H_
diff --git a/platform/include/chre/platform/power_control_manager.h b/platform/include/chre/platform/power_control_manager.h
new file mode 100644
index 0000000..79eb24a
--- /dev/null
+++ b/platform/include/chre/platform/power_control_manager.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef CHRE_PLATFORM_POWER_CONTROL_MANAGER_H
+#define CHRE_PLATFORM_POWER_CONTROL_MANAGER_H
+
+#include <cstddef>
+
+#include "chre/target_platform/power_control_manager_base.h"
+#include "chre/util/non_copyable.h"
+
+namespace chre {
+
+/**
+ * An abstraction for an entity that performs power-related controls for the
+ * underlying platform.
+ */
+class PowerControlManager : public PowerControlManagerBase,
+                            public NonCopyable {
+ public:
+  /**
+   * Perform power-related control after a single process of the event loop.
+   *
+   * @param numPendingEvents The current size of the event queue.
+   */
+  void postEventLoopProcess(size_t numPendingEvents);
+};
+
+} // namespace chre
+
+#endif // CHRE_PLATFORM_POWER_CONTROL_MANAGER_H
diff --git a/platform/linux/chre_api_re.cc b/platform/linux/chre_api_re.cc
index c6e2063..0e79f87 100644
--- a/platform/linux/chre_api_re.cc
+++ b/platform/linux/chre_api_re.cc
@@ -18,8 +18,28 @@
 
 #include "chre_api/chre/re.h"
 #include "chre/platform/log.h"
+#include "chre/util/macros.h"
 
-void chreAbort(uint32_t abortCode) {
-  LOGE("Aborting with error code %" PRIu32, abortCode);
-  abort();
+DLL_EXPORT void chreLog(enum chreLogLevel level, const char *formatStr, ...) {
+  char logBuf[512];
+  va_list args;
+
+  va_start(args, formatStr);
+  vsnprintf(logBuf, sizeof(logBuf), formatStr, args);
+  va_end(args);
+
+  switch (level) {
+    case CHRE_LOG_ERROR:
+      LOGE("%s", logBuf);
+      break;
+    case CHRE_LOG_WARN:
+      LOGW("%s", logBuf);
+      break;
+    case CHRE_LOG_INFO:
+      LOGI("%s", logBuf);
+      break;
+    case CHRE_LOG_DEBUG:
+    default:
+      LOGD("%s", logBuf);
+  }
 }
diff --git a/platform/linux/include/chre/target_platform/condition_variable_impl.h b/platform/linux/include/chre/target_platform/condition_variable_impl.h
index 2ec02bc..41aae9c 100644
--- a/platform/linux/include/chre/target_platform/condition_variable_impl.h
+++ b/platform/linux/include/chre/target_platform/condition_variable_impl.h
@@ -31,6 +31,12 @@
   mConditionVariable.wait(mutex);
 }
 
+inline bool ConditionVariable::wait_for(Mutex& mutex, Nanoseconds timeout) {
+  std::cv_status result = mConditionVariable.wait_for(
+      mutex, std::chrono::nanoseconds(timeout.toRawNanoseconds()));
+  return (result != std::cv_status::timeout);
+}
+
 }  // namespace chre
 
 #endif  // CHRE_PLATFORM_LINUX_CONDITION_VARIABLE_IMPL_H_
diff --git a/platform/linux/include/chre/target_platform/platform_nanoapp_base.h b/platform/linux/include/chre/target_platform/platform_nanoapp_base.h
index 1dd1543..4454596 100644
--- a/platform/linux/include/chre/target_platform/platform_nanoapp_base.h
+++ b/platform/linux/include/chre/target_platform/platform_nanoapp_base.h
@@ -18,6 +18,9 @@
 #define CHRE_PLATFORM_LINUX_PLATFORM_NANOAPP_BASE_H_
 
 #include <cstdint>
+#include <string>
+
+#include "chre/platform/shared/nanoapp_support_lib_dso.h"
 
 #include "chre/util/entry_points.h"
 
@@ -26,18 +29,71 @@
 /**
  * Linux-specific nanoapp functionality.
  */
-struct PlatformNanoappBase {
-  //! The function pointer of the nanoapp start entry point.
-  chreNanoappStartFunction *mStart;
+class PlatformNanoappBase {
+ public:
+  /**
+   * Associate this Nanoapp with a nanoapp included in a .so that is pre-loaded
+   * onto the filesystem. Actually loading the .so into memory is done when
+   * start() is called.
+   *
+   * @param filename The name of the .so file in /vendor/lib/dsp that holds this
+   *        nanoapp. This string is not deep-copied, so the memory must remain
+   *        valid for the lifetime of this Nanoapp instance.
+   */
+  void loadFromFile(const std::string& filename);
 
-  //! The function pointer of the nanoapp handle event entry point.
-  chreNanoappHandleEventFunction *mHandleEvent;
+  /**
+   * Associate this Nanoapp instance with a nanoapp that is statically built
+   * into the CHRE binary with the given app info structure.
+   */
+  void loadStatic(const struct chreNslNanoappInfo *appInfo);
 
-  //! The function pointer of the nanoapp end entry point.
-  chreNanoappEndFunction *mEnd;
+  /**
+   * @return true if the app's binary data is resident in memory, i.e. a
+   *         previous call to loadFromBuffer() or loadStatic() was successful
+   */
+  bool isLoaded() const;
 
-  uint64_t mAppId;
-  uint32_t mAppVersion;
+ protected:
+  //! The app ID we received in the metadata alongside the nanoapp binary. This
+  //! is also included in (and checked against) mAppInfo.
+  uint64_t mExpectedAppId;
+
+  //! The dynamic shared object (DSO) handle returned by dlopen.
+  void *mDsoHandle = nullptr;
+
+  //! Pointer to the app info structure within this nanoapp
+  const struct chreNslNanoappInfo *mAppInfo = nullptr;
+
+  bool mIsStatic = false;
+
+  //! If this is a pre-loaded, but non-static nanoapp (i.e. loaded from
+  //! loadFromFile), this will be set to the filename string to pass to dlopen()
+  std::string mFilename;
+
+  /**
+   * Calls through to openNanoappFromFile if the nanoapp was loaded from a
+   * shared object or returns true if the nanoapp is static.
+   *
+   * @return true if the nanoapp was loaded successfully.
+   */
+  bool openNanoapp();
+
+  /**
+   * Calls dlopen on the app filename, and fetches and validates the app info
+   * pointer. This will result in execution of any on-load handlers (e.g.
+   * static global constructors) in the nanoapp.
+   *
+   * @return true if the app was opened successfully and the app info
+   *         structure passed validation
+   */
+  bool openNanoappFromFile();
+
+  /**
+   * Releases the DSO handle if it was active, by calling dlclose(). This will
+   * result in execution of any unload handlers in the nanoapp.
+   */
+  void closeNanoapp();
 };
 
 }  // namespace chre
diff --git a/platform/shared/memory.cc b/platform/linux/include/chre/target_platform/power_control_manager_base.h
similarity index 68%
copy from platform/shared/memory.cc
copy to platform/linux/include/chre/target_platform/power_control_manager_base.h
index c137eb8..908e361 100644
--- a/platform/shared/memory.cc
+++ b/platform/linux/include/chre/target_platform/power_control_manager_base.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2016 The Android Open Source Project
+ * Copyright (C) 2017 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,18 +14,13 @@
  * limitations under the License.
  */
 
-#include "chre/platform/memory.h"
-
-#include <stdlib.h>
+#ifndef CHRE_PLATFORM_POWER_CONTROL_MANAGER_BASE_H
+#define CHRE_PLATFORM_POWER_CONTROL_MANAGER_BASE_H
 
 namespace chre {
 
-void *memoryAlloc(size_t size) {
-  return malloc(size);
-}
+class PowerControlManagerBase {};
 
-void memoryFree(void *pointer) {
-  free(pointer);
-}
+} // namespace chre
 
-}  // namespace chre
+#endif // CHRE_PLATFORM_POWER_CONTROL_MANAGER_BASE_H
diff --git a/platform/linux/include/chre/target_platform/static_nanoapp_init.h b/platform/linux/include/chre/target_platform/static_nanoapp_init.h
index c53a122..5d92fdf 100644
--- a/platform/linux/include/chre/target_platform/static_nanoapp_init.h
+++ b/platform/linux/include/chre/target_platform/static_nanoapp_init.h
@@ -29,23 +29,33 @@
  * when creating the global instance of the nanoapp.
  * @param appId the app's unique 64-bit ID
  */
-#define CHRE_STATIC_NANOAPP_INIT(appName, appId, appVersion) \
-namespace chre {                                             \
-                                                             \
-UniquePtr<Nanoapp> initializeStaticNanoapp##appName() {      \
-  UniquePtr<Nanoapp> nanoapp = MakeUnique<Nanoapp>();        \
-  if (nanoapp.isNull()) {                                    \
-    FATAL_ERROR("Failed to allocate nanoapp " #appName);     \
-  } else {                                                   \
-    nanoapp->mStart = nanoappStart;                          \
-    nanoapp->mHandleEvent = nanoappHandleEvent;              \
-    nanoapp->mEnd = nanoappEnd;                              \
-    nanoapp->mAppId = appId;                                 \
-    nanoapp->mAppVersion = appVersion;                       \
-  }                                                          \
-                                                             \
-  return nanoapp;                                            \
-}                                                            \
+#define CHRE_STATIC_NANOAPP_INIT(appName, appId_, appVersion_)   \
+namespace chre {                                                 \
+                                                                 \
+UniquePtr<Nanoapp> initializeStaticNanoapp##appName() {          \
+  UniquePtr<Nanoapp> nanoapp = MakeUnique<Nanoapp>();            \
+  static struct chreNslNanoappInfo appInfo;                      \
+  appInfo.magic = CHRE_NSL_NANOAPP_INFO_MAGIC;                   \
+  appInfo.structMinorVersion =                                   \
+    CHRE_NSL_NANOAPP_INFO_STRUCT_MINOR_VERSION;                  \
+  appInfo.targetApiVersion = CHRE_API_VERSION;                   \
+  appInfo.vendor = "Google"; /* TODO: make this configurable */  \
+  appInfo.name = #appName;                                       \
+  appInfo.isSystemNanoapp = true;                                \
+  appInfo.isTcmNanoapp = false;                                  \
+  appInfo.appId = appId_;                                        \
+  appInfo.appVersion = appVersion_;                              \
+  appInfo.entryPoints.start = nanoappStart;                      \
+  appInfo.entryPoints.handleEvent = nanoappHandleEvent;          \
+  appInfo.entryPoints.end = nanoappEnd;                          \
+  if (nanoapp.isNull()) {                                        \
+    FATAL_ERROR("Failed to allocate nanoapp " #appName);         \
+  } else {                                                       \
+    nanoapp->loadStatic(&appInfo);                               \
+  }                                                              \
+                                                                 \
+  return nanoapp;                                                \
+}                                                                \
 }  /* namespace chre */
 
 #endif  // CHRE_PLATFORM_LINUX_STATIC_NANOAPP_INIT_H_
diff --git a/platform/linux/init.cc b/platform/linux/init.cc
index b1fa069..d7cb749 100644
--- a/platform/linux/init.cc
+++ b/platform/linux/init.cc
@@ -27,11 +27,20 @@
 #include "chre/util/time.h"
 
 #include <csignal>
+#include <tclap/CmdLine.h>
 #include <thread>
 
 using chre::EventLoopManagerSingleton;
 using chre::Milliseconds;
 
+//! A description of the simulator.
+constexpr char kSimDescription[] = "A simulation environment for the Context "
+                                   "Hub Runtime Environment (CHRE)";
+
+//! The version of the simulator. This is not super important but is assigned by
+//! rules of semantic versioning.
+constexpr char kSimVersion[] = "0.1.0";
+
 namespace {
 
 extern "C" void signalHandler(int sig) {
@@ -42,21 +51,46 @@
 
 }
 
-int main() {
-  chre::PlatformLogSingleton::init();
-  chre::init();
+int main(int argc, char **argv) {
+  try {
+    // Parse command-line arguments.
+    TCLAP::CmdLine cmd(kSimDescription, ' ', kSimVersion);
+    TCLAP::SwitchArg no_static_nanoapps_arg("", "no_static_nanoapps",
+        "disable running static nanoapps", cmd, false);
+    TCLAP::MultiArg<std::string> nanoapps_arg("", "nanoapp",
+        "a nanoapp shared object to load and execute", false, "path", cmd);
+    cmd.parse(argc, argv);
 
-  // Register a signal handler.
-  std::signal(SIGINT, signalHandler);
+    // Initialize the system.
+    chre::PlatformLogSingleton::init();
+    chre::init();
 
-  // Load any static nanoapps and start the event loop.
-  std::thread chreThread([&]() {
-    chre::loadStaticNanoapps();
-    EventLoopManagerSingleton::get()->getEventLoop().run();
-  });
-  chreThread.join();
+    // Register a signal handler.
+    std::signal(SIGINT, signalHandler);
 
-  chre::deinit();
-  chre::PlatformLogSingleton::deinit();
+    // Load any static nanoapps and start the event loop.
+    std::thread chreThread([&]() {
+      // Load static nanoapps unless they are disabled by a command-line flag.
+      if (!no_static_nanoapps_arg.getValue()) {
+        chre::loadStaticNanoapps();
+      }
+
+      // Load dynamic nanoapps specified on the command-line.
+      chre::DynamicVector<chre::UniquePtr<chre::Nanoapp>> dynamicNanoapps;
+      for (const auto& nanoapp : nanoapps_arg.getValue()) {
+        dynamicNanoapps.push_back(chre::MakeUnique<chre::Nanoapp>());
+        dynamicNanoapps.back()->loadFromFile(nanoapp);
+        EventLoopManagerSingleton::get()->getEventLoop()
+            .startNanoapp(dynamicNanoapps.back());
+      }
+
+      EventLoopManagerSingleton::get()->getEventLoop().run();
+    });
+    chreThread.join();
+
+    chre::deinit();
+    chre::PlatformLogSingleton::deinit();
+  } catch (TCLAP::ExitException) {}
+
   return 0;
 }
diff --git a/platform/shared/memory.cc b/platform/linux/memory.cc
similarity index 80%
rename from platform/shared/memory.cc
rename to platform/linux/memory.cc
index c137eb8..ad92c85 100644
--- a/platform/shared/memory.cc
+++ b/platform/linux/memory.cc
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2016 The Android Open Source Project
+ * Copyright (C) 2017 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -24,8 +24,16 @@
   return malloc(size);
 }
 
+void *palSystemApiMemoryAlloc(size_t size) {
+  return malloc(size);
+}
+
 void memoryFree(void *pointer) {
   free(pointer);
 }
 
+void palSystemApiMemoryFree(void *pointer) {
+  free(pointer);
+}
+
 }  // namespace chre
diff --git a/platform/shared/memory.cc b/platform/linux/memory_manager.cc
similarity index 66%
copy from platform/shared/memory.cc
copy to platform/linux/memory_manager.cc
index c137eb8..6d55a64 100644
--- a/platform/shared/memory.cc
+++ b/platform/linux/memory_manager.cc
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2016 The Android Open Source Project
+ * Copyright (C) 2017 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,18 +14,18 @@
  * limitations under the License.
  */
 
-#include "chre/platform/memory.h"
+#include "chre/platform/memory_manager.h"
 
-#include <stdlib.h>
+#include "chre/util/memory.h"
 
 namespace chre {
 
-void *memoryAlloc(size_t size) {
-  return malloc(size);
+void *MemoryManager::doAlloc(Nanoapp *app, uint32_t bytes) {
+  return chre::memoryAlloc(bytes);
 }
 
-void memoryFree(void *pointer) {
-  free(pointer);
+void MemoryManager::doFree(Nanoapp *app, void *ptr) {
+  chre::memoryFree(ptr);
 }
 
 }  // namespace chre
diff --git a/platform/linux/platform_nanoapp.cc b/platform/linux/platform_nanoapp.cc
index 7b9eb7b..28be2a8 100644
--- a/platform/linux/platform_nanoapp.cc
+++ b/platform/linux/platform_nanoapp.cc
@@ -14,33 +14,43 @@
  * limitations under the License.
  */
 
-#include "chre_api/chre/version.h"
 #include "chre/platform/platform_nanoapp.h"
 
+#include <cinttypes>
+#include <dlfcn.h>
+
+#include "chre_api/chre/version.h"
+#include "chre/platform/assert.h"
+#include "chre/platform/log.h"
+#include "chre/platform/shared/nanoapp_dso_util.h"
+
 namespace chre {
 
-PlatformNanoapp::~PlatformNanoapp() {}
+PlatformNanoapp::~PlatformNanoapp() {
+  closeNanoapp();
+}
 
 bool PlatformNanoapp::start() {
-  return mStart();
+  return openNanoapp() && mAppInfo->entryPoints.start();
 }
 
 void PlatformNanoapp::handleEvent(uint32_t senderInstanceId,
                                   uint16_t eventType,
                                   const void *eventData) {
-  mHandleEvent(senderInstanceId, eventType, eventData);
+  mAppInfo->entryPoints.handleEvent(senderInstanceId, eventType, eventData);
 }
 
 void PlatformNanoapp::end() {
-  mEnd();
+  mAppInfo->entryPoints.end();
+  closeNanoapp();
 }
 
 uint64_t PlatformNanoapp::getAppId() const {
-  return mAppId;
+  return (mAppInfo == nullptr ? 0 : mAppInfo->appId);
 }
 
 uint32_t PlatformNanoapp::getAppVersion() const {
-  return mAppVersion;
+  return mAppInfo->appVersion;
 }
 
 uint32_t PlatformNanoapp::getTargetApiVersion() const {
@@ -56,4 +66,74 @@
   return true;
 }
 
+void PlatformNanoappBase::loadFromFile(const std::string& filename) {
+  CHRE_ASSERT(!isLoaded());
+  mFilename = filename;
+}
+
+void PlatformNanoappBase::loadStatic(const struct chreNslNanoappInfo *appInfo) {
+  CHRE_ASSERT(!isLoaded());
+  mIsStatic = true;
+  mAppInfo = appInfo;
+}
+
+bool PlatformNanoappBase::isLoaded() const {
+  return (mIsStatic || mDsoHandle != nullptr);
+}
+
+bool PlatformNanoappBase::openNanoapp() {
+  bool success = false;
+
+  if (mIsStatic) {
+    success = true;
+  } else if (!mFilename.empty()) {
+    success = openNanoappFromFile();
+  } else {
+    CHRE_ASSERT(false);
+  }
+
+  return success;
+}
+
+bool PlatformNanoappBase::openNanoappFromFile() {
+  CHRE_ASSERT(!mFilename.empty());
+  CHRE_ASSERT_LOG(mDsoHandle == nullptr, "Re-opening nanoapp");
+  bool success = false;
+
+  mDsoHandle = dlopen(mFilename.c_str(), RTLD_NOW | RTLD_GLOBAL);
+  if (mDsoHandle == nullptr) {
+    LOGE("Failed to load nanoapp from file %s: %s",
+         mFilename.c_str(), dlerror());
+  } else {
+    mAppInfo = static_cast<const struct chreNslNanoappInfo *>(
+        dlsym(mDsoHandle, CHRE_NSL_DSO_NANOAPP_INFO_SYMBOL_NAME));
+    if (mAppInfo == nullptr) {
+      LOGE("Failed to find app info symbol in %s: %s",
+           mFilename.c_str(), dlerror());
+    } else {
+      success = validateAppInfo(0 /* skip ID validation */, 0, mAppInfo,
+                                true /* ignoreAppVersion */);
+      if (!success) {
+        mAppInfo = nullptr;
+      } else {
+        LOGI("Successfully loaded nanoapp %s (0x%016" PRIx64 ") version 0x%"
+             PRIx32 " uimg %d system %d from file %s", mAppInfo->name,
+             mAppInfo->appId, mAppInfo->appVersion, mAppInfo->isTcmNanoapp,
+             mAppInfo->isSystemNanoapp, mFilename.c_str());
+      }
+    }
+  }
+
+  return success;
+}
+
+void PlatformNanoappBase::closeNanoapp() {
+  if (mDsoHandle != nullptr) {
+    if (dlclose(mDsoHandle) != 0) {
+      LOGE("dlclose failed: %s", dlerror());
+    }
+    mDsoHandle = nullptr;
+  }
+}
+
 }  // namespace chre
diff --git a/platform/shared/memory.cc b/platform/linux/platform_pal.cc
similarity index 72%
copy from platform/shared/memory.cc
copy to platform/linux/platform_pal.cc
index c137eb8..1ca25c9 100644
--- a/platform/shared/memory.cc
+++ b/platform/linux/platform_pal.cc
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2016 The Android Open Source Project
+ * Copyright (C) 2017 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,18 +14,10 @@
  * limitations under the License.
  */
 
-#include "chre/platform/memory.h"
-
-#include <stdlib.h>
+#include "chre/platform/shared/platform_pal.h"
 
 namespace chre {
 
-void *memoryAlloc(size_t size) {
-  return malloc(size);
-}
-
-void memoryFree(void *pointer) {
-  free(pointer);
-}
+void PlatformPal::prePalApiCall() {}
 
 }  // namespace chre
diff --git a/platform/shared/memory.cc b/platform/linux/power_control_manager.cc
similarity index 70%
copy from platform/shared/memory.cc
copy to platform/linux/power_control_manager.cc
index c137eb8..2b06b61 100644
--- a/platform/shared/memory.cc
+++ b/platform/linux/power_control_manager.cc
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2016 The Android Open Source Project
+ * Copyright (C) 2017 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,18 +14,10 @@
  * limitations under the License.
  */
 
-#include "chre/platform/memory.h"
-
-#include <stdlib.h>
+#include "chre/platform/power_control_manager.h"
 
 namespace chre {
 
-void *memoryAlloc(size_t size) {
-  return malloc(size);
-}
+void PowerControlManager::postEventLoopProcess(size_t numPendingEvents) {}
 
-void memoryFree(void *pointer) {
-  free(pointer);
-}
-
-}  // namespace chre
+} // namespace chre
diff --git a/platform/platform.mk b/platform/platform.mk
index 9002c86..c3d1ec1 100644
--- a/platform/platform.mk
+++ b/platform/platform.mk
@@ -31,20 +31,27 @@
 HEXAGON_SRCS += platform/shared/chre_api_wwan.cc
 HEXAGON_SRCS += platform/shared/host_protocol_chre.cc
 HEXAGON_SRCS += platform/shared/host_protocol_common.cc
-HEXAGON_SRCS += platform/shared/memory.cc
+HEXAGON_SRCS += platform/shared/memory_manager.cc
+HEXAGON_SRCS += platform/shared/nanoapp/nanoapp_dso_util.cc
 HEXAGON_SRCS += platform/shared/pal_system_api.cc
 HEXAGON_SRCS += platform/shared/platform_gnss.cc
 HEXAGON_SRCS += platform/shared/platform_wifi.cc
 HEXAGON_SRCS += platform/shared/platform_wwan.cc
 HEXAGON_SRCS += platform/shared/system_time.cc
+HEXAGON_SRCS += platform/slpi/chre_api_re.cc
 HEXAGON_SRCS += platform/slpi/fatal_error.cc
 HEXAGON_SRCS += platform/slpi/host_link.cc
 HEXAGON_SRCS += platform/slpi/init.cc
+HEXAGON_SRCS += platform/slpi/memory.cc
+HEXAGON_SRCS += platform/slpi/memory_manager.cc
 HEXAGON_SRCS += platform/slpi/platform_log.cc
 HEXAGON_SRCS += platform/slpi/platform_nanoapp.cc
+HEXAGON_SRCS += platform/slpi/platform_pal.cc
 HEXAGON_SRCS += platform/slpi/platform_sensor.cc
 HEXAGON_SRCS += platform/slpi/platform_sensor_util.cc
+HEXAGON_SRCS += platform/slpi/power_control_manager.cc
 HEXAGON_SRCS += platform/slpi/preloaded_nanoapps.cc
+HEXAGON_SRCS += platform/slpi/smr_helper.cc
 HEXAGON_SRCS += platform/slpi/system_time.cc
 HEXAGON_SRCS += platform/slpi/system_timer.cc
 
@@ -55,10 +62,15 @@
 
 # x86-specific Source Files ####################################################
 
+X86_SRCS += platform/linux/chre_api_re.cc
 X86_SRCS += platform/linux/context.cc
 X86_SRCS += platform/linux/fatal_error.cc
 X86_SRCS += platform/linux/host_link.cc
+X86_SRCS += platform/linux/memory.cc
+X86_SRCS += platform/linux/memory_manager.cc
 X86_SRCS += platform/linux/platform_log.cc
+X86_SRCS += platform/linux/platform_pal.cc
+X86_SRCS += platform/linux/power_control_manager.cc
 X86_SRCS += platform/linux/system_time.cc
 X86_SRCS += platform/linux/system_timer.cc
 X86_SRCS += platform/linux/platform_nanoapp.cc
@@ -70,7 +82,8 @@
 X86_SRCS += platform/shared/chre_api_version.cc
 X86_SRCS += platform/shared/chre_api_wifi.cc
 X86_SRCS += platform/shared/chre_api_wwan.cc
-X86_SRCS += platform/shared/memory.cc
+X86_SRCS += platform/shared/memory_manager.cc
+X86_SRCS += platform/shared/nanoapp/nanoapp_dso_util.cc
 X86_SRCS += platform/shared/pal_gnss_stub.cc
 X86_SRCS += platform/shared/pal_wifi_stub.cc
 X86_SRCS += platform/shared/pal_wwan_stub.cc
diff --git a/platform/shared/chre_api_core.cc b/platform/shared/chre_api_core.cc
index 13a92a2..976b63b 100644
--- a/platform/shared/chre_api_core.cc
+++ b/platform/shared/chre_api_core.cc
@@ -116,27 +116,3 @@
   chre::Nanoapp *nanoapp = EventLoopManager::validateChreApiCall(__func__);
   nanoapp->configureNanoappInfoEvents(enable);
 }
-
-DLL_EXPORT void chreLog(enum chreLogLevel level, const char *formatStr, ...) {
-  char logBuf[512];
-  va_list args;
-
-  va_start(args, formatStr);
-  vsnprintf(logBuf, sizeof(logBuf), formatStr, args);
-  va_end(args);
-
-  switch (level) {
-    case CHRE_LOG_ERROR:
-      LOGE("%s", logBuf);
-      break;
-    case CHRE_LOG_WARN:
-      LOGW("%s", logBuf);
-      break;
-    case CHRE_LOG_INFO:
-      LOGI("%s", logBuf);
-      break;
-    case CHRE_LOG_DEBUG:
-    default:
-      LOGD("%s", logBuf);
-  }
-}
diff --git a/platform/shared/chre_api_re.cc b/platform/shared/chre_api_re.cc
index d35e5f5..58dcfa5 100644
--- a/platform/shared/chre_api_re.cc
+++ b/platform/shared/chre_api_re.cc
@@ -63,5 +63,7 @@
 }
 
 DLL_EXPORT void chreHeapFree(void *ptr) {
-  chre::EventLoopManagerSingleton::get()->getMemoryManager().nanoappFree(ptr);
+  chre::Nanoapp *nanoapp = EventLoopManager::validateChreApiCall(__func__);
+  chre::EventLoopManagerSingleton::get()->getMemoryManager().
+      nanoappFree(nanoapp, ptr);
 }
diff --git a/platform/shared/include/chre/platform/shared/nanoapp_dso_util.h b/platform/shared/include/chre/platform/shared/nanoapp_dso_util.h
new file mode 100644
index 0000000..bd9d0eb
--- /dev/null
+++ b/platform/shared/include/chre/platform/shared/nanoapp_dso_util.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef CHRE_PLATFORM_NANOAPP_DSO_UTIL_H_
+#define CHRE_PLATFORM_NANOAPP_DSO_UTIL_H_
+
+#include "chre/platform/shared/nanoapp_support_lib_dso.h"
+
+namespace chre {
+
+/**
+ * Performs sanity checks on the app info structure included in a dynamically
+ * loaded nanoapp.
+ *
+ * @param expectedAppId The app ID passed alongside the binary
+ * @param expectedAppVersion The app version number passed alongside the binary
+ * @param appInfo App info structure included in the nanoapp binary
+ * @param skipVersionValidation if true, ignore the expectedAppVersion parameter
+ *
+ * @return true if validation was successful
+ */
+bool validateAppInfo(uint64_t expectedAppId, uint32_t expectedAppVersion,
+                     const struct chreNslNanoappInfo *appInfo,
+                     bool skipVersionValidation = false);
+
+}  // namespace chre
+
+#endif  // CHRE_PLATFORM_NANOAPP_DSO_UTIL_H_
diff --git a/platform/shared/include/chre/platform/shared/nanoapp_support_lib_dso.h b/platform/shared/include/chre/platform/shared/nanoapp_support_lib_dso.h
index e824927..6617879 100644
--- a/platform/shared/include/chre/platform/shared/nanoapp_support_lib_dso.h
+++ b/platform/shared/include/chre/platform/shared/nanoapp_support_lib_dso.h
@@ -40,7 +40,7 @@
 #define CHRE_NSL_NANOAPP_INFO_MAGIC  UINT32_C(0x50e69977)
 
 //! The minor version in the nanoapp info structure helps identify whether
-#define CHRE_NSL_NANOAPP_INFO_STRUCT_MINOR_VERSION  UINT8_C(0)
+#define CHRE_NSL_NANOAPP_INFO_STRUCT_MINOR_VERSION  UINT8_C(1)
 
 //! The symbol name expected from the nanoapp's definition of its info struct
 #define CHRE_NSL_DSO_NANOAPP_INFO_SYMBOL_NAME  "_chreNslDsoNanoappInfo"
@@ -65,9 +65,16 @@
   //! functionality beneath the HAL.
   uint8_t isSystemNanoapp:1;
 
+  //! Set to 1 if this nanoapp runs in tightly coupled memory. This flag is only
+  //! relevant to platforms that have the ability to run nanoapps within tightly
+  //! coupled memory.
+  //!
+  //! @since minor version 1
+  uint8_t isTcmNanoapp:1;
+
   //! Reserved for future use, set to 0. Assignment of this field to some use
   //! must be accompanied by an increase of the struct minor version.
-  uint8_t reservedFlags:7;
+  uint8_t reservedFlags:6;
   uint8_t reserved;
 
   //! The CHRE API version that the nanoapp was compiled against
diff --git a/platform/shared/include/chre/platform/shared/pal_system_api.h b/platform/shared/include/chre/platform/shared/pal_system_api.h
index a5217e8..0413d83 100644
--- a/platform/shared/include/chre/platform/shared/pal_system_api.h
+++ b/platform/shared/include/chre/platform/shared/pal_system_api.h
@@ -18,6 +18,20 @@
 
 namespace chre {
 
+/**
+ * Memory allocation coming from the PAL. The semantics are the same as malloc.
+ * This function needs to be implemented by the platform, and is not provided by
+ * the shared code.
+ */
+void *palSystemApiMemoryAlloc(size_t size);
+
+/**
+ * Memory free coming from the PAL. The semantics are the same as free.
+ * This function needs to be implemented by the platform, and is not provided by
+ * the shared code.
+ */
+void palSystemApiMemoryFree(void *pointer);
+
 //! Provides a global instance of the PAL system API for all PAL subsystems to
 //! leverage.
 extern const chrePalSystemApi gChrePalSystemApi;
diff --git a/platform/shared/include/chre/platform/shared/platform_pal.h b/platform/shared/include/chre/platform/shared/platform_pal.h
new file mode 100644
index 0000000..d3f004b
--- /dev/null
+++ b/platform/shared/include/chre/platform/shared/platform_pal.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef CHRE_PLATFORM_SHARED_PLATFORM_PAL_H_
+#define CHRE_PLATFORM_SHARED_PLATFORM_PAL_H_
+
+namespace chre {
+
+/**
+ * Provides an instance of the PlatformPal class that uses the CHRE PAL.
+ */
+class PlatformPal {
+ protected:
+   /**
+    * Routine to be performed before any call to a platform PAL API.
+    */
+  void prePalApiCall();
+};
+
+}  // namespace chre
+
+#endif  // CHRE_PLATFORM_SHARED_PLATFORM_PAL_H_
diff --git a/platform/shared/include/chre/target_platform/platform_gnss_base.h b/platform/shared/include/chre/target_platform/platform_gnss_base.h
index 9031694..e5267d9 100644
--- a/platform/shared/include/chre/target_platform/platform_gnss_base.h
+++ b/platform/shared/include/chre/target_platform/platform_gnss_base.h
@@ -17,6 +17,7 @@
 #ifndef CHRE_PLATFORM_SHARED_PLATFORM_GNSS_BASE_H_
 #define CHRE_PLATFORM_SHARED_PLATFORM_GNSS_BASE_H_
 
+#include "chre/platform/shared/platform_pal.h"
 #include "chre/pal/gnss.h"
 
 namespace chre {
@@ -25,7 +26,7 @@
  * Provides an instance of the PlatformGnssBase class that uses the CHRE PAL to
  * access the wifi subsystem.
  */
-class PlatformGnssBase {
+class PlatformGnssBase : public PlatformPal {
  protected:
   //! The instance of the CHRE PAL API. This will be set to nullptr if the
   //! platform does not supply an implementation.
diff --git a/platform/shared/include/chre/target_platform/platform_wifi_base.h b/platform/shared/include/chre/target_platform/platform_wifi_base.h
index a2e919c..09b166d 100644
--- a/platform/shared/include/chre/target_platform/platform_wifi_base.h
+++ b/platform/shared/include/chre/target_platform/platform_wifi_base.h
@@ -17,6 +17,7 @@
 #ifndef CHRE_PLATFORM_SHARED_PLATFORM_WIFI_BASE_H_
 #define CHRE_PLATFORM_SHARED_PLATFORM_WIFI_BASE_H_
 
+#include "chre/platform/shared/platform_pal.h"
 #include "chre/pal/wifi.h"
 
 namespace chre {
@@ -25,7 +26,7 @@
  * Provides an instance of the PlatformWifiBase class that uses the CHRE PAL to
  * access the wifi subsystem.
  */
-class PlatformWifiBase {
+class PlatformWifiBase : public PlatformPal {
  protected:
   //! The instance of the CHRE PAL API. This will be set to nullptr if the
   //! platform does not supply an implementation.
diff --git a/platform/shared/include/chre/target_platform/platform_wwan_base.h b/platform/shared/include/chre/target_platform/platform_wwan_base.h
index a6c0648..f45684b 100644
--- a/platform/shared/include/chre/target_platform/platform_wwan_base.h
+++ b/platform/shared/include/chre/target_platform/platform_wwan_base.h
@@ -17,6 +17,7 @@
 #ifndef CHRE_PLATFORM_SHARED_PLATFORM_WWAN_BASE_H_
 #define CHRE_PLATFORM_SHARED_PLATFORM_WWAN_BASE_H_
 
+#include "chre/platform/shared/platform_pal.h"
 #include "chre/pal/wwan.h"
 
 namespace chre {
@@ -25,7 +26,7 @@
  * Provides an instance of the PlatformWwanBase class that uses the CHRE PAL to
  * access the WWAN subsystem.
  */
-class PlatformWwanBase {
+class PlatformWwanBase : public PlatformPal {
  protected:
   //! The instance of the CHRE PAL API. This will be set to nullptr if the
   //! platform does not supply an implementation.
diff --git a/core/memory_manager.cc b/platform/shared/memory_manager.cc
similarity index 89%
rename from core/memory_manager.cc
rename to platform/shared/memory_manager.cc
index ed18539..810ae10 100644
--- a/core/memory_manager.cc
+++ b/platform/shared/memory_manager.cc
@@ -14,14 +14,12 @@
  * limitations under the License.
  */
 
-#include "chre/core/memory_manager.h"
+#include "chre/platform/memory_manager.h"
+
 #include "chre/util/system/debug_dump.h"
 
 namespace chre {
 
-MemoryManager::MemoryManager()
-    : mTotalAllocatedBytes(0), mAllocationCount(0) {}
-
 void *MemoryManager::nanoappAlloc(Nanoapp *app, uint32_t bytes) {
   AllocHeader *header = nullptr;
   if (bytes > 0) {
@@ -33,7 +31,7 @@
            ": not enough space.", app->getInstanceId());
     } else {
       header = static_cast<AllocHeader*>(
-          chre::memoryAlloc(sizeof(AllocHeader) + bytes));
+          doAlloc(app, sizeof(AllocHeader) + bytes));
 
       if (header != nullptr) {
         mTotalAllocatedBytes += bytes;
@@ -47,7 +45,7 @@
   return header;
 }
 
-void MemoryManager::nanoappFree(void *ptr) {
+void MemoryManager::nanoappFree(Nanoapp *app, void *ptr) {
   if (ptr != nullptr) {
     AllocHeader *header = static_cast<AllocHeader*>(ptr);
     header--;
@@ -61,7 +59,7 @@
       mAllocationCount--;
     }
 
-    memoryFree(header);
+    doFree(app, header);
   }
 }
 
diff --git a/platform/shared/nanoapp/nanoapp_dso_util.cc b/platform/shared/nanoapp/nanoapp_dso_util.cc
new file mode 100644
index 0000000..5b48976
--- /dev/null
+++ b/platform/shared/nanoapp/nanoapp_dso_util.cc
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "chre/platform/shared/nanoapp_dso_util.h"
+
+#include <cinttypes>
+#include <cstring>
+#include <chre/version.h>
+
+#include "chre/platform/log.h"
+
+namespace chre {
+
+bool validateAppInfo(uint64_t expectedAppId, uint32_t expectedAppVersion,
+                     const struct chreNslNanoappInfo *appInfo,
+                     bool skipVersionValidation) {
+  uint32_t ourApiMajorVersion = CHRE_EXTRACT_MAJOR_VERSION(chreGetApiVersion());
+  uint32_t targetApiMajorVersion = CHRE_EXTRACT_MAJOR_VERSION(
+      appInfo->targetApiVersion);
+
+  bool success = false;
+  if (appInfo->magic != CHRE_NSL_NANOAPP_INFO_MAGIC) {
+    LOGE("Invalid app info magic: got 0x%08" PRIx32 " expected 0x%08" PRIx32,
+         appInfo->magic, static_cast<uint32_t>(CHRE_NSL_NANOAPP_INFO_MAGIC));
+  } else if (appInfo->appId == 0) {
+    LOGE("Rejecting invalid app ID 0");
+  } else if (expectedAppId != 0 && expectedAppId != appInfo->appId) {
+    LOGE("Expected app ID (0x%016" PRIx64 ") doesn't match internal one (0x%016"
+         PRIx64 ")", expectedAppId, appInfo->appId);
+  } else if (!skipVersionValidation
+      && expectedAppVersion != appInfo->appVersion) {
+    LOGE("Expected app version (0x%" PRIx32 ") doesn't match internal one (0x%"
+         PRIx32 ")", expectedAppVersion, appInfo->appVersion);
+  } else if (targetApiMajorVersion != ourApiMajorVersion) {
+    LOGE("App targets a different major API version (%" PRIu32 ") than what we "
+         "provide (%" PRIu32 ")", targetApiMajorVersion, ourApiMajorVersion);
+  } else if (strlen(appInfo->name) > CHRE_NSL_DSO_NANOAPP_STRING_MAX_LEN) {
+    LOGE("App name is too long");
+  } else if (strlen(appInfo->vendor) > CHRE_NSL_DSO_NANOAPP_STRING_MAX_LEN) {
+    LOGE("App vendor is too long");
+  } else {
+    success = true;
+  }
+
+  return success;
+}
+
+}  // namespace chre
diff --git a/platform/shared/nanoapp/nanoapp_support_lib_dso.c b/platform/shared/nanoapp/nanoapp_support_lib_dso.c
index 00b0331..d35c2ab 100644
--- a/platform/shared/nanoapp/nanoapp_support_lib_dso.c
+++ b/platform/shared/nanoapp/nanoapp_support_lib_dso.c
@@ -28,6 +28,12 @@
  * implement cross-version compatibility features as needed.
  */
 
+#ifdef CHRE_SLPI_UIMG_ENABLED
+static const int kIsTcmNanoapp = 1;
+#else
+static const int kIsTcmNanoapp = 0;
+#endif  // CHRE_SLPI_UIMG_ENABLED
+
 DLL_EXPORT const struct chreNslNanoappInfo _chreNslDsoNanoappInfo = {
   .magic = CHRE_NSL_NANOAPP_INFO_MAGIC,
   .structMinorVersion = CHRE_NSL_NANOAPP_INFO_STRUCT_MINOR_VERSION,
@@ -37,6 +43,7 @@
   .vendor = NANOAPP_VENDOR_STRING,
   .name = NANOAPP_NAME_STRING,
   .isSystemNanoapp = NANOAPP_IS_SYSTEM_NANOAPP,
+  .isTcmNanoapp = kIsTcmNanoapp,
   .appId = NANOAPP_ID,
   .appVersion = NANOAPP_VERSION,
 
diff --git a/platform/shared/pal_system_api.cc b/platform/shared/pal_system_api.cc
index e80a5e9..f633264 100644
--- a/platform/shared/pal_system_api.cc
+++ b/platform/shared/pal_system_api.cc
@@ -59,14 +59,6 @@
   }
 }
 
-void *palSystemApiMemoryAlloc(size_t size) {
-  return memoryAlloc(size);
-}
-
-void palSystemApiMemoryFree(void *pointer) {
-  memoryFree(pointer);
-}
-
 // Initialize the CHRE System API with function implementations provided above.
 const chrePalSystemApi gChrePalSystemApi = {
   CHRE_PAL_SYSTEM_API_CURRENT_VERSION, /* version */
diff --git a/platform/shared/platform_gnss.cc b/platform/shared/platform_gnss.cc
index 36298ad..3c0a561 100644
--- a/platform/shared/platform_gnss.cc
+++ b/platform/shared/platform_gnss.cc
@@ -27,12 +27,14 @@
 PlatformGnss::~PlatformGnss() {
   if (mGnssApi != nullptr) {
     LOGD("Platform GNSS closing");
+    prePalApiCall();
     mGnssApi->close();
     LOGD("Platform GNSS closed");
   }
 }
 
 void PlatformGnss::init() {
+  prePalApiCall();
   mGnssApi = chrePalGnssGetApi(CHRE_PAL_GNSS_API_CURRENT_VERSION);
   if (mGnssApi != nullptr) {
     mGnssCallbacks.requestStateResync =
@@ -57,6 +59,7 @@
 
 uint32_t PlatformGnss::getCapabilities() {
   if (mGnssApi != nullptr) {
+    prePalApiCall();
     return mGnssApi->getCapabilities();
   } else {
     return CHRE_GNSS_CAPABILITIES_NONE;
@@ -66,6 +69,7 @@
 bool PlatformGnss::controlLocationSession(bool enable, Milliseconds minInterval,
                                           Milliseconds minTimeToNextFix) {
   if (mGnssApi != nullptr) {
+    prePalApiCall();
     return mGnssApi->controlLocationSession(enable,
         static_cast<uint32_t>(minInterval.getMilliseconds()),
         static_cast<uint32_t>(minTimeToNextFix.getMilliseconds()));
@@ -76,6 +80,7 @@
 
 void PlatformGnss::releaseLocationEvent(chreGnssLocationEvent *event) {
   if (mGnssApi != nullptr) {
+    prePalApiCall();
     mGnssApi->releaseLocationEvent(event);
   }
 }
diff --git a/platform/shared/platform_wifi.cc b/platform/shared/platform_wifi.cc
index e366a78..398f26c 100644
--- a/platform/shared/platform_wifi.cc
+++ b/platform/shared/platform_wifi.cc
@@ -27,12 +27,14 @@
 PlatformWifi::~PlatformWifi() {
   if (mWifiApi != nullptr) {
     LOGD("Platform WiFi closing");
+    prePalApiCall();
     mWifiApi->close();
     LOGD("Platform WiFi closed");
   }
 }
 
 void PlatformWifi::init() {
+  prePalApiCall();
   mWifiApi = chrePalWifiGetApi(CHRE_PAL_WIFI_API_CURRENT_VERSION);
   if (mWifiApi != nullptr) {
     mWifiCallbacks.scanMonitorStatusChangeCallback =
@@ -53,6 +55,7 @@
 
 uint32_t PlatformWifi::getCapabilities() {
   if (mWifiApi != nullptr) {
+    prePalApiCall();
     return mWifiApi->getCapabilities();
   } else {
     return CHRE_WIFI_CAPABILITIES_NONE;
@@ -61,6 +64,7 @@
 
 bool PlatformWifi::configureScanMonitor(bool enable) {
   if (mWifiApi != nullptr) {
+    prePalApiCall();
     return mWifiApi->configureScanMonitor(enable);
   } else {
     return false;
@@ -69,6 +73,7 @@
 
 bool PlatformWifi::requestScan(const struct chreWifiScanParams *params) {
   if (mWifiApi != nullptr) {
+    prePalApiCall();
     return mWifiApi->requestScan(params);
   } else {
     return false;
@@ -77,6 +82,7 @@
 
 void PlatformWifi::releaseScanEvent(struct chreWifiScanEvent *event) {
   if (mWifiApi != nullptr) {
+    prePalApiCall();
     mWifiApi->releaseScanEvent(event);
   }
 }
diff --git a/platform/shared/platform_wwan.cc b/platform/shared/platform_wwan.cc
index f107936..f958a2f 100644
--- a/platform/shared/platform_wwan.cc
+++ b/platform/shared/platform_wwan.cc
@@ -27,12 +27,14 @@
 PlatformWwan::~PlatformWwan() {
   if (mWwanApi != nullptr) {
     LOGD("Platform WWAN closing");
+    prePalApiCall();
     mWwanApi->close();
     LOGD("Platform WWAN closed");
   }
 }
 
 void PlatformWwan::init() {
+  prePalApiCall();
   mWwanApi = chrePalWwanGetApi(CHRE_PAL_WWAN_API_CURRENT_VERSION);
   if (mWwanApi != nullptr) {
     mWwanCallbacks.cellInfoResultCallback =
@@ -49,6 +51,7 @@
 
 uint32_t PlatformWwan::getCapabilities() {
   if (mWwanApi != nullptr) {
+    prePalApiCall();
     return mWwanApi->getCapabilities();
   } else {
     return CHRE_WWAN_CAPABILITIES_NONE;
@@ -57,6 +60,7 @@
 
 bool PlatformWwan::requestCellInfo() {
   if (mWwanApi != nullptr) {
+    prePalApiCall();
     return mWwanApi->requestCellInfo();
   } else {
     return false;
@@ -65,6 +69,7 @@
 
 void PlatformWwan::releaseCellInfoResult(chreWwanCellInfoResult *result) {
   if (mWwanApi != nullptr) {
+    prePalApiCall();
     mWwanApi->releaseCellInfoResult(result);
   }
 }
diff --git a/platform/slpi/chre_api_re.cc b/platform/slpi/chre_api_re.cc
new file mode 100644
index 0000000..b0adccc
--- /dev/null
+++ b/platform/slpi/chre_api_re.cc
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ash/debug.h"
+#include "chre_api/chre/re.h"
+#include "chre/util/macros.h"
+
+DLL_EXPORT void chreLog(enum chreLogLevel level, const char *formatStr, ...) {
+  enum ashLogLevel ashLevel;
+  va_list args;
+
+  switch (level) {
+    case CHRE_LOG_ERROR:
+      ashLevel = ASH_LOG_ERROR;
+      break;
+    case CHRE_LOG_WARN:
+      ashLevel = ASH_LOG_WARN;
+      break;
+    case CHRE_LOG_INFO:
+      ashLevel = ASH_LOG_INFO;
+      break;
+    case CHRE_LOG_DEBUG:
+    default:
+      ashLevel = ASH_LOG_DEBUG;
+  }
+
+  va_start(args, formatStr);
+  ashVaLog(ASH_SOURCE_CHRE, ashLevel, formatStr, args);
+  va_end(args);
+}
diff --git a/platform/slpi/host_link.cc b/platform/slpi/host_link.cc
index 0b9f007..ce37382 100644
--- a/platform/slpi/host_link.cc
+++ b/platform/slpi/host_link.cc
@@ -25,6 +25,7 @@
 #include "chre/platform/shared/host_protocol_chre.h"
 #include "chre/platform/shared/platform_log.h"
 #include "chre/platform/slpi/fastrpc.h"
+#include "chre/platform/slpi/power_control_util.h"
 #include "chre/platform/slpi/system_time.h"
 #include "chre/util/fixed_size_blocking_queue.h"
 #include "chre/util/macros.h"
@@ -142,6 +143,28 @@
 }
 
 /**
+ * Wrapper function to enqueue a message on the outbound message queue. All
+ * outgoing message to the host must be called through this function.
+ *
+ * @param message The message to send to host.
+ *
+ * @return true if the message was successfully added to the queue.
+ */
+bool enqueueMessage(PendingMessage message) {
+  // Vote for big image temporarily when waking up the main thread waiting for
+  // the message
+  bool voteSuccess = slpiForceBigImage();
+  bool success = gOutboundQueue.push(message);
+
+  // Remove the vote only if we successfully made a big image transition
+  if (voteSuccess) {
+    slpiRemoveBigImageVote();
+  }
+
+  return success;
+}
+
+/**
  * Helper function that takes care of the boilerplate for allocating a
  * FlatBufferBuilder on the heap and adding it to the outbound message queue.
  *
@@ -169,7 +192,7 @@
 
     // TODO: if this fails, ideally we should block for some timeout until
     // there's space in the queue
-    if (!gOutboundQueue.push(PendingMessage(msgType, builder.get()))) {
+    if (!enqueueMessage(PendingMessage(msgType, builder.get()))) {
       LOGE("Couldn't push message type %d to outbound queue",
            static_cast<int>(msgType));
     } else {
@@ -237,10 +260,13 @@
         startedSuccessfully);
   };
 
+  // Re-wrap the callback data struct, so it is destructed and freed, ensuring
+  // we don't leak the embedded UniquePtr<Nanoapp>
+  UniquePtr<LoadNanoappCallbackData> dataWrapped(
+      static_cast<LoadNanoappCallbackData *>(data));
   constexpr size_t kInitialBufferSize = 48;
   buildAndEnqueueMessage(PendingMessageType::LoadNanoappResponse,
                          kInitialBufferSize, msgBuilder, data);
-  memoryFree(data);
 }
 
 void handleUnloadNanoappCallback(uint16_t /*eventType*/, void *data) {
@@ -511,7 +537,7 @@
 }
 
 bool HostLink::sendMessage(const MessageToHost *message) {
-  return gOutboundQueue.push(
+  return enqueueMessage(
       PendingMessage(PendingMessageType::NanoappMessageToHost, message));
 }
 
@@ -535,7 +561,7 @@
   // a state where it's not blocked in chre_slpi_get_message_to_host().
   int retryCount = 5;
   FARF(MEDIUM, "Shutting down host link");
-  while (!gOutboundQueue.push(PendingMessage(PendingMessageType::Shutdown))
+  while (!enqueueMessage(PendingMessage(PendingMessageType::Shutdown))
          && --retryCount > 0) {
     qurt_timer_sleep(kPollingIntervalUsec);
   }
@@ -567,7 +593,7 @@
 }
 
 void requestHostLinkLogBufferFlush() {
-  if (!gOutboundQueue.push(PendingMessage(PendingMessageType::LogMessage))) {
+  if (!enqueueMessage(PendingMessage(PendingMessageType::LogMessage))) {
     // Use FARF as there is a problem sending logs to the host.
     FARF(ERROR, "Failed to enqueue log flush");
     CHRE_ASSERT(false);
@@ -590,7 +616,7 @@
 void HostMessageHandlers::handleHubInfoRequest(uint16_t hostClientId) {
   // We generate the response in the context of chre_slpi_get_message_to_host
   LOGD("Got hub info request from client ID %" PRIu16, hostClientId);
-  gOutboundQueue.push(PendingMessage(
+  enqueueMessage(PendingMessage(
       PendingMessageType::HubInfoResponse, hostClientId));
 }
 
diff --git a/platform/slpi/include/chre/platform/slpi/memory.h b/platform/slpi/include/chre/platform/slpi/memory.h
new file mode 100644
index 0000000..9a535d1
--- /dev/null
+++ b/platform/slpi/include/chre/platform/slpi/memory.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef CHRE_PLATFORM_SLPI_MEMORY_H_
+#define CHRE_PLATFORM_SLPI_MEMORY_H_
+
+#include <cstddef>
+
+namespace chre {
+
+/**
+ * Memory allocation specifically using the big image heap.
+ * The semantics are the same as malloc.
+ */
+void *memoryAllocBigImage(size_t size);
+
+/**
+ * Memory free from memory allocated using the big image heap.
+ * The semantics are the same as free.
+ */
+void memoryFreeBigImage(void *pointer);
+
+}  // namespace chre
+
+#endif  // CHRE_PLATFORM_SLPI_MEMORY_H_
diff --git a/platform/slpi/include/chre/platform/slpi/power_control_util.h b/platform/slpi/include/chre/platform/slpi/power_control_util.h
new file mode 100644
index 0000000..870b1c8
--- /dev/null
+++ b/platform/slpi/include/chre/platform/slpi/power_control_util.h
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef CHRE_PLATFORM_POWER_CONTROL_UTIL_H
+#define CHRE_PLATFORM_POWER_CONTROL_UTIL_H
+
+extern "C" {
+
+#include "qurt_island.h"
+
+}  // extern "C"
+
+#include "chre/core/event_loop_manager.h"
+#include "chre/platform/slpi/uimg_util.h"
+
+namespace chre {
+
+/**
+ * @return true if we're currently running in micro-image, aka island mode.
+ */
+inline bool slpiInUImage() {
+  return (qurt_island_get_status() == 1);
+}
+
+/**
+ * @return true if the successfully made a micro to big image transition.
+ */
+inline bool slpiForceBigImage() {
+  bool success = false;
+  if (isSlpiUimgSupported() && slpiInUImage()) {
+    success = EventLoopManagerSingleton::get()->getEventLoop().
+        getPowerControlManager().votePowerMode(SNS_IMG_MODE_BIG);
+  }
+
+  return success;
+}
+
+/**
+ * Removes a big image vote from CHRE. Should only be called when the system is
+ * idle.
+ *
+ * @return true if the vote succeeds.
+ */
+inline bool slpiRemoveBigImageVote() {
+  return EventLoopManagerSingleton::get()->getEventLoop().
+      getPowerControlManager().votePowerMode(SNS_IMG_MODE_NOCLIENT);
+}
+
+} // namespace chre
+
+#endif // CHRE_PLATFORM_POWER_CONTROL_UTIL_H
diff --git a/platform/slpi/include/chre/platform/slpi/smgr_client.h b/platform/slpi/include/chre/platform/slpi/smgr_client.h
index f0fd90e..2c3d116 100644
--- a/platform/slpi/include/chre/platform/slpi/smgr_client.h
+++ b/platform/slpi/include/chre/platform/slpi/smgr_client.h
@@ -17,18 +17,35 @@
 #ifndef CHRE_PLATFORM_SLPI_SMGR_CLIENT_H_
 #define CHRE_PLATFORM_SLPI_SMGR_CLIENT_H_
 
-extern "C" {
+#include "chre/platform/slpi/smr_helper.h"
+#include "chre/util/singleton.h"
 
-#include "qmi_client.h"
-
-}  // extern "C"
+/**
+ * @file
+ * Exposes the SMR helper and SMGR (non-internal) client handle used by the
+ * platform sensor implementation, for use in other modules.
+ */
 
 namespace chre {
 
+//! A singleton instance of SmrHelper that can be used for making synchronous
+//! sensor requests while remaining in micro-image. This must only be used from
+//! the CHRE thread.
+typedef Singleton<SmrHelper> SmrHelperSingleton;
+
 /**
- * @return A QMI sensor service client handle
+ * Convenience method for fetching the SMR helper singleton instance. Must only
+ * be used from the CHRE thread.
  */
-qmi_client_type getSensorServiceQmiClientHandle();
+inline SmrHelper *getSmrHelper() {
+  return SmrHelperSingleton::get();
+}
+
+/**
+ * @return The SMR client handle to the SMGR (non-internal) API, created by the
+ *         SLPI platform-specific sensors implementation
+ */
+smr_client_hndl getSensorServiceSmrClientHandle();
 
 }  // namespace chre
 
diff --git a/platform/slpi/include/chre/platform/slpi/smr_helper.h b/platform/slpi/include/chre/platform/slpi/smr_helper.h
new file mode 100644
index 0000000..a076501
--- /dev/null
+++ b/platform/slpi/include/chre/platform/slpi/smr_helper.h
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef CHRE_PLATFORM_SLPI_SMR_HELPER_H_
+#define CHRE_PLATFORM_SLPI_SMR_HELPER_H_
+
+#include <type_traits>
+
+extern "C" {
+
+#include "qurt.h"
+#include "sns_usmr.h"
+
+}
+
+#include "chre/platform/condition_variable.h"
+#include "chre/platform/mutex.h"
+#include "chre/util/non_copyable.h"
+#include "chre/util/time.h"
+#include "chre/util/unique_ptr.h"
+
+namespace chre {
+
+//! Default timeout for sendReqSync
+constexpr Nanoseconds kDefaultSmrTimeout = Seconds(1);
+
+//! Default timeout for waitForService. Have a longer timeout since there may be
+//! external dependencies blocking SMGR initialization.
+constexpr Nanoseconds kDefaultSmrWaitTimeout = Seconds(5);
+
+/**
+ * A helper class for making synchronous requests to SMR (Sensors Message
+ * Router). Not safe to use from multiple threads.
+ */
+class SmrHelper : public NonCopyable {
+ public:
+  /**
+   * Wrapper to convert the async smr_client_release() to a synchronous call.
+   *
+   * @param clientHandle SMR handle to release
+   * @param timeout How long to wait for the response before abandoning it
+   *
+   * @return Result code returned by smr_client_release(), or SMR_TIMEOUT_ERR if
+   *         the timeout was reached
+   */
+  smr_err releaseSync(smr_client_hndl clientHandle,
+                      Nanoseconds timeout = kDefaultSmrTimeout);
+
+  /**
+   * Wrapper to convert the async smr_client_send_req() to a synchronous call.
+   *
+   * Only one request can be pending at a time per instance of SmrHelper.
+   *
+   * @param ReqStruct QMI IDL-generated request structure
+   * @param RespStruct QMI IDL-generated response structure
+   * @param clientHandle SMR handle previously given by smr_client_init()
+   * @param msgId QMI message ID of the request to send
+   * @param req Pointer to populated request structure
+   * @param resp Pointer to structure to receive the response
+   * @param timeout How long to wait for the response before abandoning it
+   *
+   * @return Result code returned by smr_client_send_req(), or SMR_TIMEOUT_ERR
+   *         if the supplied timeout was reached
+   */
+  template<typename ReqStruct, typename RespStruct>
+  smr_err sendReqSync(
+      smr_client_hndl clientHandle, unsigned int msgId,
+      UniquePtr<ReqStruct> *req, UniquePtr<RespStruct> *resp,
+      Nanoseconds timeout = kDefaultSmrTimeout) {
+    // Try to catch copy/paste errors at compile time - QMI always has a
+    // different struct definition for request and response
+    static_assert(!std::is_same<ReqStruct, RespStruct>::value,
+                  "Request and response structures must be different");
+
+    smr_err result;
+    bool timedOut = !sendReqSyncUntyped(
+        clientHandle, msgId, req->get(), sizeof(ReqStruct),
+        resp->get(), sizeof(RespStruct), timeout, &result);
+
+    // Unlike QMI, SMR does not support canceling an in-flight transaction.
+    // SMR's internal request structure maintains a pointer to the client
+    // request and response buffers, so in the event of a timeout, it is unsafe
+    // for us to free the memory because the service may try to send the
+    // response later on.
+    if (timedOut) {
+      req->release();
+      resp->release();
+    }
+
+    return result;
+  }
+
+  /**
+   * Wrapper to convert the async smr_client_check_ext() to a synchronous call.
+   * Waits for an SMR service to become available.
+   *
+   * @param serviceObj The SMR service object to wait for.
+   * @param timeout The wait timeout in microseconds.
+   *
+   * @return Result code returned by smr_client_check_ext, or SMR_TIMEOUT_ERR if
+   *         the timeout was reached
+   */
+  smr_err waitForService(qmi_idl_service_object_type serviceObj,
+                         Microseconds timeout = kDefaultSmrWaitTimeout);
+
+ private:
+  /**
+   * Implements sendReqSync(), but with accepting untyped (void*) buffers.
+   * snake_case parameters exactly match those given to smr_client_send_req().
+   *
+   * @param timeout How long to wait for the response before abandoning it
+   * @param result If smr_client_send_req() returns an error, then that error
+   *        code, otherwise the transport error code given in the SMR response
+   *        callback (assuming there was no timeout)
+   * @return false on timeout, otherwise true (includes the case where
+   *         smr_client_send_req() returns an immediate error)
+   *
+   * @see sendReqSync()
+   * @see smr_client_send_req()
+   */
+  bool sendReqSyncUntyped(
+      smr_client_hndl client_handle, unsigned int msg_id,
+      void *req_c_struct, unsigned int req_c_struct_len,
+      void *resp_c_struct, unsigned int resp_c_struct_len,
+      Nanoseconds timeout, smr_err *result);
+
+
+  /**
+   * Processes an SMR response callback
+   *
+   * @see smr_client_resp_cb
+   */
+  void handleResp(smr_client_hndl client_handle, unsigned int msg_id,
+                  void *resp_c_struct, unsigned int resp_c_struct_len,
+                  smr_err transp_err);
+
+  /**
+   * SMR release complete callback used with releaseSync()
+   *
+   * @see smr_client_release_cb
+   */
+  static void smrReleaseCb(void *release_cb_data);
+
+  /**
+   * Extracts "this" from resp_cb_data and calls through to handleResp()
+   *
+   * @see smr_client_resp_cb
+   */
+  static void smrRespCb(smr_client_hndl client_handle, unsigned int msg_id,
+                        void *resp_c_struct, unsigned int resp_c_struct_len,
+                        void *resp_cb_data, smr_err transp_err);
+
+  /**
+   * SMR wait for service callback used with waitForService()
+   *
+   * @see smr_client_init_ext_cb
+   */
+  static void smrWaitForServiceCb(qmi_idl_service_object_type service_obj,
+                                  qmi_service_instance instance_id,
+                                  bool timeout_expired,
+                                  void *wait_for_service_cb_data);
+
+  ConditionVariable mCond;
+  Mutex mMutex;
+
+  //! true if we are waiting on an async response
+  bool mWaiting = false;
+
+  //! While waiting on a response, set to the response buffer given to
+  //! sendReqSync()
+  void *mPendingRespBuf = nullptr;
+
+  //! true if timed out while waiting for a service to become available
+  bool mServiceTimedOut = false;
+
+  //! The (transport) error code given in the response callback
+  smr_err mTranspErr;
+};
+
+}  // namespace chre
+
+#endif  // CHRE_PLATFORM_SLPI_SMR_HELPER_H_
diff --git a/platform/shared/memory.cc b/platform/slpi/include/chre/platform/slpi/uimg_util.h
similarity index 61%
copy from platform/shared/memory.cc
copy to platform/slpi/include/chre/platform/slpi/uimg_util.h
index c137eb8..01e0e38 100644
--- a/platform/shared/memory.cc
+++ b/platform/slpi/include/chre/platform/slpi/uimg_util.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2016 The Android Open Source Project
+ * Copyright (C) 2017 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,18 +14,22 @@
  * limitations under the License.
  */
 
-#include "chre/platform/memory.h"
-
-#include <stdlib.h>
+#ifndef CHRE_PLATFORM_SLPI_UIMG_UTIL_H_
+#define CHRE_PLATFORM_SLPI_UIMG_UTIL_H_
 
 namespace chre {
 
-void *memoryAlloc(size_t size) {
-  return malloc(size);
-}
-
-void memoryFree(void *pointer) {
-  free(pointer);
+/**
+ * @return true if CHRE on micro-image is supported
+ */
+constexpr bool isSlpiUimgSupported() {
+#ifdef CHRE_SLPI_UIMG_ENABLED
+  return true;
+#else
+  return false;
+#endif // CHRE_SLPI_UIMG_ENABLED
 }
 
 }  // namespace chre
+
+#endif  // CHRE_PLATFORM_SLPI_UIMG_UTIL_H_
diff --git a/platform/slpi/include/chre/target_platform/condition_variable_base.h b/platform/slpi/include/chre/target_platform/condition_variable_base.h
index bbbf514..6189cff 100644
--- a/platform/slpi/include/chre/target_platform/condition_variable_base.h
+++ b/platform/slpi/include/chre/target_platform/condition_variable_base.h
@@ -23,12 +23,20 @@
 
 }  // extern "C"
 
+#include "chre/platform/system_timer.h"
+
 namespace chre {
 
 class ConditionVariableBase {
  protected:
   //! The underlying QURT condition variable.
   qurt_cond_t mConditionVariable;
+
+  //! The timer used for timed condition variable wait.
+  SystemTimer mTimeoutTimer;
+
+  //! Set to true when the timeout timer is initialized.
+  bool mTimerInitialized = false;
 };
 
 }  // namespace chre
diff --git a/platform/slpi/include/chre/target_platform/condition_variable_impl.h b/platform/slpi/include/chre/target_platform/condition_variable_impl.h
index 603304b..2097233 100644
--- a/platform/slpi/include/chre/target_platform/condition_variable_impl.h
+++ b/platform/slpi/include/chre/target_platform/condition_variable_impl.h
@@ -19,6 +19,9 @@
 
 #include "chre/platform/condition_variable.h"
 
+#include "chre/platform/fatal_error.h"
+#include "chre/platform/log.h"
+
 namespace chre {
 
 inline ConditionVariable::ConditionVariable() {
@@ -37,6 +40,43 @@
   qurt_cond_wait(&mConditionVariable, &mutex.mMutex);
 }
 
+// Note: The wait_for function is designed to work for a single thread waiting
+// on the condition variable.
+inline bool ConditionVariable::wait_for(Mutex& mutex, Nanoseconds timeout) {
+  if (!mTimerInitialized) {
+    if (!mTimeoutTimer.init()) {
+      FATAL_ERROR("Failed to initialize condition variable timer");
+    } else {
+      mTimerInitialized = true;
+    }
+  }
+
+  struct TimeoutCallbackData {
+    ConditionVariable *cvPtr;
+    bool timedOut;
+  };
+  auto callback = [](void *data) {
+    auto cbData = static_cast<TimeoutCallbackData*>(data);
+    cbData->timedOut = true;
+    cbData->cvPtr->notify_one();
+  };
+
+  TimeoutCallbackData callbackData;
+  callbackData.cvPtr = this;
+  callbackData.timedOut = false;
+  if (!mTimeoutTimer.set(callback, &callbackData, timeout)) {
+    LOGE("Failed to set condition variable timer");
+  }
+
+  wait(mutex);
+  if (mTimeoutTimer.isActive()) {
+    if (!mTimeoutTimer.cancel()) {
+      LOGD("Failed to cancel condition variable timer");
+    }
+  }
+  return !callbackData.timedOut;
+}
+
 }  // namespace chre
 
 #endif  // CHRE_PLATFORM_SLPI_CONDITION_VARIABLE_IMPL_H_
diff --git a/platform/slpi/include/chre/target_platform/platform_nanoapp_base.h b/platform/slpi/include/chre/target_platform/platform_nanoapp_base.h
index 9750e88..8b6ed8e 100644
--- a/platform/slpi/include/chre/target_platform/platform_nanoapp_base.h
+++ b/platform/slpi/include/chre/target_platform/platform_nanoapp_base.h
@@ -70,6 +70,11 @@
    */
   bool isLoaded() const;
 
+  /**
+   * @return true if the app runs in micro-image.
+   */
+  bool isUimgApp() const;
+
  protected:
   //! The app ID we received in the metadata alongside the nanoapp binary. This
   //! is also included in (and checked against) mAppInfo.
@@ -100,6 +105,9 @@
   //! applicable.
   bool mIsStatic = false;
 
+  //! True if the nanoapp runs in micro-image.
+  bool mIsUimgApp = false;
+
   /**
    * Calls through to openNanoappFromBuffer or openNanoappFromFile, depending on
    * how this nanoapp was loaded.
diff --git a/platform/slpi/include/chre/target_platform/power_control_manager_base.h b/platform/slpi/include/chre/target_platform/power_control_manager_base.h
new file mode 100644
index 0000000..bb27c23
--- /dev/null
+++ b/platform/slpi/include/chre/target_platform/power_control_manager_base.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef CHRE_PLATFORM_POWER_CONTROL_MANAGER_BASE_H
+#define CHRE_PLATFORM_POWER_CONTROL_MANAGER_BASE_H
+
+extern "C" {
+
+#include "qurt.h"
+#include "sns_pm.h"
+
+} // extern "C"
+
+namespace chre {
+
+class PowerControlManagerBase {
+ public:
+  PowerControlManagerBase();
+
+  ~PowerControlManagerBase();
+
+  /**
+    * Votes for a power mode to the SLPI power manager.
+    *
+    * @param mode The power mode to vote for.
+    *
+    * @return true if the vote returned SNS_PM_SUCCESS.
+    */
+  bool votePowerMode(sns_pm_img_mode_e mode);
+
+ protected:
+  //! Client handle for the subscription to the power manager
+  sns_pm_handle_t mClientHandle = nullptr;
+};
+
+} // namespace chre
+
+#endif // CHRE_PLATFORM_POWER_CONTROL_MANAGER_BASE_H
diff --git a/platform/slpi/include/chre/target_platform/static_nanoapp_init.h b/platform/slpi/include/chre/target_platform/static_nanoapp_init.h
index 4567955..4b2e1f5 100644
--- a/platform/slpi/include/chre/target_platform/static_nanoapp_init.h
+++ b/platform/slpi/include/chre/target_platform/static_nanoapp_init.h
@@ -20,6 +20,7 @@
 #include "chre/core/static_nanoapps.h"
 #include "chre/platform/fatal_error.h"
 #include "chre/platform/shared/nanoapp_support_lib_dso.h"
+#include "chre/platform/slpi/uimg_util.h"
 
 /**
  * Initializes a static nanoapp that is based on the SLPI implementation of
@@ -43,6 +44,7 @@
   appInfo.vendor = "Google"; /* TODO: make this configurable */\
   appInfo.name = #appName;                                     \
   appInfo.isSystemNanoapp = true;                              \
+  appInfo.isTcmNanoapp = isSlpiUimgSupported();                \
   appInfo.appId = appId_;                                      \
   appInfo.appVersion = appVersion_;                            \
   appInfo.entryPoints.start = nanoappStart;                    \
diff --git a/platform/slpi/include/chre/target_platform/system_timer_base.h b/platform/slpi/include/chre/target_platform/system_timer_base.h
index 37eca4d..fcc39a7 100644
--- a/platform/slpi/include/chre/target_platform/system_timer_base.h
+++ b/platform/slpi/include/chre/target_platform/system_timer_base.h
@@ -19,11 +19,38 @@
 
 extern "C" {
 
-// TODO: Investigate switching to utimer.h. The symbols are not currently
-// exported by the static image. I have tested a static image with utimer
-// symbols exported but an SLPI crash occurs when the callback is invoked.
+#ifdef CHRE_SLPI_UIMG_ENABLED
+#include "utimer.h"
+
+typedef utimer_type SlpiTimerHandle;
+typedef utimer_cb_data_type SlpiTimerCallbackDataType;
+typedef utimer_timetick_type SlpiTimerTickType;
+typedef utimer_error_type SlpiTimerErrorType;
+
+#define SlpiTimerTickUnit UT_TICK
+#define SlpiTimerMicroUnit UT_USEC
+#define SLPI_TIMER_SUCCESS UTE_SUCCESS
+#define slpiTimerClr64 utimer_clr_64
+#define slpiTimerGet64 utimer_get_64
+#define slpiTimerSet64 utimer_set_64
+#define slpiTimerUndef utimer_undef
+#else
 #include "timer.h"
 
+typedef timer_type SlpiTimerHandle;
+typedef timer_cb_data_type SlpiTimerCallbackDataType;
+typedef time_timetick_type SlpiTimerTickType;
+typedef timer_error_type SlpiTimerErrorType;
+
+#define SlpiTimerTickUnit T_TICK
+#define SlpiTimerMicroUnit T_USEC
+#define SLPI_TIMER_SUCCESS TE_SUCCESS
+#define slpiTimerClr64 timer_clr_64
+#define slpiTimerGet64 timer_get_64
+#define slpiTimerSet64 timer_set_64
+#define slpiTimerUndef timer_undef
+#endif  // CHRE_SLPI_UIMG_ENABLED
+
 }  // extern "C"
 
 namespace chre {
@@ -31,13 +58,13 @@
 class SystemTimerBase {
  public:
   //! The underlying QURT timer.
-  timer_type mTimerHandle;
+  SlpiTimerHandle mTimerHandle;
 
   //! Tracks whether the timer has been initialized correctly.
   bool mInitialized = false;
 
   //! A static method that is invoked by the underlying QURT timer.
-  static void systemTimerNotifyCallback(timer_cb_data_type data);
+  static void systemTimerNotifyCallback(SlpiTimerCallbackDataType data);
 };
 
 }  // namespace chre
diff --git a/platform/slpi/init.cc b/platform/slpi/init.cc
index 5adc9ad..cd0e8ab 100644
--- a/platform/slpi/init.cc
+++ b/platform/slpi/init.cc
@@ -35,6 +35,7 @@
 #include "chre/platform/shared/platform_log.h"
 #include "chre/platform/slpi/fastrpc.h"
 #include "chre/platform/slpi/preloaded_nanoapps.h"
+#include "chre/platform/slpi/uimg_util.h"
 #include "chre/util/lock_guard.h"
 
 using chre::EventLoop;
@@ -57,9 +58,10 @@
 constexpr size_t kStackSize = (8 * 1024);
 
 //! Memory partition where the thread control block (TCB) should be stored,
-//! which controls micro-image support (0 = big image, 1 = micro image).
+//! which controls micro-image support.
 //! @see qurt_thread_attr_set_tcb_partition
-constexpr unsigned char kTcbPartition = 0;
+constexpr unsigned char kTcbPartition = chre::isSlpiUimgSupported() ?
+    QURT_THREAD_ATTR_TCB_PARTITION_TCM : QURT_THREAD_ATTR_TCB_PARTITION_RAM;
 
 //! The priority to set for the CHRE thread (value between 1-255, with 1 being
 //! the highest).
@@ -78,9 +80,7 @@
 
 //! Protects access to thread metadata, like gThreadRunning, during critical
 //! sections (starting/stopping the CHRE thread).
-Mutex *gThreadMutex;
-typename std::aligned_storage<sizeof(Mutex), alignof(Mutex)>::type
-    gThreadMutexStorage;
+Mutex gThreadMutex;
 
 //! Set to true when the CHRE thread starts, and false when it exits normally.
 bool gThreadRunning;
@@ -90,22 +90,14 @@
 int gTlsKey;
 bool gTlsKeyValid;
 
-// TODO: We would prefer to just use static global C++ constructor/destructor
-// support, but currently, destructors do not seem to get called. These work as
-// a temporary workaround, though.
 __attribute__((constructor))
 void onLoad(void) {
   // Initialize the platform logging as early as possible.
   chre::PlatformLogSingleton::init();
-
-  gThreadMutex = new(&gThreadMutexStorage) Mutex();
 }
 
 __attribute__((destructor))
 void onUnload(void) {
-  gThreadMutex->~Mutex();
-  gThreadMutex = nullptr;
-
   // Defer platform logging deinitialization to as late as possible.
   chre::PlatformLogSingleton::deinit();
 }
@@ -168,7 +160,7 @@
  */
 extern "C" int chre_slpi_start_thread(void) {
   // This lock ensures that we only start the thread once
-  LockGuard<Mutex> lock(*gThreadMutex);
+  LockGuard<Mutex> lock(gThreadMutex);
   int fastRpcResult = CHRE_FASTRPC_ERROR;
 
   if (gThreadRunning) {
@@ -238,7 +230,7 @@
 extern "C" int chre_slpi_stop_thread(void) {
   // This lock ensures that we will complete shutdown before the thread can be
   // started again
-  LockGuard<Mutex> lock(*gThreadMutex);
+  LockGuard<Mutex> lock(gThreadMutex);
 
   if (!gThreadRunning) {
     LOGD("Tried to stop CHRE thread, but not running");
@@ -287,7 +279,7 @@
  * @return 0 on success, nonzero on failure (per FastRPC requirements)
  */
 extern "C" int chre_slpi_initialize_reverse_monitor(void) {
-  LockGuard<Mutex> lock(*gThreadMutex);
+  LockGuard<Mutex> lock(gThreadMutex);
 
   if (!gTlsKeyValid) {
     int result = qurt_tls_create_key(&gTlsKey, onHostProcessTerminated);
diff --git a/platform/slpi/memory.cc b/platform/slpi/memory.cc
new file mode 100644
index 0000000..98a8913
--- /dev/null
+++ b/platform/slpi/memory.cc
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "chre/platform/memory.h"
+#include "chre/platform/slpi/memory.h"
+
+extern "C" {
+
+#include "qurt.h"
+#include "sns_memmgr.h"
+
+} // extern "C"
+
+namespace chre {
+
+void *memoryAlloc(size_t size) {
+#ifdef CHRE_SLPI_UIMG_ENABLED
+  return SNS_OS_U_MALLOC(SNS_CHRE, size);
+#else
+  return malloc(size);
+#endif // CHRE_SLPI_UIMG_ENABLED
+}
+
+void *memoryAllocBigImage(size_t size) {
+  return malloc(size);
+}
+
+void *palSystemApiMemoryAlloc(size_t size) {
+  return malloc(size);
+}
+
+void memoryFree(void *pointer) {
+#ifdef CHRE_SLPI_UIMG_ENABLED
+  SNS_OS_FREE(pointer);
+#else
+  free(pointer);
+#endif // CHRE_SLPI_UIMG_ENABLED
+}
+
+void memoryFreeBigImage(void *pointer) {
+  free(pointer);
+}
+
+void palSystemApiMemoryFree(void *pointer) {
+  free(pointer);
+}
+
+}  // namespace chre
diff --git a/platform/slpi/memory_manager.cc b/platform/slpi/memory_manager.cc
new file mode 100644
index 0000000..ad5eed0
--- /dev/null
+++ b/platform/slpi/memory_manager.cc
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "chre/platform/memory_manager.h"
+
+#include "chre/platform/slpi/memory.h"
+#include "chre/util/memory.h"
+
+namespace chre {
+
+void *MemoryManager::doAlloc(Nanoapp *app, uint32_t bytes) {
+  if (app->isUimgApp()) {
+    return chre::memoryAlloc(bytes);
+  } else {
+    return chre::memoryAllocBigImage(bytes);
+  }
+}
+
+void MemoryManager::doFree(Nanoapp *app, void *ptr) {
+  if (app->isUimgApp()) {
+    chre::memoryFree(ptr);
+  } else {
+    chre::memoryFreeBigImage(ptr);
+  }
+}
+
+}  // namespace chre
diff --git a/platform/slpi/platform_nanoapp.cc b/platform/slpi/platform_nanoapp.cc
index d8f6c87..f241840 100644
--- a/platform/slpi/platform_nanoapp.cc
+++ b/platform/slpi/platform_nanoapp.cc
@@ -16,10 +16,14 @@
 
 #include "chre/platform/platform_nanoapp.h"
 
+#include "chre/core/event_loop_manager.h"
 #include "chre/platform/assert.h"
 #include "chre/platform/log.h"
 #include "chre/platform/memory.h"
+#include "chre/platform/shared/nanoapp_dso_util.h"
 #include "chre/platform/shared/nanoapp_support_lib_dso.h"
+#include "chre/platform/slpi/memory.h"
+#include "chre/platform/slpi/power_control_util.h"
 #include "chre/util/system/debug_dump.h"
 #include "chre_api/chre/version.h"
 
@@ -30,74 +34,37 @@
 
 namespace chre {
 
-namespace {
-
-/**
- * Performs sanity checks on the app info structure included in a dynamically
- * loaded nanoapp.
- *
- * @param expectedAppId The app ID passed alongside the binary
- * @param expectedAppVersion The app version number passed alongside the binary
- * @param appInfo App info structure included in the nanoapp binary
- * @param skipVersionValidation if true, ignore the expectedAppVersion parameter
- *
- * @return true if validation was successful
- */
-bool validateAppInfo(uint64_t expectedAppId, uint32_t expectedAppVersion,
-                     const struct chreNslNanoappInfo *appInfo,
-                     bool skipVersionValidation = false) {
-  uint32_t ourApiMajorVersion = CHRE_EXTRACT_MAJOR_VERSION(chreGetApiVersion());
-  uint32_t targetApiMajorVersion = CHRE_EXTRACT_MAJOR_VERSION(
-      appInfo->targetApiVersion);
-
-  bool success = false;
-  if (appInfo->magic != CHRE_NSL_NANOAPP_INFO_MAGIC) {
-    LOGE("Invalid app info magic: got 0x%08" PRIx32 " expected 0x%08" PRIx32,
-         appInfo->magic, static_cast<uint32_t>(CHRE_NSL_NANOAPP_INFO_MAGIC));
-  } else if (appInfo->appId == 0) {
-    LOGE("Rejecting invalid app ID 0");
-  } else if (expectedAppId != appInfo->appId) {
-    LOGE("Expected app ID (0x%016" PRIx64 ") doesn't match internal one (0x%016"
-         PRIx64 ")", expectedAppId, appInfo->appId);
-  } else if (!skipVersionValidation
-      && expectedAppVersion != appInfo->appVersion) {
-    LOGE("Expected app version (0x%" PRIx32 ") doesn't match internal one (0x%"
-         PRIx32 ")", expectedAppVersion, appInfo->appVersion);
-  } else if (targetApiMajorVersion != ourApiMajorVersion) {
-    LOGE("App targets a different major API version (%" PRIu32 ") than what we "
-         "provide (%" PRIu32 ")", targetApiMajorVersion, ourApiMajorVersion);
-  } else if (strlen(appInfo->name) > CHRE_NSL_DSO_NANOAPP_STRING_MAX_LEN) {
-    LOGE("App name is too long");
-  } else if (strlen(appInfo->vendor) > CHRE_NSL_DSO_NANOAPP_STRING_MAX_LEN) {
-    LOGE("App vendor is too long");
-  } else {
-    success = true;
-  }
-
-  return success;
-}
-
-}  // anonymous namespace
-
 PlatformNanoapp::~PlatformNanoapp() {
   closeNanoapp();
   if (mAppBinary != nullptr) {
-    memoryFree(mAppBinary);
+    memoryFreeBigImage(mAppBinary);
   }
 }
 
 bool PlatformNanoapp::start() {
   // Invoke the start entry point after successfully opening the app
-  return openNanoapp() ? mAppInfo->entryPoints.start() : false;
+  if (!isUimgApp()) {
+    slpiForceBigImage();
+  }
+
+  return openNanoapp() && mAppInfo->entryPoints.start();
 }
 
 void PlatformNanoapp::handleEvent(uint32_t senderInstanceId,
                                   uint16_t eventType,
                                   const void *eventData) {
+  if (!isUimgApp()) {
+    slpiForceBigImage();
+  }
+
   mAppInfo->entryPoints.handleEvent(senderInstanceId, eventType, eventData);
 }
 
 void PlatformNanoapp::end() {
+  if (!isUimgApp()) {
+    slpiForceBigImage();
+  }
+
   mAppInfo->entryPoints.end();
   closeNanoapp();
 }
@@ -112,7 +79,7 @@
   if (appBinaryLen > kMaxAppSize) {
     LOGE("Rejecting app size %zu above limit %zu", appBinaryLen, kMaxAppSize);
   } else {
-    mAppBinary = memoryAlloc(appBinaryLen);
+    mAppBinary = memoryAllocBigImage(appBinaryLen);
     if (mAppBinary == nullptr) {
       LOGE("Couldn't allocate %zu byte buffer for nanoapp 0x%016" PRIx64,
            appBinaryLen, appId);
@@ -141,15 +108,17 @@
 }
 
 bool PlatformNanoappBase::isLoaded() const {
-  return (mIsStatic || mAppBinary != nullptr);
+  return (mIsStatic || mAppBinary != nullptr || mDsoHandle != nullptr);
+}
+
+bool PlatformNanoappBase::isUimgApp() const {
+  return mIsUimgApp;
 }
 
 void PlatformNanoappBase::closeNanoapp() {
   if (mDsoHandle != nullptr) {
-    mAppInfo = nullptr;
     if (dlclose(mDsoHandle) != 0) {
-      const char *name = (mAppInfo != nullptr) ? mAppInfo->name : "unknown";
-      LOGE("dlclose of %s failed: %s", name, dlerror());
+      LOGE("dlclose failed: %s", dlerror());
     }
     mDsoHandle = nullptr;
   }
@@ -168,6 +137,12 @@
     CHRE_ASSERT(false);
   }
 
+  // Save this flag locally since it may be referenced while the system is in
+  // micro-image
+  if (mAppInfo != nullptr) {
+    mIsUimgApp = mAppInfo->isTcmNanoapp;
+  }
+
   return success;
 }
 
@@ -197,7 +172,11 @@
         mAppInfo = nullptr;
       } else {
         LOGI("Successfully loaded nanoapp: %s (0x%016" PRIx64 ") version 0x%"
-             PRIx32, mAppInfo->name, mAppInfo->appId, mAppInfo->appVersion);
+             PRIx32 " uimg %d system %d", mAppInfo->name, mAppInfo->appId,
+             mAppInfo->appVersion, mAppInfo->isTcmNanoapp,
+             mAppInfo->isSystemNanoapp);
+        memoryFreeBigImage(mAppBinary);
+        mAppBinary = nullptr;
       }
     }
   }
@@ -225,8 +204,9 @@
         mAppInfo = nullptr;
       } else {
         LOGI("Successfully loaded nanoapp %s (0x%016" PRIx64 ") version 0x%"
-             PRIx32 " from file %s", mAppInfo->name, mAppInfo->appId,
-             mAppInfo->appVersion, mFilename);
+             PRIx32 " uimg %d system %d from file %s", mAppInfo->name,
+             mAppInfo->appId, mAppInfo->appVersion, mAppInfo->isTcmNanoapp,
+             mAppInfo->isSystemNanoapp, mFilename);
         // Save the app version field in case this app gets disabled and we
         // still get a query request for the version later on. We are OK not
         // knowing the version prior to the first load because we assume that
diff --git a/platform/shared/memory.cc b/platform/slpi/platform_pal.cc
similarity index 73%
copy from platform/shared/memory.cc
copy to platform/slpi/platform_pal.cc
index c137eb8..b7f41af 100644
--- a/platform/shared/memory.cc
+++ b/platform/slpi/platform_pal.cc
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2016 The Android Open Source Project
+ * Copyright (C) 2017 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,18 +14,14 @@
  * limitations under the License.
  */
 
-#include "chre/platform/memory.h"
+#include "chre/platform/shared/platform_pal.h"
 
-#include <stdlib.h>
+#include "chre/platform/slpi/power_control_util.h"
 
 namespace chre {
 
-void *memoryAlloc(size_t size) {
-  return malloc(size);
-}
-
-void memoryFree(void *pointer) {
-  free(pointer);
+void PlatformPal::prePalApiCall() {
+  slpiForceBigImage();
 }
 
 }  // namespace chre
diff --git a/platform/slpi/platform_sensor.cc b/platform/slpi/platform_sensor.cc
index d697b03..ea41757 100644
--- a/platform/slpi/platform_sensor.cc
+++ b/platform/slpi/platform_sensor.cc
@@ -22,9 +22,9 @@
 extern "C" {
 
 #include "fixed_point.h"
-#include "qmi_client.h"
 #include "sns_smgr_api_v01.h"
 #include "sns_smgr_internal_api_v02.h"
+#include "sns_usmr.h"
 #include "timetick.h"
 
 }  // extern "C"
@@ -38,20 +38,47 @@
 #include "chre/platform/system_time.h"
 #include "chre/platform/slpi/platform_sensor_util.h"
 #include "chre/platform/slpi/smgr_client.h"
+#include "chre/platform/slpi/smr_helper.h"
+#include "chre/platform/slpi/uimg_util.h"
 #include "chre/util/macros.h"
 
-// TODO: [Passive] explain passive sensor design
+// As SMGR doesn't support passive sensor request, it's now implemented on the
+// client (CHRE) side using a combination of the SNS_SMGR_INTERNAL_API_V02 and a
+// modified SNS_SMGR_API_V01.
+//
+// Here's a summary of its design:
+// 1. A sensor status monitor is added in addSensorMonitor() to receive the
+//    SNS_SMGR_SENSOR_STATUS_MONITOR_IND_V02 message the first time a sensor is
+//    requested.
+// 2. When a request is made in PlatformSensor::applyRequest(), it checkes
+//    whether it's allowed at that point and makes a corresponding QMI request.
+//    1) The request is allowed if
+//       - it's an active or an off request, or
+//       - it's a passive request and the merged mode (to be explained
+//         shortly) is active or there exist other SMGR clients.
+//    2) If the request is allowed, a QMI request to add the sensor request is
+//       made. Otherwise, a QMI request to remove the sensor request is made to
+//       handle the potential active-and-allowed to passive-and-disallowed
+//       transition.
+//    3) The merged mode of a sensor is the strongest mode of all sensor
+//       requests of the same sensor ID, with active > passive > off.
+// 3. When SNS_SMGR_SENSOR_STATUS_MONITOR_IND_V02 from SMGR is received, a
+//    SNS_SMGR_CLIENT_REQUEST_INFO_REQ_V01 message is sent to query SMGR on the
+//    existence of other clients.
+//    - If a transition from absence-to-presence of other clients is detected,
+//      all pending passive requests are made.
+//    - If a transition from presence-to-absence of other clients is deteted,
+//      all passive requests are removed if the merged mode is passive.
+//
+// Note that currently the sensor status monitor indication only supports
+// primary sensor status change. So for a secondary sensor that can be requested
+// without an accompanying primary sensor (Light), this design doesn't work.
+// In PlatformSensor::applyRequest(), a passive Light sensor request is
+// overridden to be an active one.
 
 namespace chre {
 namespace {
 
-//! Timeout for QMI client initialization, in milliseconds. Allow more time here
-//! due to external dependencies that may block initialization of SMGR.
-constexpr uint32_t kQmiInitTimeoutMs = 5000;
-
-//! The timeout for QMI messages in milliseconds.
-constexpr uint32_t kQmiTimeoutMs = 1000;
-
 //! The constant used to convert from SMGR to Android unit for magnetometer.
 constexpr float kMicroTeslaPerGauss = 100.0f;
 
@@ -65,22 +92,13 @@
 constexpr uint64_t kTickRolloverOffset =
     ((1ULL << 32) * Seconds(1).toRawNanoseconds()) / TIMETICK_NOMINAL_FREQ_HZ;
 
-//! The QMI sensor service client handle.
-qmi_client_type gPlatformSensorServiceQmiClientHandle = nullptr;
-
-//! The QMI sensor internal service client handle.
-qmi_client_type gPlatformSensorInternalServiceQmiClientHandle = nullptr;
-
-//! A sensor report indication for deserializing sensor sample indications
-//! into. This global instance is used to avoid thrashy use of the heap by
-//! allocating and freeing this on the heap for every new sensor sample. This
-//! relies on the assumption that the QMI callback is not reentrant.
-sns_smgr_buffering_ind_msg_v01 gSmgrBufferingIndMsg;
+smr_client_hndl gPlatformSensorServiceSmrClientHandle;
+smr_client_hndl gPlatformSensorInternalServiceSmrClientHandle;
 
 //! A struct to store the number of SMGR clients of a sensor ID.
 struct SensorMonitor {
   uint8_t sensorId;
-  uint8_t numClients;
+  bool otherClientPresent;
 };
 
 //! A vector that tracks the SensorMonitor of each supported sensor ID.
@@ -230,11 +248,12 @@
  *
  * @return true if it's a valid pair of indices length and report ID.
  */
-bool isValidIndicesLength() {
-  return ((gSmgrBufferingIndMsg.Indices_len == 1
-           && !isSecondaryTemperature(gSmgrBufferingIndMsg.ReportId))
-          || (gSmgrBufferingIndMsg.Indices_len == 2
-              && isSecondaryTemperature(gSmgrBufferingIndMsg.ReportId)));
+bool isValidIndicesLength(
+    const sns_smgr_buffering_ind_msg_v01& bufferingIndMsg) {
+  return ((bufferingIndMsg.Indices_len == 1
+           && !isSecondaryTemperature(bufferingIndMsg.ReportId))
+          || (bufferingIndMsg.Indices_len == 2
+              && isSecondaryTemperature(bufferingIndMsg.ReportId)));
 }
 
 /**
@@ -345,13 +364,14 @@
 }
 
 void populateThreeAxisEvent(
+    const sns_smgr_buffering_ind_msg_v01& bufferingIndMsg,
     SensorType sensorType, chreSensorThreeAxisData *data,
     const sns_smgr_buffering_sample_index_s_v01& sensorIndex) {
   populateSensorDataHeader(sensorType, &data->header, sensorIndex);
 
   for (size_t i = 0; i < sensorIndex.SampleCount; i++) {
     const sns_smgr_buffering_sample_s_v01& sensorData =
-        gSmgrBufferingIndMsg.Samples[i + sensorIndex.FirstSampleIdx];
+        bufferingIndMsg.Samples[i + sensorIndex.FirstSampleIdx];
 
     // TimeStampOffset has max value of < 2 sec so it will not overflow here.
     data->readings[i].timestampDelta =
@@ -373,13 +393,14 @@
 }
 
 void populateFloatEvent(
+    const sns_smgr_buffering_ind_msg_v01& bufferingIndMsg,
     SensorType sensorType, chreSensorFloatData *data,
     const sns_smgr_buffering_sample_index_s_v01& sensorIndex) {
   populateSensorDataHeader(sensorType, &data->header, sensorIndex);
 
   for (size_t i = 0; i < sensorIndex.SampleCount; i++) {
     const sns_smgr_buffering_sample_s_v01& sensorData =
-        gSmgrBufferingIndMsg.Samples[i + sensorIndex.FirstSampleIdx];
+        bufferingIndMsg.Samples[i + sensorIndex.FirstSampleIdx];
 
     // TimeStampOffset has max value of < 2 sec so it will not overflow.
     data->readings[i].timestampDelta =
@@ -389,13 +410,14 @@
 }
 
 void populateByteEvent(
+    const sns_smgr_buffering_ind_msg_v01& bufferingIndMsg,
     SensorType sensorType, chreSensorByteData *data,
     const sns_smgr_buffering_sample_index_s_v01& sensorIndex) {
   populateSensorDataHeader(sensorType, &data->header, sensorIndex);
 
   for (size_t i = 0; i < sensorIndex.SampleCount; i++) {
     const sns_smgr_buffering_sample_s_v01& sensorData =
-        gSmgrBufferingIndMsg.Samples[i + sensorIndex.FirstSampleIdx];
+        bufferingIndMsg.Samples[i + sensorIndex.FirstSampleIdx];
 
     // TimeStampOffset has max value of < 2 sec so it will not overflow.
     data->readings[i].timestampDelta =
@@ -408,13 +430,14 @@
 }
 
 void populateOccurrenceEvent(
+    const sns_smgr_buffering_ind_msg_v01& bufferingIndMsg,
     SensorType sensorType, chreSensorOccurrenceData *data,
     const sns_smgr_buffering_sample_index_s_v01& sensorIndex) {
   populateSensorDataHeader(sensorType, &data->header, sensorIndex);
 
   for (size_t i = 0; i < sensorIndex.SampleCount; i++) {
     const sns_smgr_buffering_sample_s_v01& sensorData =
-        gSmgrBufferingIndMsg.Samples[i + sensorIndex.FirstSampleIdx];
+        bufferingIndMsg.Samples[i + sensorIndex.FirstSampleIdx];
 
     // TimeStampOffset has max value of < 2 sec so it will not overflow.
     data->readings[i].timestampDelta =
@@ -426,6 +449,7 @@
  * Allocate event memory according to SensorType and populate event readings.
  */
 void *allocateAndPopulateEvent(
+    const sns_smgr_buffering_ind_msg_v01& bufferingIndMsg,
     SensorType sensorType,
     const sns_smgr_buffering_sample_index_s_v01& sensorIndex) {
   SensorSampleType sampleType = getSensorSampleTypeFromSensorType(sensorType);
@@ -437,7 +461,7 @@
       auto *event =
           static_cast<chreSensorThreeAxisData *>(memoryAlloc(memorySize));
       if (event != nullptr) {
-        populateThreeAxisEvent(sensorType, event, sensorIndex);
+        populateThreeAxisEvent(bufferingIndMsg, sensorType, event, sensorIndex);
       }
       return event;
     }
@@ -448,7 +472,7 @@
       auto *event =
           static_cast<chreSensorFloatData *>(memoryAlloc(memorySize));
       if (event != nullptr) {
-        populateFloatEvent(sensorType, event, sensorIndex);
+        populateFloatEvent(bufferingIndMsg, sensorType, event, sensorIndex);
       }
       return event;
     }
@@ -459,7 +483,7 @@
       auto *event =
           static_cast<chreSensorByteData *>(memoryAlloc(memorySize));
       if (event != nullptr) {
-        populateByteEvent(sensorType, event, sensorIndex);
+        populateByteEvent(bufferingIndMsg, sensorType, event, sensorIndex);
       }
       return event;
     }
@@ -470,7 +494,8 @@
       auto *event =
           static_cast<chreSensorOccurrenceData *>(memoryAlloc(memorySize));
       if (event != nullptr) {
-        populateOccurrenceEvent(sensorType, event, sensorIndex);
+        populateOccurrenceEvent(
+            bufferingIndMsg, sensorType, event, sensorIndex);
       }
       return event;
     }
@@ -553,89 +578,83 @@
 /**
  * Handles sensor data provided by the SMGR framework.
  *
- * @param userHandle The userHandle is used by the QMI decode function.
- * @param buffer The buffer to decode sensor data from.
- * @param bufferLength The size of the buffer to decode.
+ * @param bufferingIndMsg Decoded buffering indication message
  */
-void handleSensorDataIndication(void *userHandle, void *buffer,
-                                unsigned int bufferLength) {
-  int status = qmi_client_message_decode(
-      userHandle, QMI_IDL_INDICATION, SNS_SMGR_BUFFERING_IND_V01, buffer,
-      bufferLength, &gSmgrBufferingIndMsg,
-      sizeof(sns_smgr_buffering_ind_msg_v01));
-  if (status != QMI_NO_ERR) {
-    LOGE("Error parsing sensor data indication %d", status);
-  } else {
-    // We only requested one sensor per request except for a secondary
-    // temperature sensor.
-    bool validReport = isValidIndicesLength();
-    CHRE_ASSERT_LOG(validReport,
-                    "Got buffering indication from %" PRIu32
-                    " sensors with report ID %" PRIu8,
-                    gSmgrBufferingIndMsg.Indices_len,
-                    gSmgrBufferingIndMsg.ReportId);
-    if (validReport) {
-      // Identify the index for the desired sensor. It is always 0 except
-      // possibly for a secondary temperature sensor.
-      uint32_t index = 0;
-      if (isSecondaryTemperature(gSmgrBufferingIndMsg.ReportId)) {
-        index = (gSmgrBufferingIndMsg.Indices[0].DataType
-                 == SNS_SMGR_DATA_TYPE_SECONDARY_V01) ? 0 : 1;
-      }
-      const sns_smgr_buffering_sample_index_s_v01& sensorIndex =
-          gSmgrBufferingIndMsg.Indices[index];
+void handleSensorDataIndication(
+    const sns_smgr_buffering_ind_msg_v01& bufferingIndMsg) {
+  // We only requested one sensor per request except for a secondary
+  // temperature sensor.
+  bool validReport = isValidIndicesLength(bufferingIndMsg);
+  CHRE_ASSERT_LOG(validReport,
+                  "Got buffering indication from %" PRIu32
+                  " sensors with report ID %" PRIu8,
+                  bufferingIndMsg.Indices_len,
+                  bufferingIndMsg.ReportId);
+  if (validReport) {
+    // Identify the index for the desired sensor. It is always 0 except
+    // possibly for a secondary temperature sensor.
+    uint32_t index = 0;
+    if (isSecondaryTemperature(bufferingIndMsg.ReportId)) {
+      index = (bufferingIndMsg.Indices[0].DataType
+               == SNS_SMGR_DATA_TYPE_SECONDARY_V01) ? 0 : 1;
+    }
+    const sns_smgr_buffering_sample_index_s_v01& sensorIndex =
+        bufferingIndMsg.Indices[index];
 
-      // Use ReportID to identify sensors as
-      // gSmgrBufferingIndMsg.Samples[i].Flags are not populated.
-      SensorType sensorType = getSensorTypeFromReportId(
-          gSmgrBufferingIndMsg.ReportId);
-      if (sensorType == SensorType::Unknown) {
-        LOGW("Received sensor sample for unknown sensor %" PRIu8 " %" PRIu8,
-             sensorIndex.SensorId, sensorIndex.DataType);
-      } else if (sensorIndex.SampleCount == 0) {
-        LOGW("Received sensorType %d event with 0 sample",
-             static_cast<int>(sensorType));
+    // Use ReportID to identify sensors as
+    // bufferingIndMsg.Samples[i].Flags are not populated.
+    SensorType sensorType = getSensorTypeFromReportId(
+        bufferingIndMsg.ReportId);
+    if (sensorType == SensorType::Unknown) {
+      LOGW("Received sensor sample for unknown sensor %" PRIu8 " %" PRIu8,
+           sensorIndex.SensorId, sensorIndex.DataType);
+    } else if (sensorIndex.SampleCount == 0) {
+      LOGW("Received sensorType %d event with 0 sample",
+           static_cast<int>(sensorType));
+    } else {
+      void *eventData = allocateAndPopulateEvent(
+          bufferingIndMsg, sensorType, sensorIndex);
+      if (eventData == nullptr) {
+        LOGW("Dropping event due to allocation failure");
       } else {
-        void *eventData = allocateAndPopulateEvent(sensorType, sensorIndex);
-        if (eventData == nullptr) {
-          LOGW("Dropping event due to allocation failure");
-        } else {
-          // Schedule a deferred callback to update on-change sensor's last
-          // event in the main thread.
-          if (sensorTypeIsOnChange(sensorType)) {
-            updateLastEvent(sensorType, eventData);
-          }
-
-          EventLoopManagerSingleton::get()->getEventLoop().postEvent(
-              getSampleEventTypeForSensorType(sensorType), eventData,
-              smgrSensorDataEventFree);
+        // Schedule a deferred callback to update on-change sensor's last
+        // event in the main thread.
+        if (sensorTypeIsOnChange(sensorType)) {
+          updateLastEvent(sensorType, eventData);
         }
+
+        EventLoopManagerSingleton::get()->getEventLoop().postEvent(
+            getSampleEventTypeForSensorType(sensorType), eventData,
+            smgrSensorDataEventFree);
       }
-    }  // if (validReport)
-  }
+    }
+  }  // if (validReport)
 }
 
 /**
- * This callback is invoked by the QMI framework when an asynchronous message is
- * delivered. Unhandled messages are logged. The signature is defined by the QMI
- * library.
+ * This callback is invoked by the SMR framework when an asynchronous message is
+ * delivered. Unhandled messages are logged.
  *
- * @param userHandle The userHandle is used by the QMI library.
- * @param messageId The type of the message to decode.
- * @param buffer The buffer to decode.
- * @param bufferLength The length of the buffer to decode.
+ * @param handle Handle for the SMR client this indication was received on.
+ * @param messageId The message ID number.
+ * @param buffer Buffer containing decoded (C struct) message data.
+ * @param bufferLength Size of the decoded buffer in bytes.
  * @param callbackData Data that is provided as a context to this callback. This
  *                     is not used in this context.
+ *
+ * @see smr_client_ind_cb
  */
-void platformSensorServiceQmiIndicationCallback(void *userHandle,
-                                                unsigned int messageId,
-                                                void *buffer,
-                                                unsigned int bufferLength,
-                                                void *callbackData) {
+void platformSensorServiceIndicationCallback(
+    smr_client_hndl handle, unsigned int messageId, void *decodedInd,
+    unsigned int decodedIndLen, void *callbackData) {
   switch (messageId) {
-    case SNS_SMGR_BUFFERING_IND_V01:
-      handleSensorDataIndication(userHandle, buffer, bufferLength);
+    case SNS_SMGR_BUFFERING_IND_V01: {
+      CHRE_ASSERT(decodedIndLen >= sizeof(sns_smgr_buffering_ind_msg_v01));
+      auto *bufferingInd =
+          static_cast<sns_smgr_buffering_ind_msg_v01 *>(decodedInd);
+      handleSensorDataIndication(*bufferingInd);
       break;
+    }
     default:
       LOGW("Received unhandled sensor service message: 0x%x", messageId);
       break;
@@ -688,30 +707,6 @@
 }
 
 /**
- * Obtains the number of SMGR clients of a sensor ID that were originated by
- * CHRE.
- *
- * @param sensorId The sensor ID as provided by the SMGR.
- * @return The number of CHRE clients.
- */
-size_t getNumChreClients(uint8_t sensorId) {
-  size_t numChreClients = 0;
-
-  // Identify sensor types of the specified sensor ID
-  SensorType sensorTypes[kMaxNumSensorsPerSensorId];
-  size_t numSensorTypes = populateSensorTypeArrayFromSensorId(
-      sensorId, sensorTypes);
-  for (size_t i = 0; i < numSensorTypes; i++) {
-    const Sensor *sensor = EventLoopManagerSingleton::get()
-        ->getSensorRequestManager().getSensor(sensorTypes[i]);
-    if (sensor != nullptr && !sensor->isSensorOff) {
-      numChreClients++;
-    }
-  }
-  return numChreClients;
-}
-
-/**
  * Obtains the merged SensorMode of the specified sensor ID, with sensorType's
  * sensor request replaced by the supplied request.
  *
@@ -742,33 +737,30 @@
 }
 
 /**
- * Makes or removes passive sensor requests when the number of SMGR clients
- * changes.
+ * Makes or removes passive sensor requests when the presence of other SMGR
+ * clients changes.
  *
  * @param sensorID The sensor ID being monitored.
- * @param prevNumClients The previous number of SMGR clients.
- * @param currNumclients The current number of SMGR clients.
+ * @param otherClientPresent The presence of other SMGR clients.
  */
-void onNumSmgrClientsChange(uint8_t sensorId, uint8_t prevNumClients,
-                            uint8_t currNumClients) {
-  bool makeAllRequests = (prevNumClients == 0 && currNumClients > 0);
+void onOtherClientPresenceChange(uint8_t sensorId, bool otherClientPresent) {
+  bool makeAllRequests = otherClientPresent;
+
   SensorRequest dummyRequest;
   SensorMode mode = getMergedMode(sensorId, SensorType::Unknown, dummyRequest);
-  bool removeAllRequests = (sensorModeIsPassive(mode)
-                            && currNumClients < prevNumClients
-                            && currNumClients == getNumChreClients(sensorId)
-                            && currNumClients > 0);
-  bool qmiRequestMade = false;
+  bool removeAllRequests = (sensorModeIsPassive(mode) && !otherClientPresent);
+
+  bool requestMade = false;
   if (makeAllRequests) {
-    qmiRequestMade = makeAllPendingRequests(sensorId);
+    requestMade = makeAllPendingRequests(sensorId);
   } else if (removeAllRequests) {
-    qmiRequestMade = removeAllPassiveRequests(sensorId);
+    requestMade = removeAllPassiveRequests(sensorId);
   }
 
-  if (qmiRequestMade) {
-    LOGD("%s: id %" PRIu8 ", prev %" PRIu8 " curr %" PRIu8 ", mode %d, chre %d",
-         makeAllRequests ? "+" : "-", sensorId, prevNumClients, currNumClients,
-         static_cast<size_t>(mode), getNumChreClients(sensorId));
+  if (requestMade) {
+    LOGD("%s: id %" PRIu8 ", otherClientPresent %d, mode %d",
+         makeAllRequests ? "+" : "-", sensorId, otherClientPresent,
+         static_cast<size_t>(mode));
   }
 }
 
@@ -783,10 +775,38 @@
     LOGE("Sensor status monitor update of invalid sensor ID %" PRIu64,
          status.sensor_id);
   } else {
-    uint8_t numClients = gSensorMonitors[index].numClients;
-    if (numClients != status.num_clients) {
-      onNumSmgrClientsChange(status.sensor_id, numClients, status.num_clients);
-      gSensorMonitors[index].numClients = status.num_clients;
+    // Use asynchronous sensor status monitor indication message as a cue to
+    // query and obtain the synchronous client request info.
+    // As the info conveyed in the indication is asynchronous from current
+    // status and is insufficient to determine other clients' status, relying on
+    // it to enable/disable passive sensors may put the system in an incorrect
+    // state or lead to a request loop.
+    auto infoRequest =
+        MakeUniqueZeroFill<sns_smgr_client_request_info_req_msg_v01>();
+    auto infoResponse = MakeUnique<sns_smgr_client_request_info_resp_msg_v01>();
+
+    if (infoRequest.isNull() || infoResponse.isNull()) {
+      LOGE("Failed to allocate client request info message");
+    } else {
+      infoRequest->sensor_id = status.sensor_id;
+
+      smr_err smrStatus = getSmrHelper()->sendReqSync(
+          gPlatformSensorServiceSmrClientHandle,
+          SNS_SMGR_CLIENT_REQUEST_INFO_REQ_V01,
+          &infoRequest, &infoResponse);
+
+      if (smrStatus != SMR_NO_ERR) {
+        LOGE("Error requesting client request info: %d", smrStatus);
+      } else if (infoResponse->resp.sns_result_t != SNS_RESULT_SUCCESS_V01) {
+        LOGE("Client request info failed with error: %" PRIu8 ", id %" PRIu8,
+             infoResponse->resp.sns_err_t, infoRequest->sensor_id);
+      }
+
+      bool otherClientPresent = infoResponse->other_client_present;
+      if (gSensorMonitors[index].otherClientPresent != otherClientPresent) {
+        onOtherClientPresenceChange(status.sensor_id, otherClientPresent);
+        gSensorMonitors[index].otherClientPresent = otherClientPresent;
+      }
     }
   }
 }
@@ -817,11 +837,11 @@
 }
 
 /**
- * Updates the sampling status after the QMI sensor request is accepted by SMGR.
+ * Updates the sampling status after the sensor request is accepted by SMGR.
  */
 void updateSamplingStatus(Sensor *sensor, const SensorRequest& request) {
   // With SMGR's implementation, sampling interval will be filtered to be the
-  // same as requeted. Latency can be shorter if there were other SMGR clients
+  // same as requested. Latency can be shorter if there were other SMGR clients
   // with proc_type also set to SNS_PROC_SSC_V01.
   // If the request is passive, 'enabled' may change over time and needs to be
   // updated.
@@ -864,63 +884,58 @@
 /**
  * Handles sensor status provided by the SMGR framework.
  *
- * @param userHandle The userHandle is used by the QMI decode function.
- * @param buffer The buffer to decode sensor data from.
- * @param bufferLength The size of the buffer to decode.
+ * @param smgrMonitorIndMsg Indication message received from SMGR
  */
-void handleSensorStatusMonitorIndication(void *userHandle, void *buffer,
-                                         unsigned int bufferLength) {
-  sns_smgr_sensor_status_monitor_ind_msg_v02 smgrMonitorIndMsg;
-
-  int status = qmi_client_message_decode(
-      userHandle, QMI_IDL_INDICATION, SNS_SMGR_SENSOR_STATUS_MONITOR_IND_V02,
-      buffer, bufferLength, &smgrMonitorIndMsg, sizeof(smgrMonitorIndMsg));
-  if (status != QMI_NO_ERR) {
-    LOGE("Error parsing sensor status monitor indication %d", status);
+void handleSensorStatusMonitorIndication(
+    const sns_smgr_sensor_status_monitor_ind_msg_v02& smgrMonitorIndMsg) {
+  auto *callbackData =
+      memoryAlloc<sns_smgr_sensor_status_monitor_ind_msg_v02>();
+  if (callbackData == nullptr) {
+    LOGE("Failed to allocate status update deferred callback memory");
   } else {
-    auto *callbackData = memoryAlloc<
-      sns_smgr_sensor_status_monitor_ind_msg_v02>();
-    if (callbackData == nullptr) {
-      LOGE("Failed to allocate status update deferred callback memory");
-    } else {
-      *callbackData = smgrMonitorIndMsg;
-      auto callback = [](uint16_t /* type */, void *data) {
-        auto *cbData =
-            static_cast<sns_smgr_sensor_status_monitor_ind_msg_v02 *>(data);
-        onStatusChange(*cbData);
-        memoryFree(cbData);
-      };
+    *callbackData = smgrMonitorIndMsg;
+    auto callback = [](uint16_t /* type */, void *data) {
+      auto *cbData =
+          static_cast<sns_smgr_sensor_status_monitor_ind_msg_v02 *>(data);
+      onStatusChange(*cbData);
+      memoryFree(cbData);
+    };
 
-      // Schedule a deferred callback to handle sensor status change in the main
-      // thread.
-      if (!EventLoopManagerSingleton::get()->deferCallback(
-          SystemCallbackType::SensorStatusUpdate, callbackData, callback)) {
-        LOGE("Failed to schedule a deferred callback for status update");
-        memoryFree(callbackData);
-      }
+    // Schedule a deferred callback to handle sensor status change in the main
+    // thread.
+    if (!EventLoopManagerSingleton::get()->deferCallback(
+        SystemCallbackType::SensorStatusUpdate, callbackData, callback)) {
+      LOGE("Failed to schedule a deferred callback for status update");
+      memoryFree(callbackData);
     }
   }
 }
 
 /**
- * This callback is invoked by the QMI framework when an asynchronous message is
- * delivered. Unhandled messages are logged. The signature is defined by the QMI
- * library.
+ * This callback is invoked by the SMR framework when an asynchronous message is
+ * delivered. Unhandled messages are logged.
  *
- * @param userHandle The userHandle is used by the QMI library.
- * @param messageId The type of the message to decode.
- * @param buffer The buffer to decode.
- * @param bufferLength The length of the buffer to decode.
+ * @param handle Handle for the SMR client this indication was received on.
+ * @param messageId The message ID number.
+ * @param decodedInd Buffer containing decoded (C struct) message data.
+ * @param decodedIndLen Size of the decoded buffer in bytes.
  * @param callbackData Data that is provided as a context to this callback. This
  *                     is not used in this context.
+ *
+ * @see smr_client_ind_cb
  */
-void platformSensorInternalServiceQmiIndicationCallback(void *userHandle,
-    unsigned int messageId, void *buffer, unsigned int bufferLength,
-    void *callbackData) {
+void platformSensorInternalServiceIndicationCallback(
+    smr_client_hndl handle, unsigned int messageId, void *decodedInd,
+    unsigned int decodedIndLen, void *callbackData) {
   switch (messageId) {
-    case SNS_SMGR_SENSOR_STATUS_MONITOR_IND_V02:
-      handleSensorStatusMonitorIndication(userHandle, buffer, bufferLength);
+    case SNS_SMGR_SENSOR_STATUS_MONITOR_IND_V02: {
+      CHRE_ASSERT(decodedIndLen >=
+                  sizeof(sns_smgr_sensor_status_monitor_ind_msg_v02));
+      auto *monitorInd =
+          static_cast<sns_smgr_sensor_status_monitor_ind_msg_v02 *>(decodedInd);
+      handleSensorStatusMonitorIndication(*monitorInd);
       break;
+    }
     default:
       LOGW("Received unhandled sensor internal service message: 0x%x",
            messageId);
@@ -935,23 +950,28 @@
  * @param enable true to add and false to remove the status monitor.
  */
 void setSensorMonitorRequest(uint8_t sensorId, bool enable) {
-  sns_smgr_sensor_status_monitor_req_msg_v02 monitorRequest;
-  sns_smgr_sensor_status_monitor_resp_msg_v02 monitorResponse;
-  monitorRequest.sensor_id = sensorId;
-  monitorRequest.registering = enable ? TRUE : FALSE;
+  auto monitorRequest =
+      MakeUniqueZeroFill<sns_smgr_sensor_status_monitor_req_msg_v02>();
+  auto monitorResponse =
+      MakeUnique<sns_smgr_sensor_status_monitor_resp_msg_v02>();
 
-  qmi_client_error_type status = qmi_client_send_msg_sync(
-      gPlatformSensorInternalServiceQmiClientHandle,
-      SNS_SMGR_SENSOR_STATUS_MONITOR_REQ_V02,
-      &monitorRequest, sizeof(monitorRequest),
-      &monitorResponse, sizeof(monitorResponse), kQmiTimeoutMs);
+  if (monitorRequest.isNull() || monitorResponse.isNull()) {
+    LOGE("Failed to allocate monitor request/response");
+  } else {
+    monitorRequest->sensor_id = sensorId;
+    monitorRequest->registering = enable;
 
-  if (status != QMI_NO_ERR) {
-    LOGE("Error setting sensor status monitor: %d", status);
-  } else if (monitorResponse.resp.sns_result_t != SNS_RESULT_SUCCESS_V01) {
-    LOGE("Sensor status monitor request failed with error: %" PRIu8
-         " sensor ID %" PRIu8 " enable %d",
-         monitorResponse.resp.sns_err_t, sensorId, enable);
+    smr_err status = getSmrHelper()->sendReqSync(
+        gPlatformSensorInternalServiceSmrClientHandle,
+        SNS_SMGR_SENSOR_STATUS_MONITOR_REQ_V02,
+        &monitorRequest, &monitorResponse);
+    if (status != SMR_NO_ERR) {
+      LOGE("Error setting sensor status monitor: %d", status);
+    } else if (monitorResponse->resp.sns_result_t != SNS_RESULT_SUCCESS_V01) {
+      LOGE("Sensor status monitor request failed with error: %" PRIu8
+           " sensor ID %" PRIu8 " enable %d",
+           monitorResponse->resp.sns_err_t, sensorId, enable);
+    }
   }
 }
 
@@ -966,13 +986,13 @@
   if (index == gSensorMonitors.size()) {
     LOGD("Adding sensor status monitor for sensor ID %" PRIu8, sensorId);
 
-    // Initialize sensor monitor status before making a QMI request.
+    // Initialize sensor monitor status before making the request.
     SensorMonitor monitor;
     monitor.sensorId = sensorId;
-    monitor.numClients = 0;
+    monitor.otherClientPresent = false;
     gSensorMonitors.push_back(monitor);
 
-    // Make a QMI request to add the status monitor
+    // Make a request to add the status monitor
     setSensorMonitorRequest(sensorId, true);
   }
 }
@@ -987,51 +1007,58 @@
  */
 bool getSensorsForSensorId(uint8_t sensorId,
                            DynamicVector<Sensor> *sensors) {
-  sns_smgr_single_sensor_info_req_msg_v01 sensorInfoRequest;
-  sns_smgr_single_sensor_info_resp_msg_v01 sensorInfoResponse;
-
-  sensorInfoRequest.SensorID = sensorId;
-
-  qmi_client_error_type status = qmi_client_send_msg_sync(
-      gPlatformSensorServiceQmiClientHandle, SNS_SMGR_SINGLE_SENSOR_INFO_REQ_V01,
-      &sensorInfoRequest, sizeof(sns_smgr_single_sensor_info_req_msg_v01),
-      &sensorInfoResponse, sizeof(sns_smgr_single_sensor_info_resp_msg_v01),
-      kQmiTimeoutMs);
-
   bool success = false;
-  if (status != QMI_NO_ERR) {
-    LOGE("Error requesting single sensor info: %d", status);
-  } else if (sensorInfoResponse.Resp.sns_result_t != SNS_RESULT_SUCCESS_V01) {
-    LOGE("Single sensor info request failed with error: %d",
-         sensorInfoResponse.Resp.sns_err_t);
+  auto sensorInfoRequest =
+      MakeUniqueZeroFill<sns_smgr_single_sensor_info_req_msg_v01>();
+  auto sensorInfoResponse =
+      MakeUnique<sns_smgr_single_sensor_info_resp_msg_v01>();
+
+  if (sensorInfoRequest.isNull() || sensorInfoResponse.isNull()) {
+    LOGE("Failed to allocate sensor info msg");
   } else {
-    const sns_smgr_sensor_info_s_v01& sensorInfoList =
-        sensorInfoResponse.SensorInfo;
-    for (uint32_t i = 0; i < sensorInfoList.data_type_info_len; i++) {
-      const sns_smgr_sensor_datatype_info_s_v01& sensorInfo =
-          sensorInfoList.data_type_info[i];
-      LOGD("SensorID %" PRIu8 ", DataType %" PRIu8 ", MaxRate %" PRIu16
-           "Hz, SensorName %s",
-           sensorInfo.SensorID, sensorInfo.DataType,
-           sensorInfo.MaxSampleRate, sensorInfo.SensorName);
+    sensorInfoRequest->SensorID = sensorId;
 
-      SensorType sensorType = getSensorTypeFromSensorId(
-          sensorInfo.SensorID, sensorInfo.DataType,
-          SNS_SMGR_CAL_SEL_FULL_CAL_V01);
-      if (sensorType != SensorType::Unknown) {
-        addSensor(sensorInfo, SNS_SMGR_CAL_SEL_FULL_CAL_V01, sensors);
+    smr_err status = getSmrHelper()->sendReqSync(
+        gPlatformSensorServiceSmrClientHandle,
+        SNS_SMGR_SINGLE_SENSOR_INFO_REQ_V01,
+        &sensorInfoRequest, &sensorInfoResponse);
 
-        // Add an uncalibrated version if defined.
-        SensorType uncalibratedType = getSensorTypeFromSensorId(
+    if (status != SMR_NO_ERR) {
+      LOGE("Error requesting single sensor info: %d", status);
+    } else if (sensorInfoResponse->Resp.sns_result_t !=
+                   SNS_RESULT_SUCCESS_V01) {
+      LOGE("Single sensor info request failed with error: %d",
+           sensorInfoResponse->Resp.sns_err_t);
+    } else {
+      const sns_smgr_sensor_info_s_v01& sensorInfoList =
+          sensorInfoResponse->SensorInfo;
+      for (uint32_t i = 0; i < sensorInfoList.data_type_info_len; i++) {
+        const sns_smgr_sensor_datatype_info_s_v01& sensorInfo =
+            sensorInfoList.data_type_info[i];
+        LOGD("SensorID %" PRIu8 ", DataType %" PRIu8 ", MaxRate %" PRIu16
+             "Hz, SensorName %s",
+             sensorInfo.SensorID, sensorInfo.DataType,
+             sensorInfo.MaxSampleRate, sensorInfo.SensorName);
+
+        SensorType sensorType = getSensorTypeFromSensorId(
             sensorInfo.SensorID, sensorInfo.DataType,
-            SNS_SMGR_CAL_SEL_FACTORY_CAL_V01);
-        if (sensorType != uncalibratedType) {
-          addSensor(sensorInfo, SNS_SMGR_CAL_SEL_FACTORY_CAL_V01, sensors);
+            SNS_SMGR_CAL_SEL_FULL_CAL_V01);
+        if (sensorType != SensorType::Unknown) {
+          addSensor(sensorInfo, SNS_SMGR_CAL_SEL_FULL_CAL_V01, sensors);
+
+          // Add an uncalibrated version if defined.
+          SensorType uncalibratedType = getSensorTypeFromSensorId(
+              sensorInfo.SensorID, sensorInfo.DataType,
+              SNS_SMGR_CAL_SEL_FACTORY_CAL_V01);
+          if (sensorType != uncalibratedType) {
+            addSensor(sensorInfo, SNS_SMGR_CAL_SEL_FACTORY_CAL_V01, sensors);
+          }
         }
       }
+      success = true;
     }
-    success = true;
   }
+
   return success;
 }
 
@@ -1146,35 +1173,7 @@
 }
 
 /**
- * Obtains the number of SMGR clients that were not originated by CHRE.
- *
- * @param sensorId The sensorID as provided by the SMGR.
- * @return The number of non-CHRE clients.
- */
-size_t getNumNonChreClients(uint8_t sensorId) {
-  size_t numChreClients = getNumChreClients(sensorId);
-  size_t numSmgrClients = 0;
-  size_t index = getSensorMonitorIndex(sensorId);
-  if (index == gSensorMonitors.size()) {
-    LOGE("Accessing sensor monitor with invalid sensorId %" PRIu8, sensorId);
-  } else {
-    numSmgrClients = gSensorMonitors[index].numClients;
-  }
-
-  size_t numNonChreClients = 0;
-  if (numChreClients > numSmgrClients) {
-    // SMGR status monitor indication may lag behind if back-to-back requests
-    // are made.
-    LOGW("numChreClients %zu > numSmgrClients %zu",
-         numChreClients, numSmgrClients);
-  } else {
-    numNonChreClients = numSmgrClients - numChreClients;
-  }
-  return numNonChreClients;
-}
-
-/**
- * Determines whether a requet is allowed. A passive request is not always
+ * Determines whether a request is allowed. A passive request is not always
  * allowed.
  *
  * @param sensorType The SensorType of this request
@@ -1187,22 +1186,29 @@
   const Sensor *sensor = EventLoopManagerSingleton::get()
       ->getSensorRequestManager().getSensor(sensorType);
   if (sensor != nullptr) {
-    // If it's an ACTIVE or an OFF request, it's always allowed.
-    allowed = true;
     if (sensorModeIsPassive(request.getMode())) {
-      size_t numNonChreClients = getNumNonChreClients(sensor->sensorId);
-      SensorMode mode = getMergedMode(sensor->sensorId, sensorType, request);
-      allowed = (numNonChreClients > 0 || sensorModeIsActive(mode));
-      LOGD("sensorType %d allowed %d: mergedMode %d, numNonChreClients %zu",
-           static_cast<size_t>(sensorType), allowed, static_cast<int>(mode),
-           numNonChreClients);
+      size_t index = getSensorMonitorIndex(sensor->sensorId);
+      if (index == gSensorMonitors.size()) {
+        LOGE("SensorId %" PRIu8 " doesn't have a monitor", sensor->sensorId);
+      } else {
+        SensorMode mergedMode = getMergedMode(
+            sensor->sensorId, sensorType, request);
+        bool otherClientPresent = gSensorMonitors[index].otherClientPresent;
+        allowed = (sensorModeIsActive(mergedMode) || otherClientPresent);
+        LOGD("sensorType %d allowed %d: mergedMode %d, otherClientPresent %d",
+             static_cast<size_t>(sensorType), allowed,
+             static_cast<int>(mergedMode), otherClientPresent);
+      }
+    } else {
+      // If it's an ACTIVE or an OFF request, it's always allowed.
+      allowed = true;
     }
   }
   return allowed;
 }
 
 /**
- * Makes a QMI SNS_SMGR_BUFFERING_REQ request based on the arguments provided.
+ * Makes a SNS_SMGR_BUFFERING_REQ request based on the arguments provided.
  *
  * @param sensorId The sensorID as provided by the SMGR.
  * @param dataType The dataType for the sesnor as provided by the MSGR.
@@ -1211,27 +1217,23 @@
  * @param request The sensor request
  * @return true if the request has been made successfully.
  */
-bool makeQmiRequest(uint8_t sensorId, uint8_t dataType, uint8_t calType,
-                    uint64_t minInterval, const SensorRequest& request) {
+bool makeBufferingReq(uint8_t sensorId, uint8_t dataType, uint8_t calType,
+                      uint64_t minInterval, const SensorRequest& request) {
   bool success = false;
+  auto sensorRequest = MakeUniqueZeroFill<sns_smgr_buffering_req_msg_v01>();
+  auto sensorResponse = MakeUnique<sns_smgr_buffering_resp_msg_v01>();
 
-  // Allocate request and response for the sensor request.
-  auto *sensorRequest = memoryAlloc<sns_smgr_buffering_req_msg_v01>();
-  auto *sensorResponse = memoryAlloc<sns_smgr_buffering_resp_msg_v01>();
-
-  if (sensorRequest == nullptr || sensorResponse == nullptr) {
-    LOGE("Failed to allocate sensor request/response: out of memory");
+  if (sensorRequest.isNull() || sensorResponse.isNull()) {
+    LOGE("Failed to allocate buffering msg");
   } else {
     populateSensorRequest(request, sensorId, dataType, calType,
-                          minInterval, sensorRequest);
+                          minInterval, sensorRequest.get());
 
-    qmi_client_error_type status = qmi_client_send_msg_sync(
-        gPlatformSensorServiceQmiClientHandle, SNS_SMGR_BUFFERING_REQ_V01,
-        sensorRequest, sizeof(*sensorRequest),
-        sensorResponse, sizeof(*sensorResponse),
-        kQmiTimeoutMs);
+    smr_err status = getSmrHelper()->sendReqSync(
+        gPlatformSensorServiceSmrClientHandle, SNS_SMGR_BUFFERING_REQ_V01,
+        &sensorRequest, &sensorResponse);
 
-    if (status != QMI_NO_ERR) {
+    if (status != SMR_NO_ERR) {
       LOGE("Error requesting sensor data: %d", status);
     } else if (sensorResponse->Resp.sns_result_t != SNS_RESULT_SUCCESS_V01
         || (sensorResponse->AckNak != SNS_SMGR_RESPONSE_ACK_SUCCESS_V01
@@ -1242,13 +1244,12 @@
       success = true;
     }
   }
-  memoryFree(sensorRequest);
-  memoryFree(sensorResponse);
+
   return success;
 }
 
 /**
- * Makes a QMI SNS_SMGR_BUFFERING_REQ request if necessary.
+ * Makes a SNS_SMGR_BUFFERING_REQ request if necessary.
  *
  * @param sensorType The sensor type of the request.
  * @param request The sensor request to be made.
@@ -1259,23 +1260,21 @@
 
   Sensor *sensor = EventLoopManagerSingleton::get()->getSensorRequestManager()
       .getSensor(sensorType);
-  if (sensor == nullptr) {
-    LOGE("Invalid sensorType %d", static_cast<size_t>(sensorType));
-  } else {
-    // Do not make a QMI off request if the sensor is off. Otherwise, SMGR
+  if (sensor != nullptr) {
+    // Do not make an off request if the sensor is already off. Otherwise, SMGR
     // returns an error.
     if (request.getMode() == SensorMode::Off) {
       success = sensor->isSensorOff;
     }
 
-    // Make a QMI buffering request if necessary.
+    // Make a SMGR buffering request if necessary.
     if (!success) {
-      success = makeQmiRequest(sensor->sensorId, sensor->dataType,
-                               sensor->calType, sensor->minInterval, request);
+      success = makeBufferingReq(sensor->sensorId, sensor->dataType,
+                                 sensor->calType, sensor->minInterval, request);
     }
   }
 
-  // TODO: handle makeQmiRequest failures
+  // TODO: handle makeBufferingReq failures
   if (success) {
     // Update internal states if request was accepted by SMGR.
     sensor->isSensorOff = (request.getMode() == SensorMode::Off);
@@ -1318,7 +1317,7 @@
  * Identifies and removes passive requests that have been made to the SMGR, and
  * adds them to the sensor monitor.
  *
- * @param sensorId The sensor ID whose passive QMI requests are to be removed.
+ * @param sensorId The sensor ID whose passive requests are to be removed.
  * @return true if a DELETE request has been accepted.
  */
 bool removeAllPassiveRequests(uint8_t sensorId) {
@@ -1353,81 +1352,99 @@
 }
 
 void PlatformSensor::init() {
+  // Timeout for SMR client initialization, in milliseconds.
+  constexpr uint32_t kSmrInitTimeoutMs = 10;
+
+  SmrHelperSingleton::init();
+
   // sns_smgr_api_v01
-  qmi_idl_service_object_type sensorServiceObject =
+  qmi_idl_service_object_type smgrSvcObj =
       SNS_SMGR_SVC_get_service_object_v01();
-  if (sensorServiceObject == nullptr) {
+  if (smgrSvcObj == nullptr) {
     FATAL_ERROR("Failed to obtain the SNS SMGR service instance");
   }
 
-  qmi_client_os_params sensorContextOsParams;
-  qmi_client_error_type status = qmi_client_init_instance(sensorServiceObject,
-      QMI_CLIENT_INSTANCE_ANY, &platformSensorServiceQmiIndicationCallback,
-      nullptr, &sensorContextOsParams, kQmiInitTimeoutMs,
-      &gPlatformSensorServiceQmiClientHandle);
-  if (status != QMI_NO_ERR) {
-    FATAL_ERROR("Failed to initialize the sensor service QMI client: %d",
-                status);
+  smr_err result = getSmrHelper()->waitForService(smgrSvcObj);
+  if (result != SMR_NO_ERR) {
+    FATAL_ERROR("Failed while waiting for SNS SMGR service");
+  }
+
+  // Note: giving nullptr for err_cb prevents this from degrading to a regular
+  // QMI client if the service is not found.
+  smr_err status = smr_client_init(
+      smgrSvcObj, SMR_CLIENT_INSTANCE_ANY,
+      platformSensorServiceIndicationCallback, nullptr /* ind_cb_data */,
+      kSmrInitTimeoutMs, nullptr /* err_cb */, nullptr /* err_cb_data */,
+      &gPlatformSensorServiceSmrClientHandle, isSlpiUimgSupported());
+  if (status != SMR_NO_ERR) {
+    FATAL_ERROR("Failed to initialize SMGR client: %d", status);
   }
 
   // sns_smgr_interal_api_v02
-  sensorServiceObject = SNS_SMGR_INTERNAL_SVC_get_service_object_v02();
-  if (sensorServiceObject == nullptr) {
+  qmi_idl_service_object_type smgrInternalSvcObj =
+      SNS_SMGR_INTERNAL_SVC_get_service_object_v02();
+  if (smgrInternalSvcObj == nullptr) {
     FATAL_ERROR("Failed to obtain the SNS SMGR internal service instance");
   }
 
-  status = qmi_client_init_instance(sensorServiceObject,
-      QMI_CLIENT_INSTANCE_ANY,
-      &platformSensorInternalServiceQmiIndicationCallback, nullptr,
-      &sensorContextOsParams, kQmiInitTimeoutMs,
-      &gPlatformSensorInternalServiceQmiClientHandle);
-  if (status != QMI_NO_ERR) {
-    FATAL_ERROR("Failed to initialize the sensor internal service QMI client: "
-                "%d", status);
+  result = getSmrHelper()->waitForService(smgrInternalSvcObj);
+  if (result != SMR_NO_ERR) {
+    FATAL_ERROR("Failed while waiting for SNS SMGR internal service");
+  }
+
+  status = smr_client_init(
+      smgrInternalSvcObj, SMR_CLIENT_INSTANCE_ANY,
+      platformSensorInternalServiceIndicationCallback,
+      nullptr /* ind_cb_data */, kSmrInitTimeoutMs, nullptr /* err_cb */,
+      nullptr /* err_cb_data */, &gPlatformSensorInternalServiceSmrClientHandle,
+      isSlpiUimgSupported());
+  if (status != SMR_NO_ERR) {
+    FATAL_ERROR("Failed to initialize SMGR internal client: %d", status);
   }
 }
 
 void PlatformSensor::deinit() {
-  qmi_client_error_type err = qmi_client_release(
-      gPlatformSensorServiceQmiClientHandle);
-  if (err != QMI_NO_ERR) {
-    LOGE("Failed to release SensorService QMI client: %d", err);
+  smr_err err = getSmrHelper()->releaseSync(
+      gPlatformSensorServiceSmrClientHandle);
+  if (err != SMR_NO_ERR) {
+    LOGE("Failed to release SMGR client: %d", err);
   }
-  gPlatformSensorServiceQmiClientHandle = nullptr;
+  gPlatformSensorServiceSmrClientHandle = nullptr;
 
-  err = qmi_client_release(gPlatformSensorInternalServiceQmiClientHandle);
-  if (err != QMI_NO_ERR) {
-    LOGE("Failed to release SensorInternalService QMI client: %d", err);
+  err = getSmrHelper()->releaseSync(
+      gPlatformSensorInternalServiceSmrClientHandle);
+  if (err != SMR_NO_ERR) {
+    LOGE("Failed to release SMGR internal client: %d", err);
   }
-  gPlatformSensorInternalServiceQmiClientHandle = nullptr;
+  gPlatformSensorInternalServiceSmrClientHandle = nullptr;
 
-  // Clearing all sensor status monitors. Releaseing a QMI client also
-  // releases all sensor status monitor requests.
+  // Clearing all sensor status monitors. Releasing an SMR client also releases
+  // all sensor status monitor requests.
   gSensorMonitors.clear();
+  SmrHelperSingleton::deinit();
 }
 
 bool PlatformSensor::getSensors(DynamicVector<Sensor> *sensors) {
   CHRE_ASSERT(sensors);
 
-  sns_smgr_all_sensor_info_req_msg_v01 sensorListRequest;
-  sns_smgr_all_sensor_info_resp_msg_v01 sensorListResponse;
+  auto sensorListRequest =
+      MakeUniqueZeroFill<sns_smgr_all_sensor_info_req_msg_v01>();
+  auto sensorListResponse = MakeUnique<sns_smgr_all_sensor_info_resp_msg_v01>();
 
-  qmi_client_error_type status = qmi_client_send_msg_sync(
-      gPlatformSensorServiceQmiClientHandle, SNS_SMGR_ALL_SENSOR_INFO_REQ_V01,
-      &sensorListRequest, sizeof(sns_smgr_all_sensor_info_req_msg_v01),
-      &sensorListResponse, sizeof(sns_smgr_all_sensor_info_resp_msg_v01),
-      kQmiTimeoutMs);
+  smr_err status = getSmrHelper()->sendReqSync(
+      gPlatformSensorServiceSmrClientHandle, SNS_SMGR_ALL_SENSOR_INFO_REQ_V01,
+      &sensorListRequest, &sensorListResponse);
 
   bool success = false;
-  if (status != QMI_NO_ERR) {
+  if (status != SMR_NO_ERR) {
     LOGE("Error requesting sensor list: %d", status);
-  } else if (sensorListResponse.Resp.sns_result_t != SNS_RESULT_SUCCESS_V01) {
+  } else if (sensorListResponse->Resp.sns_result_t != SNS_RESULT_SUCCESS_V01) {
     LOGE("Sensor list lequest failed with error: %d",
-         sensorListResponse.Resp.sns_err_t);
+         sensorListResponse->Resp.sns_err_t);
   } else {
     success = true;
-    for (uint32_t i = 0; i < sensorListResponse.SensorInfo_len; i++) {
-      uint8_t sensorId = sensorListResponse.SensorInfo[i].SensorID;
+    for (uint32_t i = 0; i < sensorListResponse->SensorInfo_len; i++) {
+      uint8_t sensorId = sensorListResponse->SensorInfo[i].SensorID;
       if (!getSensorsForSensorId(sensorId, sensors)) {
         success = false;
         break;
@@ -1439,16 +1456,39 @@
 }
 
 bool PlatformSensor::applyRequest(const SensorRequest& request) {
-  // Adds a sensor monitor the first time this sensor is requested.
-  addSensorMonitor(this->sensorId);
+  bool success;
 
-  // Determines whether a (passive) request is allowed at this point.
-  bool requestAllowed = isRequestAllowed(getSensorType(), request);
+  if (!SmrHelperSingleton::isInitialized()) {
+    // Off requests made as part of shutdown come after PlatformSensor::deinit()
+    // which releases our SMGR clients, removing all requests. Report success in
+    // this case.
+    success = (request.getMode() == SensorMode::Off) ? true : false;
+    CHRE_ASSERT_LOG(success, "Sensor request made before init/after deinit");
+  } else {
+    // Adds a sensor monitor the first time this sensor is requested.
+    addSensorMonitor(this->sensorId);
 
-  // If request is not allowed, turn off the sensor. Otherwise, make request.
-  SensorRequest offRequest;
-  bool success = makeRequest(getSensorType(),
-                             requestAllowed ? request : offRequest);
+    // As sensor status monior indication doesn't support secondary sensor
+    // status change, Light sensor (a secondary one) is always overridden to be
+    // requested with an active mode.
+    bool passiveLight = (getSensorType() == SensorType::Light
+                         && sensorModeIsPassive(request.getMode()));
+    if (passiveLight) {
+      LOGE("Passive request for Light sensor is not supported. "
+           "Overriding request to active");
+    }
+    SensorRequest localRequest(
+        passiveLight ? SensorMode::ActiveContinuous : request.getMode(),
+        request.getInterval(), request.getLatency());
+
+    // Determines whether a (passive) request is allowed at this point.
+    bool requestAllowed = isRequestAllowed(getSensorType(), localRequest);
+
+    // If request is not allowed, turn off the sensor. Otherwise, make request.
+    SensorRequest offRequest;
+    success = makeRequest(getSensorType(),
+                          requestAllowed ? localRequest : offRequest);
+  }
   return success;
 }
 
@@ -1514,8 +1554,8 @@
   this->lastEventValid = true;
 }
 
-qmi_client_type getSensorServiceQmiClientHandle() {
-  return gPlatformSensorServiceQmiClientHandle;
+smr_client_hndl getSensorServiceSmrClientHandle() {
+  return gPlatformSensorServiceSmrClientHandle;
 }
 
 }  // namespace chre
diff --git a/platform/slpi/power_control_manager.cc b/platform/slpi/power_control_manager.cc
new file mode 100644
index 0000000..700958c
--- /dev/null
+++ b/platform/slpi/power_control_manager.cc
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "chre/platform/power_control_manager.h"
+
+#include "chre/platform/fatal_error.h"
+#include "chre/platform/log.h"
+#include "chre/platform/slpi/power_control_util.h"
+
+namespace chre {
+
+PowerControlManagerBase::PowerControlManagerBase() {
+#ifdef CHRE_SLPI_UIMG_ENABLED
+  char kClientName[] = "CHRE";
+  sns_pm_err_code_e result = sns_pm_client_init(
+      &mClientHandle, nullptr, kClientName, SNS_PM_CLIENT_ID_CHRE);
+  if (result != SNS_PM_SUCCESS) {
+    FATAL_ERROR("Power manager client init failed.");
+  }
+#endif // CHRE_SLPI_UIMG_ENABLED
+}
+
+PowerControlManagerBase::~PowerControlManagerBase() {
+#ifdef CHRE_SLPI_UIMG_ENABLED
+  sns_pm_client_close(mClientHandle);
+#endif // CHRE_SLPI_UIMG_ENABLED
+}
+
+bool PowerControlManagerBase::votePowerMode(
+    sns_pm_img_mode_e mode) {
+#ifdef CHRE_SLPI_UIMG_ENABLED
+  sns_pm_err_code_e result = sns_pm_vote_img_mode(mClientHandle, mode);
+  if (result != SNS_PM_SUCCESS) {
+    LOGE("Failed to vote for power mode %d with result %d", mode, result);
+  }
+
+  return (result == SNS_PM_SUCCESS);
+#else
+  return true;
+#endif // CHRE_SLPI_UIMG_ENABLED
+}
+
+void PowerControlManager::postEventLoopProcess(size_t numPendingEvents) {
+  if (numPendingEvents == 0 && !slpiInUImage()) {
+    votePowerMode(SNS_IMG_MODE_NOCLIENT);
+  }
+}
+
+} // namespace chre
diff --git a/platform/slpi/preloaded_nanoapps.cc b/platform/slpi/preloaded_nanoapps.cc
index 43ffea0..96ff5f8 100644
--- a/platform/slpi/preloaded_nanoapps.cc
+++ b/platform/slpi/preloaded_nanoapps.cc
@@ -36,12 +36,12 @@
     UniquePtr<Nanoapp> nanoapp;
   };
 
-  // These nanoapps will be delivered via GMS Core in a future GMS release, but
-  // are pre-loaded for now.
+  // The list of nanoapps to be loaded from the filesystem of the device.
   // TODO: allow these to be overridden by target-specific build configuration
   static PreloadedNanoappDescriptor preloadedNanoapps[] = {
     { 0x476f6f676c00100b, "activity.so", MakeUnique<Nanoapp>() },
     { 0x476f6f676c001004, "geofence.so", MakeUnique<Nanoapp>() },
+    { 0x476f6f676c00100c, "wifi_offload.so", MakeUnique<Nanoapp>() },
   };
 
   for (size_t i = 0; i < ARRAY_SIZE(preloadedNanoapps); i++) {
diff --git a/platform/slpi/smr_helper.cc b/platform/slpi/smr_helper.cc
new file mode 100644
index 0000000..70a876c
--- /dev/null
+++ b/platform/slpi/smr_helper.cc
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "chre/platform/slpi/smr_helper.h"
+
+#include <inttypes.h>
+
+#include "chre/platform/assert.h"
+#include "chre/platform/log.h"
+#include "chre/platform/slpi/power_control_util.h"
+#include "chre/util/lock_guard.h"
+
+namespace chre {
+
+smr_err SmrHelper::releaseSync(smr_client_hndl clientHandle,
+                               Nanoseconds timeout) {
+  // smr_client_release is synchronous for SMR services in the current
+  // implementation, so we can't hold the lock while calling it otherwise we'll
+  // deadlock in the callback.
+  {
+    LockGuard<Mutex> lock(mMutex);
+    CHRE_ASSERT(!mWaiting);
+    mWaiting = true;
+  }
+
+  smr_err result = smr_client_release(
+      clientHandle, SmrHelper::smrReleaseCb, this);
+  if (result == SMR_NO_ERR) {
+    LockGuard<Mutex> lock(mMutex);
+    bool waitSuccess = true;
+    while (mWaiting && waitSuccess) {
+      waitSuccess = mCond.wait_for(mMutex, timeout);
+    }
+
+    if (!waitSuccess) {
+      LOGE("Releasing SMR client timed out");
+      result = SMR_TIMEOUT_ERR;
+      mWaiting = false;
+    }
+  }
+
+  return result;
+}
+
+smr_err SmrHelper::waitForService(qmi_idl_service_object_type serviceObj,
+                                  Microseconds timeout) {
+  // smr_client_check_ext is synchronous if the service already exists,
+  // so don't hold the lock while calling to prevent deadlock in the callback.
+  {
+    LockGuard<Mutex> lock(mMutex);
+    CHRE_ASSERT(!mWaiting);
+    mWaiting = true;
+  }
+
+  smr_err result = smr_client_check_ext(serviceObj, SMR_CLIENT_INSTANCE_ANY,
+                                        timeout.getMicroseconds(),
+                                        SmrHelper::smrWaitForServiceCb, this);
+  if (result == SMR_NO_ERR) {
+    LockGuard<Mutex> lock(mMutex);
+    while (mWaiting) {
+      mCond.wait(mMutex);
+    }
+
+    if (mServiceTimedOut) {
+      LOGE("Wait for SMR service timed out");
+      result = SMR_TIMEOUT_ERR;
+      mServiceTimedOut = false;
+    }
+  }
+
+  return result;
+}
+
+bool SmrHelper::sendReqSyncUntyped(
+    smr_client_hndl client_handle, unsigned int msg_id,
+    void *req_c_struct, unsigned int req_c_struct_len,
+    void *resp_c_struct, unsigned int resp_c_struct_len,
+    Nanoseconds timeout, smr_err *result) {
+  LockGuard<Mutex> lock(mMutex);
+  CHRE_ASSERT(!mWaiting);
+  bool waitSuccess = true;
+
+  // Force big image since smr_client_send_req is not supported in micro-image
+  slpiForceBigImage();
+
+  // Note that null txn_handle means we can't abandon the transaction, but it's
+  // only supported for QMI (non-SMR) services, and we don't expect that anyway.
+  // SMR itself does not support canceling transactions made to SMR services.
+  *result = smr_client_send_req(
+      client_handle, msg_id, req_c_struct, req_c_struct_len, resp_c_struct,
+      resp_c_struct_len, SmrHelper::smrRespCb, this, nullptr /* txn_handle */);
+  if (*result != SMR_NO_ERR) {
+    LOGE("Failed to send request (msg_id 0x%02x): %d", msg_id, *result);
+  } else {
+    mWaiting = true;
+    mPendingRespBuf = resp_c_struct;
+
+    while (mWaiting && waitSuccess) {
+      waitSuccess = mCond.wait_for(mMutex, timeout);
+    }
+
+    if (waitSuccess) {
+      *result = mTranspErr;
+    } else {
+      LOGE("SMR request for msg_id 0x%02x timed out after %" PRIu64 " ms",
+           msg_id, Milliseconds(timeout).getMilliseconds());
+      *result = SMR_TIMEOUT_ERR;
+      mWaiting = false;
+    }
+    mPendingRespBuf = nullptr;
+  }
+
+  return waitSuccess;
+}
+
+void SmrHelper::handleResp(smr_client_hndl client_handle, unsigned int msg_id,
+                           void *resp_c_struct, unsigned int resp_c_struct_len,
+                           smr_err transp_err) {
+  LockGuard<Mutex> lock(mMutex);
+
+  if (!mWaiting) {
+    LOGE("Got SMR response when none pending!");
+  } else if (mPendingRespBuf != resp_c_struct) {
+    LOGE("Got SMR response with unexpected buffer, msg_id 0x%02x: %p vs. %p",
+         msg_id, mPendingRespBuf, resp_c_struct);
+  } else {
+    // SMR will handle copying the response into the buffer passed in to
+    // smr_client_send_req(), so we just need to unblock the waiting thread
+    mTranspErr = transp_err;
+    mWaiting = false;
+    mCond.notify_one();
+  }
+}
+
+void SmrHelper::smrReleaseCb(void *release_cb_data) {
+  SmrHelper *obj = static_cast<SmrHelper *>(release_cb_data);
+  LockGuard<Mutex> lock(obj->mMutex);
+  obj->mWaiting = false;
+  obj->mCond.notify_one();
+}
+
+void SmrHelper::smrRespCb(smr_client_hndl client_handle, unsigned int msg_id,
+                          void *resp_c_struct, unsigned int resp_c_struct_len,
+                          void *resp_cb_data, smr_err transp_err) {
+  SmrHelper *obj = static_cast<SmrHelper *>(resp_cb_data);
+  obj->handleResp(client_handle, msg_id, resp_c_struct, resp_c_struct_len,
+                  transp_err);
+}
+
+void SmrHelper::smrWaitForServiceCb(qmi_idl_service_object_type /* service_obj */,
+                                    qmi_service_instance /* instance_id */,
+                                    bool timeout_expired,
+                                    void *wait_for_service_cb_data) {
+  SmrHelper *obj = static_cast<SmrHelper *>(wait_for_service_cb_data);
+  LockGuard<Mutex> lock(obj->mMutex);
+  obj->mServiceTimedOut = timeout_expired;
+  obj->mWaiting = false;
+  obj->mCond.notify_one();
+}
+
+}  // namespace chre
diff --git a/platform/slpi/system_time.cc b/platform/slpi/system_time.cc
index 9ff957c..481535e 100644
--- a/platform/slpi/system_time.cc
+++ b/platform/slpi/system_time.cc
@@ -62,7 +62,19 @@
 } // anonymous namespace
 
 Nanoseconds SystemTime::getMonotonicTime() {
-  return Microseconds(uTimetick_CvtFromTicks(uTimetick_Get(), T_USEC));
+  constexpr uint64_t kClockFreq = 19200000;  // 19.2MHz QTimer clock
+
+  uint64_t ticks = uTimetick_Get();
+  uint64_t nsec = 0;
+  if (ticks >= kClockFreq) {
+    uint64_t seconds = (ticks / kClockFreq);
+    ticks %= kClockFreq;
+
+    nsec = (seconds * kOneSecondInNanoseconds);
+  }
+  nsec += (ticks * kOneSecondInNanoseconds) / kClockFreq;
+
+  return Nanoseconds(nsec);
 }
 
 int64_t SystemTime::getEstimatedHostTimeOffset() {
diff --git a/platform/slpi/system_timer.cc b/platform/slpi/system_timer.cc
index 5d58318..806909e 100644
--- a/platform/slpi/system_timer.cc
+++ b/platform/slpi/system_timer.cc
@@ -23,18 +23,28 @@
 SystemTimer::SystemTimer() {}
 
 SystemTimer::~SystemTimer() {
-  timer_undef(&mTimerHandle);
+  if (mInitialized) {
+    slpiTimerUndef(&mTimerHandle);
+  }
 }
 
 bool SystemTimer::init() {
   if (mInitialized) {
     LOGW("Tried re-initializing timer");
   } else {
-    timer_error_type status = timer_def_osal(
+#ifdef CHRE_SLPI_UIMG_ENABLED
+    SlpiTimerErrorType status = utimer_def_osal(
+        &mTimerHandle, UTIMER_FUNC1_CB_TYPE,
+        reinterpret_cast<utimer_osal_notify_obj_ptr>(systemTimerNotifyCallback),
+        reinterpret_cast<utimer_osal_notify_data>(this));
+#else
+    SlpiTimerErrorType status = timer_def_osal(
         &mTimerHandle, &timer_non_defer_group, TIMER_FUNC1_CB_TYPE,
         reinterpret_cast<time_osal_notify_obj_ptr>(systemTimerNotifyCallback),
         reinterpret_cast<time_osal_notify_data>(this));
-    if (status != TE_SUCCESS) {
+#endif  // CHRE_SLPI_UIMG_ENABLED
+
+    if (status != SLPI_TIMER_SUCCESS) {
       LOGE("Error initializing timer %d", status);
     } else {
       mInitialized = true;
@@ -50,9 +60,9 @@
   if (mInitialized) {
     mCallback = callback;
     mData = data;
-    timer_error_type status = timer_set_64(&mTimerHandle,
-        Microseconds(delay).getMicroseconds(), 0, T_USEC);
-    if (status != TE_SUCCESS) {
+    SlpiTimerErrorType status = slpiTimerSet64(&mTimerHandle,
+        Microseconds(delay).getMicroseconds(), 0, SlpiTimerMicroUnit);
+    if (status != SLPI_TIMER_SUCCESS) {
       LOGE("Error setting timer %d", status);
     } else {
       wasSet = true;
@@ -65,7 +75,8 @@
 bool SystemTimer::cancel() {
   bool wasCancelled = false;
   if (mInitialized) {
-    time_timetick_type ticksRemaining = timer_clr_64(&mTimerHandle, T_TICK);
+    SlpiTimerTickType ticksRemaining = slpiTimerClr64(&mTimerHandle,
+                                                      SlpiTimerTickUnit);
     wasCancelled = (ticksRemaining > 0);
   }
 
@@ -73,10 +84,12 @@
 }
 
 bool SystemTimer::isActive() {
-  return (mInitialized && timer_get_64(&mTimerHandle, T_TICK) > 0);
+  SlpiTimerTickType ticksRemaining = slpiTimerGet64(&mTimerHandle,
+                                                    SlpiTimerTickUnit);
+  return (mInitialized && ticksRemaining > 0);
 }
 
-void SystemTimerBase::systemTimerNotifyCallback(timer_cb_data_type data) {
+void SystemTimerBase::systemTimerNotifyCallback(SlpiTimerCallbackDataType data) {
   SystemTimer *systemTimer = reinterpret_cast<SystemTimer *>(data);
   systemTimer->mCallback(systemTimer->mData);
 }
diff --git a/run_sim.sh b/run_sim.sh
index d406aa4..1144121 100755
--- a/run_sim.sh
+++ b/run_sim.sh
@@ -10,4 +10,4 @@
 export CHRE_VARIANT_MK_INCLUDES=variant/simulator/variant.mk
 
 make google_x86_linux_debug -j$JOB_COUNT
-./out/google_x86_linux_debug/libchre
+./out/google_x86_linux_debug/libchre ${@:1}
diff --git a/util/include/chre/util/container_support.h b/util/include/chre/util/container_support.h
new file mode 100644
index 0000000..f70160e
--- /dev/null
+++ b/util/include/chre/util/container_support.h
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef CHRE_UTIL_CONTAINER_SUPPORT_H_
+#define CHRE_UTIL_CONTAINER_SUPPORT_H_
+
+/**
+ * @file Provides replacements for macros and functions that are normally
+ * provided by the CHRE framework implementation. These portable implementations
+ * are implemented using the CHRE API rather than private system APIs.
+ */
+
+#ifdef CHRE_IS_NANOAPP_BUILD
+
+#include <chre.h>
+
+#include "chre/util/nanoapp/log.h"
+
+/**
+ * Provides the CHRE_ASSERT macro that uses chreAbort to abort the nanoapp upon
+ * failure.
+ *
+ * @param the condition to check for non-zero.
+ */
+#define CHRE_ASSERT(condition) do { \
+  if (!(condition)) {               \
+    chreAbort(UINT32_MAX);          \
+  }                                 \
+} while (0)
+
+
+/**
+ * Provides the CHRE_ASSERT_LOG macro that logs the assertion failure followed
+ * by CHRE_ASSERT if the condition is non-zero.
+ *
+ * @param condition the condition to check for non-zero.
+ * @param fmt the format string to log.
+ * @param ... arguments to format into the log message.
+ */
+#define CHRE_ASSERT_LOG(condition, fmt, ...) do { \
+  if (!(condition)) {                             \
+    LOGE("Assert: " fmt, ##__VA_ARGS__);          \
+    CHRE_ASSERT(condition);                       \
+  }                                               \
+} while (0)
+
+namespace chre {
+
+/**
+ * Provides the memoryAlloc function that is normally provided by the CHRE
+ * runtime. It maps into chreHeapAlloc.
+ *
+ * @param size the size of the allocation to make.
+ * @return a pointer to allocated memory or nullptr if allocation failed.
+ */
+inline void *memoryAlloc(size_t size) {
+  return chreHeapAlloc(static_cast<uint32_t>(size));
+}
+
+/**
+ * Provides the memoryFree function that is normally provided by the CHRE
+ * runtime. It maps into chreHeapFree.
+ *
+ * @param pointer the allocation to release.
+ */
+inline void memoryFree(void *pointer) {
+  chreHeapFree(pointer);
+}
+
+}  // namespace chre
+
+#else
+#include "chre/platform/assert.h"
+#include "chre/platform/memory.h"
+#endif
+
+#endif  // CHRE_UTIL_CONTAINER_SUPPORT_H_
diff --git a/util/include/chre/util/dynamic_vector_impl.h b/util/include/chre/util/dynamic_vector_impl.h
index 4d5cb26..72fca18 100644
--- a/util/include/chre/util/dynamic_vector_impl.h
+++ b/util/include/chre/util/dynamic_vector_impl.h
@@ -21,8 +21,7 @@
 #include <new>
 #include <utility>
 
-#include "chre/platform/assert.h"
-#include "chre/platform/memory.h"
+#include "chre/util/container_support.h"
 #include "chre/util/memory.h"
 
 namespace chre {
diff --git a/util/include/chre/util/fixed_size_blocking_queue.h b/util/include/chre/util/fixed_size_blocking_queue.h
index 5efbbf6..eefe089 100644
--- a/util/include/chre/util/fixed_size_blocking_queue.h
+++ b/util/include/chre/util/fixed_size_blocking_queue.h
@@ -56,6 +56,11 @@
    */
   bool empty();
 
+  /**
+   * Determines the current size of the BlockingQueue.
+   */
+  size_t size();
+
  private:
   //! The mutex used to ensure thread-safety.
   Mutex mMutex;
diff --git a/util/include/chre/util/fixed_size_blocking_queue_impl.h b/util/include/chre/util/fixed_size_blocking_queue_impl.h
index abfa4eb..3aa5235 100644
--- a/util/include/chre/util/fixed_size_blocking_queue_impl.h
+++ b/util/include/chre/util/fixed_size_blocking_queue_impl.h
@@ -54,6 +54,12 @@
   return mQueue.empty();
 }
 
+template<typename ElementType, size_t kSize>
+size_t FixedSizeBlockingQueue<ElementType, kSize>::size() {
+  LockGuard<Mutex> lock(mMutex);
+  return mQueue.size();
+}
+
 }  // namespace chre
 
 #endif  // CHRE_UTIL_BLOCKING_QUEUE_IMPL_H_
diff --git a/util/include/chre/util/memory.h b/util/include/chre/util/memory.h
index ccfb5ce..d5ad30c 100644
--- a/util/include/chre/util/memory.h
+++ b/util/include/chre/util/memory.h
@@ -50,6 +50,13 @@
 void uninitializedMoveOrCopy(ElementType *source, size_t count,
                              ElementType *dest);
 
+/**
+ * Allocates memory for an object of size T and constructs the object in the
+ * newly allocated object by forwarding the provided parameters.
+ */
+template<typename T, typename... Args>
+T *memoryAlloc(Args&&... args);
+
 }  // namespace chre
 
 #include "chre/util/memory_impl.h"
diff --git a/util/include/chre/util/memory_impl.h b/util/include/chre/util/memory_impl.h
index cf9b712..9d03d39 100644
--- a/util/include/chre/util/memory_impl.h
+++ b/util/include/chre/util/memory_impl.h
@@ -22,6 +22,8 @@
 #include <type_traits>
 #include <utility>
 
+#include "chre/util/container_support.h"
+
 namespace chre {
 
 template<typename ElementType>
@@ -107,6 +109,16 @@
       //typename std::is_trivially_copy_constructible<ElementType>::type());
 }
 
+template<typename T, typename... Args>
+inline T *memoryAlloc(Args&&... args) {
+  auto *storage = static_cast<T *>(memoryAlloc(sizeof(T)));
+  if (storage != nullptr) {
+    new(storage) T(std::forward<Args>(args)...);
+  }
+
+  return storage;
+}
+
 }  // namespace chre
 
 #endif  // CHRE_UTIL_MEMORY_IMPL_H_
diff --git a/util/include/chre/util/unique_ptr.h b/util/include/chre/util/unique_ptr.h
index 78feb53..8af2e9a 100644
--- a/util/include/chre/util/unique_ptr.h
+++ b/util/include/chre/util/unique_ptr.h
@@ -52,6 +52,16 @@
   UniquePtr(UniquePtr<ObjectType>&& other);
 
   /**
+   * Constructs a new UniquePtr via moving the Object from another UniquePtr.
+   * This constructor allows conversion (ie: upcast) to another type if
+   * possible.
+   *
+   * @param other UniquePtr instance to move and convert into this object.
+   */
+  template<typename OtherObjectType>
+  UniquePtr(UniquePtr<OtherObjectType>&& other);
+
+  /**
    * Deconstructs the object (if necessary) and releases associated memory.
    */
   ~UniquePtr();
@@ -105,6 +115,11 @@
   UniquePtr<ObjectType>& operator=(UniquePtr<ObjectType>&& other);
 
  private:
+  // Befriend this class to itself to allow the templated conversion constructor
+  // permission to access mObject below.
+  template<typename OtherObjectType>
+  friend class UniquePtr;
+
   //! A pointer to the underlying storage for this object.
   ObjectType *mObject;
 };
@@ -119,6 +134,14 @@
 template<typename ObjectType, typename... Args>
 UniquePtr<ObjectType> MakeUnique(Args&&... args);
 
+/**
+ * Just like MakeUnique(), except it zeros out any allocated memory. Intended to
+ * be used for creating objects that have trivial constructors (e.g. C structs)
+ * but should start with a known state.
+ */
+template<typename ObjectType>
+UniquePtr<ObjectType> MakeUniqueZeroFill();
+
 }  // namespace chre
 
 #include "chre/util/unique_ptr_impl.h"
diff --git a/util/include/chre/util/unique_ptr_impl.h b/util/include/chre/util/unique_ptr_impl.h
index aac1411..69be9d0 100644
--- a/util/include/chre/util/unique_ptr_impl.h
+++ b/util/include/chre/util/unique_ptr_impl.h
@@ -17,9 +17,11 @@
 #ifndef CHRE_UTIL_UNIQUE_PTR_IMPL_H_
 #define CHRE_UTIL_UNIQUE_PTR_IMPL_H_
 
+#include <string.h>
+#include <type_traits>
 #include <utility>
 
-#include "chre/platform/memory.h"
+#include "chre/util/memory.h"
 
 namespace chre {
 
@@ -36,6 +38,13 @@
 }
 
 template<typename ObjectType>
+template<typename OtherObjectType>
+UniquePtr<ObjectType>::UniquePtr(UniquePtr<OtherObjectType>&& other) {
+  mObject = other.mObject;
+  other.mObject = nullptr;
+}
+
+template<typename ObjectType>
 UniquePtr<ObjectType>::~UniquePtr() {
   if (mObject != nullptr) {
     mObject->~ObjectType();
@@ -91,6 +100,22 @@
       std::forward<Args>(args)...));
 }
 
+template<typename ObjectType>
+inline UniquePtr<ObjectType> MakeUniqueZeroFill() {
+  // For simplicity, we call memset *after* memoryAlloc<ObjectType>() - this is
+  // only valid for types that have a trivial constructor. This utility function
+  // is really meant to be used with trivial types only - if there's a desire to
+  // zero things out in a non-trivial type, the right place for that is in its
+  // constructor.
+  static_assert(std::is_trivial<ObjectType>::value,
+                "MakeUniqueZeroFill is only supported for trivial types");
+  auto ptr = UniquePtr<ObjectType>(memoryAlloc<ObjectType>());
+  if (!ptr.isNull()) {
+    memset(ptr.get(), 0, sizeof(ObjectType));
+  }
+  return ptr;
+}
+
 }  // namespace chre
 
 #endif  // CHRE_UTIL_UNIQUE_PTR_IMPL_H_
diff --git a/util/tests/unique_ptr_test.cc b/util/tests/unique_ptr_test.cc
index 5d53901..31dc95d 100644
--- a/util/tests/unique_ptr_test.cc
+++ b/util/tests/unique_ptr_test.cc
@@ -1,9 +1,12 @@
+#include <cstring>
+
 #include "gtest/gtest.h"
 
 #include "chre/util/unique_ptr.h"
 
 using chre::UniquePtr;
 using chre::MakeUnique;
+using chre::MakeUniqueZeroFill;
 
 struct Value {
   Value(int value) : value(value) {
@@ -34,6 +37,21 @@
   EXPECT_EQ(myInt[0].value, 0xcafe);
 }
 
+struct BigArray {
+  int x[2048];
+};
+
+TEST(UniquePtr, MakeUniqueZeroFill) {
+  BigArray baseline = {};
+  auto myArray = MakeUniqueZeroFill<BigArray>();
+  ASSERT_FALSE(myArray.isNull());
+  // Note that this doesn't actually test things properly, because we don't
+  // guarantee that malloc is not already giving us zeroed out memory. To
+  // properly do it, we could inject the allocator, but this function is simple
+  // enough that it's not really worth the effort.
+  EXPECT_EQ(std::memcmp(&baseline, myArray.get(), sizeof(baseline)), 0);
+}
+
 TEST(UniquePtr, MoveConstruct) {
   UniquePtr<Value> myInt = MakeUnique<Value>(0xcafe);
   ASSERT_FALSE(myInt.isNull());
diff --git a/variant/simulator/variant.mk b/variant/simulator/variant.mk
index e37c007..affcb4a 100644
--- a/variant/simulator/variant.mk
+++ b/variant/simulator/variant.mk
@@ -16,6 +16,9 @@
 # nanoapp list.
 COMMON_CFLAGS += -DCHRE_VARIANT_SUPPLIES_STATIC_NANOAPP_LIST
 
+# Enable exceptions for TCLAP.
+GOOGLE_X86_LINUX_CFLAGS += -fexceptions
+
 # Common Source Files ##########################################################
 
 COMMON_SRCS += variant/simulator/static_nanoapps.cc